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.

Vlevo tři překrývající se ohraničující rámečky kolem jediného objektu, označené skóre 0.92, 0.83 a 0.71. Šipka ukazuje vpravo, kde zůstává jeden přeživší rámeček se skóre 0.92. Dva rámečky s nižším skóre jsou potlačeny tím s vyšším skóre, protože jejich intersection-over-union s ním překračuje práh.

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.