7.13. Tłumienie niemaksymalne (NMS)¶
Sieć wykrywająca obiekty zazwyczaj generuje kilka nakładających się na siebie kandydujących ramek wokół tego samego rzeczywistego obiektu: każda kotwica w pobliżu obiektu uruchamia się z podobnym wynikiem, a post-processor widzi je wszystkie. Tłumienie niemaksymalne (NMS) to krok, który przekształca taki klaster w jedną ramkę.
Algorytm jest krótki: posortuj kandydujące ramki według wyniku, weź tę o najwyższym wyniku, stłum każdą inną ramkę, która nakłada się na nią powyżej wybranego progu, następnie weź kolejną o najwyższym wyniku spośród pozostałych i powtórz. Miarą nakładania się jest przecięcie podzielone przez sumę (IoU) – wspólny obszar dwóch ramek podzielony przez ich łączny obszar, wartość pomiędzy 0 (brak nakładania) a 1 (identyczne ramki). Argument konstruktora nms_threshold w każdym dostarczanym post-processorze to wartość graniczna, powyżej której ramki są traktowane jako duplikaty już zachowanej ramki.
NMS redukuje klaster nakładających się wykryć do tego o najwyższym wyniku.¶
7.13.1. Soft-NMS¶
Dostarczana klasa ml.utils.NMS implementuje Soft-NMS, udoskonalenie, które zmniejsza wynik nakładającej się ramki o wartość zależną od tego, jak bardzo się ona nakłada, zamiast całkowicie ją odrzucać. Jeśli obniżony wynik spadnie poniżej progu, ramka jest odrzucana; w przeciwnym razie przetrwa ze zredukowanym wynikiem i konkuruje w następnej rundzie.
Parametr nms_sigma kontroluje, jak agresywny jest spadek. Przy małym nms_sigma (dostarczana domyślna wartość 0.1) spadek jest stromy: silnie nakładająca się ramka ma wynik sprowadzany niemal do zera, a Soft-NMS redukuje się do klasycznego NMS. Przy większym nms_sigma spadek jest łagodny, a nakładające się ramki różnych obiektów częściej przetrwają, co ma znaczenie, gdy rzeczywiste obiekty tej samej klasy faktycznie się nakładają (tłum twarzy, skupisko dłoni).
Ustawienie nms_sigma na <= 0 całkowicie wyłącza spadek: nakładające się ramki przechodzą z oryginalnymi wynikami, a filtruje je jedynie próg wyniku.
7.13.2. Tworzenie go bezpośrednio¶
Każdy dostarczany post-processor tworzy nowy obiekt NMS dla każdego wnioskowania, dodaje do niego każdego kandydata i na końcu wywołuje get_bounding_boxes(). Niestandardowy post-processor stosuje ten sam wzorzec:
from ml.utils import NMS
iw = model.input_shape[0][2]
ih = model.input_shape[0][1]
nms = NMS(iw, ih, inputs[0].roi)
for box, score, class_idx in candidates:
nms.add_bounding_box(box.xmin, box.ymin,
box.xmax, box.ymax,
score, class_idx)
result = nms.get_bounding_boxes(threshold=nms_threshold,
sigma=nms_sigma)
Konstruktor przyjmuje szerokość i wysokość wejścia sieci w pikselach oraz ROI oryginalnego obrazu, na którym uruchomiono model; add_bounding_box() przyjmuje współrzędne ramki w tej przestrzeni pikselowej wejścia sieci, a get_bounding_boxes() odwzorowuje ramki, które przetrwały, z powrotem na współrzędne obrazu przy użyciu ROI. Odwzorowanie automatycznie uwzględnia rozciągnięcie normalizacji – to samo ROI, które widział predyktor, jest używane do rzutowania ramek z powrotem – dzięki czemu zwracane ramki są gotowe do narysowania na przechwyconej ramce.
Zwracana struktura to lista list dla poszczególnych klas, indeksowana przez label_index przekazany do add_bounding_box. Puste listy klas są zachowywane, aby indeks odpowiadał indeksowi klasy modelu; enumerate(result) przechodzi przez klasy wraz z ich wykryciami.