ONNX
ytfeng commited on
Commit
0d53279
·
1 Parent(s): 578736a

Merge pull request #109 fengyuentau:fix_yolox_issues

Browse files

Resolves #108:

- Renamed `YoloX.py` to `yolox.py` for import.

- Reimplemented batched-nms.

- Put anchor generation in the initialization stage to avoid re-generating in inference.

Files changed (2) hide show
  1. YoloX.py +36 -40
  2. yolox.py +89 -0
YoloX.py CHANGED
@@ -17,6 +17,8 @@ class YoloX:
17
  self.net.setPreferableBackend(self.backendId)
18
  self.net.setPreferableTarget(self.targetId)
19
 
 
 
20
  @property
21
  def name(self):
22
  return self.__class__.__name__
@@ -43,51 +45,45 @@ class YoloX:
43
  return predictions
44
 
45
  def postprocess(self, outputs):
46
- grids = []
47
- expanded_strides = []
48
- hsizes = [self.input_size[0] // stride for stride in self.strides]
49
- wsizes = [self.input_size[1] // stride for stride in self.strides]
50
 
51
- for hsize, wsize, stride in zip(hsizes, wsizes, self.strides):
52
- xv, yv = np.meshgrid(np.arange(hsize), np.arange(wsize))
53
- grid = np.stack((xv, yv), 2).reshape(1, -1, 2)
54
- grids.append(grid)
55
- shape = grid.shape[:2]
56
- expanded_strides.append(np.full((*shape, 1), stride))
57
-
58
- grids = np.concatenate(grids, 1)
59
- expanded_strides = np.concatenate(expanded_strides, 1)
60
- outputs[..., :2] = (outputs[..., :2] + grids) * expanded_strides
61
- outputs[..., 2:4] = np.exp(outputs[..., 2:4]) * expanded_strides
62
-
63
- predictions = outputs[0]
64
-
65
- boxes = predictions[:, :4]
66
- scores = predictions[:, 4:5] * predictions[:, 5:]
67
 
 
 
68
  boxes_xyxy = np.ones_like(boxes)
69
  boxes_xyxy[:, 0] = boxes[:, 0] - boxes[:, 2] / 2.
70
  boxes_xyxy[:, 1] = boxes[:, 1] - boxes[:, 3] / 2.
71
  boxes_xyxy[:, 2] = boxes[:, 0] + boxes[:, 2] / 2.
72
  boxes_xyxy[:, 3] = boxes[:, 1] + boxes[:, 3] / 2.
73
 
74
- # multi-class nms
75
- final_dets = []
76
- for cls_ind in range(scores.shape[1]):
77
- cls_scores = scores[:, cls_ind]
78
- valid_score_mask = cls_scores > self.confThreshold
79
- if valid_score_mask.sum() == 0:
80
- continue
81
- else:
82
- # call nms
83
- indices = cv2.dnn.NMSBoxes(boxes_xyxy.tolist(), cls_scores.tolist(), self.confThreshold, self.nmsThreshold)
84
-
85
- classids_ = np.ones((len(indices), 1)) * cls_ind
86
- final_dets.append(
87
- np.concatenate([boxes_xyxy[indices], cls_scores[indices, None], classids_], axis=1)
88
- )
89
-
90
- if len(final_dets) == 0:
91
- return np.array([])
92
-
93
- return np.concatenate(final_dets, 0)
 
 
 
 
 
 
 
 
 
 
17
  self.net.setPreferableBackend(self.backendId)
18
  self.net.setPreferableTarget(self.targetId)
19
 
20
+ self.generateAnchors()
21
+
22
  @property
23
  def name(self):
24
  return self.__class__.__name__
 
45
  return predictions
46
 
47
  def postprocess(self, outputs):
48
+ dets = outputs[0]
 
 
 
49
 
50
+ dets[:, :2] = (dets[:, :2] + self.grids) * self.expanded_strides
51
+ dets[:, 2:4] = np.exp(dets[:, 2:4]) * self.expanded_strides
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
 
53
+ # get boxes
54
+ boxes = dets[:, :4]
55
  boxes_xyxy = np.ones_like(boxes)
56
  boxes_xyxy[:, 0] = boxes[:, 0] - boxes[:, 2] / 2.
57
  boxes_xyxy[:, 1] = boxes[:, 1] - boxes[:, 3] / 2.
58
  boxes_xyxy[:, 2] = boxes[:, 0] + boxes[:, 2] / 2.
59
  boxes_xyxy[:, 3] = boxes[:, 1] + boxes[:, 3] / 2.
60
 
61
+ # get scores and class indices
62
+ scores = dets[:, 4:5] * dets[:, 5:]
63
+ max_scores = np.amax(scores, axis=1)
64
+ max_scores_idx = np.argmax(scores, axis=1)
65
+
66
+ # batched-nms, TODO: replace with cv2.dnn.NMSBoxesBatched when OpenCV 4.7.0 is released
67
+ max_coord = boxes_xyxy.max()
68
+ offsets = max_scores_idx * (max_coord + 1)
69
+ boxes_for_nms = boxes_xyxy + offsets[:, None]
70
+ keep = cv2.dnn.NMSBoxes(boxes_for_nms.tolist(), max_scores.tolist(), self.confThreshold, self.nmsThreshold)
71
+
72
+ candidates = np.concatenate([boxes_xyxy, max_scores[:, None], max_scores_idx[:, None]], axis=1)
73
+ return candidates[keep]
74
+
75
+ def generateAnchors(self):
76
+ self.grids = []
77
+ self.expanded_strides = []
78
+ hsizes = [self.input_size[0] // stride for stride in self.strides]
79
+ wsizes = [self.input_size[1] // stride for stride in self.strides]
80
+
81
+ for hsize, wsize, stride in zip(hsizes, wsizes, self.strides):
82
+ xv, yv = np.meshgrid(np.arange(hsize), np.arange(wsize))
83
+ grid = np.stack((xv, yv), 2).reshape(1, -1, 2)
84
+ self.grids.append(grid)
85
+ shape = grid.shape[:2]
86
+ self.expanded_strides.append(np.full((*shape, 1), stride))
87
+
88
+ self.grids = np.concatenate(self.grids, 1)
89
+ self.expanded_strides = np.concatenate(self.expanded_strides, 1)
yolox.py ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import cv2
3
+
4
+ class YoloX:
5
+ def __init__(self, modelPath, confThreshold=0.35, nmsThreshold=0.5, objThreshold=0.5, backendId=0, targetId=0):
6
+ self.num_classes = 80
7
+ self.net = cv2.dnn.readNet(modelPath)
8
+ self.input_size = (640, 640)
9
+ self.mean = np.array([0.485, 0.456, 0.406], dtype=np.float32).reshape(1, 1, 3)
10
+ self.std = np.array([0.229, 0.224, 0.225], dtype=np.float32).reshape(1, 1, 3)
11
+ self.strides = [8, 16, 32]
12
+ self.confThreshold = confThreshold
13
+ self.nmsThreshold = nmsThreshold
14
+ self.objThreshold = objThreshold
15
+ self.backendId = backendId
16
+ self.targetId = targetId
17
+ self.net.setPreferableBackend(self.backendId)
18
+ self.net.setPreferableTarget(self.targetId)
19
+
20
+ self.generateAnchors()
21
+
22
+ @property
23
+ def name(self):
24
+ return self.__class__.__name__
25
+
26
+ def setBackend(self, backenId):
27
+ self.backendId = backendId
28
+ self.net.setPreferableBackend(self.backendId)
29
+
30
+ def setTarget(self, targetId):
31
+ self.targetId = targetId
32
+ self.net.setPreferableTarget(self.targetId)
33
+
34
+ def preprocess(self, img):
35
+ blob = np.transpose(img, (2, 0, 1))
36
+ return blob[np.newaxis, :, :, :]
37
+
38
+ def infer(self, srcimg):
39
+ input_blob = self.preprocess(srcimg)
40
+
41
+ self.net.setInput(input_blob)
42
+ outs = self.net.forward(self.net.getUnconnectedOutLayersNames())
43
+
44
+ predictions = self.postprocess(outs[0])
45
+ return predictions
46
+
47
+ def postprocess(self, outputs):
48
+ dets = outputs[0]
49
+
50
+ dets[:, :2] = (dets[:, :2] + self.grids) * self.expanded_strides
51
+ dets[:, 2:4] = np.exp(dets[:, 2:4]) * self.expanded_strides
52
+
53
+ # get boxes
54
+ boxes = dets[:, :4]
55
+ boxes_xyxy = np.ones_like(boxes)
56
+ boxes_xyxy[:, 0] = boxes[:, 0] - boxes[:, 2] / 2.
57
+ boxes_xyxy[:, 1] = boxes[:, 1] - boxes[:, 3] / 2.
58
+ boxes_xyxy[:, 2] = boxes[:, 0] + boxes[:, 2] / 2.
59
+ boxes_xyxy[:, 3] = boxes[:, 1] + boxes[:, 3] / 2.
60
+
61
+ # get scores and class indices
62
+ scores = dets[:, 4:5] * dets[:, 5:]
63
+ max_scores = np.amax(scores, axis=1)
64
+ max_scores_idx = np.argmax(scores, axis=1)
65
+
66
+ # batched-nms, TODO: replace with cv2.dnn.NMSBoxesBatched when OpenCV 4.7.0 is released
67
+ max_coord = boxes_xyxy.max()
68
+ offsets = max_scores_idx * (max_coord + 1)
69
+ boxes_for_nms = boxes_xyxy + offsets[:, None]
70
+ keep = cv2.dnn.NMSBoxes(boxes_for_nms.tolist(), max_scores.tolist(), self.confThreshold, self.nmsThreshold)
71
+
72
+ candidates = np.concatenate([boxes_xyxy, max_scores[:, None], max_scores_idx[:, None]], axis=1)
73
+ return candidates[keep]
74
+
75
+ def generateAnchors(self):
76
+ self.grids = []
77
+ self.expanded_strides = []
78
+ hsizes = [self.input_size[0] // stride for stride in self.strides]
79
+ wsizes = [self.input_size[1] // stride for stride in self.strides]
80
+
81
+ for hsize, wsize, stride in zip(hsizes, wsizes, self.strides):
82
+ xv, yv = np.meshgrid(np.arange(hsize), np.arange(wsize))
83
+ grid = np.stack((xv, yv), 2).reshape(1, -1, 2)
84
+ self.grids.append(grid)
85
+ shape = grid.shape[:2]
86
+ self.expanded_strides.append(np.full((*shape, 1), stride))
87
+
88
+ self.grids = np.concatenate(self.grids, 1)
89
+ self.expanded_strides = np.concatenate(self.expanded_strides, 1)