meaculpitt commited on
Commit
37bd630
·
verified ·
1 Parent(s): 17b7462

scorevision: push artifact

Browse files
Files changed (6) hide show
  1. README.md +20 -25
  2. __pycache__/miner.cpython-312.pyc +0 -0
  3. class_names.txt +0 -79
  4. miner.py +103 -125
  5. model_type.json +1 -1
  6. weights.onnx +2 -2
README.md CHANGED
@@ -1,13 +1,14 @@
1
  ---
2
  tags:
3
  - element_type:detect
4
- - model:yolov11-small
5
- - object:vehicle
6
  manako:
7
  description: >
8
- YOLO11s vehicle detector fine-tuned on COCO vehicles + BDD100K + VisDrone.
9
- FP16 ONNX, 1280x1280 input. Trained R6: 59,870 images, 50 epochs.
10
- source: meaculpitt/Detect-Vehicle
 
11
  prompt_hints: null
12
  input_payload:
13
  - name: frame
@@ -16,32 +17,26 @@ manako:
16
  output_payload:
17
  - name: detections
18
  type: detections
19
- description: Bounding boxes for detected vehicles
20
- evaluation_score: 0.7701
21
  last_benchmark:
22
- type: visdrone_val
23
- ran_at: 2026-03-25T17:34:00+00:00
24
  result_path: null
25
  ---
26
 
27
- # Detect-Vehicle — SN44
28
 
29
- YOLO11s fine-tuned for vehicle detection (car, bus, truck, motorcycle).
30
 
31
  | Metric | Value |
32
  |--------|-------|
33
- | mAP@50 | 77.01% |
34
- | Model | YOLO11s (FP16 ONNX) |
35
- | Input size | 1280x1280 |
36
- | Model size | 19.2 MB |
37
- | Training data | COCO vehicles + BDD100K + VisDrone (59,870 images) |
38
- | Baseline to beat | 40.72% |
39
 
40
- ## Classes
41
-
42
- | Output ID | Class |
43
- |-----------|-------|
44
- | 0 | car |
45
- | 1 | bus |
46
- | 2 | truck |
47
- | 3 | motorcycle |
 
1
  ---
2
  tags:
3
  - element_type:detect
4
+ - model:yolov11-nano
5
+ - object:person
6
  manako:
7
  description: >
8
+ YOLOv11-nano fine-tuned for ground-level CCTV person detection on SN44.
9
+ Trained on CrowdHuman (15k, dense crowds) + BDD100K street pedestrians.
10
+ Conf threshold raised to 0.35 to minimise false positives.
11
+ source: meaculpitt/Detect-Person
12
  prompt_hints: null
13
  input_payload:
14
  - name: frame
 
17
  output_payload:
18
  - name: detections
19
  type: detections
20
+ description: Bounding boxes for detected persons
21
+ evaluation_score: 0.5563
22
  last_benchmark:
23
+ type: coco_val2017
24
+ ran_at: '2026-03-25T02:58:57+00:00'
25
  result_path: null
26
  ---
27
 
28
+ # Detect-Person — SN44
29
 
30
+ YOLOv11-nano fine-tuned for ground-level CCTV person detection.
31
 
32
  | Metric | Value |
33
  |--------|-------|
34
+ | mAP@50 (COCO val2017) | 55.63% |
35
+ | Precision (conf=0.35) | 56.86% |
36
+ | Recall | 50.67% |
37
+ | Baseline to beat | 37.55% |
38
+ | Model size | 5.6 MB |
39
+ | Input size | 1280×1280 |
40
 
41
+ **Training data**: CrowdHuman (15k) + BDD100K (3.2k pedestrians)
42
+ **Validation**: COCO val2017 persons (2,693 images)
 
 
 
 
 
 
__pycache__/miner.cpython-312.pyc CHANGED
Binary files a/__pycache__/miner.cpython-312.pyc and b/__pycache__/miner.cpython-312.pyc differ
 
class_names.txt CHANGED
@@ -1,80 +1 @@
1
  person
