Procesarea Imaginilor — Pseudocod Algoritmi

Laboratoarele 1–7  ·  Pași + formule cheie · Pregătire examen

BASICS Operații de bază OpenCV — Snippets C++
Citire / afișare imagine
// Citire
Mat src = imread("Images/img.bmp", IMREAD_GRAYSCALE);
Mat src = imread("Images/img.bmp", IMREAD_COLOR);

// Afisare
imshow("Titlu fereastra", src);
waitKey(0);  // asteapta tasta

// Iesire cu ESC in loop
if (waitKey(1) == 27) break;
Creare imagine nouă
// Grayscale, CV_8UC1
Mat dst(src.rows, src.cols, CV_8UC1);
Mat dst(src.rows, src.cols, CV_8UC1, Scalar(255)); // alba

// Color, CV_8UC3
Mat dst(src.rows, src.cols, CV_8UC3, Scalar(255,255,255));

// Etichete (int), CV_32SC1
Mat labels(src.rows, src.cols, CV_32SC1, Scalar(0));
Acces pixel
// Grayscale: citire / scriere
uchar val = src.at<uchar>(i, j);
dst.at<uchar>(i, j) = 128;

// Color BGR: citire / scriere
Vec3b p = src.at<Vec3b>(i, j);
uchar B = p[0], G = p[1], R = p[2];
dst.at<Vec3b>(i, j) = Vec3b(B, G, R);

// Eticheta int
int lbl = labels.at<int>(i, j);
isInside + saturate_cast
bool isInside(Mat img, int i, int j) {
  return i >= 0 && i < img.rows
      && j >= 0 && j < img.cols;
}

// Clamp la [0,255]
dst.at<uchar>(i,j) =
  saturate_cast<uchar>(val + 50);

// Iesire din functie
if (!isInside(img, i, j)) continue;
Tipuri Mat — cheat sheet
CV_8UC1  → grayscale (uchar, 1 canal)
CV_8UC3  → color BGR (uchar, 3 canale)
CV_16SC1 → short cu semn, 1 canal
CV_32SC1 → int, 1 canal (etichete)
CV_32FC1 → float, 1 canal

// Dimensiuni
src.rows   // H (linii)
src.cols   // W (coloane)
src.channels() // nr canale
Structuri utile
#include <queue>
queue<pair<int,int>> Q;
Q.push(make_pair(i, j));
pair<int,int> p = Q.front(); Q.pop();
int r = p.first, c = p.second;

// Culori random
#include <random>
default_random_engine gen;
uniform_int_distribution<int> d(0,255);
Vec3b col = {(uchar)d(gen),(uchar)d(gen),(uchar)d(gen)};
LAB 1 Operații de bază pe imagini
1. Negare imagine
dst[i][j] = 255 - src[i][j]
PENTRU fiecare pixel (i,j):
  dst[i][j] = 255 - src[i][j]
2. Modificare luminozitate (aditiv)
dst[i][j] = clamp(src[i][j] + gamma, 0, 255)
CITESTE gamma (poate fi negativ)
PENTRU fiecare pixel (i,j):
  val = src[i][j] + gamma
  dst[i][j] = clamp(val, 0, 255)

// In C++: saturate_cast<uchar>(val + gamma)
gamma > 0 → mai luminoasă; gamma < 0 → mai întunecată
3. Modificare contrast (multiplicativ)
dst[i][j] = clamp(src[i][j] * factor, 0, 255)
CITESTE factor (float, ex: 1.5)
PENTRU fiecare pixel (i,j):
  val = src[i][j] * factor
  dst[i][j] = clamp(val, 0, 255)

// In C++: saturate_cast<uchar>(src.at<uchar>(i,j) * factor)
factor > 1 → mai mult contrast; 0 < factor < 1 → mai puțin
LAB 2 Spații de culoare: RGB, Grayscale, Binarizare, HSV
1. Conversie RGB → Grayscale (mediere)
Gray = (R + G + B) / 3
PENTRU fiecare pixel (i,j):
  p = src[i][j]   // Vec3b: p[0]=B, p[1]=G, p[2]=R
  gray[i][j] = (p[0] + p[1] + p[2]) / 3
