meaculpitt commited on
Commit
a39ad03
Β·
verified Β·
1 Parent(s): 6b9d0d6

scorevision: push artifact

Browse files
Files changed (1) hide show
  1. miner.py +87 -52
miner.py CHANGED
@@ -1,5 +1,5 @@
1
  """
2
- Score Vision SN44 β€” Unified miner v3.21 (2026-04-04). YOLO12s + TRT + bus fix + petrol.
3
  Tri-model: vehicle (YOLO11m INT8 1280) + person (YOLO12s FP16 960 TRT) + petrol (end2end 640).
4
  Pose model: YOLOv8n-pose FP16 640 for false-positive filtering + keypoint box refinement.
5
  Vehicle weights loaded from secondary HF repo (meaculpitt/ScoreVision-Vehicle).
@@ -211,10 +211,10 @@ VEH_NMS_IOU = 0.50
211
  # ── Per-class vehicle confidence thresholds (output cls_id) ────────────────
212
  # Raising from uniform 0.35: reduces FP (avg 4.1 FFPI β†’ target <2.0)
213
  VEH_CLASS_CONF: dict[int, float] = {
214
- 1: 0.45, # car β€” most FP-prone class (75% of training data, overconfident)
215
- 2: 0.45, # truck β€” moderate raise
216
- 3: 0.45, # motorcycle β€” raised from 0.35 to reduce FP (small targets, easy to miss)
217
- 0: 0.45, # bus β€” same threshold as car/truck (v3.20: unsuppressed)
218
  }
219
 
220
  # ── Per-class vehicle aspect ratio bounds (min_ratio, max_ratio) ───────────
@@ -269,8 +269,12 @@ VEH_PARTS_PLATE_MIN_PX = 120 # only check plates on medium+ vehicles # Min
269
  VEH_PARTS_PLATE_CONF = 0.35 # Min plate detection confidence
270
 
271
  # ── Person config (TTA consensus) ───────────────────────────────────────────
272
- PER_CONF_LOW = 0.55
273
- PER_CONF_HIGH = 0.58
 
 
 
 
274
  PER_CONSENSUS_IOU = 0.50
275
  PER_RTF_BUDGET = 8.0
276
 
@@ -285,7 +289,14 @@ PER_TILE_OVERLAP = 0.20 # 20% overlap between tiles
285
  PER_TILE_MIN_DIM_RATIO = 1.15 # tile when image dim > model_dim * this (~1104px for 960 model)
286
  PER_TILE_CONF = 0.55 # raised from 0.40 to match PER_CONF_LOW
287
  PER_NMS_IOU = 0.50 # NMS IoU for merging across passes (max-conf wins)
288
- PER_MAX_DET = 30 # hard cap on person detections per image (raised from 15: 17% of frames were hitting cap)
 
 
 
 
 
 
 
289
 
290
  # ── Frame quality gating (Laplacian variance) ───────────────────────────────
291
  PER_BLUR_THRESHOLD = 50.0 # Laplacian variance below this = severely blurry
@@ -350,7 +361,7 @@ TRT_WORKSPACE_GB = 4
350
  WBF_SKIP_THR = 0.0001
351
 
352
  # ── Speed config ────────────────────────────────────────────────────────────
353
- ENABLE_TTA = False
354
  ENABLE_PARALLEL = True
355
 
356
  # ── Secondary HF repo for vehicle weights ───────────────────────────────────
@@ -796,20 +807,35 @@ class Miner:
796
  return self._veh_decode(raw, ratio, pl, pt, ow, oh, conf_thresh)
797
 
798
  def _infer_vehicle(self, image_bgr):
799
- """Vehicle detection: single-pass 1280px, per-class NMS + confidence + aspect filter.
800
-
801
- Pipeline (v3.19 β€” TTA removed for RTF, saves ~9ms/frame Γ— 274 frames):
802
- 1. Single pass at VEH_CONF_THRES
803
- 2. Remap classes, per-class NMS
804
- 3. Per-class confidence filter (higher thresholds reduce FP)
805
- 4. Per-class aspect ratio filter
806
- 5. All 4 classes scored (v3.20: bus unsuppressed, cls_id=0)
 