2
- bicycle
3
- car
4
- motorcycle
5
- airplane
6
- bus
7
- train
8
- truck
9
- boat
10
- traffic light
11
- fire hydrant
12
- stop sign
13
- parking meter
14
- bench
15
- bird
16
- cat
17
- dog
18
- horse
19
- sheep
20
- cow
21
- elephant
22
- bear
23
- zebra
24
- giraffe
25
- backpack
26
- umbrella
27
- handbag
28
- tie
29
- suitcase
30
- frisbee
31
- skis
32
- snowboard
33
- sports ball
34
- kite
35
- baseball bat
36
- baseball glove
37
- skateboard
38
- surfboard
39
- tennis racket
40
- bottle
41
- wine glass
42
- cup
43
- fork
44
- knife
45
- spoon
46
- bowl
47
- banana
48
- apple
49
- sandwich
50
- orange
51
- broccoli
52
- carrot
53
- hot dog
54
- pizza
55
- donut
56
- cake
57
- chair
58
- couch
59
- potted plant
60
- bed
61
- dining table
62
- toilet
63
- tv
64
- laptop
65
- mouse
66
- remote
67
- keyboard
68
- cell phone
69
- microwave
70
- oven
71
- toaster
72
- sink
73
- refrigerator
74
- book
75
- clock
76
- vase
77
- scissors
78
- teddy bear
79
- hair drier
80
- toothbrush
 
1
  person
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
miner.py CHANGED
@@ -1,12 +1,7 @@
1
  """
2
- Score Vision SN44 — VehicleDetect miner v5 (2026-03-26).
3
- TTA (3-pass) + inline WBF. Per-class NMS. Letterbox preprocessing.
4
-
5
- Model: YOLO11s ONNX, 4 classes trained as:
6
- 0 = car, 1 = bus, 2 = truck, 3 = motorcycle
7
-
8
- Official submission order (remapped in MODEL_TO_OUT):
9
- 0 = bus, 1 = car, 2 = truck, 3 = motorcycle
10
  """
11
 
12
  from pathlib import Path
@@ -18,12 +13,7 @@ import onnxruntime as ort
18
  from numpy import ndarray
19
  from pydantic import BaseModel
20
 
21
- MODEL_TO_OUT: dict[int, int] = {0: 1, 1: 0, 2: 2, 3: 3}
22
- OUT_NAMES = ["bus", "car", "truck", "motorcycle"]
23
- NUM_CLASSES = 4
24
-
25
- IMG_SIZE = 1280
26
- CONF_THRESH = 0.55
27
  IOU_THRESH = 0.45
28
  WBF_IOU_THR = 0.55
29
  WBF_SKIP_THR = 0.0001
@@ -31,80 +21,68 @@ TTA_SCALE = 1.2
31
 
32
 
33
  def _wbf(boxes_list: list[np.ndarray], scores_list: list[np.ndarray],
34
- labels_list: list[np.ndarray], iou_thr: float = 0.55,
35
- skip_box_thr: float = 0.0001) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
36
- """Weighted Boxes Fusion (inline, no external dep). Boxes in [0,1] normalized coords."""
37
  if not boxes_list:
38
- return np.empty((0, 4)), np.empty(0), np.empty(0)
39
 
40
- # Collect all boxes with model index
41
- all_boxes, all_scores, all_labels = [], [], []
42
- for model_idx, (bx, sc, lb) in enumerate(zip(boxes_list, scores_list, labels_list)):
43
  for i in range(len(bx)):
44
  if sc[i] < skip_box_thr:
45
  continue
46
  all_boxes.append(bx[i])
47
  all_scores.append(sc[i])
48
- all_labels.append(int(lb[i]))
49
 
50
  if not all_boxes:
51
- return np.empty((0, 4)), np.empty(0), np.empty(0)
52
 
53
  all_boxes = np.array(all_boxes)
54
  all_scores = np.array(all_scores)
55
- all_labels = np.array(all_labels, dtype=int)
56
-
57
  n_models = len(boxes_list)
