test2 / src /postprocessor.py
mlengineer01's picture
Update src/postprocessor.py
722a4cd verified
"""
postprocessor.py (Optimized)
----------------------------
Vectorized Non-Maximum Suppression (NMS) utilizing NumPy matrix operations.
Eliminates heavy Python O(N^2) loops for extreme execution speed.
"""
import numpy as np
from typing import List, Dict
class PostProcessor:
def __init__(
self,
iou_threshold: float = 0.3,
min_confidence: float = 0.4,
min_box_area: int = 16,
):
self.iou_threshold = iou_threshold
self.min_confidence = min_confidence
self.min_box_area = min_box_area
def _filter(self, detections: List[Dict]) -> List[Dict]:
filtered = []
for d in detections:
if d["confidence"] < self.min_confidence:
continue
x, y, w, h = d["bbox"]
if w * h < self.min_box_area or w <= 0 or h <= 0:
continue
filtered.append(d)
return filtered
def nms(self, detections: List[Dict]) -> List[Dict]:
"""Thuật toán Vectorized NMS xử lý hàng loạt box trùng lặp."""
candidates = self._filter(detections)
if not candidates:
return []
bboxes = np.array([c["bbox"] for c in candidates], dtype=np.float32)
scores = np.array([c["confidence"] for c in candidates], dtype=np.float32)
x1 = bboxes[:, 0]
y1 = bboxes[:, 1]
w = bboxes[:, 2]
h = bboxes[:, 3]
x2 = x1 + w
y2 = y1 + h
areas = w * h
order = scores.argsort()[::-1]
keep_indices = []
while order.size > 0:
i = order[0]
keep_indices.append(i)
if order.size == 1:
break
xx1 = np.maximum(x1[i], x1[order[1:]])
yy1 = np.maximum(y1[i], y1[order[1:]])
xx2 = np.minimum(x2[i], x2[order[1:]])
yy2 = np.minimum(y2[i], y2[order[1:]])
inter_w = np.maximum(0.0, xx2 - xx1)
inter_h = np.maximum(0.0, yy2 - yy1)
intersection = inter_w * inter_h
union = areas[i] + areas[order[1:]] - intersection
iou = np.where(union > 0, intersection / union, 0.0)
inds = np.where(iou <= self.iou_threshold)[0]
order = order[inds + 1]
return [candidates[idx] for idx in keep_indices]
def clamp_boxes(
self, detections: List[Dict], img_h: int, img_w: int
) -> List[Dict]:
clamped = []
for d in detections:
x, y, w, h = d["bbox"]
cx = max(0, min(x, img_w - 1))
cy = max(0, min(y, img_h - 1))
cw = max(1, min(w, img_w - cx))
ch = max(1, min(h, img_h - cy))
clamped.append({**d, "bbox": (cx, cy, cw, ch)})
return clamped
def process(
self,
detections: List[Dict],
img_h: int,
img_w: int,
) -> List[Dict]:
deduplicated = self.nms(detections)
clamped = self.clamp_boxes(deduplicated, img_h, img_w)
return clamped