5.17. Un catalog de nuclee standard

Procesarea clasică a imaginilor a acumulat un catalog destul de bogat de tipare de ponderi pentru nuclee care apar din nou și din nou – detectoare de muchii, accentuatoare de claritate, embosări, netezitoare, neclarități de mișcare – și fiecare dintre ele rulează prin morph(). Fiecare este scurt, fiecare face un singur lucru, iar cele mai multe sunt ușor de citit odată ce logica de bază a ponderilor capătă sens.

Nucleele de mai jos sunt toate de 3 pe 3, dacă nu se specifică altfel, așa că toate folosesc size=1 în apel. Structura ponderilor fiecărui nucleu este descrisă alături de el, deoarece citirea ponderilor este ceea ce construiește intuiția pentru a înțelege de ce un nucleu embosează, iar altul accentuează claritatea.

5.17.1. Nucleul identitate

Cel mai simplu nucleu posibil este identitatea – un unu în centru, zero peste tot în rest:

identity = [0, 0, 0,
            0, 1, 0,
            0, 0, 0]

img.morph(1, identity)

Fiecare pixel de ieșire își ia valoarea din centrul vecinătății, care este pixelul de intrare aflat în aceeași poziție. Imaginea trece neschimbată. Identitatea nu are utilitate practică ca filtru, dar este punctul de referință util pentru înțelegerea oricărui alt nucleu: orice nucleu care nu este identitatea este identitatea plus o anumită modificare.

Un nucleu a cărui pondere centrală este mare, cu mici ponderi negative în jur, scade vecinătatea din centru. Un nucleu cu pondere centrală zero ignoră pixelul în sine și răspunde numai la diferențele dintre vecinii săi. Citirea unui nucleu în acest mod – ce face ponderea centrală pixelului, ce adaugă sau scad ponderile din jur – este cea mai rapidă cale de a-i prezice efectul.

5.17.2. Detectarea muchiilor

Nucleele de detectare a muchiilor răspund puternic la pozițiile unde luminozitatea se schimbă rapid într-o anumită direcție și produc o ieșire aproape nulă acolo unde luminozitatea este uniformă. Ele constituie familia ale cărei ponderi însumează zero: o suprafață plată (toți pixelii cu aceeași valoare) produce ieșire zero, deoarece fiecare pondere pozitivă este anulată exact de o pondere negativă de magnitudine egală.

Sobel-x este exemplul canonic. Detectează muchii verticale (tranziții de luminozitate stânga/dreapta):

sobel_x = [-1,  0,  1,
           -2,  0,  2,
           -1,  0,  1]

img.morph(1, sobel_x, mul=0.25, add=128)

Corespondentul Sobel-y este același tipar rotit cu 90 de grade; detectează muchii orizontale (tranziții de luminozitate sus/jos):

sobel_y = [-1, -2, -1,
            0,  0,  0,
            1,  2,  1]

Rândul din mijloc al lui Sobel-x are ponderile -2 și 2 în loc de -1 și 1. Ponderea suplimentară de pe rândul central conferă nucleului o mică netezire încorporată în direcția de-a lungul muchiei, ceea ce îl face mai robust la zgomot decât operatorul Prewitt, mai simplu, care renunță la aceste magnitudini suplimentare:

prewitt_x = [-1, 0, 1,
             -1, 0, 1,
             -1, 0, 1]

prewitt_y = [-1, -1, -1,
              0,  0,  0,
              1,  1,  1]

Prewitt cântărește fiecare rând în mod egal, astfel încât răspunsul său este puțin mai accentuat decât al lui Sobel, cu prețul unei sensibilități mai mari la zgomotul de un singur pixel (costul rulării nucleului este identic – convoluția face aceeași muncă indiferent de ponderi). Pe o imagine curată, cu muchii puternice, este un înlocuitor perfect funcțional pentru Sobel.

Scharr merge în direcția opusă. Ponderile sale sunt mai mari și reglate pentru detectarea precisă a direcției muchiilor la unghiuri mai fine:

scharr_x = [-3,   0,  3,
            -10,  0, 10,
            -3,   0,  3]

img.morph(1, scharr_x, mul=0.0625, add=128)

Divizorul mul=0.0625 (1/16) readuce ieșirea în intervalul 0255 după suma de produse mai mare. Scharr este răspunsul potrivit atunci când aplicația are nevoie de cel mai fidel geometric răspuns de gradient și este dispusă să plătească un pic mai multă aritmetică pentru asta.

5.17.3. Laplacianul

Un nucleu Laplacian răspunde la muchii în orice direcție deodată. Acolo unde fiecare Sobel detectează schimbările de luminozitate de-a lungul unei axe, tiparul simetric de ponderi al Laplacianului răspunde la fel indiferent de direcția în care se desfășoară muchia:

laplacian_4 = [ 0, -1,  0,
               -1,  4, -1,
                0, -1,  0]

img.morph(1, laplacian_4, add=128)

Structura: ponderea centrală 4, patru vecini orizontali/verticali cu ponderea -1, cele patru diagonale cu ponderea zero. Nucleul însumează zero, deci suprafețele plate produc ieșire zero. Acolo unde luminozitatea se schimbă, valoarea centrală diferă de media celor patru vecini cardinali ai săi, iar ieșirea reprezintă mărimea acelei diferențe.

Varianta cu 8 conexiuni include vecinii diagonali:

laplacian_8 = [-1, -1, -1,
               -1,  8, -1,
               -1, -1, -1]

Fiecare nucleu detectează lucruri ușor diferite. Versiunea cu 4 conexiuni produce o ieșire mai curată pe muchiile orizontale și verticale; cea cu 8 conexiuni este mai izotropă – răspunde la fel de bine în orice direcție – dar produce o ieșire ușor mai zgomotoasă. Nucleul cu 8 conexiuni circulă și sub numele de outline (contur), datorită utilizării sale pentru vizualizarea muchiilor.

5.17.4. Accentuarea clarității

Un nucleu de accentuare a clarității este identitatea plus un nucleu de răspuns la muchii. Ieșirea este imaginea originală plus o copie a muchiilor, astfel încât caracteristicile de înaltă frecvență sunt amplificate față de zonele interioare netede.

Nucleul standard de accentuare a clarității cu 4 conexiuni adaugă Laplacianul cu 4 conexiuni la identitate:

sharpen = [ 0, -1,  0,
           -1,  5, -1,
            0, -1,  0]

img.morph(1, sharpen)

Citirea nucleului: ponderea centrală este identity (1) + Laplacian centre (4) = 5, iar vecinătatea corespunde celei a Laplacianului. Suprafețele plate produc 5 * 1 - 4 * 1 = 1 ori valoarea centrală – identitatea. Muchiile produc originalul plus răspunsul Laplacianului. Suma ponderilor este 1, așa că mul și add rămân la valorile lor implicite.

Pentru o accentuare mai puternică a clarității, varianta cu 8 conexiuni merge mai departe:

sharpen_strong = [-1, -1, -1,
                  -1,  9, -1,
                  -1, -1, -1]

img.morph(1, sharpen_strong)

Ponderea centrală 9 este identity (1) + Laplacian-8 centre (8). Aceeași logică, mai multă amplificare, mai mult risc de a amplifica totodată și zgomotul senzorului.

Nucleele puternice de accentuare a clarității sunt în esență gaussian() cu unsharp=True, exprimate doar direct sub formă de nucleu în loc de prin indicatorul de mască unsharp. Comportamentul la nivel de pixel este același; alegerea este între comoditatea metodei denumite și controlul fin al unui nucleu reglat manual.

5.17.5. Emboss

Un nucleu emboss produce efectul de iluminare dintr-o parte întâlnit în editoarele clasice de imagini. Ieșirea arată ca și cum imaginea ar fi fost extrudată într-un relief și apoi iluminată dintr-un colț:

emboss = [-2, -1,  0,
          -1,  1,  1,
           0,  1,  2]

img.morph(1, emboss, add=128)

Trucul constă în asimetria de-a lungul diagonalei. Colțul din stânga sus are cea mai negativă pondere, cel din dreapta jos are cea mai pozitivă pondere, iar diagonala de la un colț la celălalt trece de la negativ, prin unu, la pozitiv. La fiecare pixel, nucleul calculează în esență „luminozitatea din dreapta-jos a mea minus luminozitatea din stânga-sus a mea”, ceea ce este pozitiv acolo unde imaginea devine mai luminoasă în acea direcție și negativ acolo unde devine mai întunecată. Adăugarea lui 128 recentrează ieșirea cu semn la gri mediu, astfel încât efectul să fie vizibil.

Rotirea asimetriei de-a lungul celeilalte diagonale embosează din direcția opusă:

emboss_alt = [ 0,  1,  2,
              -1,  1,  1,
              -2, -1,  0]

img.morph(1, emboss_alt, add=128)

Cele două direcții de emboss sunt utile în combinație – scăzând una din cealaltă sau rulându-le pe fiecare pe aceeași imagine și comparând răspunsurile – atunci când o aplicație are nevoie să detecteze orientarea.

5.17.6. Netezirea

Nucleele de netezire constituie familia ale cărei ponderi însumează unu (și sunt toate nenegative). O suprafață plată trecută printr-un astfel de nucleu produce aceeași luminozitate plată, deoarece nucleul mediază valorile pixelilor împreună în loc să le amplifice diferențele.