OpenCV: culoarea e BGR, nu RGB. p[0]=Blue, p[1]=Green, p[2]=Red
2. Binarizare cu prag (Thresholding)
dst[i][j] = 0 dacă src[i][j] < thresh, altfel 255
CITESTE thresh (ex: 128)
PENTRU fiecare pixel (i,j):
  DACA src[i][j] < thresh:
    bin[i][j] = 0    // obiect = negru
  ALTFEL:
    bin[i][j] = 255  // fundal = alb
Convenție: obiect = 0 (negru), fundal = 255 (alb)
3. Conversie RGB → HSV
H ∈ [0,360], S ∈ [0,1], V ∈ [0,1] → normalizat: H*255/360, S*255, V*255
PENTRU fiecare pixel (i,j):
  r = R/255.0;  g = G/255.0;  b = B/255.0  // normalizare
  M = max(r, g, b)
  m = min(r, g, b)
  C = M - m

  V = M

  DACA V != 0:  S = C / V
  ALTFEL:       S = 0  // negru

  DACA C != 0:
    DACA M == r:  H = 60 * (g - b) / C
    DACA M == g:  H = 120 + 60 * (b - r) / C
    DACA M == b:  H = 240 + 60 * (r - g) / C
  ALTFEL:         H = 0  // grayscale

  DACA H < 0:   H = H + 360

  H_norm = H * 255 / 360
  S_norm = S * 255
  V_norm = V * 255
H=nuanță (0°=roșu, 120°=verde, 240°=albastru), S=saturație, V=luminozitate
4. isInside — verificare limite imagine
FUNCTIE isInside(img, i, j):
  RETURNEAZA i >= 0 SI i < img.rows SI j >= 0 SI j < img.cols

// Folosire: inainte de accesarea oricarui vecin
DACA isInside(img, i+di[k], j+dj[k]):
  // acceseaza pixelul vecin
LAB 3 Histograma nivelurilor de intensitate
1. Calcul histogramă
h[g] = numărul de pixeli cu intensitatea g
int hist[256] = {0}
PENTRU fiecare pixel (i,j):
  g = src[i][j]
  hist[g]++
2. FDP (histogramă normalizată)
p[g] = h[g] / M, unde M = rows * cols
Proprietate: suma p[g] = 1
M = src.rows * src.cols
float fdp[256]
PENTRU g = 0..255:
  fdp[g] = (float)hist[g] / M
3. Determinare maxime din histogramă
Fereastră: 2*WH+1 (WH=5), Prag: TH=0.0003
PASUL 1 — Calculeaza FDP (fdp[0..255])
PASUL 2 — Gaseste maximele:
  maxime = []
  PENTRU k = WH ... 255-WH:
    v = media fdp[k-WH ... k+WH]  // media a 2*WH+1 valori
    DACA fdp[k] > v + TH
    SI fdp[k] >= toate fdp[k-WH ... k+WH]:
      maxime.adauga(k)

PASUL 3 — Adauga 0 la inceput si 255 la sfarsit:
  maxime = [0] + maxime + [255]

PASUL 4 — Cuantizare (asignare la maximul cel mai apropiat):
  PENTRU fiecare pixel (i,j):
    g = src[i][j]
    src[i][j] = maximul din maxime cel mai apropiat de g
