5.20. Regression och likhet

Ytterligare två mätningar på klassen Image sammanfattar bilden som något annat än en fördelning av pixelvärden. Linjär regression av trösklade pixlar ger en applikation en linje som den kan agera på – den klassiska indatan till en linjeföljande robot. Likhetsmätning ger en applikation ett enda tal som beskriver hur lika två bilder är – den naturliga indatan till ett regressionstest mot en gyllene bild eller en detektor för grova förändringar.

5.20.1. Linjär regression

När förgrundspixlarna i en bild råkar bilda en linje tvärs över bildrutan – tejpen på en bana som en robot följer, horisontens linje, kanten på en väg eller en korridor – vill applikationen vanligtvis inte ha varje enskild förgrundspixel. Den vill ha den bäst anpassade linjen genom dem alla, parametriserad så att den kan avgöra hur linjen är orienterad och var den korsar bildrutan.

get_regression() gör den anpassningen. Den tar samma tröskeltupelform som binary() och find_blobs() använder, identifierar varje pixel som matchar tröskeln och returnerar ett enda line-resultat som beskriver den bäst anpassade linjen genom dessa pixlar:

line = img.get_regression([(0, 60)])
if line:
    img.draw_line((line.x1(), line.y1(),
                   line.x2(), line.y2()),
                  color=(255, 0, 0))

Anpassningen är Theil-Sen-linjär regression – en robust metod som tolererar avvikare bättre än den mer välkända minsta-kvadrat-anpassningen. En liten handfull pixlar långt från den sanna linjen förvränger inte resultatet så som de skulle med minsta kvadrat, vilket matchar verkligheten med brusig förgrund hos en verklig tröskelutdata.

line-resultatet bär ändpunkterna klippta till bildrektangeln (x1, y1, x2, y2), linjens längd och magnitud (length, magnitude) och linjens geometriska beskrivning i polär form (theta, rho) – linjens vinkel från horisontalplanet och dess vinkelräta avstånd från origo. Den polära formen är vad en styrslinga vanligtvis vill ha: theta talar om för roboten åt vilket håll linjen lutar, rho talar om var linjen korsar bilden, och en återkopplingsslinga på de två håller roboten centrerad på linjen.

En handfull nyckelordsargument finjusterar robustheten och kostnaden. x_stride och y_stride hoppar över pixlar under anpassningen – större steg gör regressionen billigare till priset av att färre pixlar anpassas. area_threshold och pixels_threshold förkastar linjer som inte har tillräckligt med matchande pixlar bakom sig. target_size skalar om indatan till en mindre storlek innan anpassningen – regressionen körs snabbare på en ersättning av bilden på 80 gånger 60 utan större förlust i precisionen för linjeriktningen.

Om ingen godtagbar linje kunde anpassas – om tröskeln inte matchade några pixlar, eller matchade ett mönster som inte ser ut som en linje – returnerar metoden None. Verklig linjeföljande kod skyddar varje anrop till get_regression() med en None-kontroll innan den når efter linjens attribut.

5.20.2. Bildlikhet

En annan sorts mätning: i stället för att fråga ”vad innehåller bilden?”, fråga ”hur lika är dessa två bilder?”. Operationen att ta till är get_similarity(), som beräknar Structural Similarity Index (SSIM) mellan källbilden och en referensbild.

s = img.get_similarity(reference)
print(s.mean, s.stdev)

SSIM är det standardmått på bildlikhet som används överallt inom bildbehandling eftersom det beter sig på det sätt som en människas intuition om likhet beter sig – en liten förskjutning eller en liten ändring i ljusstyrka sänker poängen något, medan en stor strukturell förändring (saknat objekt, annan scen) sänker den dramatiskt. Poängen sträcker sig från -1 till +1: +1 betyder att de två bilderna är identiska, 0 betyder att de är orelaterade och -1 betyder att de är strukturellt motsatta. Ett returnerat similarity-objekt exponerar medel-SSIM över bilden, plus standardavvikelsen, min och max av poängen per ruta.

För den sortens jämförelse där ett litet tal är bättre än ett stort – ett regressionstest som ska rapportera noll vid ”inget ändrades” och stiga allteftersom förändringar ackumuleras – returnerar flaggan dssim=True den strukturella olikheten: medel-SSIM subtraherat från 1, så att returvärdet är 0.0 för identiska bilder och stiger allteftersom de skiljer sig.

5.20.3. Användningsfall för SSIM

De två vanliga applikationerna:

Regressionstestning mot en gyllene bild. Ett testramverk fångar en referensbildruta under kända bra förhållanden och lagrar den som den gyllene bilden. Efterföljande testkörningar fångar under samma förhållanden och jämför mot den gyllene bilden med SSIM. En poäng över något tröskelvärde (0.95 eller 0.98 beroende på tolerans) är godkänt; under är underkänt. Testramverket behöver inte veta vad som ändrades – SSIM-poängen är signalen.

Detektering av grova förändringar. En applikation som vill ha en grövre version av bildrutedifferentiering – en som ignorerar små ändringar i ljusstyrka men reagerar på stora strukturella förändringar – kan använda SSIM mot en referensbildruta i stället för den pixelvisa difference() följt av en tröskel. SSIM är mindre känsligt för ljusdrift än pixelvis differentiering, vilket gör det till det bättre valet när målet är att upptäcka att ”scenen ser materiellt annorlunda ut” snarare än ”någon enskild pixel ändrades.”

Båda applikationerna använder samma anrop – img.get_similarity(reference) – och utlöses på ett tröskelvärde för den returnerade poängen. Skillnaden är bara om tröskeln är hög (regressionstest, som letar efter en nästan identisk matchning) eller låg (förändringsdetektering, som letar efter någon stor strukturell förändring).

5.20.4. Transformera-och-jämför-formen

En användbar subtilitet: get_similarity() accepterar samma parametrar x, y, x_scale, y_scale, roi, rgb_channel, alpha, color_palette, alpha_palette, hint och transform som draw_image(). Referensbilden positioneras, skalas och transformeras av dessa parametrar innan SSIM-jämförelsen körs.

Det betyder att en applikation kan fråga ”hur lik är denna scen en referensbildruta efter en känd förskjutning / rotation / skalning” utan att förbereda en förtransformerad referensbild. Det är det billiga sättet att bygga en spårare som söker igenom ett parameterrum och rapporterar vilken transform av referensen som bäst matchar den aktuella bildrutan.