58
- fused_boxes, fused_scores, fused_labels = [], [], []
59
-
60
- for cls in np.unique(all_labels):
61
- cls_mask = all_labels == cls
62
- cls_boxes = all_boxes[cls_mask]
63
- cls_scores = all_scores[cls_mask]
64
-
65
- order = cls_scores.argsort()[::-1]
66
- cls_boxes = cls_boxes[order]
67
- cls_scores = cls_scores[order]
68
-
69
- clusters: list[list[int]] = []
70
- cluster_boxes: list[np.ndarray] = []
71
-
72
- for i in range(len(cls_boxes)):
73
- matched = -1
74
- best_iou = iou_thr
75
- for c_idx, c_box in enumerate(cluster_boxes):
76
- xx1 = max(cls_boxes[i, 0], c_box[0])
77
- yy1 = max(cls_boxes[i, 1], c_box[1])
78
- xx2 = min(cls_boxes[i, 2], c_box[2])
79
- yy2 = min(cls_boxes[i, 3], c_box[3])
80
- inter = max(0, xx2 - xx1) * max(0, yy2 - yy1)
81
- a1 = (cls_boxes[i, 2] - cls_boxes[i, 0]) * (cls_boxes[i, 3] - cls_boxes[i, 1])
82
- a2 = (c_box[2] - c_box[0]) * (c_box[3] - c_box[1])
83
- iou = inter / (a1 + a2 - inter + 1e-9)
84
- if iou > best_iou:
85
- best_iou = iou
86
- matched = c_idx
87
- if matched >= 0:
88
- clusters[matched].append(i)
89
- # Update cluster box as weighted average
90
- idxs = clusters[matched]
91
- weights = cls_scores[idxs]
92
- w_sum = weights.sum()
93
- cluster_boxes[matched] = (cls_boxes[idxs] * weights[:, None]).sum(0) / w_sum
94
- else:
95
- clusters.append([i])
96
- cluster_boxes.append(cls_boxes[i].copy())
97
-
98
- for c_idx, idxs in enumerate(clusters):
99
- weights = cls_scores[idxs]
100
- score = weights.sum() / n_models
101
- fused_boxes.append(cluster_boxes[c_idx])
102
- fused_scores.append(score)
103
- fused_labels.append(cls)
104
 
105
  if not fused_boxes:
106
- return np.empty((0, 4)), np.empty(0), np.empty(0)
107
- return np.array(fused_boxes), np.array(fused_scores), np.array(fused_labels)
108
 
109
 
110
  class BoundingBox(BaseModel):
@@ -125,109 +103,109 @@ class TVFrameResult(BaseModel):
125
  class Miner:
126
  def __init__(self, path_hf_repo: Path) -> None:
127
  self.path_hf_repo = path_hf_repo
 
128
  self.session = ort.InferenceSession(
129
  str(path_hf_repo / "weights.onnx"),
130
  providers=["CUDAExecutionProvider", "CPUExecutionProvider"],
131
  )
132
  self.input_name = self.session.get_inputs()[0].name
 
 
 
133
  self.conf_threshold = CONF_THRESH
134
  self.iou_threshold = IOU_THRESH
135
 
136
  def __repr__(self) -> str:
137
- return f"VehicleDetect Miner v5 TTA+WBF session={type(self.session).__name__}"
138
-
139
- def _letterbox(self, img: ndarray) -> tuple[np.ndarray, float, int, int]:
140
- h, w = img.shape[:2]
141
- r = min(IMG_SIZE / h, IMG_SIZE / w)
142
- new_w, new_h = int(round(w * r)), int(round(h * r))
143
- img_r = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_LINEAR)
144
- dw, dh = IMG_SIZE - new_w, IMG_SIZE - new_h
145
- pad_l, pad_t = dw // 2, dh // 2
146
- img_p = cv2.copyMakeBorder(
147
- img_r, pad_t, dh - pad_t, pad_l, dw - pad_l,
148
- cv2.BORDER_CONSTANT, value=(114, 114, 114),
149
- )
150
- return img_p, r, pad_l, pad_t
151
-
152
- def _preprocess(self, image_bgr: ndarray) -> tuple[np.ndarray, float, int, int]:
153
- img_p, ratio, pad_l, pad_t = self._letterbox(image_bgr)
154
- img_rgb = cv2.cvtColor(img_p, cv2.COLOR_BGR2RGB)
155
- inp = img_rgb.astype(np.float32) / 255.0
156
- inp = np.ascontiguousarray(inp.transpose(2, 0, 1)[np.newaxis])
157
- return inp, ratio, pad_l, pad_t
158
-
159
- def _decode_raw(self, raw: np.ndarray, ratio: float, pad_l: int, pad_t: int,
160
- orig_w: int, orig_h: int) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
161
  pred = raw[0]
 
 
