7.13. Potlačení nemaximálních hodnot (non-max suppression)¶
Detekční síť obvykle kolem téhož reálného objektu vytvoří několik překrývajících se kandidátních rámečků: každá kotva poblíž objektu se aktivuje s podobným skóre a post-procesor je vidí všechny. Potlačení nemaximálních hodnot (NMS) je krok, který tento shluk promění v jediný rámeček.
Algoritmus je krátký: seřaďte kandidátní rámečky podle skóre, vezměte ten s nejvyšším skóre, potlačte každý další rámeček, který se s ním překrývá nad zvolený práh, poté vezměte další nejvyšší ze zbývajících a opakujte. Metrikou překryvu je intersection-over-union (IoU) – sdílená plocha dvou rámečků dělená jejich společnou plochou, hodnota mezi 0 (žádný překryv) a 1 (totožné rámečky). Argument konstruktoru nms_threshold u každého dodávaného post-procesoru je mez, nad níž jsou rámečky považovány za duplikáty již ponechaného rámečku.
NMS sloučí shluk překrývajících se detekcí na tu s nejvyšším skóre.¶
7.13.1. Soft-NMS¶
Dodávaná třída ml.utils.NMS implementuje Soft-NMS, vylepšení, které snižuje skóre překrývajícího se rámečku o hodnotu závisející na míře překryvu, místo aby rámeček rovnou zahodilo. Pokud snížené skóre klesne pod práh, rámeček je zahozen; jinak přežije se sníženým skóre a soutěží v dalším kole.
Parametr nms_sigma řídí, jak agresivní je toto snižování. S malou nms_sigma (dodávaná výchozí hodnota 0.1) je pokles strmý: silně překrývajícímu se rámečku je skóre staženo téměř k nule a Soft-NMS se redukuje na klasické NMS. S větší nms_sigma je pokles mírný a překrývající se rámečky různých objektů přežívají častěji, což je důležité, když se reálné objekty stejné třídy skutečně překrývají (dav tváří, shluk dlaní).
Nastavení nms_sigma na hodnotu <= 0 snižování zcela vypne: překrývající se rámečky projdou se svým původním skóre a filtruje je pouze prahování skóre.
7.13.2. Sestavení přímo¶
Každý dodávaný post-procesor sestaví pro každou inferenci nový objekt NMS, přidá do něj každého kandidáta a na konci zavolá get_bounding_boxes(). Vlastní post-procesor se řídí stejným vzorem:
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 přijímá vstupní šířku a výšku sítě v pixelech a ROI původního obrazu, vůči němuž model běžel; add_bounding_box() přijímá souřadnice rámečku v tomto pixelovém prostoru vstupu sítě a get_bounding_boxes() přemapuje přeživší zpět do souřadnic obrazu pomocí ROI. Přemapování automaticky zohledňuje roztažení při normalizaci – pro projekci rámečků zpět se používá stejná ROI, jakou viděl prediktor – takže vrácené rámečky jsou připravené ke kreslení do zachyceného snímku.
Návratovým tvarem je seznam seznamů podle tříd, indexovaný hodnotou label_index předanou do add_bounding_box. Prázdné seznamy tříd jsou zachovány, aby index odpovídal indexu třídy modelu; enumerate(result) prochází třídy spolu s jejich detekcemi.