v3 weights (yolo11s, 204k merged plate dataset, val mAP50=0.944)
Browse files
miner.py
CHANGED
|
@@ -359,6 +359,51 @@ class Miner:
|
|
| 359 |
dets.append((xa, ya, xb, yb, float(confs[i]), int(cls_ids[i])))
|
| 360 |
return dets
|
| 361 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 362 |
def _quad4_raw_dets(
|
| 363 |
self,
|
| 364 |
image_bgr: ndarray,
|
|
@@ -415,6 +460,13 @@ class Miner:
|
|
| 415 |
(orig_w - x2f, y1, orig_w - x1f, y2, conf, cls_id)
|
| 416 |
)
|
| 417 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 418 |
dets = self._soft_nms(all_dets)
|
| 419 |
|
| 420 |
out_boxes: list[BoundingBox] = []
|
|
|
|
| 359 |
dets.append((xa, ya, xb, yb, float(confs[i]), int(cls_ids[i])))
|
| 360 |
return dets
|
| 361 |
|
| 362 |
+
def _cluster_dedup(
|
| 363 |
+
self,
|
| 364 |
+
dets: list[tuple[float, float, float, float, float, int]],
|
| 365 |
+
iou_thresh: float = 0.5,
|
| 366 |
+
) -> list[tuple[float, float, float, float, float, int]]:
|
| 367 |
+
"""Greedy near-duplicate suppression — for any pair with IoU >=
|
| 368 |
+
``iou_thresh``, keep only the higher-conf detection.
|
| 369 |
+
|
| 370 |
+
Purpose: collapse TTA-induced duplicates of the same plate before
|
| 371 |
+
Soft-NMS, which would otherwise decay (but not kill) the lower-conf
|
| 372 |
+
copy, leaving multiple boxes per plate past our low score_threshold.
|
| 373 |
+
Mirrors the TTA-cluster-merge step in alfred8995/arabic000's miner.py.
|
| 374 |
+
|
| 375 |
+
Applied on *every* call (not just TTA) because the quad-4 overlap
|
| 376 |
+
band can also produce near-duplicate detections near tile seams.
|
| 377 |
+
IoU threshold 0.5 is loose enough that adjacent-but-distinct plates
|
| 378 |
+
(IoU < 0.5) stay separate; tight enough that same-plate variants
|
| 379 |
+
(IoU > 0.9 in practice) collapse.
|
| 380 |
+
"""
|
| 381 |
+
if not dets:
|
| 382 |
+
return []
|
| 383 |
+
# Sort by conf desc (index 4)
|
| 384 |
+
srt = sorted(dets, key=lambda d: -d[4])
|
| 385 |
+
kept: list[tuple[float, float, float, float, float, int]] = []
|
| 386 |
+
suppressed = [False] * len(srt)
|
| 387 |
+
for i in range(len(srt)):
|
| 388 |
+
if suppressed[i]:
|
| 389 |
+
continue
|
| 390 |
+
x1i, y1i, x2i, y2i = srt[i][0], srt[i][1], srt[i][2], srt[i][3]
|
| 391 |
+
area_i = max(0.0, x2i - x1i) * max(0.0, y2i - y1i)
|
| 392 |
+
kept.append(srt[i])
|
| 393 |
+
for j in range(i + 1, len(srt)):
|
| 394 |
+
if suppressed[j]:
|
| 395 |
+
continue
|
| 396 |
+
x1j, y1j, x2j, y2j = srt[j][0], srt[j][1], srt[j][2], srt[j][3]
|
| 397 |
+
ix1 = max(x1i, x1j); iy1 = max(y1i, y1j)
|
| 398 |
+
ix2 = min(x2i, x2j); iy2 = min(y2i, y2j)
|
| 399 |
+
iw = max(0.0, ix2 - ix1); ih = max(0.0, iy2 - iy1)
|
| 400 |
+
inter = iw * ih
|
| 401 |
+
area_j = max(0.0, x2j - x1j) * max(0.0, y2j - y1j)
|
| 402 |
+
union = area_i + area_j - inter
|
| 403 |
+
if union > 0 and inter / union >= iou_thresh:
|
| 404 |
+
suppressed[j] = True
|
| 405 |
+
return kept
|
| 406 |
+
|
| 407 |
def _quad4_raw_dets(
|
| 408 |
self,
|
| 409 |
image_bgr: ndarray,
|
|
|
|
| 460 |
(orig_w - x2f, y1, orig_w - x1f, y2, conf, cls_id)
|
| 461 |
)
|
| 462 |
|
| 463 |
+
# TTA-aware cluster-dedup: collapse near-duplicate detections of the
|
| 464 |
+
# same plate (e.g. original + unflipped TTA view) BEFORE Soft-NMS,
|
| 465 |
+
# which would otherwise decay but not kill the lower-conf copy at
|
| 466 |
+
# our low score_threshold=0.01. Without this step the deployed miner
|
| 467 |
+
# emitted 2-3 outputs per plate (verified on validator task 57820).
|
| 468 |
+
all_dets = self._cluster_dedup(all_dets, iou_thresh=0.5)
|
| 469 |
+
|
| 470 |
dets = self._soft_nms(all_dets)
|
| 471 |
|
| 472 |
out_boxes: list[BoundingBox] = []
|