scorevision: push artifact
Browse files
miner.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
"""
|
| 2 |
-
Score Vision SN44 β Unified miner v3.
|
| 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.
|
| 215 |
-
2: 0.45, # truck β
|
| 216 |
-
3: 0.
|
| 217 |
-
0: 0.45, # bus β
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 =
|
| 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:
|
| 800 |
-
|
| 801 |
-
Pipeline (v3.
|
| 802 |
-
1.
|
| 803 |
-
2.
|
| 804 |
-
3.
|
| 805 |
-
4. Per-class
|
| 806 |
-
5.
|
|
|
|
| 807 |
"""
|
| 808 |
oh, ow = image_bgr.shape[:2]
|
| 809 |
|
| 810 |
-
#
|
| 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 |
-
|
| 1431 |
-
|
| 1432 |
-
|
| 1433 |
-
|
| 1434 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1435 |
"""
|
| 1436 |
if len(boxes) == 0:
|
| 1437 |
return np.empty((0, 4)), np.empty(0)
|
| 1438 |
|
| 1439 |
-
|
| 1440 |
-
|
| 1441 |
-
|
| 1442 |
-
|
| 1443 |
-
|
| 1444 |
-
keep_b, keep_s = [], []
|
| 1445 |
-
suppressed = set()
|
| 1446 |
|
| 1447 |
-
for i in range(
|
| 1448 |
-
|
| 1449 |
-
|
| 1450 |
-
|
| 1451 |
-
|
| 1452 |
-
|
| 1453 |
-
#
|
| 1454 |
-
|
| 1455 |
-
|
| 1456 |
-
|
| 1457 |
-
|
| 1458 |
-
|
| 1459 |
-
|
| 1460 |
-
|
| 1461 |
-
|
| 1462 |
-
|
| 1463 |
-
|
|
|
|
|
|
|
|
|
|
| 1464 |
iou = inter / (a1 + a2 - inter + 1e-9)
|
| 1465 |
-
if iou >
|
| 1466 |
-
|
| 1467 |
|
| 1468 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 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 |
-
#
|
|
|
|
| 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]
|