5.5. Regio’s en maskers

Elke bewerking in de image-module raakt standaard elke pixel van zijn bronafbeelding aan. Dat is het eenvoudigste gedrag om te beschrijven, en het juiste wanneer de taak van het algoritme werkelijk het hele frame omvat – een uniforme kleurcorrectie, een globaal histogram, een coderingspass voor verzending. Maar in de praktijk willen de meeste algoritmen naar minder dan dat kijken. Een blob-tracker die een gekleurde markering volgt, geeft om het deel van de scene waar de markering kan verschijnen, niet om de muur erachter. Een morfologische opschoonpass is alleen veilig over de pixels die een eerdere fase als kandidaten heeft aangemerkt. Een gezichtsdetector draait misschien alleen binnen het begrenzingsvak dat een grovere detector al heeft afgebakend. De image-module ondersteunt dat werk via twee mechanismen die een bewerking beperken tot een deelverzameling van pixels: rechthoekige aandachtsgebieden, en binaire maskers. Ze laten zich vrij combineren, en bijna elke methode die pixels aanraakt accepteert het een of het ander – of beide – als keyword-argument.

5.5.1. Aandachtsgebieden

Een aandachtsgebied is een rechthoek van pixels die wordt aangeduid met de (x, y, w, h) vier-tupel die op de coordinatenpagina is geintroduceerd. Ongeveer dertig methoden in de interface accepteren een roi keyword-argument; wanneer dit aanwezig is, draait de bewerking alleen op de pixels binnen die rechthoek en laat de rest van de afbeelding onaangeroerd. Wanneer roi None is of wordt weggelaten, draait de bewerking over de hele afbeelding – hetzelfde als wanneer roi=(0, 0, width, height) was doorgegeven.

In code staat het keyword naast welke andere argumenten de bewerking ook aanneemt:

# Compute a histogram over a centred crop of the image.
h = img.get_histogram(roi=(64, 64, 128, 128))

Het eerste wat ROI’s opleveren is beheersing van valse positieven. Een kleurtracker die alleen naar de tafel kijkt, zal nooit afgaan op het shirt dat erlangs loopt; een randdetector die alleen binnen het gedefinieerde werkgebied draait, zal nooit de randen van de camerabeugel zelf rapporteren. Het zoekgebied terugbrengen tot het deel van de scene waar het algoritme werkelijk om geeft, is de goedkoopste verbetering die een pijplijn aan zijn eigen betrouwbaarheid kan aanbrengen.

Het tweede wat ze opleveren is de grof-naar-fijn-pijplijn. Detectieresultaat-objecten – een blob, een rect, een apriltag, enzovoort – stellen hun begrenzingsvakken bloot als dezelfde (x, y, w, h) vier-tupel die roi accepteert. Zo kan een grove eerste fase een begrenzingsvak teruggeven, valt het vak rechtstreeks in de roi van de volgende fase, en draait de tweede fase over het smallere gebied. Elke voortgaande versmalling versnelt zowel de volgende fase als dat het de resultaten betrouwbaarder maakt, omdat de zoekruimte al is gefilterd.

5.5.2. Binaire maskers

Een rechthoek is de juiste vorm wanneer het aandachtsgebied as-uitgelijnd is. Wanneer dat niet zo is – een gebogen gebied, een niet-convex gebied, de pixels die een eerdere fase als “overeenkomsten” heeft geclassificeerd – moet de bewerking in plaats daarvan worden verteld zich te beperken tot een willekeurig patroon van pixels. Het mechanisme daarvoor is een binair masker: een aparte Image, met dezelfde afmetingen als de bron, gebruikt als een aan/uit-schakelaar per pixel. Een niet-nul pixel in het masker zegt “neem de overeenkomstige bronpixel mee”; een nul-pixel zegt “laat de bronpixel met rust.”

Een masker is meestal een BINARY-afbeelding – het formaat met een bit per pixel dat precies voor dit doel bestaat – maar elke afbeelding met een enkel kanaal werkt, omdat de verbruiker elke niet-nulwaarde als aan behandelt.

Filter-, drempelwaarde- en rekenkundige methoden accepteren een mask keyword-argument. De vorm is bij elk dezelfde: een apart toegewezen binaire afbeelding, met dezelfde afmetingen als de bron, doorgegeven.

