alfred8995 commited on
Commit
e75579a
·
verified ·
1 Parent(s): b0ab1a9

Upload folder using huggingface_hub

Browse files
Files changed (3) hide show
  1. chute_config.yml +21 -0
  2. miner.py +496 -0
  3. weights.onnx +3 -0
chute_config.yml ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Image:
2
+ from_base: parachutes/python:3.12
3
+ run_command:
4
+ - pip install --upgrade setuptools wheel
5
+ - pip install 'numpy>=1.23' 'onnxruntime-gpu[cuda,cudnn]>=1.16' 'opencv-python>=4.7' 'pillow>=9.5' 'huggingface_hub>=0.19.4' 'pydantic>=2.0' 'pyyaml>=6.0' 'aiohttp>=3.9'
6
+ - pip install torch torchvision
7
+
8
+ NodeSelector:
9
+ gpu_count: 1
10
+ min_vram_gb_per_gpu: 16
11
+ max_hourly_price_per_gpu: 2
12
+ include:
13
+ - pro_6000
14
+
15
+ Chute:
16
+ timeout_seconds: 900
17
+ concurrency: 4
18
+ max_instances: 5
19
+ scaling_threshold: 0.5
20
+ shutdown_after_seconds: 288000
21
+ tee: true
miner.py ADDED
@@ -0,0 +1,496 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pathlib import Path
2
+ import math
3
+
4
+ import cv2
5
+ import numpy as np
6
+ import onnxruntime as ort
7
+ from numpy import ndarray
8
+ from pydantic import BaseModel
9
+
10
+
11
+ class BoundingBox(BaseModel):
12
+ x1: int
13
+ y1: int
14
+ x2: int
15
+ y2: int
16
+ cls_id: int
17
+ conf: float
18
+
19
+
20
+ class TVFrameResult(BaseModel):
21
+ frame_id: int
22
+ boxes: list[BoundingBox]
23
+ keypoints: list[tuple[int, int]]
24
+
25
+
26
+ class Miner:
27
+ """ONNX Runtime miner for single-class road-sign detection.
28
+ Pipeline: letterbox → ORT → conf filter (+ rescue) → unletterbox →
29
+ sanity filter → per-class hard NMS → cap max_det.
30
+ """
31
+
32
+ class_names = ["road_sign"]
33
+ _model_class_order = ["road_sign"]
34
+
35
+ iou_thres = 0.5
36
+ max_det = 150
37
+
38
+ _conf_thres_array = np.array([0.28], dtype=np.float32)
39
+ _bonus_array = np.array([0.1], dtype=np.float32)
40
+
41
+ min_box_area = 8 * 8
42
+ min_side = 3
43
+ max_aspect_ratio = 12.0
44
+
45
+ def __init__(self, path_hf_repo: Path) -> None:
46
+ model_path = path_hf_repo / "weights.onnx"
47
+ print("ORT version:", ort.__version__)
48
+
49
+ try:
50
+ ort.preload_dlls()
51
+ print("✅ onnxruntime.preload_dlls() success")
52
+ except Exception as e:
53
+ print(f"⚠️ preload_dlls failed: {e}")
54
+
55
+ print("ORT available providers BEFORE session:", ort.get_available_providers())
56
+
57
+ sess_options = ort.SessionOptions()
58
+ sess_options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
59
+ sess_options.intra_op_num_threads = 2
60
+ sess_options.inter_op_num_threads = 1
61
+ sess_options.execution_mode = ort.ExecutionMode.ORT_SEQUENTIAL
62
+
63
+ self.session = ort.InferenceSession(
64
+ str(model_path),
65
+ sess_options=sess_options,
66
+ providers=["CPUExecutionProvider"],
67
+ )
68
+ print("ORT session providers:", self.session.get_providers())
69
+
70
+ model_class_order = self._read_model_class_order()
71
+ if model_class_order is None:
72
+ model_class_order = list(self._model_class_order)
73
+ print(f"cls order: no usable ONNX metadata, FALLBACK {model_class_order}")
74
+ else:
75
+ print(f"cls order: from ONNX metadata {model_class_order}")
76
+ self.cls_remap = np.array(
77
+ [self.class_names.index(n) for n in model_class_order],
78
+ dtype=np.int32,
79
+ )
80
+
81
+ for inp in self.session.get_inputs():
82
+ print("INPUT:", inp.name, inp.shape, inp.type)
83
+ for out in self.session.get_outputs():
84
+ print("OUTPUT:", out.name, out.shape, out.type)
85
+
86
+ self.input_name = self.session.get_inputs()[0].name
87
+ self.output_names = [output.name for output in self.session.get_outputs()]
88
+ self.input_shape = self.session.get_inputs()[0].shape
89
+ self.input_height = self._safe_dim(self.input_shape[2], default=1280)
90
+ self.input_width = self._safe_dim(self.input_shape[3], default=1280)
91
+
92
+ print(f"✅ ONNX model loaded from: {model_path}")
93
+ print(f"✅ ONNX input: name={self.input_name}, shape={self.input_shape}")
94
+ print(f"✅ ONNX input size: {self.input_width}x{self.input_height}")
95
+ print(
96
+ "per-class conf: "
97
+ + ", ".join(
98
+ f"{n}={t:.3f}"
99
+ for n, t in zip(self.class_names, self._conf_thres_array.tolist())
100
+ )
101
+ )
102
+
103
+ self._warmup()
104
+
105
+ def _warmup(self, iters: int = 3) -> None:
106
+ try:
107
+ dummy = np.zeros((720, 1280, 3), dtype=np.uint8)
108
+ for _ in range(max(1, iters)):
109
+ self.predict_batch(batch_images=[dummy], offset=0, n_keypoints=0)
110
+ print(f"✅ warmup: {iters} dummy predict_batch call(s) done")
111
+ except Exception as e:
112
+ print(f"⚠️ warmup skipped: {e}")
113
+
114
+ def _read_model_class_order(self) -> list[str] | None:
115
+ try:
116
+ import ast
117
+
118
+ meta = self.session.get_modelmeta().custom_metadata_map
119
+ names = ast.literal_eval(meta["names"])
120
+ if isinstance(names, dict):
121
+ order = [str(names[i]) for i in sorted(names)]
122
+ else:
123
+ order = [str(n) for n in names]
124
+ except Exception as e:
125
+ print(f"cls order: could not read ONNX names metadata ({e})")
126
+ return None
127
+ if sorted(order) != sorted(self.class_names):
128
+ print(
129
+ f"cls order: ONNX names {order} do not match expected classes "
130
+ f"{self.class_names}; ignoring metadata"
131
+ )
132
+ return None
133
+ return order
134
+
135
+ def __repr__(self) -> str:
136
+ return (
137
+ f"ONNXRuntime(session={type(self.session).__name__}, "
138
+ f"providers={self.session.get_providers()})"
139
+ )
140
+
141
+ @staticmethod
142
+ def _safe_dim(value, default: int) -> int:
143
+ return value if isinstance(value, int) and value > 0 else default
144
+
145
+ def _letterbox(
146
+ self,
147
+ image: ndarray,
148
+ new_shape: tuple[int, int],
149
+ color=(114, 114, 114),
150
+ ) -> tuple[ndarray, float, tuple[float, float]]:
151
+ h, w = image.shape[:2]
152
+ new_w, new_h = new_shape
153
+
154
+ ratio = min(new_w / w, new_h / h)
155
+ resized_w = int(round(w * ratio))
156
+ resized_h = int(round(h * ratio))
157
+
158
+ if (resized_w, resized_h) != (w, h):
159
+ interp = cv2.INTER_CUBIC if ratio > 1.0 else cv2.INTER_LINEAR
160
+ image = cv2.resize(image, (resized_w, resized_h), interpolation=interp)
161
+
162
+ dw = (new_w - resized_w) / 2.0
163
+ dh = (new_h - resized_h) / 2.0
164
+ left = int(round(dw - 0.1))
165
+ right = int(round(dw + 0.1))
166
+ top = int(round(dh - 0.1))
167
+ bottom = int(round(dh + 0.1))
168
+
169
+ padded = cv2.copyMakeBorder(
170
+ image,
171
+ top,
172
+ bottom,
173
+ left,
174
+ right,
175
+ borderType=cv2.BORDER_CONSTANT,
176
+ value=color,
177
+ )
178
+ return padded, ratio, (dw, dh)
179
+
180
+ def _preprocess(
181
+ self, image: ndarray
182
+ ) -> tuple[np.ndarray, float, tuple[float, float], tuple[int, int]]:
183
+ orig_h, orig_w = image.shape[:2]
184
+ img, ratio, pad = self._letterbox(image, (self.input_width, self.input_height))
185
+ blob = cv2.dnn.blobFromImage(img, scalefactor=1.0 / 255.0, swapRB=True)
186
+ return blob, ratio, pad, (orig_w, orig_h)
187
+
188
+ @staticmethod
189
+ def _clip_boxes(boxes: np.ndarray, image_size: tuple[int, int]) -> np.ndarray:
190
+ w, h = image_size
191
+ boxes[:, 0] = np.clip(boxes[:, 0], 0, w - 1)
192
+ boxes[:, 1] = np.clip(boxes[:, 1], 0, h - 1)
193
+ boxes[:, 2] = np.clip(boxes[:, 2], 0, w - 1)
194
+ boxes[:, 3] = np.clip(boxes[:, 3], 0, h - 1)
195
+ return boxes
196
+
197
+ @staticmethod
198
+ def _xywh_to_xyxy(boxes: np.ndarray) -> np.ndarray:
199
+ out = np.empty_like(boxes)
200
+ out[:, 0] = boxes[:, 0] - boxes[:, 2] / 2.0
201
+ out[:, 1] = boxes[:, 1] - boxes[:, 3] / 2.0
202
+ out[:, 2] = boxes[:, 0] + boxes[:, 2] / 2.0
203
+ out[:, 3] = boxes[:, 1] + boxes[:, 3] / 2.0
204
+ return out
205
+
206
+ @staticmethod
207
+ def _hard_nms(
208
+ boxes: np.ndarray, scores: np.ndarray, iou_thresh: float
209
+ ) -> np.ndarray:
210
+ n = len(boxes)
211
+ if n == 0:
212
+ return np.array([], dtype=np.intp)
213
+ order = np.argsort(-scores)
214
+ keep: list[int] = []
215
+ while len(order) > 0:
216
+ i = int(order[0])
217
+ keep.append(i)
218
+ if len(order) == 1:
219
+ break
220
+ rest = order[1:]
221
+ xx1 = np.maximum(boxes[i, 0], boxes[rest, 0])
222
+ yy1 = np.maximum(boxes[i, 1], boxes[rest, 1])
223
+ xx2 = np.minimum(boxes[i, 2], boxes[rest, 2])
224
+ yy2 = np.minimum(boxes[i, 3], boxes[rest, 3])
225
+ inter = np.maximum(0.0, xx2 - xx1) * np.maximum(0.0, yy2 - yy1)
226
+ a_i = max(0.0, boxes[i, 2] - boxes[i, 0]) * max(
227
+ 0.0, boxes[i, 3] - boxes[i, 1]
228
+ )
229
+ a_r = np.maximum(0.0, boxes[rest, 2] - boxes[rest, 0]) * np.maximum(
230
+ 0.0, boxes[rest, 3] - boxes[rest, 1]
231
+ )
232
+ iou = inter / (a_i + a_r - inter + 1e-7)
233
+ order = rest[iou <= iou_thresh]
234
+ return np.array(keep, dtype=np.intp)
235
+
236
+ def _per_class_hard_nms(
237
+ self,
238
+ boxes: np.ndarray,
239
+ scores: np.ndarray,
240
+ cls_ids: np.ndarray,
241
+ iou_thresh: float,
242
+ ) -> np.ndarray:
243
+ if len(boxes) == 0:
244
+ return np.array([], dtype=np.intp)
245
+ all_keep: list[int] = []
246
+ for c in np.unique(cls_ids):
247
+ mask = cls_ids == c
248
+ indices = np.where(mask)[0]
249
+ keep = self._hard_nms(boxes[mask], scores[mask], iou_thresh)
250
+ all_keep.extend(indices[keep].tolist())
251
+ all_keep.sort()
252
+ return np.array(all_keep, dtype=np.intp)
253
+
254
+ def _conf_filter_mask(
255
+ self, scores: np.ndarray, cls_ids: np.ndarray
256
+ ) -> np.ndarray:
257
+ if len(scores) == 0:
258
+ return np.zeros(0, dtype=bool)
259
+ thr = self._conf_thres_array[cls_ids]
260
+ keep = scores >= thr
261
+ for c in np.unique(cls_ids):
262
+ b = float(self._bonus_array[c])
263
+ if b <= 0.0:
264
+ continue
265
+ cm = cls_ids == c
266
+ if keep[cm].any():
267
+ continue
268
+ idx = np.where(cm)[0]
269
+ top = int(idx[int(np.argmax(scores[idx]))])
270
+ if scores[top] >= self._conf_thres_array[c] - b:
271
+ keep[top] = True
272
+ return keep
273
+
274
+ def _filter_sane_boxes(
275
+ self,
276
+ boxes: np.ndarray,
277
+ scores: np.ndarray,
278
+ cls_ids: np.ndarray,
279
+ orig_size: tuple[int, int],
280
+ ) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
281
+ if len(boxes) == 0:
282
+ return boxes, scores, cls_ids
283
+ orig_w, orig_h = orig_size
284
+ image_area = float(orig_w * orig_h)
285
+ keep = []
286
+ for i, box in enumerate(boxes):
287
+ x1, y1, x2, y2 = box.tolist()
288
+ bw = x2 - x1
289
+ bh = y2 - y1
290
+ if bw <= 0 or bh <= 0:
291
+ continue
292
+ if bw < self.min_side or bh < self.min_side:
293
+ continue
294
+ area = bw * bh
295
+ if area < self.min_box_area:
296
+ continue
297
+ if area > 0.95 * image_area:
298
+ continue
299
+ ar = max(bw / max(bh, 1e-6), bh / max(bw, 1e-6))
300
+ if ar > self.max_aspect_ratio:
301
+ continue
302
+ keep.append(i)
303
+ if not keep:
304
+ return (
305
+ np.empty((0, 4), dtype=np.float32),
306
+ np.empty((0,), dtype=np.float32),
307
+ np.empty((0,), dtype=np.int32),
308
+ )
309
+ k = np.array(keep, dtype=np.intp)
310
+ return boxes[k], scores[k], cls_ids[k]
311
+
312
+ def _nms_and_cap(
313
+ self,
314
+ boxes: np.ndarray,
315
+ scores: np.ndarray,
316
+ cls_ids: np.ndarray,
317
+ ) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
318
+ if len(boxes) > 1:
319
+ keep = self._per_class_hard_nms(boxes, scores, cls_ids, self.iou_thres)
320
+ boxes, scores, cls_ids = boxes[keep], scores[keep], cls_ids[keep]
321
+ if len(scores) > self.max_det:
322
+ top = np.argsort(-scores)[: self.max_det]
323
+ boxes, scores, cls_ids = boxes[top], scores[top], cls_ids[top]
324
+ return boxes, scores, cls_ids
325
+
326
+ @staticmethod
327
+ def _build_results(
328
+ boxes: np.ndarray, scores: np.ndarray, cls_ids: np.ndarray
329
+ ) -> list[BoundingBox]:
330
+ results: list[BoundingBox] = []
331
+ for box, conf, cls_id in zip(boxes, scores, cls_ids):
332
+ x1, y1, x2, y2 = box.tolist()
333
+ if x2 <= x1 or y2 <= y1:
334
+ continue
335
+ results.append(
336
+ BoundingBox(
337
+ x1=int(math.floor(x1)),
338
+ y1=int(math.floor(y1)),
339
+ x2=int(math.ceil(x2)),
340
+ y2=int(math.ceil(y2)),
341
+ cls_id=int(cls_id),
342
+ conf=float(conf),
343
+ )
344
+ )
345
+ return results
346
+
347
+ def _decode_final_dets(
348
+ self,
349
+ preds: np.ndarray,
350
+ ratio: float,
351
+ pad: tuple[float, float],
352
+ orig_size: tuple[int, int],
353
+ ) -> list[BoundingBox]:
354
+ if preds.ndim == 3 and preds.shape[0] == 1:
355
+ preds = preds[0]
356
+ if preds.ndim != 2 or preds.shape[1] < 6:
357
+ raise ValueError(f"Unexpected ONNX final-det output shape: {preds.shape}")
358
+
359
+ boxes = preds[:, :4].astype(np.float32)
360
+ scores = preds[:, 4].astype(np.float32)
361
+ cls_ids = self.cls_remap[preds[:, 5].astype(np.int32)]
362
+
363
+ keep = self._conf_filter_mask(scores, cls_ids)
364
+ boxes, scores, cls_ids = boxes[keep], scores[keep], cls_ids[keep]
365
+ if len(boxes) == 0:
366
+ return []
367
+
368
+ pad_w, pad_h = pad
369
+ boxes[:, [0, 2]] -= pad_w
370
+ boxes[:, [1, 3]] -= pad_h
371
+ boxes /= ratio
372
+ boxes = self._clip_boxes(boxes, orig_size)
373
+
374
+ boxes, scores, cls_ids = self._filter_sane_boxes(
375
+ boxes, scores, cls_ids, orig_size
376
+ )
377
+ if len(boxes) == 0:
378
+ return []
379
+
380
+ boxes, scores, cls_ids = self._nms_and_cap(boxes, scores, cls_ids)
381
+ return self._build_results(boxes, scores, cls_ids)
382
+
383
+ def _decode_raw_yolo(
384
+ self,
385
+ preds: np.ndarray,
386
+ ratio: float,
387
+ pad: tuple[float, float],
388
+ orig_size: tuple[int, int],
389
+ ) -> list[BoundingBox]:
390
+ if preds.ndim != 3 or preds.shape[0] != 1:
391
+ raise ValueError(f"Unexpected raw ONNX output shape: {preds.shape}")
392
+ preds = preds[0]
393
+ if preds.shape[0] <= 16 and preds.shape[1] > preds.shape[0]:
394
+ preds = preds.T
395
+ if preds.ndim != 2 or preds.shape[1] < 5:
396
+ raise ValueError(f"Unexpected raw output shape: {preds.shape}")
397
+
398
+ boxes_xywh = preds[:, :4].astype(np.float32)
399
+ cls_part = preds[:, 4:].astype(np.float32)
400
+ if cls_part.shape[1] == 1:
401
+ scores = cls_part[:, 0]
402
+ cls_ids = np.zeros(len(scores), dtype=np.int32)
403
+ else:
404
+ cls_ids = np.argmax(cls_part, axis=1).astype(np.int32)
405
+ scores = cls_part[np.arange(len(cls_part)), cls_ids]
406
+ cls_ids = self.cls_remap[cls_ids]
407
+
408
+ keep = self._conf_filter_mask(scores, cls_ids)
409
+ boxes_xywh, scores, cls_ids = (
410
+ boxes_xywh[keep],
411
+ scores[keep],
412
+ cls_ids[keep],
413
+ )
414
+ if len(boxes_xywh) == 0:
415
+ return []
416
+ boxes = self._xywh_to_xyxy(boxes_xywh)
417
+
418
+ pad_w, pad_h = pad
419
+ boxes[:, [0, 2]] -= pad_w
420
+ boxes[:, [1, 3]] -= pad_h
421
+ boxes /= ratio
422
+ boxes = self._clip_boxes(boxes, orig_size)
423
+
424
+ boxes, scores, cls_ids = self._filter_sane_boxes(
425
+ boxes, scores, cls_ids, orig_size
426
+ )
427
+ if len(boxes) == 0:
428
+ return []
429
+
430
+ boxes, scores, cls_ids = self._nms_and_cap(boxes, scores, cls_ids)
431
+ return self._build_results(boxes, scores, cls_ids)
432
+
433
+ def _postprocess(
434
+ self,
435
+ output: np.ndarray,
436
+ ratio: float,
437
+ pad: tuple[float, float],
438
+ orig_size: tuple[int, int],
439
+ ) -> list[BoundingBox]:
440
+ if output.ndim == 2 and output.shape[1] >= 6:
441
+ return self._decode_final_dets(output, ratio, pad, orig_size)
442
+ if output.ndim == 3 and output.shape[0] == 1 and output.shape[2] == 6:
443
+ return self._decode_final_dets(output, ratio, pad, orig_size)
444
+ return self._decode_raw_yolo(output, ratio, pad, orig_size)
445
+
446
+ def _predict_single(self, image: np.ndarray) -> list[BoundingBox]:
447
+ if image is None:
448
+ raise ValueError("Input image is None")
449
+ if not isinstance(image, np.ndarray):
450
+ raise TypeError(f"Input is not numpy array: {type(image)}")
451
+ if image.ndim != 3:
452
+ raise ValueError(f"Expected HWC image, got shape={image.shape}")
453
+ if image.shape[0] <= 0 or image.shape[1] <= 0:
454
+ raise ValueError(f"Invalid image shape={image.shape}")
455
+ if image.shape[2] != 3:
456
+ raise ValueError(f"Expected 3 channels, got shape={image.shape}")
457
+ if image.dtype != np.uint8:
458
+ image = image.astype(np.uint8)
459
+
460
+ input_tensor, ratio, pad, orig_size = self._preprocess(image)
461
+ expected = (1, 3, self.input_height, self.input_width)
462
+ if input_tensor.shape != expected:
463
+ raise ValueError(
464
+ f"Bad input tensor shape={input_tensor.shape}, expected={expected}"
465
+ )
466
+
467
+ outputs = self.session.run(
468
+ self.output_names, {self.input_name: input_tensor}
469
+ )
470
+ return self._postprocess(outputs[0], ratio, pad, orig_size)
471
+
472
+ def predict_batch(
473
+ self,
474
+ batch_images: list[ndarray],
475
+ offset: int,
476
+ n_keypoints: int,
477
+ ) -> list[TVFrameResult]:
478
+ results: list[TVFrameResult] = []
479
+ for frame_number_in_batch, image in enumerate(batch_images):
480
+ try:
481
+ boxes = self._predict_single(image)
482
+ except Exception as e:
483
+ print(
484
+ f"⚠️ Inference failed for frame "
485
+ f"{offset + frame_number_in_batch}: {e}"
486
+ )
487
+ boxes = []
488
+ results.append(
489
+ TVFrameResult(
490
+ frame_id=offset + frame_number_in_batch,
491
+ boxes=boxes,
492
+ keypoints=[(0, 0) for _ in range(max(0, int(n_keypoints)))],
493
+ )
494
+ )
495
+ return results
496
+
weights.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c697a08fca5a49b68050eca673f6fd2876f13689f8b104b8d5ad4e47b86ca1b6
3
+ size 9859105