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
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ă
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]++
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]
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)
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