807
  """
808
  oh, ow = image_bgr.shape[:2]
809
 
810
- # Single pass β€” flip TTA removed in v3.19 (RTF 0.89β†’0.65 for 274 frames)
811
  boxes, confs, cls_ids = self._veh_run_pass(image_bgr, VEH_CONF_THRES)
812
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
813
  if len(boxes) == 0:
814
  return []
815
 
@@ -1427,45 +1453,53 @@ class Miner:
1427
  return boxes, confs
1428
 
1429
  @staticmethod
1430
- def _nms_max_conf(boxes, scores, iou_thr):
1431
- """NMS that keeps max confidence when boxes overlap.
1432
-
1433
- Unlike WBF which averages scores (diluting strong detections),
1434
- this preserves sharp confidence values β€” critical for FP scoring.
 
 
 
 
1435
  """
1436
  if len(boxes) == 0:
1437
  return np.empty((0, 4)), np.empty(0)
1438
 
1439
- # Sort by confidence descending
1440
- order = np.argsort(-scores)
1441
- boxes = boxes[order]
1442
- scores = scores[order]
1443
-
1444
- keep_b, keep_s = [], []
1445
- suppressed = set()
1446
 
1447
- for i in range(len(boxes)):
1448
- if i in suppressed:
1449
- continue
1450
- keep_b.append(boxes[i])
1451
- keep_s.append(scores[i])
1452
-
1453
- # Suppress lower-conf overlapping boxes
1454
- for j in range(i + 1, len(boxes)):
1455
- if j in suppressed:
1456
- continue
1457
- xx1 = max(boxes[i, 0], boxes[j, 0])
1458
- yy1 = max(boxes[i, 1], boxes[j, 1])
1459
- xx2 = min(boxes[i, 2], boxes[j, 2])
1460
- yy2 = min(boxes[i, 3], boxes[j, 3])
1461
- inter = max(0, xx2 - xx1) * max(0, yy2 - yy1)
1462
- a1 = (boxes[i, 2] - boxes[i, 0]) * (boxes[i, 3] - boxes[i, 1])
1463
- a2 = (boxes[j, 2] - boxes[j, 0]) * (boxes[j, 3] - boxes[j, 1])
 
 
 
1464
  iou = inter / (a1 + a2 - inter + 1e-9)
1465
- if iou >= iou_thr:
1466
- suppressed.add(j)
1467
 
1468
- return np.array(keep_b), np.array(keep_s)
 
 
 
 
1469
 
1470
  # ── Pose FP filter + box refinement ──────────────────────────────────
1471
 
@@ -1844,7 +1878,7 @@ class Miner:
1844
  1. Full-image pass at native 960px
1845
  2. Flip TTA pass
1846
  3. Dynamic NMS merge (adapts IoU threshold to scene density)
1847
- 4. Sanity filters + PER_MAX_DET cap
1848
  5. Pose FP filter + box refinement (if time allows)
1849
  """
1850
  oh, ow = image_bgr.shape[:2]
@@ -1894,7 +1928,8 @@ class Miner:
1894
  nms_iou = 0.60 if n_raw > 30 else (0.40 if n_raw < 10 else PER_NMS_IOU)
1895
  merged_b, merged_s = self._nms_max_conf(merged_b, merged_s, nms_iou)
1896
 
1897
- # Hard cap on max detections (FP protection)
 
1898
  if len(merged_s) > PER_MAX_DET:
1899
  top_idx = np.argsort(merged_s)[-PER_MAX_DET:]
1900
  merged_b = merged_b[top_idx]
 
