File size: 3,030 Bytes
41e54c4
722a4cd
 
 
 
41e54c4
 
 
722a4cd
41e54c4
 
 
 
 
 
 
 
 
 
 
722a4cd
41e54c4
 
 
 
 
 
 
722a4cd
41e54c4
 
 
 
 
722a4cd
41e54c4
 
 
 
722a4cd
 
 
 
 
 
 
 
 
 
 
 
41e54c4
722a4cd
 
 
 
41e54c4
722a4cd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41e54c4
 
 
 
 
 
 
722a4cd
 
 
 
 
41e54c4
 
 
 
 
 
 
 
 
 
722a4cd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
"""
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