WH = lățimea jumătate a ferestrei (valoare bună: 5), TH = prag (valoare bună: 0.0003)
4. Algoritmul Floyd-Steinberg (dithering)
Matrice distribuire eroare: dreapta=7/16, stânga-jos=3/16, jos=5/16, dreapta-jos=1/16
PENTRU i = 0 .. rows-1:
  PENTRU j = 0 .. cols-1:
    oldpixel = img[i][j]
    newpixel = maximul_cel_mai_apropiat(oldpixel)  // din lista maxime
    img[i][j] = newpixel
    eroare = oldpixel - newpixel

    DACA isInside(img, i,   j+1): img[i][j+1]   += 7*eroare/16
    DACA isInside(img, i+1, j-1): img[i+1][j-1] += 3*eroare/16
    DACA isInside(img, i+1, j):   img[i+1][j]   += 5*eroare/16
    DACA isInside(img, i+1, j+1): img[i+1][j+1] += 1*eroare/16

// Matricea de distributie (X = pixelul curent):
//  0    0    0
//  0    X   7/16
// 3/16 5/16 1/16
Distribuie eroarea de cuantizare la vecinii din dreapta și de dedesubt — rezultat vizual mai bun față de cuantizarea simplă
LAB 4 Trăsături geometrice ale obiectelor binare
Convenție: pixel obiect = 0 (negru), pixel fundal = 255 (alb). Toți indicii calculați per obiect (etichetă).
1. Aria
A = numărul de pixeli ai obiectului
A = 0
PENTRU fiecare pixel (i,j):
  DACA src[i][j] == 0:  // obiect
    A++
2. Centrul de masă
r_bar = (1/A) * suma(r * I(r,c))
c_bar = (1/A) * suma(c * I(r,c))
sumR = 0, sumC = 0, A = 0
PENTRU fiecare pixel (i,j) cu src[i][j]==0:
  sumR += i
  sumC += j
  A++
r_bar = sumR / A
c_bar = sumC / A
3. Axa de alungire — unghi phi
tan(2φ) = 2*sum((r-r̄)(c-c̄)) / (sum((c-c̄)²) - sum((r-r̄)²))
phi = atan2(numarator, numitor) / 2 → rezultat în (-π/2, π/2)
// Calculezi mai intai r_bar si c_bar (centrul de masa)
num = 0, den = 0
PENTRU fiecare pixel (i,j) cu src[i][j]==0:
  dr = i - r_bar
  dc = j - c_bar
  num += 2 * dr * dc
  den += dc*dc - dr*dr

phi = 0.5 * atan2(num, den)  // atan2 evita impartirea la 0

// Trasare axa (in C++):
int len = 50
Point p1(c_bar - len*cos(phi), r_bar - len*sin(phi))
Point p2(c_bar + len*cos(phi), r_bar + len*sin(phi))
line(dst, p1, p2, Scalar(0,0,255), 2)
Atenție: line() primește (x,y) = (coloana, linia), nu (rând, coloană)!
4. Perimetrul
P = nr. pixeli obiect cu cel puțin un vecin fundal
// Vectori vecini N4:
di4[] = {-1, 0, 1, 0}
dj4[] = { 0,-1, 0, 1}

P = 0
PENTRU fiecare pixel (i,j) cu src[i][j]==0:
  esteContur = false
  PENTRU k = 0..3:
    (ni, nj) = (i+di4[k], j+dj4[k])
    DACA isInside(img,ni,nj) SI src[ni][nj]==255:
      esteContur = true
  DACA esteContur:
    P++
5. Factorul de subțiere (Thinness Ratio)
T = 4π * A / P²
T ∈ (0, 1], T=1 → cerc perfect
T = 4.0 * PI * A / (P * P)

// PI este definit in common.h
// Sau: #define M_PI 3.14159265

// Clasificare:
DACA T > 0.7: "rotund / compact"
ALTFEL:       "alungit / neregulat"
6. Elongația (factorul de aspect R)
R = (c_max - c_min + 1) / (r_max - r_min + 1)
rmin=H, rmax=0, cmin=W, cmax=0
PENTRU fiecare pixel (i,j) cu src[i][j]==0:
  rmin = min(rmin, i)
  rmax = max(rmax, i)
  cmin = min(cmin, j)
  cmax = max(cmax, j)

