5.9. Operații aritmetice

Familia de desenare din secțiunea anterioară pictează în interiorul unei imagini. Familia aritmetică combină două imagini într-o a treia – adunând valorile pixelilor lor, scăzând una din cealaltă, luând minimul sau maximul la fiecare poziție. Acel set restrâns de operații aritmetice pixel cu pixel este fundamentul pe care sunt construite diferențierea de cadre, scăderea fundalului, stivuirea expunerilor și o serie de alte modele clasice.

Familia aritmetică din clasa Image este suficient de mică încât să o enumerăm dintr-o dată:

  • add()self + other per pixel, decupat la valoarea maximă a formatului.

  • sub()self - other per pixel, decupat la 0 la limita inferioară.

  • rsub()other - self per pixel, decupat la 0 (aceeași aritmetică precum sub, dar cu operanzii inversați).

  • min() – minimul per pixel al celor două valori.

  • max() – maximul per pixel.

  • difference()|self - other| per pixel, diferența absolută.

Plus două operații înrudite pe o singură imagine:

  • invert() – înlocuiește fiecare pixel cu 255 - pixel (sau cu maximul echivalent pentru format).

  • negate() – un alias pentru invert().

Două bare de gradient orizontale în partea de sus, reprezentând imaginile sursă A și B -- A trecând de la întunecat la luminos de la stânga la dreapta, B trecând de la luminos la întunecat de la stânga la dreapta. Sub ele, cinci bare de gradient reprezentând rezultatul fiecărei operații pe perechi aplicate asupra lui A și B: A.add(B) apare uniform alb deoarece fiecare poziție însumează peste 255 și se decupează; A.sub(B) este zero pe jumătatea stângă și se luminează spre dreapta; A.difference(B) arată o formă de V, luminoasă la fiecare capăt și întunecată la mijloc; A.min(B) este întunecată la capete și mai luminoasă la mijloc; A.max(B) este luminoasă la capete și gri la mijloc.

Două gradiente sursă A și B și rezultatul fiecărei operații pe perechi aplicate asupra lor. Fiecare operație rulează poziție cu poziție – ceea ce apare în rezultat la o anumită locație depinde doar de cei doi pixeli sursă de la acea locație.

5.9.1. Două forme de operand

Fiecare dintre metodele pe două imagini acceptă oricare dintre forme pentru al doilea operand:

  • O altă Image de aceleași dimensiuni. Aritmetica rulează poziție cu poziție – rezultatul la (x, y) este operația aplicată pixelilor sursă de la (x, y) ai ambelor imagini.

  • O valoare scalară – un întreg pentru tonuri de gri, un tuplu (r, g, b) pentru RGB565. Același scalar se aplică la fiecare poziție.

Forma scalară este utilă atunci când aplicația dorește să deplaseze fiecare pixel cu o valoare constantă. img.add(40) luminează întreaga imagine cu 40; img.sub((20, 20, 20)) întunecă fiecare pixel cu 20 pe canal; img.max(50) ridică orice pixel sub 50 până la 50 și îi lasă pe ceilalți neatinși – genul de operație care transformă un prag de zgomot aproape negru al senzorului într-un gri închis uniform, cu care etapele ulterioare pot lucra.

5.9.2. Decuparea

Valorile pixelilor rămân în intervalul formatului în cursul fiecărei operații. Pentru un canal pe 8 biți, asta înseamnă 0255: orice ar fi depășit 255 este decupat înapoi la 255, iar orice ar fi coborât sub 0 este decupat în sus la 0. Nu există revenire ciclică (wrap-around).

Această alegere contează în practică. add care luminează pixeli nu produce niciodată un artefact de întunecare bruscă la capătul luminos, unde altfel calculul s-ar revărsa; sub care întunecă pixeli nu produce niciodată un artefact de luminare bruscă la capătul întunecat, unde altfel s-ar produce subdepășire. Rezultatele rămân semnificative din punct de vedere vizual, cu prețul unei pierderi de informație la extremele saturate.

Decuparea este și motivul pentru care sub și rsub returnează rezultate diferite unul de celălalt. img_a.sub(img_b) dă partea din a care este mai luminoasă decât b și zero în rest; img_a.rsub(img_b) dă partea din b care este mai luminoasă decât a. Oricare este utilă pentru detectarea unilaterală a schimbării – dacă aplicația se interesează doar de pixelii care s-au luminat sau doar de cei care s-au întunecat – dar niciuna nu surprinde toată schimbarea dintre două cadre.

5.9.3. Operația de diferență

Pentru detectarea bilaterală a schimbării, operația la care trebuie să recurgeți este difference(), care calculează |self - other| la fiecare poziție – diferența absolută, fără semn. Fiecare pixel care s-a schimbat în oricare direcție apare ca o valoare nenulă în rezultat, cu o magnitudine proporțională cu cât de mult s-a schimbat la acea poziție.

Această proprietate – nenul exact acolo unde cele două imagini diferă – este ceea ce face din difference calul de povară al detectării schimbării cadru cu cadru. Un cadru de referință stocat la pornire și o captură proaspătă, trecute prin difference, produc o imagine ai cărei pixeli nenuli marchează fiecare poziție în care ceva din scenă s-a mișcat sau și-a schimbat luminozitatea.

5.9.4. Delimitarea cu mască

Toate metodele aritmetice acceptă argumentul-cuvânt-cheie mask introdus pe pagina despre regiuni și măști. Atunci când se transmite o mască, operația rulează doar la pozițiile în care masca este nenulă; în rest, imaginea de destinație este lăsată neatinsă.

Această compoziție apare în două modele. Primul este constrângerea unei operații la o zonă cunoscută: adunarea a două cadre, de exemplu, doar în interiorul casetei de încadrare a unui marcaj detectat. Al doilea este construirea unui cadru compozit bucată cu bucată – min peste o secvență de cadre în interiorul unei măști de prim-plan, max peste aceeași secvență în interiorul măștii complementare – acel gen de model.

5.9.5. Pe loc și păstrând intrările

Toate metodele aritmetice respectă convenția de operare stabilită anterior: fiecare modifică imaginea sursă pe loc și returnează aceeași imagine pentru înlănțuire. Pixelii sursei dispar după apel – înlocuiți cu rezultatul operației aplicate față de orice a fost transmis ca al doilea operand.

Atunci când aplicația trebuie să păstreze ambele intrări, modelul sigur este să copieze mai întâi una dintre ele:

diff = current.copy()       # leaves current intact
diff.difference(reference)  # diff now holds the absolute difference

Acel model – copiază, apoi operează – este coloana vertebrală a oricărei secvențe de diferențiere a cadrelor, în care cadrul de referință trebuie să supraviețuiască comparației, ca să poată fi reutilizat pe următorul cadru capturat.

Cu șase operații de combinare, două operații pe o singură imagine, un cal de povară al diferenței absolute și argumentul-cuvânt-cheie mask pentru delimitare, setul de instrumente de aritmetică pe pixeli acoperă combinațiile de luminozitate și canale de care are nevoie viziunea artificială clasică. Restul instrumentelor de tip aritmetic care urmează lucrează bit cu bit, mai degrabă decât valoare cu valoare.