162
  if pred.shape[0] < pred.shape[1]:
163
- pred = pred.T
164
- bboxes_cx = pred[:, :4]
 
 
 
165
  cls_scores = pred[:, 4:]
166
- cls_ids = np.argmax(cls_scores, axis=1)
 
 
167
  confs = np.max(cls_scores, axis=1)
168
- mask = confs >= self.conf_threshold
169
- if not mask.any():
170
- return np.empty((0, 4)), np.empty(0), np.empty(0, dtype=int)
171
- bboxes_cx, confs, cls_ids = bboxes_cx[mask], confs[mask], cls_ids[mask]
172
- cx, cy, bw, bh = bboxes_cx[:, 0], bboxes_cx[:, 1], bboxes_cx[:, 2], bboxes_cx[:, 3]
173
- x1 = np.clip((cx - bw / 2 - pad_l) / ratio, 0, orig_w)
174
- y1 = np.clip((cy - bh / 2 - pad_t) / ratio, 0, orig_h)
175
- x2 = np.clip((cx + bw / 2 - pad_l) / ratio, 0, orig_w)
176
- y2 = np.clip((cy + bh / 2 - pad_t) / ratio, 0, orig_h)
177
- return np.stack([x1, y1, x2, y2], axis=1), confs, cls_ids
178
-
179
- def _run_single_pass(self, image_bgr: ndarray) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
 
 
 
180
  orig_h, orig_w = image_bgr.shape[:2]
181
- inp, ratio, pad_l, pad_t = self._preprocess(image_bgr)
182
  raw = self.session.run(None, {self.input_name: inp})[0]
183
- return self._decode_raw(raw, ratio, pad_l, pad_t, orig_w, orig_h)
184
 
185
  def _infer_single(self, image_bgr: ndarray) -> list[BoundingBox]:
186
  orig_h, orig_w = image_bgr.shape[:2]
187
 
188
- all_boxes, all_scores, all_labels = [], [], []
189
 
190
- def _collect(boxes, confs, cls_ids):
191
  if len(boxes) == 0:
192
  return
193
- out_cls = np.array([MODEL_TO_OUT[int(c)] for c in cls_ids])
194
  norm = boxes.copy()
195
  norm[:, [0, 2]] /= orig_w
196
  norm[:, [1, 3]] /= orig_h
197
  norm = np.clip(norm, 0, 1)
198
  all_boxes.append(norm)
199
  all_scores.append(confs)
200
- all_labels.append(out_cls)
201
 
202
  # Pass 1: original
203
  _collect(*self._run_single_pass(image_bgr))
204
 
205
  # Pass 2: horizontal flip
206
  flipped = cv2.flip(image_bgr, 1)
207
- boxes_f, confs_f, cls_f = self._run_single_pass(flipped)
208
  if len(boxes_f):
209
  boxes_f[:, 0], boxes_f[:, 2] = orig_w - boxes_f[:, 2], orig_w - boxes_f[:, 0]
210
- _collect(boxes_f, confs_f, cls_f)
211
 
212
  # Pass 3: 1.2x scale center crop
213
  sh, sw = int(orig_h * TTA_SCALE), int(orig_w * TTA_SCALE)
214
  scaled = cv2.resize(image_bgr, (sw, sh), interpolation=cv2.INTER_LINEAR)
215
  yo, xo = (sh - orig_h) // 2, (sw - orig_w) // 2
216
  cropped = scaled[yo:yo + orig_h, xo:xo + orig_w]
217
- boxes_s, confs_s, cls_s = self._run_single_pass(cropped)
218
  if len(boxes_s):