R = (cmax - cmin + 1.0) / (rmax - rmin + 1.0)

// R > 1 → mai lat decat inalt
// R < 1 → mai inalt decat lat
7. Proiecții
Orizontală: h(r) = suma pe c a I(r,c)
Verticală: v(c) = suma pe r a I(r,c)
// Proiectie orizontala (pe fiecare linie):
PENTRU r = 0..H-1:
  h[r] = 0
  PENTRU c = 0..W-1:
    DACA src[r][c] == 0: h[r]++

// Proiectie verticala (pe fiecare coloana):
PENTRU c = 0..W-1:
  v[c] = 0
  PENTRU r = 0..H-1:
    DACA src[r][c] == 0: v[c]++
LAB 5 Etichetarea componentelor conexe
Vecinătăți — definiții
// N4 — sus, stanga, jos, dreapta
di4[] = {-1,  0, 1, 0}
dj4[] = { 0, -1, 0, 1}

// N8 — toti 8 vecini
di8[] = {-1,-1,-1, 0, 0, 1, 1, 1}
dj8[] = {-1, 0, 1,-1, 1,-1, 0, 1}

// Np — vecini ANTERIORI (parcurgere sus→jos, stânga→dreapta)
// Np(i,j) = {(i,j-1), (i-1,j-1), (i-1,j), (i-1,j+1)}
dip[] = { 0,-1,-1,-1}
djp[] = {-1,-1, 0, 1}
Algoritm 1 — BFS (Traversare în lățime)
Structură de date: COADĂ (FIFO) → BFS  |  STIVĂ → DFS
label = 0
labels = matrice zeros(H, W)  // CV_32SC1

PENTRU i = 0..H-1:
  PENTRU j = 0..W-1:
    DACA img[i][j] == 0 SI labels[i][j] == 0:  // obiect neetichetat
      label++
      Q = coada goala
      labels[i][j] = label
      Q.push((i, j))

      CAT TIMP Q nu e goala:
        (r, c) = Q.pop()
        PENTRU fiecare (nr, nc) vecin in N8(r,c):  // sau N4
          DACA isInside(img,nr,nc)
          SI img[nr][nc] == 0
          SI labels[nr][nc] == 0:
            labels[nr][nc] = label
            Q.push((nr, nc))

// Rezultat: labels contine eticheta fiecarui pixel obiect
// label = numarul total de obiecte
Matricea labels are tipul CV_32SC1 (int). Inițializată cu 0 = neetichetat.
Algoritm 2 — Două treceri cu clase de echivalență
// --- TRECEREA 1: etichetare initiala cu Np ---
label = 0
labels = zeros(H, W)
edges = vector de liste de adiacenta (dim 1000)

PENTRU i = 0..H-1:
  PENTRU j = 0..W-1:
    DACA img[i][j] == 0:  // pixel obiect
      L = lista vecinilor etichetati din Np(i,j)
      DACA L e goala:          // nici un vecin anterior etichetat
        label++
        labels[i][j] = label
      ALTFEL:                  // cel putin un vecin etichetat
        x = min(L)
        labels[i][j] = x
        PENTRU fiecare y din L cu y != x:
          edges[x].adauga(y)   // marcheaza echivalenta x ~ y
          edges[y].adauga(x)

// --- TRECEREA 2: re-etichetare cu BFS pe graful de echivalente ---
newlabel = 0
newlabels = zeros(label+1)

PENTRU i = 1..label:
  DACA newlabels[i] == 0:
    newlabel++
    Q = coada, newlabels[i] = newlabel, Q.push(i)
    CAT TIMP Q nu e goala:
      x = Q.pop()
      PENTRU fiecare y din edges[x]:
        DACA newlabels[y] == 0:
          newlabels[y] = newlabel, Q.push(y)