Cel mai simplu este box blur, care este exact ceea ce calculează mean():

box_blur = [1, 1, 1,
            1, 1, 1,
            1, 1, 1]

img.morph(1, box_blur)

Nucleul însumează 9, așa că împărțirea automată la suma nucleului transformă suma de produse într-o medie reală peste cei nouă pixeli ai vecinătății. În practică, mean() este modalitatea mai bună de a rula acest nucleu – produce aceeași ieșire mai rapid, printr-o cale optimizată pentru calcularea mediei și nimic altceva, în timp ce morph rulează mecanismul general de convoluție. Box blur se află în catalog deoarece este punctul de referință potrivit pentru înțelegerea oricărui alt nucleu de netezire.

O aproximare de 3 pe 3 a nucleului Gaussian cântărește centrul și vecinii cardinali mai mult decât colțurile:

gaussian = [1, 2, 1,
            2, 4, 2,
            1, 2, 1]

img.morph(1, gaussian)

Ponderile reprezintă rândul 1, 2, 1 din triunghiul lui Pascal produs exterior cu el însuși. Ponderea centrală 4 este cea mai mare deoarece pixelul central contribuie cel mai mult la propria sa ieșire; colțurile sunt 1 deoarece sunt cele mai îndepărtate de centru. Nucleul însumează 16, iar împărțirea automată la suma nucleului se ocupă de normalizare – nu este necesar niciun argument mul. Forma de 3 pe 3 este o aproximare grosieră a unui Gaussian real și nu se poate distinge de gaussian() la size=1; forma morph este utilă mai ales atunci când o aplicație dorește să compună netezirea cu o altă operație în aceeași trecere.

5.17.7. Neclaritatea de mișcare

Un nucleu de neclaritate de mișcare mediază pixelii de-a lungul unei singure direcții, lăsând direcția perpendiculară neînceațoșată. Cel mai simplu caz este cel orizontal:

motion_h = [0, 0, 0,
            1, 1, 1,
            0, 0, 0]

img.morph(1, motion_h)

Rândul din mijloc mediază trei pixeli de-a lungul axei orizontale; rândurile de sus și de jos sunt zero. Nucleul însumează 3, așa că împărțirea automată la suma nucleului produce o medie reală pe trei pixeli fără a fi nevoie de vreun mul. Ieșirea este o copie întinsă orizontal a intrării – efectul pe care îl captează o cameră atunci când subiectul se mișcă lateral în timpul expunerii. Neclaritatea de mișcare verticală este același tipar rotit:

motion_v = [0, 1, 0,
            0, 1, 0,
            0, 1, 0]

O neclaritate de mișcare diagonală folosește diagonala principală:

motion_diag = [1, 0, 0,
               0, 1, 0,
               0, 0, 1]

img.morph(1, motion_diag)

Nucleele de neclaritate de mișcare sunt utile atât ca efect (înceațoșarea deliberată a unui cadru în scopuri vizuale), cât și ca tipar de testare pentru algoritmi care trebuie să fie robuști față de artefactele de mișcare (se rulează algoritmul pe o intrare cu neclaritate de mișcare și se verifică dacă produce în continuare răspunsul corect).

5.17.8. Citirea nucleelor dintr-o privire

Câteva reguli empirice fac ca noile nuclee să fie mai ușor de citit la prima vedere:

  • Sumă-egală-cu-unu cu ponderi nenegative ⇒ netezire (păstrează luminozitatea medie).

  • Sumă-egală-cu-zero cu ponderi atât pozitive, cât și negative ⇒ răspuns la muchii (zero pe suprafețele plate).

  • Sumă-egală-cu-unu cu un centru pozitiv mare și o vecinătate negativă mică ⇒ accentuarea clarității (identitate plus răspuns la muchii).

  • Asimetric de-a lungul unei diagonale cu suma egală cu unu ⇒ embosare (evidențiază o parte a fiecărei tranziții de luminozitate).

  • Concentrat de-a lungul unei axe cu suma egală cu unu ⇒ neclaritate direcțională.

Prima dintre acestea pe care o potrivește nucleul este de obicei presupunerea corectă despre ceea ce face. Cele mai utile nuclee sunt recognoscibile numai din dispunerea tiparului lor de ponderi.

Atunci când niciunul dintre nucleele standard nu face ceea ce dorește aplicația, pasul următor este reglarea manuală a unuia. Combinația dintre regulile de mai sus și controalele mul / add acoperă aproape orice trecere liniară pe care a dorit-o vreodată o pipelină clasică de viziune artificială; de acolo este vorba doar de încercarea unor ponderi, examinarea ieșirii și iterare.