219
  boxes_s[:, 0] = (boxes_s[:, 0] + xo) / TTA_SCALE
220
  boxes_s[:, 1] = (boxes_s[:, 1] + yo) / TTA_SCALE
221
  boxes_s[:, 2] = (boxes_s[:, 2] + xo) / TTA_SCALE
222
  boxes_s[:, 3] = (boxes_s[:, 3] + yo) / TTA_SCALE
223
  boxes_s = np.clip(boxes_s, 0, [[orig_w, orig_h, orig_w, orig_h]])
224
- _collect(boxes_s, confs_s, cls_s)
225
 
226
  if not all_boxes:
227
  return []
228
 
229
- fused_boxes, fused_scores, fused_labels = _wbf(
230
- all_boxes, all_scores, all_labels,
231
  iou_thr=WBF_IOU_THR, skip_box_thr=WBF_SKIP_THR,
232
  )
233
  if len(fused_boxes) == 0:
@@ -245,7 +223,7 @@ class Miner:
245
  y1=max(0, min(orig_h, math.floor(b[1]))),
246
  x2=max(0, min(orig_w, math.ceil(b[2]))),
247
  y2=max(0, min(orig_h, math.ceil(b[3]))),
248
- cls_id=int(fused_labels[i]),
249
  conf=max(0.0, min(1.0, float(fused_scores[i]))),
250
  ))
251
  return out
 
1
  """
2
+ Score Vision SN44 — DetectPerson miner v4 (2026-03-26).
3
+ TTA (3-pass) + inline WBF. Stretch resize preprocessing.
4
+ Single class: person (cls_id=0).
 
 
 
 
 
5
  """
6
 
7
  from pathlib import Path
 
13
  from numpy import ndarray
14
  from pydantic import BaseModel
15
 
16
+ CONF_THRESH = 0.50
 
 
 
 
 
17
  IOU_THRESH = 0.45
18
  WBF_IOU_THR = 0.55
19
  WBF_SKIP_THR = 0.0001
 
21
 
22
 
23
  def _wbf(boxes_list: list[np.ndarray], scores_list: list[np.ndarray],
24
+ iou_thr: float = 0.55, skip_box_thr: float = 0.0001
25
+ ) -> tuple[np.ndarray, np.ndarray]:
26
+ """Weighted Boxes Fusion for single-class detection. Boxes in [0,1] normalized coords."""
27
  if not boxes_list:
28
+ return np.empty((0, 4)), np.empty(0)
29
 
30
+ all_boxes, all_scores = [], []
31
+ for bx, sc in zip(boxes_list, scores_list):
 
32
  for i in range(len(bx)):
33
  if sc[i] < skip_box_thr:
34
  continue
35
  all_boxes.append(bx[i])
36
  all_scores.append(sc[i])
 
37
 
38
  if not all_boxes:
39
+ return np.empty((0, 4)), np.empty(0)
40
 
41
  all_boxes = np.array(all_boxes)
42
  all_scores = np.array(all_scores)
 
 
43
  n_models = len(boxes_list)
44
+
45
+ order = all_scores.argsort()[::-1]
46
+ all_boxes = all_boxes[order]
47
+ all_scores = all_scores[order]
48
+
49
+ clusters: list[list[int]] = []
50
+ cluster_boxes: list[np.ndarray] = []
51
+
52
+ for i in range(len(all_boxes)):
53
+ matched = -1
54
+ best_iou = iou_thr
55
+ for c_idx, c_box in enumerate(cluster_boxes):
56
+ xx1 = max(all_boxes[i, 0], c_box[0])
57
+ yy1 = max(all_boxes[i, 1], c_box[1])
58
+ xx2 = min(all_boxes[i, 2], c_box[2])
59
+ yy2 = min(all_boxes[i, 3], c_box[3])
60
+ inter = max(0, xx2 - xx1) * max(0, yy2 - yy1)
61
+ a1 = (all_boxes[i, 2] - all_boxes[i, 0]) * (all_boxes[i, 3] - all_boxes[i, 1])
62
+ a2 = (c_box[2] - c_box[0]) * (c_box[3] - c_box[1])
63
+ iou = inter / (a1 + a2 - inter + 1e-9)
64
+ if iou > best_iou:
65
+ best_iou = iou
66
+ matched = c_idx
67
+ if matched >= 0:
68
+ clusters[matched].append(i)
69
+ idxs = clusters[matched]
70
+ weights = all_scores[idxs]
71
+ w_sum = weights.sum()
72
+ cluster_boxes[matched] = (all_boxes[idxs] * weights[:, None]).sum(0) / w_sum
73
+ else:
74
+ clusters.append([i])
75
+ cluster_boxes.append(all_boxes[i].copy())
76
+
77
+ fused_boxes, fused_scores = [], []
78
+ for c_idx, idxs in enumerate(clusters):
79
+ weights = all_scores[idxs]
80
+ fused_boxes.append(cluster_boxes[c_idx])
81
+ fused_scores.append(weights.sum() / n_models)
 
 
 
 
 
 
 
 
82
 