1
  """
2
+ Score Vision SN44 β€” Unified miner v3.22 (2026-04-04). YOLO12s + TRT + petrol + veh TTA + conf tune + soft-NMS + PER_MAX_DET=100 (loose safety ceiling) + PER_CONF_LOW=0.60 (precision floor).
3
  Tri-model: vehicle (YOLO11m INT8 1280) + person (YOLO12s FP16 960 TRT) + petrol (end2end 640).
4
  Pose model: YOLOv8n-pose FP16 640 for false-positive filtering + keypoint box refinement.
5
  Vehicle weights loaded from secondary HF repo (meaculpitt/ScoreVision-Vehicle).
 
211
  # ── Per-class vehicle confidence thresholds (output cls_id) ────────────────
212
  # Raising from uniform 0.35: reduces FP (avg 4.1 FFPI β†’ target <2.0)
213
  VEH_CLASS_CONF: dict[int, float] = {
214
+ 1: 0.50, # car β€” raised from 0.45, most FP-prone class (75% of training data)
215
+ 2: 0.45, # truck β€” keep
216
+ 3: 0.50, # motorcycle β€” raised from 0.45, small targets prone to FP
217
+ 0: 0.45, # bus β€” keep
218
  }
219
 
220
  # ── Per-class vehicle aspect ratio bounds (min_ratio, max_ratio) ───────────
 
269
  VEH_PARTS_PLATE_CONF = 0.35 # Min plate detection confidence
270
 
271
  # ── Person config (TTA consensus) ───────────────────────────────────────────
272
+ PER_CONF_LOW = 0.60 # Was 0.55. Raised 2026-04-05 to match top peer precision floor after
273
+ # observing the 3-way tied 52-box group (conf_min=0.585, composite=0.280) was
274
+ # beaten by top peer's 44-box response (conf_min=0.716, composite=0.377).
275
+ # 0.60 targets the precision/recall inflection point without the full 0.65+
276
+ # aggression that might cost recall on sparse scenes.
277
+ PER_CONF_HIGH = 0.58 # NOTE: dead code, not referenced anywhere. Kept for reference only.
278
  PER_CONSENSUS_IOU = 0.50
279
  PER_RTF_BUDGET = 8.0
280
 
 
289
  PER_TILE_MIN_DIM_RATIO = 1.15 # tile when image dim > model_dim * this (~1104px for 960 model)
290
  PER_TILE_CONF = 0.55 # raised from 0.40 to match PER_CONF_LOW
291
  PER_NMS_IOU = 0.50 # NMS IoU for merging across passes (max-conf wins)
292
+ PER_MAX_DET = 100 # Loose safety ceiling ONLY β€” not a count cap. Strategy is confidence-floor:
293
+ # PER_CONF_LOW=0.60 is the real filter; any box above threshold passes.
294
+ # Raised from 50 after 2026-04-05 investigation: top peers emit 77+ boxes on
295
+ # crowd eval images, and the currently-running chute (rev 6b9d0d6) caps at 30
296
+ # which is demonstrably hitting mAP50 0.39 on person crowd blocks. 50 would
297
+ # still clip. 100 gives real headroom β€” only triggers on pathological runaway
298
+ # FP cases where NMS has already failed. Previous values (10 spec'd, 50 first
299
+ # fix) were too tight. See FAILURE_ANALYSIS.md (2026-04-05).
300
 
301
  # ── Frame quality gating (Laplacian variance) ───────────────────────────────
302
  PER_BLUR_THRESHOLD = 50.0 # Laplacian variance below this = severely blurry
 
361
  WBF_SKIP_THR = 0.0001
362
 
363
  # ── Speed config ────────────────────────────────────────────────────────────
364
+ ENABLE_TTA = True
365
  ENABLE_PARALLEL = True
366
 
367
  # ── Secondary HF repo for vehicle weights ───────────────────────────────────
 
807
  return self._veh_decode(raw, ratio, pl, pt, ow, oh, conf_thresh)
808
 
809
  def _infer_vehicle(self, image_bgr):
810
+ """Vehicle detection: 1280px with optional flip TTA, per-class NMS + conf + aspect filter.
811
+
812
+ Pipeline (v3.22 β€” flip TTA re-enabled, RTF budget allows it):
813
+ 1. Primary pass at VEH_CONF_THRES
814
+ 2. Optional flip TTA pass at VEH_TTA_CONF (if ENABLE_TTA)
815
+ 3. Remap classes, per-class NMS
816
+ 4. Per-class confidence filter (higher thresholds reduce FP)
817
+ 5. Per-class aspect ratio filter
818
+ 6. All 4 classes scored (v3.20: bus unsuppressed, cls_id=0)
819
  """
820
  oh, ow = image_bgr.shape[:2]
821
 
822
+ # Primary pass
823
  boxes, confs, cls_ids = self._veh_run_pass(image_bgr, VEH_CONF_THRES)
824
 
825
+ # Flip TTA pass β€” horizontal flip, mirror boxes back
826
+ if ENABLE_TTA:
827
+ flipped = cv2.flip(image_bgr, 1)
828
+ f_boxes, f_confs, f_cls = self._veh_run_pass(flipped, VEH_TTA_CONF)
829
+ if len(f_boxes) > 0:
830
+ # Mirror x-coords: x1'=ow-x2, x2'=ow-x1
831
+ f_boxes[:, 0], f_boxes[:, 2] = ow - f_boxes[:, 2], ow - f_boxes[:, 0]
832
+ if len(boxes) > 0:
833
+ boxes = np.concatenate([boxes, f_boxes])
834
+ confs = np.concatenate([confs, f_confs])
835
+ cls_ids = np.concatenate([cls_ids, f_cls])
836
+ else:
837
+ boxes, confs, cls_ids = f_boxes, f_confs, f_cls
838
+
839
  if len(boxes) == 0:
840
  return []
841
 
 
1453
  return boxes, confs
1454
 
1455
  @staticmethod
1456
+ @staticmethod
1457
+ def _nms_max_conf(boxes, scores, iou_thr, sigma=0.5, min_conf=0.20):
1458
+ """Soft-NMS with Gaussian decay (replaces hard NMS).
1459
+
1460
+ Instead of suppressing overlapping boxes entirely, decays their
1461
+ confidence: score_j *= exp(-(iou^2) / sigma). This preserves
1462
+ partially-occluded detections in crowds while still penalising
1463
+ duplicates. Boxes whose confidence decays below min_conf are
1464
+ removed.
1465
  """
1466
  if len(boxes) == 0:
1467
  return np.empty((0, 4)), np.empty(0)
1468
 
1469
+ b = boxes.copy().astype(np.float64)
1470
+ s = scores.copy().astype(np.float64)
1471
+ n = len(s)
1472
+ indices = list(range(n))
 
 
 
1473
 
1474
+ for i in range(n):
1475
+ # Find current max-confidence box
1476
+ max_idx = i
1477
+ for j in range(i + 1, n):
1478
+ if s[indices[j]] > s[indices[max_idx]]:
1479
+ max_idx = j
1480
+ # Swap to front
1481
+ indices[i], indices[max_idx] = indices[max_idx], indices[i]
1482
+
1483
+ ix = indices[i]
1484
+ # Decay overlapping boxes
1485
+ for j in range(i + 1, n):
1486
+ jx = indices[j]
1487
+ xx1 = max(b[ix, 0], b[jx, 0])
1488
+ yy1 = max(b[ix, 1], b[jx, 1])
1489
+ xx2 = min(b[ix, 2], b[jx, 2])
1490
+ yy2 = min(b[ix, 3], b[jx, 3])
1491
+ inter = max(0.0, xx2 - xx1) * max(0.0, yy2 - yy1)
1492
+ a1 = (b[ix, 2] - b[ix, 0]) * (b[ix, 3] - b[ix, 1])
1493
+ a2 = (b[jx, 2] - b[jx, 0]) * (b[jx, 3] - b[jx, 1])
1494
  iou = inter / (a1 + a2 - inter + 1e-9)
1495
+ if iou > 0:
1496
+ s[jx] *= np.exp(-(iou * iou) / sigma)
1497
 
1498
+ # Keep boxes above min_conf
1499
+ keep = [indices[i] for i in range(n) if s[indices[i]] >= min_conf]
1500
+ if not keep:
1501
+ return np.empty((0, 4)), np.empty(0)
1502
+ return b[keep], s[keep]
1503
 
1504
  # ── Pose FP filter + box refinement ──────────────────────────────────
1505
 
 
1878
  1. Full-image pass at native 960px
1879
  2. Flip TTA pass
1880
  3. Dynamic NMS merge (adapts IoU threshold to scene density)
1881
+ 4. Sanity filters + PER_MAX_DET safety ceiling (conf-floor is the real filter)
1882
  5. Pose FP filter + box refinement (if time allows)
1883
  """
1884
  oh, ow = image_bgr.shape[:2]
 
1928
  nms_iou = 0.60 if n_raw > 30 else (0.40 if n_raw < 10 else PER_NMS_IOU)
1929
  merged_b, merged_s = self._nms_max_conf(merged_b, merged_s, nms_iou)
1930
 
1931
+ # Safety ceiling (not a count cap). PER_CONF_LOW=0.60 is the real filter.
1932
+ # This only activates on pathological runaway-FP cases (>50 boxes post-NMS).
1933
  if len(merged_s) > PER_MAX_DET:
1934
  top_idx = np.argsort(merged_s)[-PER_MAX_DET:]
1935
  merged_b = merged_b[top_idx]