ROI’s en maskers combineren. Geef beide door, en de bewerking draait alleen op pixels die binnen de ROI liggen en aan staan in het masker. De twee mechanismen geven applicatiecode onafhankelijke hendels – een voor het rechthoekige aandachtsgebied, een voor het willekeurige patroon erbinnen – zonder dat de ene vorm beperkingen van de andere overneemt.

Een klein raster dat een afbeelding voorstelt. Een gestippelde rechthoek die over het bovenste middendeel van het raster is getekend, markeert de ROI: alleen pixels binnen deze rechthoek worden in aanmerking genomen. Binnen de ROI markeert een ruwweg cirkelvormige verzameling gevulde cellen het masker: alleen die gevulde cellen worden daadwerkelijk gewijzigd. De overige cellen zijn licht gearceerd om aan te geven dat ze onaangeroerd blijven.

Een ROI beperkt een bewerking tot een as-uitgelijnde rechthoek. Een masker versmalt het verder tot een willekeurig patroon van pixels. De twee combineren: alleen pixels binnen de ROI en aan in het masker worden gewijzigd.

5.5.3. Maskers bouwen

Drie Image-methoden bouwen veelvoorkomende maskergeometrieen ter plekke op door de pixels buiten het gekozen gebied op nul te zetten:

Elk neemt (x, y, w, h) (voor de rechthoek en de ellips) of (x, y, radius) (voor de cirkel). Door een ervan zonder argumenten aan te roepen, wordt de geometrie gecentreerd en op maat gemaakt om de afbeelding te vullen, wat de vorm is waarnaar een applicatie grijpt wanneer het doel een eenvoudige beeldvullende ovaal of cirkel is die niets dan de hoeken verbergt.

mask = image.Image(img.width(), img.height(), image.BINARY)
mask.clear()              # start from all zeros
mask.mask_ellipse()       # centred, full-size oval

De interessante maskers komen zelden alleen van de mask_*-methoden. Ze komen van eerdere fasen van de pijplijn: een drempelwaardepass produceert een binaire afbeelding waarvan de niet-nulpixels de overeenkomsten markeren, precies de juiste vorm om in het mask=-argument van de volgende fase in te voeren. Een morfologische opschoonpass verfijnt dat masker zonder de vorm ervan te veranderen. Alles wat uiteindelijk een afbeelding met een enkel kanaal wordt, is zelf een geldig masker.

5.5.4. Hoe bewerkingen de afbeelding wijzigen

Een patroon dat zichtbaar is in elk codefragment op de laatste paar pagina’s – de bewerking die dezelfde img teruggeeft om te kunnen ketenen – is het waard om expliciet uit te lichten, zodat het niet telkens opnieuw hoeft te worden vermeld wanneer een nieuwe methode wordt geintroduceerd. Er verschijnen drie families van methoden op de Image-interface, elk die de bronafbeelding anders behandelt:

  • Bewerkende methoden wijzigen de pixels van de bron ter plekke en geven dezelfde afbeelding terug om te ketenen. De teken-, rekenkundige, drempelwaarde- en filterfamilies gedragen zich allemaal zo. img.gaussian(1) vervaagt img en geeft dezelfde img terug; hertoewijzen – img = img.gaussian(1) – is onschadelijk maar onnodig.

  • Conversiemethoden werken standaard ter plekke op dezelfde manier als bewerkende methoden, maar ze accepteren copy=True en copy_to_fb=True om een aparte resultaatafbeelding toe te wijzen wanneer de bron behouden moet blijven. De formaatconversies en de geometrische kopieen zijn de belangrijkste leden van deze familie.

  • Inspectiemethoden lezen de pixels en geven een resultaatobject terug – een lijst van gedetecteerde kenmerken, een histogram, een verzameling statistieken – zonder de bronafbeelding op enigerlei wijze te wijzigen.

Die driedeling is consistent over de hele interface. Weten tot welke familie een methode behoort, vertelt de applicatie wat ze van een aanroep mag verwachten: of de pixels van de bron intact blijven, of er een aparte resultaatafbeelding wordt toegewezen, en of de retourwaarde de bron zelf is of iets anders.