83
  if not fused_boxes:
84
+ return np.empty((0, 4)), np.empty(0)
85
+ return np.array(fused_boxes), np.array(fused_scores)
86
 
87
 
88
  class BoundingBox(BaseModel):
 
103
  class Miner:
104
  def __init__(self, path_hf_repo: Path) -> None:
105
  self.path_hf_repo = path_hf_repo
106
+ self.class_names = ['person']
107
  self.session = ort.InferenceSession(
108
  str(path_hf_repo / "weights.onnx"),
109
  providers=["CUDAExecutionProvider", "CPUExecutionProvider"],
110
  )
111
  self.input_name = self.session.get_inputs()[0].name
112
+ input_shape = self.session.get_inputs()[0].shape
113
+ self.input_h = int(input_shape[2])
114
+ self.input_w = int(input_shape[3])
115
  self.conf_threshold = CONF_THRESH
116
  self.iou_threshold = IOU_THRESH
117
 
118
  def __repr__(self) -> str:
119
+ return f"DetectPerson Miner v4 TTA+WBF session={type(self.session).__name__}"
120
+
121
+ def _preprocess(self, image_bgr: ndarray) -> tuple[np.ndarray, tuple[int, int]]:
122
+ h, w = image_bgr.shape[:2]
123
+ rgb = cv2.cvtColor(image_bgr, cv2.COLOR_BGR2RGB)
124
+ resized = cv2.resize(rgb, (self.input_w, self.input_h))
125
+ x = resized.astype(np.float32) / 255.0
126
+ x = np.transpose(x, (2, 0, 1))[None, ...]
127
+ return x, (h, w)
128
+
129
+ def _decode_raw(self, raw: np.ndarray, orig_h: int, orig_w: int
130
+ ) -> tuple[np.ndarray, np.ndarray]:
 
 
 
 
 
 
 
 
 
 
 
 
131
  pred = raw[0]
132
+ if pred.ndim != 2:
133
+ return np.empty((0, 4)), np.empty(0)
134
  if pred.shape[0] < pred.shape[1]:
135
+ pred = pred.transpose(1, 0)
136
+ if pred.shape[1] < 5:
137
+ return np.empty((0, 4)), np.empty(0)
138
+
139
+ boxes = pred[:, :4]
140
  cls_scores = pred[:, 4:]
141
+ if cls_scores.shape[1] == 0:
142
+ return np.empty((0, 4)), np.empty(0)
143
+
144
  confs = np.max(cls_scores, axis=1)
145
+ keep = confs >= self.conf_threshold
146
+ boxes, confs = boxes[keep], confs[keep]
147
+ if boxes.shape[0] == 0:
148
+ return np.empty((0, 4)), np.empty(0)
149
+
150
+ sx = orig_w / float(self.input_w)
151
+ sy = orig_h / float(self.input_h)
152
+ cx, cy, bw, bh = boxes[:, 0], boxes[:, 1], boxes[:, 2], boxes[:, 3]
153
+ x1 = np.clip((cx - bw / 2) * sx, 0, orig_w)
154
+ y1 = np.clip((cy - bh / 2) * sy, 0, orig_h)
155
+ x2 = np.clip((cx + bw / 2) * sx, 0, orig_w)
156
+ y2 = np.clip((cy + bh / 2) * sy, 0, orig_h)
157
+ return np.stack([x1, y1, x2, y2], axis=1), confs
158
+
159
+ def _run_single_pass(self, image_bgr: ndarray) -> tuple[np.ndarray, np.ndarray]:
160
  orig_h, orig_w = image_bgr.shape[:2]