// --- TRECEREA 3: aplicare noilor etichete ---
PENTRU i, j:
  labels[i][j] = newlabels[labels[i][j]]
Generare imagine color din matrice etichete
// Generare culori random — una per eticheta
Vec3b culori[1000]   // sau map
PENTRU lbl = 1..nLabels:
  culori[lbl] = Vec3b(random(0,255), random(0,255), random(0,255))

// Construire imagine color
Mat color(H, W, CV_8UC3, Scalar(255,255,255))  // fundal alb
PENTRU i, j:
  lbl = labels[i][j]
  DACA lbl > 0:
    color[i][j] = culori[lbl]
LAB 6 Algoritmul de urmărire a conturului & Chain Code
Direcțiile N8 — scheme de numerotare
// Vecinii (di, dj) corespunzători direcțiilor 0..7:
//  3  2  1
//  4  X  0   →  0=dreapta, 2=sus, 4=stanga, 6=jos
//  5  6  7       1,3,5,7 = diagonale

di[] = { 0,-1,-1,-1, 0, 1, 1, 1}
dj[] = { 1, 1, 0,-1,-1,-1, 0, 1}
// dj[d] = coloana vecinului in directia d
// di[d] = linia vecinului in directia d
Coduri pare {0,2,4,6} = orizontal/vertical; coduri impare {1,3,5,7} = diagonale
Algoritmul de urmărire a conturului (N8)
// INITIALIZARE:
// 1. Scaneaza stanga→dreapta, sus→jos pana gasesti primul pixel obiect = P0
// 2. dir = 7  (pt N8)  /  dir = 0  (pt N4)

P_curent = P0
dir = 7
contur = [P0]

// BUCLA PRINCIPALA:
REPETA:
  // Cautare vecin contur in sens INVERS acelor de ceasornic:
  DACA dir e par:
    startDir = (dir + 7) mod 8
  ALTFEL (dir e impar):
    startDir = (dir + 6) mod 8

  d = startDir
  REPETA:
    (ni, nj) = P_curent + (di[d], dj[d])
    DACA isInside SI img[ni][nj] == 0:
      P_urmator = (ni, nj)
      dir_nou = d
      BREAK
    d = (d + 1) mod 8  // incearca urmatoarea directie

  chainCode.adauga(dir_nou)
  dir = dir_nou
  P_curent = P_urmator
  contur.adauga(P_curent)

PANA CAND P_curent == P1 SI P_anterior == P0
// (conditie de oprire: am revenit la punctul de start cu aceeasi directie)

// Conturul final: P0 ... P_{n-2}
dir=7 (N8) sau dir=0 (N4). Căutarea e în sens INVERS acelor de ceasornic pornind din poziția calculată cu formula (dir+7)%8 sau (dir+6)%8.
Chain Code — proprietăți cheie
// Codul înlantuit: secventa de directii 0..7 de-a lungul conturului
chainCode = [c0, c1, ..., cn-1]  // ci in {0,1,2,3,4,5,6,7}

// Derivata codului inlantuit (invarianta la rotatie):
// diferenta modulara intre directii consecutive
PENTRU k = 0..n-1:
  derivata[k] = (chainCode[(k+1) mod n] - chainCode[k] + 8) mod 8

// Reconstructie contur (dat P0 si chainCode):
(r, c) = P0
PENTRU fiecare cod d din chainCode:
  (r, c) = (r + di[d], c + dj[d])
  deseneaza pixel la (r, c)
LAB 7 Operații morfologice pe imagini binare
Convenție: obiect = 0 (negru), fundal = 255 (alb). Elementul structural B = o mică matrice (ex: 3×3 cu N8).
1. Eroziune — A Θ B
Pixel rămâne obiect DOAR dacă TOȚI vecinii din B sunt obiect
// Erodeaza: un pixel e obiect in dst
// daca el SI toti vecinii sai din B sunt obiect in src

