7.13. Icke-maximumundertryckning

Ett detekteringsnät producerar typiskt flera överlappande kandidatrutor runt samma verkliga objekt: varje ankare nära objektet utlöses med en liknande poäng, och efterbehandlaren ser dem alla. Icke-maximumundertryckning (NMS) är steget som omvandlar det klustret till en enda ruta.

Algoritmen är kort: sortera kandidatrutorna efter poäng, ta den med högst poäng, undertryck varje annan ruta som överlappar med den över ett valt tröskelvärde, ta sedan den näst högsta av det som återstår och upprepa. Överlappningsmåttet är intersection-over-union (IoU) – den delade arean av två rutor dividerad med deras kombinerade area, ett värde mellan 0 (ingen överlappning) och 1 (identiska rutor). Konstruktorargumentet nms_threshold på varje medföljande efterbehandlare är gränsen ovanför vilken rutor behandlas som dubbletter av en redan behållen ruta.

Till vänster, tre överlappande begränsningsrutor runt ett enda motiv, märkta med poängen 0.92, 0.83 och 0.71. En pil pekar åt höger, där en överlevande ruta återstår med poängen 0.92. De två rutorna med lägre poäng undertrycks av den med högre poäng eftersom deras intersection-over-union med den överskrider tröskelvärdet.

NMS reducerar ett kluster av överlappande detekteringar till den med högst poäng.

7.13.1. Soft-NMS

Den medföljande klassen ml.utils.NMS implementerar Soft-NMS, en förfining som dämpar en överlappande rutas poäng med ett belopp som beror på hur mycket den överlappar, i stället för att förkasta rutan direkt. Om den sänkta poängen faller under tröskelvärdet förkastas rutan; annars överlever den med den reducerade poängen och tävlar i nästa omgång.

Parametern nms_sigma styr hur aggressiv dämpningen är. Med ett litet nms_sigma (det medföljande standardvärdet 0.1) är dämpningen brant: en kraftigt överlappande ruta får sin poäng driven mot nästan noll och Soft-NMS reduceras till klassisk NMS. Med ett större nms_sigma är dämpningen mild och överlappande rutor från olika objekt överlever oftare, vilket spelar roll när verkliga objekt av samma klass genuint överlappar (en folkmassa av ansikten, ett kluster av handflator).

Att sätta nms_sigma till <= 0 inaktiverar dämpningen helt: överlappande rutor passerar igenom med sina ursprungliga poäng, och endast poängtröskeln filtrerar dem.

7.13.2. Bygga en direkt

Varje medföljande efterbehandlare bygger en ny NMS per inferens, lägger till varje kandidat till den och anropar get_bounding_boxes() i slutet. En anpassad efterbehandlare följer samma mönster:

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)

Konstruktorn tar nätverkets indatabredd och indatahöjd i pixlar samt det ROI av originalbilden som modellen kördes mot; add_bounding_box() tar rutkoordinater i det nätverksindatapixelrummet, och get_bounding_boxes() mappar om de överlevande tillbaka till bildkoordinater med hjälp av ROI:t. Ommappningen tar hänsyn till Normaliseringsuttänjningen automatiskt – samma ROI som prediktorn såg används för att projicera rutor tillbaka – så de returnerade rutorna är redo att ritas på den infångade bildrutan.

Returformen är en lista av listor per klass, indexerad med det label_index som skickats till add_bounding_box. Tomma klasslistor bevaras så att indexet matchar modellens klassindex; enumerate(result) går igenom klasserna tillsammans med deras detekteringar.