161
+ inp, _ = self._preprocess(image_bgr)
162
  raw = self.session.run(None, {self.input_name: inp})[0]
163
+ return self._decode_raw(raw, orig_h, orig_w)
164
 
165
  def _infer_single(self, image_bgr: ndarray) -> list[BoundingBox]:
166
  orig_h, orig_w = image_bgr.shape[:2]
167
 
168
+ all_boxes, all_scores = [], []
169
 
170
+ def _collect(boxes, confs):
171
  if len(boxes) == 0:
172
  return
 
173
  norm = boxes.copy()
174
  norm[:, [0, 2]] /= orig_w
175
  norm[:, [1, 3]] /= orig_h
176
  norm = np.clip(norm, 0, 1)
177
  all_boxes.append(norm)
178
  all_scores.append(confs)
 
179
 
180
  # Pass 1: original
181
  _collect(*self._run_single_pass(image_bgr))
182
 
183
  # Pass 2: horizontal flip
184
  flipped = cv2.flip(image_bgr, 1)
185
+ boxes_f, confs_f = self._run_single_pass(flipped)
186
  if len(boxes_f):
187
  boxes_f[:, 0], boxes_f[:, 2] = orig_w - boxes_f[:, 2], orig_w - boxes_f[:, 0]
188
+ _collect(boxes_f, confs_f)
189
 
190
  # Pass 3: 1.2x scale center crop
191
  sh, sw = int(orig_h * TTA_SCALE), int(orig_w * TTA_SCALE)
192
  scaled = cv2.resize(image_bgr, (sw, sh), interpolation=cv2.INTER_LINEAR)
193
  yo, xo = (sh - orig_h) // 2, (sw - orig_w) // 2
194
  cropped = scaled[yo:yo + orig_h, xo:xo + orig_w]
195
+ boxes_s, confs_s = self._run_single_pass(cropped)
196
  if len(boxes_s):
197
  boxes_s[:, 0] = (boxes_s[:, 0] + xo) / TTA_SCALE
198
  boxes_s[:, 1] = (boxes_s[:, 1] + yo) / TTA_SCALE
199
  boxes_s[:, 2] = (boxes_s[:, 2] + xo) / TTA_SCALE
200
  boxes_s[:, 3] = (boxes_s[:, 3] + yo) / TTA_SCALE
201
  boxes_s = np.clip(boxes_s, 0, [[orig_w, orig_h, orig_w, orig_h]])
202
+ _collect(boxes_s, confs_s)
203
 
204
  if not all_boxes:
205
  return []
206
 
207
+ fused_boxes, fused_scores = _wbf(
208
+ all_boxes, all_scores,
209
  iou_thr=WBF_IOU_THR, skip_box_thr=WBF_SKIP_THR,
210
  )
211
  if len(fused_boxes) == 0:
 
223
  y1=max(0, min(orig_h, math.floor(b[1]))),
224
  x2=max(0, min(orig_w, math.ceil(b[2]))),
225
  y2=max(0, min(orig_h, math.ceil(b[3]))),
226
+ cls_id=0,
227
  conf=max(0.0, min(1.0, float(fused_scores[i]))),
228
  ))
229
  return out
model_type.json CHANGED
@@ -1 +1 @@
1
- {"task_type": "object-detection", "model_type": "yolov11-small", "deploy": "2026-03-26T07:43Z"}
 
1
+ {"task_type": "object-detection", "model_type": "yolov11-nano", "deploy": "2026-03-26T07:46Z"}
weights.onnx CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:e3916408ec21f8c94358c18914f922814770b78557e52fe17ff7a9ee74339a5a
3
- size 19272252
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f32ed65b9024a69693f675d494c7fc813a964766c54b241464a463377342da60
3
+ size 5607862