dst = matrice alba (255)
PENTRU fiecare pixel (i,j):
  DACA src[i][j] == 0:  // e obiect
    toti_vecini_ok = true
    PENTRU fiecare (di,dj) din B:
      DACA isInside SI src[i+di][j+dj] != 0:
        toti_vecini_ok = false; BREAK
    DACA toti_vecini_ok:
      dst[i][j] = 0  // pastreaza ca obiect
Efect: micsorează obiectele, elimină obiecte mici
2. Dilatare — A ⊕ B
Pixel devine obiect dacă CEL PUȚIN UN vecin din B e obiect
// Dilateaza: un pixel devine obiect in dst
// daca el SAU oricare vecin din B e obiect in src

dst = matrice alba (255)
PENTRU fiecare pixel (i,j):
  PENTRU fiecare (di,dj) din B:
    (ni, nj) = (i+di, j+dj)
    DACA isInside SI src[ni][nj] == 0:
      dst[i][j] = 0  // devine obiect
      BREAK
Efect: mărește obiectele, umple goluri mici
3. Deschidere (Opening) — A ◦ B
A ◦ B = (A Θ B) ⊕ B    (eroziune → dilatare)
// Pasul 1: Erodeaza
eroded = eroziune(src, B)
// Pasul 2: Dilateaza rezultatul
result = dilatare(eroded, B)
Efect: elimină obiectele mai mici decât B, netezeşte marginile
4. Închidere (Closing) — A ● B
A ● B = (A ⊕ B) Θ B    (dilatare → eroziune)
// Pasul 1: Dilateaza
dilated = dilatare(src, B)
// Pasul 2: Erodeaza rezultatul
result = eroziune(dilated, B)
Efect: umple goluri și discontinuități interioare, păstrează forma
5. Conturul morfologic — β(A)
β(A) = A − (A Θ B)    (original minus eroziunea)
// Un pixel e pe contur daca:
//   apartine lui A (obiect)  SI  NU apartine eroziunii (A Θ B)

eroded = eroziune(src, B)

PENTRU fiecare pixel (i,j):
  DACA src[i][j] == 0 SI eroded[i][j] == 255:
    contur[i][j] = 0   // pixel de contur
  ALTFEL:
    contur[i][j] = 255  // fundal
B = element structural (ex: N4 cu 4-vecini)
6. Umplere regiuni (Region Filling)
X_k = (X_{k-1} ⊕ B) ∩ A^c    — dilatări repetate + intersecție cu complementul
// Scop: umple golurile (gaurile) din interiorul unui obiect binar
// A = imaginea binara originala
// A_complement = imaginea A cu obiect si fundal inversate (NOT A)
// X0 = un pixel de start DIN INTERIORUL gaurii (ales manual sau automat)

A_complement = NOT(A)  // 0↔255

X = X0  // punct de start interior gaurii
REPETA:
  X_prev = X
  X = dilatare(X, B) intersectat cu A_complement
PANA CAND X == X_prev  // convergenta (nu se mai schimba)

// Rezultatul umplut:
rezultat = A SAU NOT(X)  // sau: union(A, X)
Dilatarea repetata "creste" din punctul interior, oprita de peretii obiectului prin intersectia cu A^c
RECAP Formulele cheie — de memorat
TrăsăturăFormula
Gray (mediere)(R+G+B)/3
Arienr. pixeli = 0
Centru masă r̄sumR/A
Centru masă c̄sumC/A
Thinness ratio T4π·A / P²
Elongație R(c_max-c_min+1)/(r_max-r_min+1)
FDPp[g] = h[g]/M
OperațieDefiniție
Eroziune A Θ BTOȚI vecinii din B = obiect
Dilatare A ⊕ BORICARE vecin din B = obiect
Deschidere A◦B(A Θ B) ⊕ B
Închidere A●B(A ⊕ B) Θ B
Contur β(A)A − (A Θ B)
BFS structurăCOADĂ (FIFO)
dir inițial (N8)7