Metode de Prelucrare ale Imaginilor
UNIVERSITATEA POLITEHNICA din TIMIȘOARA
FACULATATEA DE AUTOMATICĂ ȘI CALCULATOARE
CATEDRA CALCULATOARE
Metode de prelucrare ale imaginilor
Coordonator: Dr. ing. Sebastian Fuicu
Student: Văduva Constantin-Adrian
Timișoara 2016
CUPRINS
I. Introducere 4
II. Pezentare generală. 7
III. Implementarea mai multor tipuri de filtre spațiale pentru reducerea zgomotelor din imagini. 8
1. Filtre spatiale 8
A. Filtre de mediere 8
Filtrul medie 8
Filtrul gaussian 9
B. Filtre ordonate (filtre rang) 11
Filtrul median 11
Algoritmul Huang (pentru aplicarea filtrului median) 12
IV. Analiza unei imagini pe baza histogramei sale. Imbunatatirea unei imagini prin egalizarea histogramei. 13
1. Tehnici de imbunatatire si restaurare in domeniul spatial 13
2. Conversia nivelelor de gri 13
Imbunatatirea contrastului (Contrast stretching) 13
Aplicarea unui prag (thresholding): 13
3. Prelucrari folosind histograma imaginii 14
V. Detecția frontierelor din imagini. 17
Detectia pixelilor de front bazata pe Gradient-ul imaginii 17
Detectorul Roberts (cruce) 18
Detectorul Sobel 20
Detectorul Prewitt 22
Detecția pixelilor de front în imagini color 22
Detectia pixelilor de front bazată pe Laplacian-ul imaginii 24
Laplacian-ul unei imagini 24
Operatorul LoG (Laplacian of Gaussian) 27
4. Extragerea frontierelor 29
Binarizarea matricei amplitudinilor 29
binarizare cu prag fix 30
binarizare cu prag variabil cu procent 31
binarizare cu prag variabil 32
Detectorul Canny 34
Metoda Roberts 40
Extragerea frontierelor bazata pe predictia punctului urmator 40
VI. Transformata Hough (detectia liniilor drepte) 41
VII. Segmentarea prin aplicarea de praguri alese pe baza histogramei 45
Segmentarea prin divizare bazată pe praguri 45
VIII. Subtiere si scheletizare 45
Subtierea 45
Scheletizarea 46
IX. Descrierea aplicatiei realizate – Aplicatie SPG. 51
Prezentarea designului aplicației 51
X. Prezentarea rezultatelor execuției opțiunilor din meniul aplicației. 55
XI. Concluzii 63
XII. Bibliografie 64
Introducere
“The book of nature is written in the characters of geometry"( Galileo)
Informatica a apărut și s-a dezvoltat ca știință în a doua jumătate a secolului XX, după anul 1960, când deja apăruse calculatorul modern – conceput de matematicianul de origine maghiară John von NEUMANN (1903-1957). Pe plan mondial, în acei ani de pionierat pentru Informatică și pentru domeniul utilizării calculatorului, și România și-a adus o contribuție importantă prin școala de logică și informatică creată de matematicianul român GRIGORE C. MOISIL (1906-1973).
Profesorul Gr. C. Moisil a avut contributii remarcabile la dezvoltarea informaticii în România și la formarea primelor generații de informaticieni. A avut o contribuție însemnată la introducerea și folosirea primelor mașini electronice de calcul în țara noastră. Deosebit de valoroase sunt contribuțiile aduse de Grigore C. Moisil în domeniul teoriei algebrice a mecanismelor automate. A elaborat metode noi de analiză și sinteză a automatelor finite, precum și o teorie structurală a acestora. A introdus algebre numite de el lukasiewicziene trivalente și polivalente (numite astăzi, algebre Lukasiewicz-Moisil) și le-a întrebuințat în logică și în studiul circuitelor de comutație. Pentru aceste contribuții, post-mortem, în anul 1996, Gr. C. Moisil a primit Computer Pioneer Award al societății IEEE, premiu primit pentru lucrarea "For polyvalent logic switching circuits". Exemplul oferit de Moisil a fost urmat de generații de informaticieni ce au contribuții recunoscute pe plan internațional, atât în domeniul cercetării științifice, cât și în domeniul utilizării calculatoarelor pentru dezvoltarea generală a societății româneștiși internaționale.
Informatica are un rol important în rezolvarea problemelor care apar ca urmare a dezvoltării tehnologice. Pentru a rezolva problemele este nevoie de competență și experiență care se obțin dacă se are în vedere interdependența Sistem de calcul-Algoritmică-Programare, și dacă se fac eforturi pentru dobândirea de noi cunoștințe și pentru cunoașterea corespunzătoare a tuturor aspectelor privind atât modelul virtual, cât și modelul fizic. Întreaga activitate de cercetare și elaborare de software din domeniul Tehnologiei Informației este determinată de inventarea, conceperea, elaborarea, testarea și implementarea de algoritmi performanți și utili. Marea diversitate a algoritmilor și marea aplicabilitate a acestora în toate domeniile, face ca aceastătemă sa fie mereu actuală și într-o continuă schimbare și perfecționare.
În principiu, rezolvarea unei probleme constă într-o codificare a universului problemei și araționamentelor pentru procesul demonstrativ. Activitățile de laborator au o importanță deosebită în activitatea de învățare. Este necesar ca profesorii de informatică să îmbine eficient orele de predare cu cele de laborator prin care de fapt se încheie ciclul de rezolvare a unei probleme cu calculatorul: problema – modelul matematic-algoritmul -programul -calculator -rezultate -verificare soluții.
În practică rezolvarea problemelor folosind un limbaj de programare a dus în timp, la diverse abordări în funcție de performanța limbajului de programare, performanța calculatorului dar și în funcție de metodele și tehnicile avansate privind implementarea raționamentelor pentru demonstrațiile corespunzătoare problemelor.
Rezolvarea teoretică a unei probleme nu înseamnă și rezolvarea sa practică cu calculatorul. În general, scopul unui limbaj de programare este acela de a facilita rezolvarea unor clase de probleme și de a se adapta mai bine pentru diverse genuri de algoritmi. Este nevoie de experiență în cunoașterea calculatorului, de intuiție și compență, dar și de inspirație și creație. În astfel de cazuri este importantă cunoașterea mai multor limbaje de programare (trebuie ales limbajul de programare adecvat pentru problema din clasa de probleme de rezolvat). Dacă nu este ales limbajul de programare corespunzător, se poate obține rezolvarea unei probleme dar cu risipă de resurse timp / bani / memorie, etc., deci eficiența și performanța au de suferit. Algoritmul trebuie codificat într-un limbaj de programare modern: C, C++, Java, Foxpro, Pascal, Prolog , Oracle, , etc.
Limbajele de programare au apărut cu scopul de a procesa informațiile prin intermediul calculatorului. Teoria limbajelor formale, teoria gramaticilor și automatelor, au făcut posibilă conceperea și realizarea compilatoarelor ce se află la baza utilizării efective a limbajelor de programare în activitatea de procesare a informațiilor folosind calculatorul. De asemenea, multe limbaje de programare sunt utilizate prin intermediul interpretoarelor și emulatoarelor. Spre deosebire de interpretor, un emulator nu transformă codul sursă într-un cod mașină, ci emulează execuția unui program în cadrul unei mașini virtuale. De exemplu, un program sursă Java (fișier text cu extensia .java) nu se transformă în cod obiect, ci în cod de octeți (bytecodes), salvat într-un fișier cu extensia .class. Codul de octeți (rezultatul compilării) generat de compilatorul Java (javac) este interpretat de mașina virtuală Java (JVM – Java Virtual Machine), care convertește codul de octeți în cod mașină. În prezent, pentru majoritatea tipurilor de calculatoare există mașini virtuale Java, ce pot fi descărcate gratuit de la adresa Web www.java.sun.com. De fapt, este vorba de tehnologia Java ce oferă un mediu de programare performant destinat dezvoltării aplicațiilor Java distribuite și independente de platforme (sisteme de operare). Prin utilizarea emulatorului (mașina virtuală Java) o aplicație Java se poate executa pe orice platformă pentru care există un emulator Java.
În ultimii ani, o atenție deosebită s-a acordat Graficii pe calculator, Geometriei computaționale și Realității Virtuale pentru că “O imagine valorează cât zece mii de cuvinte ” (Proverb chinezesc).
Odată cu apariția display-ului grafic (Graphic Display), în anul 1953, s-a trecut la o nouă etapă în dezvoltarea și răspândirea calculatorului. Utilizarea bit-ului prin organizarea eficientă a memoriei calculatorului, nu oferea nici hardware, nici software posibilitatea de modelare spațială a ieșirilor (OUTPUT). Reprezentările grafice folosind caractere (numerice sau alfanumerice) nu era o soluție care să realizeze o reprezentare fidelă a obiectelor reale. Suportul hardware fiind inventat, în perioada 1960-1980 au fost nevoie de cercetări și experimente, modele, algoritmi si programe care să foloseacă aprinderea unui „pixel” (unitatea grafică indivizibilă oferită de un display grafic) ce oferea și culoare, dar mai ales o structură de reprezentare grafică. Această structură reprezintă în informatică, ceea ce reprezintăcalculul integral în analiza matematică (Newton, Riemann, Darboux, Leibniz etc.). Sistemul de diviziuni (proces de discretizare) din calculul integral este analog rezoluției (matricea de pixeli) oferite de un display grafic. Atunci s-a născut Grafica pe calculator: trasarea unui segment de dreaptă (algoritmul Bresenham), trasarea cercului și elipsei, trasarea și aproximarea curbelor, algoritmi de clipping (decupare) (algoritmul Cohen – Sutherland, algoritmul Suitherland-Hodgman, algoritmul Weiler- Atherton), tehnici de vizualizare 2Dși 3D, modele de iluminare și reflexie, modele de tip rastru, modele vectoriale, tehnici de textură. Astfel, s-au pus bazele pentru soluții integrate software și hardware pentru proiectare, analiză și producție asistată de calculator (CAD/CAM/CAE) – Computer Aided Design.
Prin implicarea utilizării calculatorului în rezolvarea problemelor din multe domenii, s-au definit și rezolvat diverse cerințe și proiecte ce în trecut erau de neconceput. Drumul deschis de Grafica pe calculator a fost continuat de Geometria computațională: domenii poligonale, orientare spațială, probleme și algoritmi de triangularizare, acoperirea convexă în 2Dși 3D (algoritmul Quick Hull, algoritmul Graham, algoritmul Jarfis de înfășurare, algoritmul lui Chan), poligoane monotone, Diagrame Voronoi (algoritmul Fortune), Triangularizări Delaunay, Graf de vizibilitate, Algoritmul lui Dijkstra, probleme și algoritmi de intersecții, dinamica mișcării obiectelor în spațiu, probleme de apartenență a punctelor la un domeniu (a se vedea lucrarea “Grafica pe calculator, autori: Florica Moldoveanu, Zoea Racovita, Gabriel Hera, Serban Petrescu,Marius Zaharia”).
După anul 1990, s-au obținut rezultate deosebite în domeniul modelării și simulării obiectelor din lumea reală, atât prin elaborarea de tehnici și algoritmi specifici, cât prin apariția produselor software care să sprijine acest domeniu. Astfel, Realitatea Virtuală (Virtual Reality) este un nou domeniu al Informaticii ce are un impact deosebit în utilizarea calculatorului pe scară largă și pentru o mare diversitate de teme.
Prezentare generală
Tema proiectului constă în realizarea unei aplicații care conține exemplificări concrete ale unor algoritmi de prelucrare grafică. Proiectul reprezintă un material didactic auxiliar, care poate fi folosit la curs sau seminar. Pornind de la obiectivul enunțat, suportul teoretic al lucrării este reprezentat de prezentarea teoriei care se referă la algoritmii implementați în lucrare. În implementarea algoritmilor am respectat formulele matematice și condițiile asociate acestora pentru fiecare caz în parte și de aceea este necesară includerea demonstrațiilor în cadrul lucrării.
Filtrarea imaginilor este necesară pentru a micșora efectele nedorite cauzate de un sistem de achiziție de rezoluție prea scăzută sau de un sistem de transmisie de imagini de bandă mică.
Scopul operatiilor de filtrare este de a micșora efectele zgomotelor introduse de cameră, valori accidentale ale unor pixeli, valori lipsă ale unor pixeli, etc.
Prelucrarea si analiza imaginilor(numita adeseori prescurtat doar prelucrarea imaginilor) s-a născut datorită ideei si necesității de a înlocui observatorul uman printr-o mașină. Este important de precizat că analiza imaginilor a mers amai departe decât simpla înlocuire a observatorului uman, deoarece au apărut soluții inovatoare pentru probleme cu care acesta nu mai fusese confruntat-ca în cazul imaginilor non-vizibile(imagini acustice , radar). Prelucrarea imaginilor înglobează posibilitatea de a dezvolta mașina totală de viziune, capabilă să realizeze funcțiile vizuale ale oricărei viețuitoare.
Câteva domenii care necesită aplicații DIP de clasificare automată a imaginilor pe baza recunoașterii obiectelor componente sunt:
Astronomie – pentru clasificarea automată a imaginilor preluate de la diferitele telescoape în benzi de frecvențe extinse;
Fizica pământului – pentru clasificarea automată (de multe ori în timp real) a imaginilor preluate de la sateliții geostaționari în vederea prognozei meteo, a stării atmosferei, a controlului creșterii vegetației (culturilor) și a comportamentului vulcanilor, etc.;
Fizica atomica sau cuantică- pentru clasificarea automată a imaginilor preluate în experimente cu particule subatomice; Microelectronică – pentru clasificarea automată și detectarea calității circuitelor VLSI pe bază de imagini microscopice;
Automatică – pentru clasificarea automată și controlul automat al calității diferitelor produse, în diverse domenii economice, ce trec pe bandă rulantă prin fața unui controlor automat al calității (aici este inclus și domeniul alimentar);
Televiziune – pentru asigurarea controlului și securității pe bază de camere video de urmărire continuă prin detectarea și recunoașterea în timp real a situațiilor (eventual a persoanelor) frauduloase (aici este inclus controlul circulației prin camere video și radar, controlul bagajelor pe aeroporturi, urmărirea persoanelor în incinta băncilor, etc.);
Armată – pentru recunoașterea și detectarea țintelor și pentru dirijarea în mod direct (orientare) a rachetelor sau avioanelor de luptă;
Inspecția non-chirurgicală – mai ales pentru detectarea și marcarea automată (un fel de diagnoză asistată) a zonelor sau organelor suspecte, din imaginile preluate in vivo prin microscopie electronică, tomografie (raze gamma), fotografierea cu raze X și prin rezonanță magnetică, radiologie;
Analiza compoziției chimice – prin clasificarea și prelucrarea automată a informației achiziționate pe criterii cromatice, spectrale, etc.;
Microscopie – pentru recunoașterea și clasificarea automată a structurilor moleculare și atomice ale substanțelor studiate;
Data mining – clasificare și regăsirea informațiilor de diverse tipuri (multimedia) în volume mari de date structurate, mai mult sau mai puțin, ca baze de date, depozite de date sau bănci de date;
Document image analysis – presupune clasificare, căutarea, regăsirea cu sau fără conversia totală în text a unor documente păstrate sub formă de imagini (copii fax, scanner, etc.);
Multimedia database querying and content retrieval – se referă mai ales la regăsirea unor secvențe video după criterii de căutare specifice inclusiv, să zicem, după chipul unui personaj;
Implementarea mai multor tipuri de filtre spațiale pentru reducerea zgomotelor din imagini.
Filtre spatiale
Filtre de mediere
Fie f(x,y) imaginea asupra căreia se aplica filtrul și w masca de convoluție, un dreptunghi cu laturile de 2*a, 2*b. Imaginea filtrată în pixelul (x,y) este dată de convoluția discretă:
a b
g(x,y) = Σ Σ w(i,j) * f( x+i, y+j)
i= -a j= -b
Filtrul medie
Acesta filtru substituie valoarea fiecărui pixel cu media aritmetică a valorilor pixelilor din fereastra de filtrare. Astfel pentru a=1, b=1 filtrul medie este definit astfel:
1 1
g(x,y) = 1/9 Σ Σ f( x+i, y+j)
i= -1 j= -1
Filtrul medie reprezintă de fapt un filtru de netezire (FTJ- filtru “trece jos”). Tot un FTJ este și filtrul cu masca următoare:
Un FTJ micșorează componentele de înaltă frecvență din imagine(pot fi zgomote). Imaginea care rezultă în urma aplicării unui FTJ este mai neclară decât cea originală. Filtrul diminuează tranzițiile bruște de intensitate astfel încât imaginea pare să aibă mai puține detalii.
Un filtru trece sus (FTS) evidențiază componentele de înaltă frecvență, dar are efect mai mic asupra componentelor de frecvență joasă. În imaginea care rezultă în urma aplicării unui FTS sunt accentuate detaliile în zonele de tranziție de intensitate.
Un model de filtru trece sus este cel cu urmatoarea mască:
W(0,0) = 9
w(i,j) = -1, pentru toate celelalte elemente
Un alt exemplu: filtrul Laplacian cu masca:
Funcția care este descrisă în cele ce urmează aplică filtrul medie pe un dreptunghi din imaginea a, reprezentat prin colturile sale (N1, M1), (N2, M2), iar rezultatul este stocat în matricea b. Masca de filtrare are w x h pixeli.
void f_net (imagine a, imagine b, int w int h, int N1, int M1, int N2, int M2)
{
float wh, s;
wh= (float)(w*h);
w2 = l/2; h2 = h/2;
for ( k = N1 + w2; k < N2 – w2; k++)
for ( l = M1 + h2; l < M2 – h2; l++)
{
s=0;
for ( i = -h2; i <= h2; i++)
for ( j = -w2; j < w2; j++)
s + = a[l+i][k+j];
b[k][l] = (unsigned char) (s/wh);
}
}
Filtrul gaussian
Funcția de distribuție Gaussiana 2D se găsește sub forma:
g(x,y) = (1 /(22)) exp( -(x2 + y2)/ (2 ))
și este folosită ca o functie de “acoperire” a pixelilor imaginii , funcția fiind realizată prin convolutie( Ideea netezirii Gaussiene). Masca filtrului reprezintă o aproximare discretă a funcției respective. Deși în teorie funcția este diferită de zero în oricare punct, în practică funcția este nulă la o distanța de aproximativ 3față de medie. Astfel, masca de convoluție este limitată la această valoare. Masca de convoluție următoare, cu valori întregi, aproximează distribuția Gaussiana cu deviația standard () = 1.0
Filtrul Gaussian reprezintă un filtru de netezire, la fel ca și filtrul medie. Gradul de netezire e calculat de valoarea deviatiei standard. Cu cât valoarea deviației e mai mare cu atât masca de convolutie e mai mare. Netezirea va fi mai puternică dacă valoarea sigma si dimensiunea măștii de convolutie gaussiană sunt mai mari. Valoarea a filtrului va fi corelată cu nivelul zgomotului din imagine : un zgomot gaussian puternic se va filtra cu o mască gaussiană si mai mari.
Filtrul Gaussian diferă de cel mediu pentru că efectuează o medie ponderată a pixelilor din fereastră, cu ponderile crescătoare spre pixelul central.
public Bitmap Gauss(Bitmap IMG, int th_min, int th_max)
{
/*
sbyte[,] gauss = { { 2, 4, 5, 4, 2 },
{ 4, 9, 12, 9, 4 },
{ 5, 12, 15, 12, 5 },
{ 4, 9, 12, 9, 4 },
{ 2, 4, 5, 4, 2 }
};
*/
sbyte[,] gauss = { { 1, 2, 1 },
{ 2, 4, 2 },
{ 1, 2, 1 }
};
//sbyte[,] dy = { { 1, 1, 1 }, { 0, 0, 0 }, { -1, -1, -1 } };
FileStream fileStream = new FileStream(@"save.jpg", FileMode.OpenOrCreate);
IMG.Save(fileStream, ImageFormat.Jpeg);
IMG = new Bitmap(fileStream);
fileStream.Close();
Bitmap dest = new Bitmap(IMG.Width, IMG.Height, PixelFormat.Format24bppRgb);
// normfact = 159;
normfact = 16;
int r, g, b;
// int[,] edgeDir = new int[IMG.Height, IMG.Width];
//float[,] gradient = new float[IMG.Height, IMG.Width];
Rectangle recta = new Rectangle(0, 0, IMG.Width, IMG.Height);
// ImageLockMode lockmod = ImageLockMode.ReadWrite;
BitmapData ImgData = IMG.LockBits(new Rectangle(0, 0, IMG.Width, IMG.Height), ImageLockMode.ReadWrite, IMG.PixelFormat);
BitmapData destData = dest.LockBits(new Rectangle(0, 0, dest.Width, dest.Height), ImageLockMode.ReadWrite, IMG.PixelFormat);
//Calculate convolution kernel size
masca = gauss;
byte Size = (byte)Math.Sqrt(masca.Length);
unsafe
{
for (int i = Size / 2; i < IMG.Height – Size / 2; i++)
for (int j = Size / 2; j < IMG.Width – Size / 2; j++)
{
r = 0;
g = 0;
b = 0;
for (int k = 0; k < Size; k++)
for (int l = 0; l < Size; l++)
{
byte* p = (byte*)(ImgData.Scan0) + (i + k – Size / 2) * ImgData.Stride + (j + l – Size / 2) * 3;
b += (int)p[0] * gauss[k, l];
g += (int)p[1] * gauss[k, l];
r += (int)p[2] * gauss[k, l];
}
// gradient[i, j] = (float)(Math.Sqrt(Math.Pow((bx + gx + rx), 2.0) + Math.Pow((by + gy + ry), 2.0)));
//normam
r = r / normfact;
g = g / normfact;
b = b / normfact;
byte* destp = (byte*)(destData.Scan0) + i * ImgData.Stride + j * 3;
destp[0] = (byte)b;
destp[1] = (byte)g;
destp[2] = (byte)r;
}
}
IMG.UnlockBits(ImgData);
dest.UnlockBits(destData);
return dest;
}
Filtre ordonate (filtre rang)
Filtrele neliniare au scopul de a reduce zgomotele, dar păstrează în același timp fronturile și detaliile din imagine.
Una din clasele de filtre neliniare este aceea a filtrelor ordonate(filtre rang). Aceste filtre se bazeaza pe o ordonare a intensităților pixelilor din fereastra de filtrare.
Fie P=(x,y) punct al imaginii, Vp= rxs o vecinatate a punctului. Fie f1,.., fN intensitatile pixelilor din vecinatatea Vp si RVp ordonarea lor crescatoare : RVp = { f1, f2, …, fN }, fi £ fi+1
Aplicarea unui filtru rang e definită astfel: g(p) = Rang j (RVp), ( intensitatea pixelului P în imaginea care este filtrată va fi intensitatea stocată în poziția j a vectorului RVp ).
Pentru j = 1 este obtinut filtrul min: g (p)= min (RVp) = min { f(p) | p Î V}
Pentru j = N este obtinut filtrul max: g (p)= max (RVp) = max { f(p) | p Î V}
Filtrul median
E definit pentru N impar. Filtrul median corespunde poziției de mijloc în vectorul R, de unde rezultă:
g (p) = Rang(N+1)/2 (RVp), g (p) reprezintă intensitatea mediană dintre cele N intensități.
Scopul filtrului max este de a elimina impulsurile negative .
Scopul filtrului min este de a elimina impulsurile pozitive.
Filtrul median elimină zgomotele fără a degrada imaginea(nu atenuează punctele de front). Filtrele liniare de netezire tind să repartizeze zgomotele doar în punctele ce înconjoară acele puncte care reprezintă zgomot.
Alegerea vecinatatii sau fereastra de filtrare
Forma vecinatatii unde se calculeaza cele N intensitati are influență asupra efectului filtrului. De obicei, vecinatatea este aleasă mică în ideea de a se evita efortul de calcul ce ar rezulta din sortarea unui vector mare de intensități.
Alegerea formei ferestrei poate fi bazată pe cunoașterea caracteristicilor de zgomot din imagine, un exemplu ar putea fi orientarea verticală sau orizontală.
Fereastra de filtrare în cazul filtrului median poate fi de forma pătrată, cruce , dreptunghiulară.
Imaginea ar putea fi filtrată de mai multe ori utilizând aceeași fereastră.
Proprietățile filtrului median
a. Reduce variația intensitătilor din imagine, având ca efect producerea unor regiuni de intensitate care este constantă( aproape constantă).
b. Netezeste oscilațiile de intensitate, cu o perioada mai mică decât lățimea ferestrei.
c. Schimbă valoarea medie a intensităților din imagine în cazul în care distribuția spațială a zgomotului nu este simetrică în fereastra.
d. Păstrează unele forme de frontiere.
e. Nu generează noi nivele de gri.
Aplicarea filtrului median
În cazul aplicării filtrelor liniare sunt efectuate calcule de adunare și înmulțire. Aplicarea filtrului median înseamnă sortarea unui anumit vector de numere întregi. Acest calcul ar putea fi eficientizat în diverse moduri. Deci, vectorul care corespunde poziției (x+1, y) poate fi obținut în urma modificării vectorului sortat , care corespunde poziției (x, y): sunt eliminați din vector pixelii din prima coloana a ferestrei, rămânând (n x m – 2n) pixeli din vectorul vechi.
public Bitmap Median(Bitmap IMG, int th_min, int th_max)
{
List<byte> Red = new List<byte>();
List<byte> Green = new List<byte>();
List<byte> Blue = new List<byte>();
FileStream fileStream = new FileStream(@"save.jpg", FileMode.OpenOrCreate);
IMG.Save(fileStream, ImageFormat.Jpeg);
IMG = new Bitmap(fileStream);
fileStream.Close();
Bitmap dest = new Bitmap(IMG.Width, IMG.Height, IMG.PixelFormat);
Rectangle rectimg = new Rectangle(0, 0, IMG.Width, IMG.Height);
Rectangle rectdest = new Rectangle(0, 0, dest.Width, dest.Height);
// ImageLockMode lockmod = ImageLockMode.ReadWrite;
BitmapData ImgData = new BitmapData();
BitmapData destData = new BitmapData();
ImgData = IMG.LockBits(rectimg, ImageLockMode.ReadWrite, IMG.PixelFormat);
destData = dest.LockBits(rectdest, ImageLockMode.ReadWrite, IMG.PixelFormat);
unsafe
{
for (int i = 1; i < IMG.Height – 1; i++)
for (int j = 1; j < IMG.Width – 1; j++)
{
if (Red.Count > 0) { Red.Clear(); }
if (Green.Count > 0) { Green.Clear(); }
if (Blue.Count > 0) { Blue.Clear(); }
for (int k = 0; k < 3; k++)
for (int l = 0; l < 3; l++)
{
byte* p1 = (byte*)(ImgData.Scan0) + (i + k – 1) * ImgData.Stride + (j + l – 1) * 3;
Blue.Add(p1[0]);
Green.Add(p1[1]);
Red.Add(p1[2]);
}
Blue.Sort();
Green.Sort();
Red.Sort();
byte* destp = (byte*)(destData.Scan0) + i * destData.Stride + j * 3;
destp[0] = (byte)Blue[4];
destp[1] = (byte)Green[4];
destp[2] = (byte)Red[4]; ;
}
}
IMG.UnlockBits(ImgData);
dest.UnlockBits(destData);
return dest;
}
Algoritmul Huang (pentru aplicarea filtrului median)
Fereastra de NxM pixeli este deplasata pe randuri, de la stanga la dreapta. Ferestrele centrate in pixelii (i,j) si (i+1, j) au in comun (M x N – 2N) pixeli.
Calculul valorii mediane din fereastra curenta se bazeaza pe valoarea mediana a ferestrei anterioare si pe histograma imaginii din fereastra.
Astfel, valoarea mediana poate fi determinata stiind ca N/2 pixeli ai imaginii din fereastra au intensitati mai mici decat ea.
Ex: 4, 6, 8, 10, 11, 12, 14 (N=7)
Valoarea mediana este 10; pozitia (N+1)/2 = 4
Daca valoarea mediana corespunde pozitiei i in histograma (nivelul i de intensitate), atunci numarul total de pixeli cu intensitatea mai mica decat i este
i-1
Σ h(j)
j=0
unde h este vectorul histograma ( l(k)=numarul de pixeli cu intensitatea k ).
Algoritmul Huang in pseudocod:
Pentru fiecare rand al imaginii
Pentru fiecare fereastra de pe rand
calculeaza histograma
construieste si sorteaza vectorul pixelilor din fereastra
determina valoarea mediana
Pentru celelalte ferestre de pe rand
actualizeaza histograma:
scazand contributiile pixelilor de pe coloana din stanga a ferestrei
adaugand contributiile pixelilor de pe coloana din dreapta a ferestrei
determina valoarea mediana a ferestrei folosind valoarea mediana a ferestrei anterioare, care se ajusteaza impunand conditia: (nr.pixeli cu intensitate < med ) = N/2
Observatie:
Filtrul nu se aplica pe marginea de latime N/2, repectiv M/2 a imaginii. Pentru filtrarea acestor margini se foloseste o fereastra trunchiata.
Analiza unei imagini pe baza histogramei sale. Imbunatatirea unei imagini prin egalizarea histogramei.
Tehnici de imbunatatire si restaurare in domeniul spatial
O imagine in mai multe nivele de gri se reprezinta in domeniul spatial printr-o functie bidimensionala, f(x,y), unde:
-(x,y) este o adresa de pixel imagine
-f(x,y) este intensitatea (nivelul de gri al) pixelului
Vom considera imaginea reprezentata printr-o matrice bi-dimensionala, img, astfel incat img[y][x] = f(x,y).
Notam cu T transformarea aplicata unei imagini g(x,y) – imaginea de intrare, care se doreste a fi imbunatatita- si cu f(x,y) imaginea rezultata prin aplicarea transformarii: f(x,y) = T(g(x,y)).
Transformarea T este definita pe o vecinatate a pixelului (x,y), de forma unei ferestre dreptunghiulare centrata in pixelul (x,y). Vecinatea poate fi redusa la pixelul (x,y).
Notam cu: 0, 1, 2, 3, …., L-1, nivelele de gri din imagine, Lmax = L-1.
Conversia nivelelor de gri
Imbunatatirea contrastului (Contrast stretching)
Fiind date 2 nivele de intensitate, Lval si Hval, Lval < Hval, transformarea este:
g(x,y) = 0 daca f(x,y) < =Lval
Lmax daca f(x,y) > = Hval
s * ( f(x,y) – Lval), Lval < f(x,y) < Hval
unde s = Lmax/(Hval – Lval), deci, intensitatile pixelilor Lval < f(x,y) < Hval sunt scalate a.i. sa fie uniform distribuite in imagine.
Aplicarea unui prag (thresholding):
g(x,y) = 0 daca f(x,y) < P
Lmax daca f(x,y) > = P
unde P este numita „valoare de prag“.
Aceste doua transformari pot fi definite si printr-o functie de transformare a nivelului de gri: T: 0 .. (L-1) 0 .. (L-1)
Domeniul de definitie reprezinta nivelele de gri din imaginea de intrare iar domeniul rezultatului nivelele de gri din imaginea de iesire.
In acest caz, T este o functie liniara definita pe portiuni. Cazul general in care T este o functie liniara definita pe portiuni poate fi reprezentat astfel:
Prelucrari folosind histograma imaginii
Histograma unei imagini cu nivelele de gri k= 0, 1, …, L-1 este o functie discreta, h(k) = nk , unde nk este numarul de pixeli din imagine, cu intensitatea k.
Histograma normalizata este definita prin functia: h(k) = nk / n, unde n este numarul total de pixeli din imagine.
Histograma imaginii da informatii importante asupra continutului imaginii. Astfel:
daca valorile mari ale histogramei sunt concentrate in zona de intensitati mici, imaginea este intunecata.
daca valorile mari ale histogramei sunt concentrate in zona de intensitati mari, imaginea este luminoasa.
daca imaginea contine 2 obiecte cu intensitati diferite sau un obiect care se distinge clar de fond, histograma prezinta doi lobi (varfuri de maxim local, respectiv un varf de minim local).
Daca histograma este concentrata intr-o zona ingusta de intensitati, contrastul imaginii este slab si caliatatea imaginii (in privinta perceperii) este slaba. Calitatea imaginii poate fi imbunatatita modificandu-i histograma, printr-o transformare numita “egalizarea histogramei sau liniarizarea histogramei”.
Probabilitatea de aparitie in imagine a nivelului de intensitate k este aproximat prin: pr(k) = nk / n , k=0,..L-1
Transformarea de egalizare a histogramei este definita astfel:
Intensitatea k din imaginea de intrare se va inlocui cu intensitatea gk * (L-1) in imaginea de iesire.
Calculul histogramei normalizate:
typedef unsigned char **imagine;
void histo(imagine a, int H, int W, int L, float *h)
{ unsigned long*lh,n;
* alocare lh
for (i=0;i<L;i++) lh[i]=0;
for (i=0; i<H; I++)
for (j=0; j<W;j++)
lh[ (unsigned int) a[i][j]]++;
n=(float) H*W;
for (i=0; i<L; i++)
h[i]=lh[i]/n;
* dealocare lh; }
Functia pentru calculul imaginii pe baza histogramei transformate:
void histEgal (imagine In, imagine Out, int H, int W, int L)
{ int i, j;
int *f = ( ) malloc( );
float * h = ( ) malloc(L*sizeof( ));
float *g = …….
// Calcul histograma imagine de intrare
histo( In, H, W, L, h);
// transforma histograma
g[0] = h[0];
for ( I=1; I<L; I++ )
g[i] = g[i-1] + h[i]
// Calculeaza intensitatile din imaginea de iesire
for(i = 0; i<L; ++ )
f[i] = g[i]* (L-1);
for(i = 0; i<H; I++ )
for(j = 0; j<W; j++ )
Out[i][j] = ((unsigned char) f[(unsigned int)In[i][j]];
* dealocare f, h, g ;}
public Bitmap Histogram(Bitmap IMG)
{
try
{
FileStream fileStream = new FileStream(@"save.jpg", FileMode.OpenOrCreate);
IMG.Save(fileStream, ImageFormat.Jpeg);
IMG = new Bitmap(fileStream);
fileStream.Close();
}
catch (Exception e)
{ }
Rectangle imgrect = new Rectangle(0, 0, IMG.Width, IMG.Height);
BitmapData ImgData = IMG.LockBits(imgrect, ImageLockMode.ReadWrite, IMG.PixelFormat);
// valorile histogramei
histogram = new long[L];
unsafe
{
for (int i = 0; i < IMG.Height; i++)
for (int j = 0; j < IMG.Width; j++)
{
//media intensitatilor r g b
byte* imgp = (byte*)(ImgData.Scan0) + i * ImgData.Stride + j * 3;
byte gray = (byte)((imgp[0] + imgp[1] + imgp[2]) / 3);
//numar cati pixeli au o anumita intensitate
histogram[gray]++;
}
}
IMG.UnlockBits(ImgData);
//valoarea maxima, pentru normare
long max = 0;
for (int i = 0; i < 256; i++)
if (histogram[i] > max)
max = histogram[i];
//generate image
Bitmap chart = new Bitmap(260, 260);
Graphics desen = Graphics.FromImage(chart);
Pen creion = new Pen(Color.Blue);
for (int x = 0; x < 256; x++)
{
//coordonata oy
//oy e desenata in sens opus,=>> calculam 1-value
int y = (int)(256 * (1 – (double)histogram[x] / max));
y = y > 255 ? 255 : y;
//desenam
desen.DrawLine(creion, new Point(x, 256), new Point(x, y));
}
return chart;
}
public Bitmap HistEgal(Bitmap IMG)
{ int i, j;
FileStream fileStream = new FileStream(@"save.jpg", FileMode.OpenOrCreate);
IMG.Save(fileStream, ImageFormat.Jpeg);
IMG = new Bitmap(fileStream);
fileStream.Close();
Bitmap newimg = Histogram(IMG);
Rectangle imgrect = new Rectangle(0, 0, IMG.Width, IMG.Height);
BitmapData ImgData = IMG.LockBits(imgrect, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
Bitmap dest = new Bitmap(IMG.Width, IMG.Height, PixelFormat.Format24bppRgb);
BitmapData destData = new BitmapData();
Rectangle rectdest = new Rectangle(0, 0, dest.Width, dest.Height);
// ImgData = newimg.LockBits(imgrect, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
destData = dest.LockBits(rectdest, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
int W = IMG.Width;
int H = IMG.Height;
// float[] h, g, f;
float[] h = new float[L];
float[] g = new float[L];
float[] f = new float[L];
float n = W * H;
/*
float max;
max = -1;
for (i = 0; i < L; i++)
{
if (histogram[i] > max) { max = (float)histogram[i]; }
}*/
for (i = 0; i < L; i++)
{
h[i] =(float)(histogram[i] / n);
}
// transforma histograma
g[0] = h[0];
for ( i = 1; i < L; i++ ){
g[i] = g[i-1] + h[i];
}
// Calculeaza intensitatile din imaginea de iesire
for(i = 0; i < L; i++ ){
f[i] = g[i]* (L-1);
}
unsafe
{
for (i = 0; i < H; i++)
for (j = 0; j < W; j++)
{
byte* imgp = (byte*)(ImgData.Scan0) + i * ImgData.Stride + j * 3;
byte* dp = (byte*)(destData.Scan0) + i * destData.Stride + j * 3;
byte gray = (byte)((imgp[0] + imgp[1] + imgp[2]) / 3);
dp[0] = (byte)f[gray];
dp[1] = (byte)f[gray];
dp[2] = (byte)f[gray];
}
}
if (imgrect.IsEmpty == false)
{
IMG.UnlockBits(ImgData);
}
if (rectdest.IsEmpty == false)
{
dest.UnlockBits(destData);
}
return dest;
}
}
Detecția frontierelor din imagini.
Detectia pixelilor de front bazata pe Gradient-ul imaginii
Notam cu și , atunci
Amplitudinea gradientului în pixelul (x,y) este:
Ea reprezinta puterea frontului si este adesea aproximata prin:
Direcția gradientului (frontului) este:
Vectorul gradient este perpendicular pe directia frontierei:
Punctele de front dintr-o imagine sunt detectate calculând gradientul in fiecare pixel și identificând acei pixeli pentru care amplitudinea gradientului este mai mare decât un prag dat.
Aproximarea prin diferențe finite. Cele 2 derivate se aproximează prin:
O altă aproximare este:
D2x și D2y sunt aproximări mai bune în mijlocul intervalului, adică în (x,y) și corespund corelării funcției imagine cu măștile:
½[-1 0 1] si ½ [ 1]
[ 0]
[-1]
Detectorul Roberts (cruce)
Componentele gradientului intr-un pixel (x,y) sunt aproximate prin:
D+ (x,y) = f(x + 1, y + 1) – f(x,y)
D- (x,y) = f(x , y + 1) – f(x + 1, y)
D+ si D- se calculeaza cu urmatoarele 2 masti de convolutie:
D- D +
Amplitudinea gradientului in (x,y) este aproximata prin:
D(x,y) = |D-(x,y)| + |D+(x,y)|
Orientarea frontului in pixelul (x,y) este data de:
θ = arctg(D+ / D-) – 3π/4
De regula, iesirea produsa de acest detector este numai matricea amplitudinilor, vizualizata sub forma unei imagini in nivele de gri. In acest caz, cele doua componente ale gradientului pot fi calculate si adunate intr-un singur pas, utilizand urmatoarea masca de pseudo-convolutie:
public Bitmap Roberts(Bitmap IMG, int th_min, int th_max)
{
//sbyte[,] roberts = { { 1, 1 }, { -1, -1 } };
sbyte[,] dx = { { 1, 0 }, { 0, -1 } };
sbyte[,] dy = { { 0, 1 }, { -1, 0 } };
Bitmap dest = new Bitmap(IMG.Width, IMG.Height, PixelFormat.Format24bppRgb);
int r, g, b, rx, gx, bx, ry, gy, by;
int[,] edgeDir = new int[IMG.Height, IMG.Width];
float[,] gradient = new float[IMG.Height, IMG.Width];
Rectangle rectimg = new Rectangle(0, 0, IMG.Width, IMG.Height);
Rectangle rectdest = new Rectangle(0, 0, dest.Width, dest.Height);
// ImageLockMode lockmod = ImageLockMode.ReadWrite;
BitmapData ImgData = new BitmapData();
BitmapData destData = new BitmapData();
ImgData = IMG.LockBits(rectimg, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
destData = dest.LockBits(rectdest, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
//Calculate convolution kernel size
byte Size = (byte)Math.Sqrt(dx.Length);
unsafe
{
for (int i = 0; i < IMG.Height – 1; i++)
for (int j = 0; j < IMG.Width – 1; j++)
{
rx = 0;
gx = 0;
bx = 0;
ry = 0;
gy = 0;
by = 0;
for (int k = 0; k < Size; k++)
for (int l = 0; l < Size; l++)
{
byte* p = (byte*)(ImgData.Scan0) + (i + k) * ImgData.Stride + (j + l) * 3;
// bx += (int)(p[0] * roberts[k, l]);
// gx += (int)(p[1] * roberts[k, l]);
// rx += (int)(p[2] * roberts[k, l]);
bx += (int)(p[0] * dx[k, l]);
gx += (int)(p[1] * dx[k, l]);
rx += (int)(p[2] * dx[k, l]);
by += (int)(p[0] * dy[k, l]);
gy += (int)(p[1] * dy[k, l]);
ry += (int)(p[2] * dy[k, l]);
}
b = (int)(Math.Sqrt(Math.Pow(bx, 2.0) + Math.Pow(by, 2.0)));
g = (int)(Math.Sqrt(Math.Pow(gx, 2.0) + Math.Pow(gy, 2.0)));
r = (int)(Math.Sqrt(Math.Pow(rx, 2.0) + Math.Pow(ry, 2.0)));
if (r < th_min) { r = 0; }
if (g < th_min) { g = 0; }
if (b < th_min) { b = 0; }
if (r > 255) { r = 255; }
if (g > 255) { g = 255; }
if (b > 255) { b = 255; }
byte* destp = (byte*)(destData.Scan0) + i * ImgData.Stride + j * 3;
destp[0] = (byte)b;
destp[1] = (byte)g;
destp[2] = (byte)r;
}
}
if (rectimg.IsEmpty == false)
{
IMG.UnlockBits(ImgData);
}
if (rectdest.IsEmpty == false)
{
dest.UnlockBits(destData);
}
return dest;
}
Detectorul Sobel
Gradientul este aproximat prin convolutia imaginii cu urmatoarele masti:
Dx Dy
Mastile sunt proiectate pentru raspuns maxim la fronturi cu directiile verticala si orizontala. Ele pot fi aplicate separat imaginii de intrare, fiind apoi folosite pentru calculul amplitudinii si al directiei frontului in fiecare pixel al imaginii de intrare.
Amplitudinea este aproximata prin: D(x,y) = |Dx| + |Dy|
iar directia prin: θ= arctg (Dy / Dx)
Unghiul zero inseamna ca directia contrastului maxim de la negru la alb este de la stanga la dreapta in imagine, celelalte unghiuri fiind masurate in sens trigonometric (invers acelor de ceas) fata de unghiul zero.
Atunci cand iesirea detectorului de fronturi este numai matricea amplitudinilor, componentele Dx si Dy pot fi calculate si adunate intr-un singur pas, folosind operatorul de pseudo-convolutie:
D(x,y) = | (P3 + 2xP6 + P9) – (P1 + 2x P4 + P7)| + | (P1 + 2xP2 + P3) – (P7 + 2x P8 + P9) | ;unde P1,…, P9 sunt pixelii acoperiti de masca de convolutie
{{{ Tabel 3 randuri, 3 coloane, in ordine se completeaza cu P_1,…P_9}}}
public Bitmap Sobel(Bitmap IMG, int th_min, int th_max)
{
FileStream fileStream = new FileStream(@"save.jpg", FileMode.OpenOrCreate);
IMG.Save(fileStream, ImageFormat.Jpeg);
IMG = new Bitmap(fileStream);
fileStream.Close();
int lun = 3;
if (IMG.PixelFormat.Equals(PixelFormat.Format32bppRgb) == true)
{
//Console.Write("aaa");
lun = 4;
}
fileStream.Close();
sbyte[,] dx = { { -1, 0, 1 },
{ -2, 0, 2 },
{ -1, 0, 1 }
};
sbyte[,] dy = { { -1, -2, -1 },
{ 0, 0, 0 },
{ 1, 2, 1 }
};
Bitmap dest = new Bitmap(IMG.Width, IMG.Height, PixelFormat.Format24bppRgb);
// dest = IMG;
double rx, gx, bx, ry, gy, by, r, g, b;
//byte r1;
int[,] edgeDir = new int[IMG.Height, IMG.Width];
float[,] gradient = new float[IMG.Height, IMG.Width];
Rectangle rectimg = new Rectangle(0, 0, IMG.Width, IMG.Height);
Rectangle rectdest = new Rectangle(0, 0, dest.Width, dest.Height);
//ImageLockMode lockmod = ImageLockMode.ReadWrite;
BitmapData ImgData = new BitmapData();
BitmapData destData = new BitmapData();
ImgData = IMG.LockBits(rectimg, ImageLockMode.ReadWrite, IMG.PixelFormat);
destData = dest.LockBits(rectdest, ImageLockMode.ReadWrite, dest.PixelFormat);
masca = dx;
byte Size = (byte)Math.Sqrt(masca.Length);
unsafe
{
for (int i = 1; i < IMG.Height – 1; i++)
for (int j = 1; j < IMG.Width – 1; j++)
{
rx = 0;
gx = 0;
bx = 0;
ry = 0;
gy = 0;
by = 0;
for (int k = 0; k < Size; k++)
for (int l = 0; l < Size; l++)
{
byte* p = (byte*)(ImgData.Scan0) + (i + k – 1) * ImgData.Stride + (j + l – 1) * lun;
bx += p[0] * dx[k, l];
gx += p[1] * dx[k, l];
rx += p[2] * dx[k, l];
by += p[0] * dy[k, l];
gy += p[1] * dy[k, l];
ry += p[2] * dy[k, l];
}
b = (Math.Sqrt(Math.Pow(bx, 2.0) + Math.Pow(by, 2.0)));
g = (Math.Sqrt(Math.Pow(gx, 2.0) + Math.Pow(gy, 2.0)));
r = (Math.Sqrt(Math.Pow(rx, 2.0) + Math.Pow(ry, 2.0)));
if (r < th_min) { r = 0; }
if (g < th_min) { g = 0; }
if (b < th_min) { b = 0; }
if (r > th_max) { r = th_max; }
if (g > th_max) { g = th_max; }
if (b > th_max) { b = th_max; }
double ddx = rx + gx + bx;
double ddy = ry + gy + by;
tangent = -1;
if (ddx != 0 && ddy != 0)
{
tangent = Math.Atan(ddy / ddx);//tre adus la (-pi,pi)
tangent = (float)(tangent * 180 / Math.PI);
// tangent = tangent – 90;
if (tangent > 0) { tangent -= 90; }
else
{
tangent += 90;
}
}
if (ddy == 0 && ddx == 0) { tangent = 0; }
if (ddy != 0 && ddx == 0) { tangent = 90; }
gradient[i, j] = (float)((r + g + b) / 3);
int tangent2;
tangent2 = 0;
// Math.
if (((tangent < 22.5) && (tangent > -22.5)) || (tangent > 157.5 && tangent <= 180) || (tangent <= -157.5 && tangent > -180))
{
tangent2 = 0;
}
if (((tangent > 22.5) && (tangent < 67.5)) || ((tangent < -112.5) && (tangent > -157.5)))
{
tangent2 = 45;
}
if (((tangent > 67.5) && (tangent < 112.5)) || ((tangent < -67.5) && (tangent > -112.5)))
{
tangent2 = 90;
}
if (((tangent > 112.5) && (tangent < 157.5)) || ((tangent < -22.5) && (tangent > -67.5)))
{
tangent2 = 135;
}
if (r > th_min || g > th_min || b > th_min)
{
edge[i, j] = tangent2;
byte* destp = (byte*)(destData.Scan0) + i * destData.Stride + j * lun;
destp[0] = (byte)b;
destp[1] = (byte)g;
destp[2] = (byte)r;
}
}
}
if (rectimg.IsEmpty == false)
{
IMG.UnlockBits(ImgData);
}
if (rectdest.IsEmpty == false)
{
dest.UnlockBits(destData);
}
return dest;
Detectorul Prewitt
Ca si precedentul (Sobel), doar cu alte Dx si Dy
Dx Dy
Detecția pixelilor de front în imagini color
Se poate baza pe diferențele în strălucire sau în nuanță (cele 3 proprietăți ale luminii (HSV) : nuanța, saturație, strălucire/intensitate).
Detecția bazată pe strălucire se tratează la fel ca pentru imagini cu mai multe nivele de gri.
Detectia bazata pe nuanțe, identifica punctele de front pe baza diferențelor cromatice.
Amplitudinea se aproximeaza prin: D = |DR| + |DG| + |DB|
O imagine in nivele de gri este reprezentata printr-o functie scalara, f(x,y). Gradientul sau intr-un punct (x,y) este un vector a carui directie indica rata maxima de schimbare a functiei f in punctul (x,y).
Culoarea este un vector intr-un spatiu tri-dimensionalFie r, g, b versorii axelor R,G,B din spatiul RGB al culorilor.
Fie vectorii u si v care reprezinta variatia de culoare RGB intr-un pixel (x,y),
Se poate arata ca directia de schimbare maxima a culorii in punctul (x,y) este data de unghiul
iar este puterea frontului (rata maximă de schimbare), unde
Prin aplicarea unui prag peste se obține harta frontului.
public Bitmap Prewitt(Bitmap IMG, int th_min, int th_max)
{
FileStream fileStream = new FileStream(@"save.jpg", FileMode.OpenOrCreate);
IMG.Save(fileStream, ImageFormat.Jpeg);
IMG = new Bitmap(fileStream);
fileStream.Close();
Bitmap dest = new Bitmap(IMG.Width, IMG.Height, IMG.PixelFormat);
sbyte[,] dx = { { -1, 0, 1 }, { -1, 0, 1 }, { -1, 0, 1 } };
sbyte[,] dy = { { 1, 1, 1 }, { 0, 0, 0 }, { -1, -1, -1 } };
int rx, gx, bx, ry, gy, by, r, g, b;
int[,] edgeDir = new int[IMG.Height, IMG.Width];
float[,] gradient = new float[IMG.Height, IMG.Width];
Rectangle recta = new Rectangle(0, 0, IMG.Width, IMG.Height);
// ImageLockMode lockmod = ImageLockMode.ReadWrite;
BitmapData ImgData = IMG.LockBits(new Rectangle(0, 0, IMG.Width, IMG.Height), ImageLockMode.ReadWrite, IMG.PixelFormat);
BitmapData destData = dest.LockBits(new Rectangle(0, 0, dest.Width, dest.Height), ImageLockMode.ReadWrite, IMG.PixelFormat);
//Calculate convolution kernel size
masca = dx;
byte Size = (byte)Math.Sqrt(masca.Length);
unsafe
{
for (int i = 1; i < IMG.Height – 1; i++)
for (int j = 1; j < IMG.Width – 1; j++)
{
rx = 0;
gx = 0;
bx = 0;
ry = 0;
gy = 0;
by = 0;
for (int k = 0; k < Size; k++)
for (int l = 0; l < Size; l++)
{
byte* p = (byte*)(ImgData.Scan0) + (i + k – 1) * ImgData.Stride + (j + l – 1) * 3;
bx += (int)p[0] * dx[k, l];
gx += (int)p[1] * dx[k, l];
rx += (int)p[2] * dx[k, l];
by += (int)p[0] * dy[k, l];
gy += (int)p[1] * dy[k, l];
ry += (int)p[2] * dy[k, l];
}
gradient[i, j] = (float)(Math.Sqrt(Math.Pow((bx + gx + rx), 2.0) + Math.Pow((by + gy + ry), 2.0)));
//normam
r = Math.Abs(rx) + Math.Abs(ry);
g = Math.Abs(gx) + Math.Abs(gy);
b = Math.Abs(bx) + Math.Abs(by);
if (r > th_max) { r = th_max; }
if (g > th_max) { g = th_max; }
if (b > th_max) { b = th_max; }
//Store the calculated value in destination image
byte* destp = (byte*)(destData.Scan0) + i * ImgData.Stride + j * 3;
destp[0] = (byte)b;
destp[1] = (byte)g;
destp[2] = (byte)r;
}
}
IMG.UnlockBits(ImgData);
dest.UnlockBits(destData);
return dest;
}
Detectia pixelilor de front bazată pe Laplacian-ul imaginii
Laplacian-ul unei imagini
Operatorul Laplacian:
Derivatele parțiale de ordinul al 2 –lea, de-a lungul direcțiilor x si y, sunt aproximate prin diferente finite astfel:
Pentru . Rezultă :
Laplacian-ul poate fi implementat prin urmatoarea masca de convolutie:
Aplicarea operatorului consta in convolutia imaginii cu masca respectiva. Intrarea operatorului este o imagine, f(x,y), in mai multe nivele de gri iar iesirea, g(x,y), de asemenea, o imagine in mai multe nivele de gri:
G(x,y) = 0 in zonele de intensitate constanta si in punctele de frontiera
<0 sau > 0 in vecinatatea frontierei
Laplacian-ul unei imagini este utilizat si pentru implementarea unor filtre de imbunatatire a imaginilor prin evidentierea frontierelor.
Alte filtre de imbunatatie a imaginilor bazate pe Laplacian sunt definite astfel:
g(x,y) = f(x,y) – f(x,y)
f(x,y) + f(x,y)
Adunand sau scazand imaginea rezultata prin aplicarea Laplacianului se obtine o imagine cu frontierele evidentiate.
Pentru detectia frontierelor folosind Laplacianul, se poate proceda in mai multe moduri:
1) Se aplica un prag peste g(x,y), rezultand o imagine binara, B(x,y):
B(x,y) = 1 , daca |g(x,y)| > prag
0, altfel
2) Sunt considerate puncte de frontiera acele puncte in care g(x,y) =0 si gradientul are valoarea locala maxima (sau peste un prag dat). Directia frontului se obtine utilizand directia in care are loc trecerea prin zero a Laplacianului.
public Bitmap Laplace(Bitmap IMG, int th_min, int th_max)
{
FileStream fileStream = new FileStream(@"save.jpg", FileMode.OpenOrCreate);
IMG.Save(fileStream, ImageFormat.Jpeg);
IMG = new Bitmap(fileStream);
fileStream.Close();
sbyte[,] dx = { { 0, 1, 0 }, { 1, -4, 1 }, { 0, 1, 0 } };
sbyte[,] dx1 = { { 0, -1, 0 }, { -1, 4, -1 }, { 0, -1, 0 } };
sbyte[,] dx2 = { { 0, 1, 0 }, { 1, 1, 1 }, { 0, 1, 0 } };
Bitmap dest = new Bitmap(IMG.Width, IMG.Height, PixelFormat.Format24bppRgb);
// dest = IMG;
// int rx, gx, bx, ry, gy, by, r, g, b;
int r, g, b;
// byte r1;
int[,] edgeDir = new int[IMG.Height, IMG.Width];
float[,] gradient = new float[IMG.Height, IMG.Width];
Rectangle rectimg = new Rectangle(0, 0, IMG.Width, IMG.Height);
Rectangle rectdest = new Rectangle(0, 0, dest.Width, dest.Height);
//ImageLockMode lockmod = ImageLockMode.ReadWrite;
BitmapData ImgData = new BitmapData();
BitmapData destData = new BitmapData();
ImgData = IMG.LockBits(rectimg, ImageLockMode.ReadWrite, IMG.PixelFormat);
destData = dest.LockBits(rectdest, ImageLockMode.ReadWrite, dest.PixelFormat);
masca = dx;
byte Size = (byte)Math.Sqrt(masca.Length);
unsafe
{
for (int i = 1; i < IMG.Height – 1; i++)
for (int j = 1; j < IMG.Width – 1; j++)
{
r = 0;
b = 0;
g = 0;
for (int k = 0; k < Size; k++)
for (int l = 0; l < Size; l++)
{
byte* p = (byte*)(ImgData.Scan0) + (i + k – 1) * ImgData.Stride + (j + l – 1) * 3;
b += (int)p[0] * dx1[k, l];
g += (int)p[1] * dx1[k, l];
r += (int)p[2] * dx1[k, l];
}
if (r < th_min) { r = 0; }
if (g < th_min) { g = 0; }
if (b < th_min) { b = 0; }
if (r > th_max) { r = th_max; }
if (g > th_max) { g = th_max; }
if (b > th_max) { b = th_max; }
if (r > th_min && g > th_min && b > th_min)
{
byte* destp = (byte*)(destData.Scan0) + i * destData.Stride + j * 3;
destp[0] = (byte)b;
destp[1] = (byte)g;
destp[2] = (byte)r;
}
}
}
if (rectimg.IsEmpty == false)
{
IMG.UnlockBits(ImgData);
}
if (rectdest.IsEmpty == false)
{
dest.UnlockBits(destData);
}
return dest;
}
Operatorul LoG (Laplacian of Gaussian)
Operatorul LoG imbina urmatoarele trei operatii:
Filtrarea, prin aplicarea unui filtru Gaussian
Imbunatatirea, prin aplicarea operatorului Laplacian
Detectia fronturilor, ca puncte de trecere prin zero a Laplacianului
Fie g(x,y) filtrul Gaussian:
g(x,y) = (1 /(22)) exp( -(x2 + y2)/ (2 ))
Aplicarea sa asupra imaginii consta in convolutia functiei g(x,y) cu f(x,y).
Convolutia discreta a celor doua functii este definita astfel:
a b
g(x,y)*f(x,y) = Σ Σ w(i,j) * f( x+i, y+j)
i= -a j= -b
unde w este masca de convolutie care aproximeaza functia g(x,y) in planul discret.
Laplacianul convolutiei celor doua functii se poate scrie astfel:
(g(x,y)*f(x,y)) = (g(x,y))*f(x,y)
LoG(x,y) = (g(x,y))
Deoarece intereseaza numai punctele de trecere prin zero, in calculul mastii, constanta 1/(πσ4) se inlocuieste cu un factor de scalare arbitrar, s. Se observa ca:
LoG(0) = – s
………………..
LoG(σ 21/2) =0
Deci, functia este nenula pe o distanta circulara de σ 21/2 de la origine. Latimea filtrului este de w = σ 21/2 = aproximativ 3 σ.
LOG poate fi folosit si pentru imbunatatirea imaginilor. Daca iesirea operatorului este adunata la imaginea originala, se obtine o imagine in care zgomotele au fost reduse iar frontierele din imaginea originala sunt evidentiate, au mai mult contrast.
Atunci cand operatorul LoG este utilizat pentru detectia frontierelor, ne intereseaza punctele (x,y) care reprezinta trecerea prin zero a Laplacianului. Detectia acestor puncte este ingreunata de urmatoarele aspecte:
Prin aplicarea operatorului LoG sunt eliminate punctele izolate de zgomot si structurile mici. Din pacate, punctele de frontiera sunt imprastiate, ceea ce ingreuneaza procesul de detectie a frontierelor.
Operatorul LoG calculeaza derivata de ordinul 2 a imaginii. Aceasta inseamna ca in zonele in care imaginea are intensitate constanta (gradientul intensitatii este zero), raspunsul operatorului va fi zero. In vecinatatea unei schimbari de intensitate (frontiere) raspunsul operatorului va fi pozitiv in partea intunecata si negativ in parte luminoasa.
In vecinatatea unei frontiere subtiri dintre 2 regiuni de intensitate uniforma dar diferita, raspunsul operatorului LoG va fi:
Zero, la o distanta mare fata de frontiera
Pozitiv de o parte a frontierei
Negativ de cealalata parte a frontierei
Zero in punctele frontierei
public Bitmap LoG(Bitmap IMG, int th_min, int th_max)
{
FileStream fileStream = new FileStream(@"save.jpg", FileMode.OpenOrCreate);
IMG.Save(fileStream, ImageFormat.Jpeg);
IMG = new Bitmap(fileStream);
fileStream.Close();
sbyte[,] dx = {{0,1,1,2,2,2,1,1,0},
{1,2,4,5,5,5,4,2,1},
{1,4,5,3,0,3,5,4,1},
{2,5,3,-12,-24,-12,3,5,2},
{2,5,0,-24,-40,-24,0,5,2},
{2,5,3,-12,-24,-12,3,5,2},
{1,4,5,3,0,3,5,4,1},
{1,2,4,5,5,5,4,2,1},
{0,1,1,2,2,2,1,1,0}
};
Bitmap dest = new Bitmap(IMG.Width, IMG.Height, PixelFormat.Format24bppRgb);
// dest = IMG;
// int rx, gx, bx, ry, gy, by, r, g, b, li, hi, lj, hj;
int r, g, b, li, hi, lj, hj;
// byte r1;
int[,] edgeDir = new int[IMG.Height, IMG.Width];
float[,] gradient = new float[IMG.Height, IMG.Width];
Rectangle rectimg = new Rectangle(0, 0, IMG.Width, IMG.Height);
Rectangle rectdest = new Rectangle(0, 0, dest.Width, dest.Height);
//ImageLockMode lockmod = ImageLockMode.ReadWrite;
BitmapData ImgData = new BitmapData();
BitmapData destData = new BitmapData();
ImgData = IMG.LockBits(rectimg, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
destData = dest.LockBits(rectdest, ImageLockMode.ReadWrite, dest.PixelFormat);
masca = dx;
byte Size = (byte)Math.Sqrt(masca.Length);
unsafe
{
for (int i = 4; i < IMG.Height – 4; i++)
for (int j = 4; j < IMG.Width – 4; j++)
{
r = 0;
g = 0;
b = 0;
li = 0;
hi = Size;
lj = 0;
hj = Size;/*
if (i <= (Size-1) / 2) { li = Size/2-i; }
if (i + (Size-1) / 2 > IMG.Height) { hi = Size / 2 + IMG.Height-i-1; }
if (j <= (Size-1) / 2) { lj = Size / 2 – j; }
if (j + (Size-1) / 2 > IMG.Width) { hj = Size / 2 + IMG.Width- j – 1; }
*/
for (int k = li; k < hi; k++)
for (int l = lj; l < hj; l++)
{
byte* p = (byte*)(ImgData.Scan0) + (i + k – Size / 2) * ImgData.Stride + (j + l – Size / 2) * 3;
b += (int)p[0] * dx[k, l];
g += (int)p[1] * dx[k, l];
r += (int)p[2] * dx[k, l];
}
if (r < th_min) { r = 0; }
if (g < th_min) { g = 0; }
if (b < th_min) { b = 0; }
if (r > 255) { r = 255; }
if (g > 255) { g = 255; }
if (b > 255) { b = 255; }
byte* destp = (byte*)(destData.Scan0) + i * destData.Stride + j * 3;
destp[0] = (byte)b;
destp[1] = (byte)g;
destp[2] = (byte)r;
}
}
if (rectimg.IsEmpty == false)
{
IMG.UnlockBits(ImgData);
}
if (rectdest.IsEmpty == false)
{
dest.UnlockBits(destData);
}
return dest;
}
Extragerea frontierelor
Binarizarea matricei amplitudinilor
Una dintre operatiile uzuale in metodele de extragere a frontierelor este aceea de aplicare a unui prag peste matricea amplitudinilor fronturilor. Operatia de aplicare a unui prag poate fi aplicata si asupra unei matrici imagine, rezultand o imagine binara.
Astfel, dacă a(k , l) este matricea amplitudinilor (sau o matrice imagine), atunci imaginea binară, B(k , l) este :
, unde T este o valoare prag.
Valoarea T poate fi aleasă inspectând histograma imaginii (matricei amplitudine), a(k , l), astfel încât numai un mic procent din elem matricei a să aibă valoarea >=T.
Binarizarea globală poate produce frontiere subțiri în unele regiuni si late sau intrerupte in altele. In aceste cazuri este de dorit o binarizare adaptată local, adică :
unde T(k,l) este pragul adaptat local. O metodă de a-l obține constă în calculul mediei aritmetice locale în matricea:
, unde p este un procent care indică nivelul pragului peste media locală.
//binarizare cu prag fix
public Bitmap Binary1(Bitmap IMG, int th_min, int th_max, int th_bin)
{
Bitmap dest = new Bitmap(IMG.Width, IMG.Height, PixelFormat.Format24bppRgb);
// dest = IMG;
Bitmap newb = new Bitmap(IMG.Width, IMG.Height, PixelFormat.Format24bppRgb);
//int rx, gx, bx, ry, gy, by, r, g, b;
// byte r1;
int[,] edgeDir = new int[IMG.Height, IMG.Width];
float[,] gradient = new float[IMG.Height, IMG.Width];
Rectangle frontimg = new Rectangle(0, 0, IMG.Width, IMG.Height);
Rectangle rectnew = new Rectangle(0, 0, IMG.Width, IMG.Height);
Rectangle rectimg = new Rectangle(0, 0, IMG.Width, IMG.Height);
Rectangle rectdest = new Rectangle(0, 0, dest.Width, dest.Height);
// ImageLockMode lockmod = ImageLockMode.ReadWrite;
BitmapData ImgData = new BitmapData();
BitmapData destData = new BitmapData();
BitmapData newData = new BitmapData();
BitmapData FData = new BitmapData();
FileStream fileStream = new FileStream(@"save.jpg", FileMode.OpenOrCreate);
IMG.Save(fileStream, ImageFormat.Jpeg);
IMG = new Bitmap(fileStream);
fileStream.Close();
ImgData = IMG.LockBits(rectimg, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
newData = newb.LockBits(rectnew, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
destData = dest.LockBits(rectdest, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
x = new int[IMG.Height, IMG.Width];
unsafe
{
for (int i = 0; i < IMG.Height; i++)
for (int j = 0; j < IMG.Width; j++)
{ x[i, j] = 0; }
for (int i = 1; i < IMG.Height – 1; i++)
for (int j = 1; j < IMG.Width – 1; j++)
{
byte* p = (byte*)(ImgData.Scan0) + i * ImgData.Stride + j * 3;
// byte* p1 = (byte*)(newData.Scan0) + i * newData.Stride + j * 3;
byte* dp = (byte*)(destData.Scan0) + i * destData.Stride + j * 3;
if (((p[0] + p[1] + p[2]) >= 3 * th_bin) && ((p[0] + p[1] + p[2]) >= 3 * th_min))
{
x[i, j] = 1;
dp[0] = 255;
dp[1] = 255;
dp[2] = 255;
}
else
{
dp[0] = 0;
dp[1] = 0;
dp[2] = 0;
}
}
}
if (rectnew.IsEmpty == false)
{
newb.UnlockBits(newData);
}
if (rectimg.IsEmpty == false)
{
IMG.UnlockBits(ImgData);
}
if (rectdest.IsEmpty == false)
{
dest.UnlockBits(destData);
}
return dest;
}
//binarizare cu prag variabil cu procent
public Bitmap Binary3(Bitmap IMG, int th_min, int th_max, int th_bin)
{
Bitmap dest = new Bitmap(IMG.Width, IMG.Height, PixelFormat.Format24bppRgb);
// dest = IMG;
Bitmap newb = new Bitmap(IMG.Width, IMG.Height, PixelFormat.Format24bppRgb);
// int rx, gx, bx, ry, gy, by, r, g, b;
// byte r1;
int r, g, b;
int[,] edgeDir = new int[IMG.Height, IMG.Width];
float[,] gradient = new float[IMG.Height, IMG.Width];
Rectangle frontimg = new Rectangle(0, 0, IMG.Width, IMG.Height);
Rectangle rectnew = new Rectangle(0, 0, IMG.Width, IMG.Height);
Rectangle rectimg = new Rectangle(0, 0, IMG.Width, IMG.Height);
Rectangle rectdest = new Rectangle(0, 0, dest.Width, dest.Height);
//ImageLockMode lockmod = ImageLockMode.ReadWrite;
BitmapData ImgData = new BitmapData();
BitmapData destData = new BitmapData();
BitmapData newData = new BitmapData();
BitmapData FData = new BitmapData();
Bitmap front = Sobel(IMG, 10, 200);
FileStream fileStream = new FileStream(@"save.jpg", FileMode.OpenOrCreate);
IMG.Save(fileStream, ImageFormat.Jpeg);
IMG = new Bitmap(fileStream);
fileStream.Close();
FData = front.LockBits(frontimg, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
ImgData = IMG.LockBits(rectimg, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
newData = newb.LockBits(rectnew, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
destData = dest.LockBits(rectdest, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
x = new int[IMG.Height, IMG.Width];
unsafe
{
for (int i = 0; i < IMG.Height; i++)
for (int j = 0; j < IMG.Width; j++)
{ x[i, j] = 0; }
for (int i = 1; i < IMG.Height – 1; i++)
for (int j = 1; j < IMG.Width – 1; j++)
{
r = 0;
b = 0;
g = 0;
byte* p1 = (byte*)(newData.Scan0) + i * newData.Stride + j * 3;
p1[0] = 0;
p1[1] = 0;
p1[2] = 0;
for (int k = 0; k < 3; k++)
for (int l = 0; l < 3; l++)
{
byte* p = (byte*)(ImgData.Scan0) + (i + k – 1) * ImgData.Stride + (j + l – 1) * 3;
b += p[0];
g += p[1];
r += p[2];
}
r = r / 9;
g = g / 9;
b = b / 9;
if (r + g + b >= 3 * th_max)
{
p1[0] = (byte)(th_max);
p1[1] = (byte)(th_max);
p1[2] = (byte)(th_max);
}
else
{
p1[0] = (byte)(b);
p1[1] = (byte)(g);
p1[2] = (byte)(r);
}
}
for (int i = 1; i < IMG.Height – 1; i++)
for (int j = 1; j < IMG.Width – 1; j++)
{
byte* p = (byte*)(ImgData.Scan0) + i * ImgData.Stride + j * 3;
byte* p1 = (byte*)(newData.Scan0) + i * newData.Stride + j * 3;
byte* dp = (byte*)(destData.Scan0) + i * destData.Stride + j * 3;
double proc = (100 + th_bin) / 100;
if (((p[0] + p[1] + p[2]) >= (p1[0] + p1[1] + p1[2]) * proc) && ((p1[0] + p1[1] + p1[2]) >= 3 * th_min))
{
x[i, j] = 1;
dp[0] = 255;
dp[1] = 255;
dp[2] = 255;
}
else
{
dp[0] = 0;
dp[1] = 0;
dp[2] = 0;
}
}
}
if (rectnew.IsEmpty == false)
{
newb.UnlockBits(newData);
}
if (rectimg.IsEmpty == false)
{
IMG.UnlockBits(ImgData);
}
if (rectdest.IsEmpty == false)
{
dest.UnlockBits(destData);
}
return dest;
}
//binarizare cu prag variabil
public Bitmap Binary2(Bitmap IMG, int th_min, int th_max, int th_bin)
{
Bitmap dest = new Bitmap(IMG.Width, IMG.Height, PixelFormat.Format24bppRgb);
// dest = IMG;
Bitmap newb = new Bitmap(IMG.Width, IMG.Height, PixelFormat.Format24bppRgb);
//int rx, gx, bx, ry, gy, by, r, g, b;
// byte r1;
int r, g, b;
int[,] edgeDir = new int[IMG.Height, IMG.Width];
float[,] gradient = new float[IMG.Height, IMG.Width];
Rectangle frontimg = new Rectangle(0, 0, IMG.Width, IMG.Height);
Rectangle rectnew = new Rectangle(0, 0, IMG.Width, IMG.Height);
Rectangle rectimg = new Rectangle(0, 0, IMG.Width, IMG.Height);
Rectangle rectdest = new Rectangle(0, 0, dest.Width, dest.Height);
// ImageLockMode lockmod = ImageLockMode.ReadWrite;
BitmapData ImgData = new BitmapData();
BitmapData destData = new BitmapData();
BitmapData newData = new BitmapData();
BitmapData FData = new BitmapData();
Bitmap front = Sobel(IMG, 10, 200);
FileStream fileStream = new FileStream(@"save.jpg", FileMode.OpenOrCreate);
IMG.Save(fileStream, ImageFormat.Jpeg);
IMG = new Bitmap(fileStream);
fileStream.Close();
FData = front.LockBits(frontimg, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
ImgData = IMG.LockBits(rectimg, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
newData = newb.LockBits(rectnew, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
destData = dest.LockBits(rectdest, ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
x = new int[IMG.Height, IMG.Width];
unsafe
{
for (int i = 0; i < IMG.Height; i++)
for (int j = 0; j < IMG.Width; j++)
{ x[i, j] = 0; }
for (int i = 1; i < IMG.Height – 1; i++)
for (int j = 1; j < IMG.Width – 1; j++)
{
r = 0;
b = 0;
g = 0;
byte* p1 = (byte*)(newData.Scan0) + i * newData.Stride + j * 3;
p1[0] = 0;
p1[1] = 0;
p1[2] = 0;
for (int k = 0; k < 3; k++)
for (int l = 0; l < 3; l++)
{
byte* p = (byte*)(ImgData.Scan0) + (i + k – 1) * ImgData.Stride + (j + l – 1) * 3;
r += p[0];
g += p[1];
b += p[2];
}
r = r / 9;
g = g / 9;
b = b / 9;
if (r + g + b >= 3 * th_max)
{
p1[0] = (byte)(th_max);
p1[1] = (byte)(th_max);
p1[2] = (byte)(th_max);
}
else
{
p1[0] = (byte)(r);
p1[1] = (byte)(g);
p1[2] = (byte)(b);
}
}
for (int i = 1; i < IMG.Height – 1; i++)
for (int j = 1; j < IMG.Width – 1; j++)
{
byte* p = (byte*)(ImgData.Scan0) + i * ImgData.Stride + j * 3;
byte* p1 = (byte*)(newData.Scan0) + i * newData.Stride + j * 3;
byte* dp = (byte*)(destData.Scan0) + i * destData.Stride + j * 3;
if (((p[0] + p[1] + p[2]) >= (p1[0] + p1[1] + p1[2])) && ((p1[0] + p1[1] + p1[2]) >= 3 * th_min))
{
x[i, j] = 1;
dp[0] = (byte)(th_max);
dp[1] = (byte)(th_max);
dp[2] = (byte)(th_max);
}
else
{
dp[0] = 0;
dp[1] = 0;
dp[2] = 0;
}
}
}
if (rectnew.IsEmpty == false)
{
newb.UnlockBits(newData);
}
if (rectimg.IsEmpty == false)
{
IMG.UnlockBits(ImgData);
}
if (rectdest.IsEmpty == false)
{
dest.UnlockBits(destData);
}
return dest;
}
Detectorul Canny
Canny imbunatateste detectoarele existente din urmatoarele p.d.v.:
scaderea ratei de eroare – detectorul sa nu piarda puncte de frontiera si sa nu raspunda la puncte care nu sunt puncte de frontiera
punctele de frontiera sa fie bine localizate – distanta dintre punctele de frontiera gasite de detector si cele reale sa fie minima
detectorul sa furnizeze un singur raspuns la un singur punct de frontiera
Algoritmul Canny consta deci din urmatorii pasi:
Pasul 1:
Se aplica imaginii de intrare un filtru Gaussian.
Cu cat latimea mastii este mai mare cu atat este mai mica senzitivitatea detectorului la zgomot.
Pasul 2:
Se aplica operatorul Sobel imaginii rezultate din pasul 1, obtinandu-se matricea amplitudinilor gradientului. D(x,y) = |Dx| + |Dy|
Pasul 3:
Se calculeaza directia gradientului in fiecare punct, obtinandu-se matricea directiilor:
θ (x,y) = arctg (Dy / Dx)
Pasul 4:
Se ajusteaza θ la una dintre directiile din spatiul discret al imaginii, si anume cea mai apropiata de valoarea lui θ.
Pasul 5 (non-maxima suppression):
Detectorul Sobel, ca toti detectorii bazati pe masti de convolutie, produce mai multe puncte de front pentru un acelasi punct de frontiera (mai multe masti pot contine un punt de frontiera). De aceea, matricea amplitudinilor poate contine zone late in jurul frontierei. In acest pas sunt eliminati pixelii care nu au amplitudinea maxima local (in vecinatatea unui pixel).
Sunt declarate puncte de frontiera acele puncte a caror amplitudine este maxima locala pe directia gradientului. Celelalte puncte sunt eliminate (setate la zero).
Pentru fiecare pixel C
Fie A si B pixelii din vecinatatea lui C pe directia gradientului din C
Daca D(A) > D(C) sau D(B) > D(C) atunci D(C) = 0
Efectul acestui pas este de subtiere a frontierei fara a o intrerupe. Notam cu I5, matricea amplitudinilor rezultata din acest pas.
Pasul 6 (hysteresis thresholding):
In acest pas se elimina pixelii falsi de frontiera (zgomotele) fara intreruperea frontierei (conturului). Pentru aceasta se utilizeaza operatia de “histerezis”, care consta in aplicarea a 2 praguri: pragul de jos, P1, si pragul de sus, P2 (P2 aproximativ egal cu 2*P1). Aceste praguri se aplica separat imaginii rezultate in pasul anterior, I5, rezultand 2 imagini binare, T1 si T2. In T1 au valoarea 1 pixelii cu amplitudinea >T1 iar in T2 aceia cu amplitudinea >T2.
Imaginea din T2 are intreruperi in frontiera dar contine mai putine puncte false.
Punctele din T2 sunt legate in contururi.
Urmarirea unei frontiere incepe cu un punct din T2. Se conecteaza puncte din T2 pana cand se ajunge intr-un pixel p care nu mai poate fi conectat cu alt punct din T2 (nu are vecini in T2 pe nici una dintre cele 4 directii). In acest moment, se cauta in T1, in vecinatatea de 8 pixeli a pixelului p, un pixel care poate fi conectat la contur. Se conecteaza la frontiera puncte din T1 pana cand se ajunge la un pixel cu valoare diferita de zero in T2 (sau se ajunge la sfarsit de contur). In acest fel, se completeaza intreruperile de frontiera din T2 cu pixeli din T1.
public Bitmap Canny3(Bitmap IMG, int th_min, int th_max)
{
// Bitmap dest = new Bitmap(IMG.Width, IMG.Height, IMG.PixelFormat);
Bitmap newimg = new Bitmap(IMG.Width, IMG.Height, IMG.PixelFormat);
newimg = Gauss(IMG, th_min, th_max);
//newimg = Sobel(newimg, th_min, th_max);
//newimg = IMG;
//newimg = Sobel(newimg, th_min, th_max);
sbyte[,] dx = { { -1, 0, 1 }, { -2, 0, 2 }, { -1, 0, 1 } };
sbyte[,] dy = { { 1, 2, 1 }, { 0, 0, 0 }, { -1, -2, -1 } };
Bitmap dest = new Bitmap(IMG.Width, IMG.Height, PixelFormat.Format24bppRgb);
Bitmap dest2 = new Bitmap(IMG.Width, IMG.Height, PixelFormat.Format24bppRgb);
int lun;
lun = 3;
if (IMG.PixelFormat.Equals(PixelFormat.Format32bppArgb) == true)
{
lun = 4;
}
int rx, gx, bx, ry, gy, by, r, g, b;
Rectangle rectimg = new Rectangle(0, 0, IMG.Width, IMG.Height);
Rectangle rectdest2 = new Rectangle(0, 0, dest.Width, dest.Height);
Rectangle rectdest = new Rectangle(0, 0, dest.Width, dest.Height);
//ImageLockMode lockmod = ImageLockMode.ReadWrite;
BitmapData ImgData = new BitmapData();
BitmapData destData = new BitmapData();
BitmapData destData2 = new BitmapData();
ImgData = newimg.LockBits(rectimg, ImageLockMode.ReadWrite, IMG.PixelFormat);
destData = dest.LockBits(rectdest, ImageLockMode.ReadWrite, IMG.PixelFormat);
destData2 = dest2.LockBits(rectdest2, ImageLockMode.ReadWrite, IMG.PixelFormat);
masca = dx;
byte Size = (byte)Math.Sqrt(masca.Length);
double ddx, ddy;
unsafe
{
for (int i = 1; i < IMG.Height – 1; i++)
for (int j = 1; j < IMG.Width – 1; j++)
{
rx = 0;
gx = 0;
bx = 0;
ry = 0;
gy = 0;
by = 0;
for (int k = 0; k < Size; k++)
for (int l = 0; l < Size; l++)
{
byte* p = (byte*)(ImgData.Scan0) + (i + k – 1) * ImgData.Stride + (j + l – 1) * lun;
bx += (int)p[0] * dx[k, l];
gx += (int)p[1] * dx[k, l];
rx += (int)p[2] * dx[k, l];
by += (int)p[0] * dy[k, l];
gy += (int)p[1] * dy[k, l];
ry += (int)p[2] * dy[k, l];
}
r = Math.Abs(rx) + Math.Abs(ry);
g = Math.Abs(gx) + Math.Abs(gy);
b = Math.Abs(bx) + Math.Abs(by);
if (r < th_min) { r = th_min; }
if (g < th_min) { g = th_min; }
if (b < th_min) { b = th_min; }
if (r > th_max) { r = th_max; }
if (g > th_max) { g = th_max; }
if (b > th_max) { b = th_max; }
ddx = rx + gx + bx;
ddy = ry + gy + by;
tangent = -1;
if (ddx != 0 && ddy != 0)
{
tangent = Math.Atan(ddy / ddx);//tre adus la (-pi,pi)
tangent = (float)(tangent * 180 / Math.PI);
// tangent = tangent – 90;
if (tangent > 0) { tangent -= 90; }
else
{
tangent += 90;
}
}
if (ddy == 0 && ddx == 0) { tangent = 0; }
if (ddy != 0 && ddx == 0) { tangent = 90; }
gradient[i, j] = (float)((r + g + b) / 3);
int tangent2;
tangent2 = 0;
// Math.
if (((tangent < 22.5) && (tangent > -22.5)) || (tangent > 157.5 && tangent <= 180) || (tangent <= -157.5 && tangent > -180))
{
tangent2 = 0;
}
if (((tangent > 22.5) && (tangent < 67.5)) || ((tangent < -112.5) && (tangent > -157.5)))
{
tangent2 = 45;
}
if (((tangent > 67.5) && (tangent < 112.5)) || ((tangent < -67.5) && (tangent > -112.5)))
{
tangent2 = 90;
}
if (((tangent > 112.5) && (tangent < 157.5)) || ((tangent < -22.5) && (tangent > -67.5)))
{
tangent2 = 135;
}
edge[i, j] = tangent2;
if (r > th_min && g > th_min && b > th_min)
{
byte* destp = (byte*)(destData2.Scan0) + i * destData2.Stride + j * lun;
destp[0] = (byte)b;
destp[1] = (byte)g;
destp[2] = (byte)r;
}
}
for (int i = Size / 2; i < ImgData.Height – Size / 2; i++)
{
for (int j = Size / 2; j < ImgData.Width – Size / 2; j++)
{
//byte* destPtr = (byte*)(destData.Scan0) + i * destData.Stride + j * 3;
byte* Ptr = (byte*)(destData2.Scan0) + i * destData2.Stride + j * lun;
byte inten = (byte)((Ptr[0] + Ptr[1] + Ptr[2]) / 3);
// if (inten < lth) { inten = (byte)lth; }
// inten = 255;
if (gradient[i, j] > th_min)
{
switch (edge[i, j])
{
case 0:
// Edges(destData2, destData, edge, gradient, i, j, 0, 1, 0, th_min);
byte* destPtr1 = (byte*)(destData2.Scan0) + i * destData2.Stride + j * lun;
destPtr1[0] = (byte)(0);
destPtr1[1] = (byte)(inten);
destPtr1[2] = (byte)(inten);
break;
case 45:
byte* destPtr2 = (byte*)(destData2.Scan0) + i * destData2.Stride + j * lun;
// Edges(destData2, destData, edge, gradient, i, j, -1, 1, 45, th_min);
destPtr2[0] = (byte)(0);
destPtr2[1] = (byte)(inten);
destPtr2[2] = (byte)(0);
break;
case 90:
byte* destPtr3 = (byte*)(destData2.Scan0) + i * destData2.Stride + j * lun;
//Edges(destData2, destData, edge, gradient, i, j, -1, 0, 90, th_min);
destPtr3[0] = (byte)(inten);
destPtr3[1] = (byte)(0);
destPtr3[2] = (byte)(0);
break;
case 135:
byte* destPtr4 = (byte*)(destData2.Scan0) + i * destData2.Stride + j * lun;
// Edges(destDa/ta2, destData, edge, gradient, i, j, -1, -1, 135, th_min);
destPtr4[0] = (byte)(0);
destPtr4[1] = (byte)(0);
destPtr4[2] = (byte)(inten);
break;
default:
byte* destPtr = (byte*)(destData2.Scan0) + i * destData2.Stride + j * lun;
destPtr[0] = (byte)(255);
destPtr[1] = (byte)(255);
destPtr[2] = (byte)(255);
break;
}
}
else
{
byte* destPtr = (byte*)(destData2.Scan0) + i * destData2.Stride + j * 3;
destPtr[0] = (byte)(0);
destPtr[1] = (byte)(0);
destPtr[2] = (byte)(0);
}
}
}
for (int i = Size / 2; i < ImgData.Height – Size / 2; i++)
{
for (int j = Size / 2; j < ImgData.Width – Size / 2; j++)
{
byte* destPtr = (byte*)(destData.Scan0) + i * destData.Stride + j * 3;
switch (edge[i, j])
{
case 0:
supressNonMax(destData2, destData, edge, gradient, i, j, 1, 0, 0, th_min);
break;
case 45:
supressNonMax(destData2, destData, edge, gradient, i, j, 1, 1, 45, th_min);
break;
case 90:
supressNonMax(destData2, destData, edge, gradient, i, j, 0, 1, 90, th_min);
break;
case 135:
supressNonMax(destData2, destData, edge, gradient, i, j, 1, -1, 135, th_min);
break;
default:
break;
}
}
}
Hysterensis(destData2, destData, th_min, th_max);
}
if (rectimg.IsEmpty == false)
{
newimg.UnlockBits(ImgData);
}
if (rectdest.IsEmpty == false)
{
dest.UnlockBits(destData);
}
return dest;
}
public void supressNonMax(BitmapData ImgData, BitmapData destData, int[,] edge, float[,] gradient, int i, int j, int I, int J, int dir, int lth)
{
int width = destData.Width, height = destData.Height;
// Boolean edgeEnd = false;
float[,] nonMax = new float[width, 3];
// int pixelCount = 0;
// int count;
float[] max = new float[3];
int k, l;
int end;
unsafe
{
byte* p = (byte*)(destData.Scan0) + i * destData.Stride + j * 3;
byte* p2 = (byte*)(ImgData.Scan0) + i * ImgData.Stride + j * 3;
p[0] = p2[0];
p[1] = p2[1];
p[2] = p2[2];
k = i + I;
l = j + J;
end = 0;
if (k < 0 || k >= height) { end = 1; }
if (l < 0 || l >= width) { end = 1; }
if (edge[k, l] != edge[i, j]) { end = 1; }
while (k > 0 && k < height && l > 0 && l < width && end == 0 && edge[i, j] == edge[k, l])
{
byte* p1 = (byte*)(ImgData.Scan0) + k * ImgData.Stride + l * 3;
if (p1[0] + p1[1] + p1[2] > p[0] + p[1] + p[2])
{
p[0] = 0;
p[1] = 0;
p[2] = 0;
end = 1;
}
if ((p1[0] + p1[1] + p1[2] == p[0] + p[1] + p[2]) && (i < k || j < l))
{
p[0] = 0;
p[1] = 0;
p[2] = 0;
end = 1;
}
k = k + I;
l = l + J;
if (k < 0 || k >= height) { end = 1; }
if (l < 0 || l >= width) { end = 1; }
}
//ambele sensuri
k = i – I;
l = j – J;
end = 0;
if (k < 0 || k >= height) { end = 1; }
if (l < 0 || l >= width) { end = 1; }
if (edge[k, l] != edge[i, j]) { end = 1; }
while (k > 0 && k < height && l > 0 && l < width && end == 0 && edge[i, j] == edge[k, l])
{
byte* p1 = (byte*)(ImgData.Scan0) + k * ImgData.Stride + l * 3;
if (p1[0] + p1[1] + p1[2] > p[0] + p[1] + p[2])
{
p[0] = 0;
p[1] = 0;
p[2] = 0;
end = 1;
}
if ((p1[0] + p1[1] + p1[2] == p[0] + p[1] + p[2]) && (i < k || j < l))
{
p[0] = 0;
p[1] = 0;
p[2] = 0;
end = 1;
}
k = k – I;
l = l – J;
if (k < 0 || k >= height) { end = 1; }
if (l < 0 || l >= width) { end = 1; }
}
}
}
void TraceEdge(BitmapData sourceImageData, BitmapData destImageData, int i, int j, int lowerThreshold)
{
int[] iNum = { 1, 1, 0, -1, -1, -1, 0, 1 };
int[] jNum = { 0, 1, 1, 1, 0, -1, -1, -1 };
int newJ, newI, k;
unsafe
{
for (k = 0; k < 8; k++)
{
newJ = j + jNum[k];
newI = i + iNum[k];
byte* sourceImagePtr = (byte*)(sourceImageData.Scan0) + newI * sourceImageData.Stride + newJ * 3;
float grad = ((sourceImagePtr[0] + sourceImagePtr[1] + sourceImagePtr[2]) / 3);
byte* destImagePtr = (byte*)(destImageData.Scan0) + newI * sourceImageData.Stride + newJ * 3;
if (destImagePtr[0] > 0 && grad >= lowerThreshold)
{
destImagePtr[0] = (byte)(255);
destImagePtr[1] = (byte)(255);
destImagePtr[2] = (byte)(255);
TraceEdge(sourceImageData, destImageData, newI, newJ, lowerThreshold);
}
}
}
}
void Hysterensis(BitmapData sourceImageData, BitmapData destImageData, int lowerThreshold, int highThreshold)
{
unsafe
{
for (int i = 0; i < sourceImageData.Height; i++)
{
for (int j = 0; j < sourceImageData.Width; j++)
{
byte* sourceImagePtr = (byte*)(sourceImageData.Scan0) + i * sourceImageData.Stride + j * 3;
float grad = ((sourceImagePtr[0] + sourceImagePtr[1] + sourceImagePtr[2]) / 3);
byte* destImagePtr = (byte*)(destImageData.Scan0) + i * sourceImageData.Stride + j * 3;
if (destImagePtr[0] > 0 && grad >= highThreshold)
{
destImagePtr[0] = (byte)255;
destImagePtr[1] = (byte)255;
destImagePtr[2] = (byte)255;
TraceEdge(sourceImageData, destImageData, i, j, lowerThreshold);
}
}
}
}
}
Metoda Roberts
1. Se determină punctele candidat
Pentru aceasta, se examinează vecinătăți de 4×4 pixeli, disjuncte. Se determină punctul dintr-o vecinătate cu valoarea maximă a amplitudinii:
,
Punctul (xc,yc) este memorat ca punct candidat dacă:
a) Valoarea sa depășește un anumit prag : D(xc,yc) > T
b) Nu este un punct de front izolat.
Pentru a determina daca punctul (xc,yc) este izolat se incearca sa se gaseasca direcția frontierei in (xc,yc) prin corelarea valorilor pixelilor din vecinătatea punctului candidat.
2. Se conectează punctele candidat pe baza direcției frontierei determinate în prima etapă.
Două puncte candidat sunt conectate dacă se află în vecinătăți adiacente iar diferența dintre valorile lor este sub o valoare de prag.
Orice punct candidat neconectat este eliminat.
Extragerea frontierelor bazata pe predictia punctului urmator
Fie:
a(P) = a(x,y) amplitudinea frontului in pixelul (x,y)
θ(P) = θ(x,y) directia frontului in pixelul (x,y)
Două puncte de front, Pi,Pj , pot fi conectate dacă diferențele în amplitudine și direcție sunt sub valorile de prag date iar amplitudinile fronturilor sunt relativ mari. Aceste criterii sunt definite prin urmatoarele conditii:
| a(Pi) – a(Pj) | <= T1
| θ (Pi) – θ (Pj ) | <= T2 mod 2π
a(Pi) >=T si a(Pj) >=T
Algoritmul constă din :
determinarea punctului de start
determinarea celorlalte puncte prin :
– predicția următorului punct
– determinarea următorului punct
Determinarea punctului de start
– Dacă se cunoște zona de imagine în care se află frontiera, se caută punctul în zona respectivă folosind amplitudinea
– Dacă se cunoaște direcția frontierei, se poate căuta un punct de front cu direcția respectivă
– Altfel : se scanează imaginea până la găsirea a 2 puncte care satisfac criteriile (1) + (2) + (3).
Predicția punctului următor
Se bazează pe punctele de frontieră deja determinate și eventual pe cunoașterea tipului de frontieră.
Determinarea punctului următor
Punctul următor este căutat în vecinătatea punctului determinat prin predictie, pe care-l numim punct anticipat. El trebuie să fie un punct de front care poate fi conectat cu ultimul punct de frontieră determinat, conform condițiilor (1) + (2) + (3).
Fie P1 și P2 primele 2 puncte de frontiera, alese conform condițiilor (1) + (2) + (3). Punctul următor anticipat, A3, se află la o distanță d de P2 pe dreapta determinată de P1 – P2.
A3 poate să nu fie adresă de pixel sau poate să nu fie un pixel de front.
Punctul de frontieră următor, P3, se caută în vecinătatea lui A3, astfel încât să fie un punct de front care împreună cu P2 să satisfacă (1) + (2) + (3):
În continuare, direcția de căutare este dată de .
Transformata Hough (detectia liniilor drepte)
Iesirea transformatei Hough este o matrice binara in care pixelii cu valoarea 1 apartin unor linii drepte. Intrarea pentru transformata Hough este matricea obținută prin binarizarea matricei amplitudinilor fronturilor.
Fie o imagine de N1xN2 pixeli. Cea mai simplă abordare a determinării liniilor drepte este de a găsi linii determinate de perechi de pixeli și apoi a găsi pixeli din imagine care aparțin acestor linii. Numărul maxim de linii posibile este N(N-1) /2, unde N = N1 x N2. În cazul cel mai defavorabil, fiecare pixel este verificat dacă aparține uneia dintre cele N(N-1) /2 linii. Deci, complexitatea calculului este O(N3), prea mare.
Transformata Hough utilizează o reprezentare parametrică a liniilor pentru reducerea complexității calculului căutării lor în imagini : y = ax + b
În spațiul parametric (a , b) fiecare linie este reprez printr-un singur punct (a , b).
Fie o linie care trece prin 2 puncte (x1 , y1), (x2 , y2) :
Liniile care trec prin (x1 , y1) se reprezintă în spațiul parametric prin linia b = – a*x1 + y1 iar cele care trec prin (x2 , y2) se reprezintă prin linia b = – a*x2 + y2 . Intersecția acestor 2 linii din spațiul parametric determină în mod unic dreapta care trece prin (xi , yi), i = 1, 2.
Pentru detecția liniilor drepte se poate proceda astfel :
1. Se discretizează spațiul parametric și se reprezintă printr-o matrice P, astfel încât P[i][j] corespunde unui punct (aj , bi)
a1<= a <= aK , aj = a1 + j*da
b1<= b <= bL, bi = b1 + i*db
Se inițializează elementele matricei P cu zero.
2. Pentru fiecare pixel (x, y), cu B(x , y) = 1
// se formeză ecuația b = – a*x + y
pentru fiecare a1<= am <= ak // ( am=a1 + m* da)
bm = – am*x + y
i = (bm – b1)/(bL – b1) * (L-1)
P[i][m] ++;
3. Pentru fiecare element al matricei P, P[i][j]
dacă P[i][j] >= T atunci
// in imagine exista dreapta y = a*x + b, unde a = a1 + j* da, b = b1 + i*db
P[i][j] – numără intersecțiile liniilor care trec prin (a , b) și indică numărul de pixeli care satisfac ecuația y = a*x + b.
Complexitatea calculului transformatei Hough este O(k*N), unde k este numărul de subintervale pe axa a iar N este numărul de pixeli din imaginea de intrare.
Utilizarea ecuației parametrice y = a*x + b produce dificultăți în reprezentarea liniilor verticale ( a-> ∞).
O reprezentare mai bună este reprezentarea polară : r = x* cos(θ) + y* sin(θ)
O linie care trece prin (x1 , y1) reprezintă o curbă sinusoidală, r = x1* cos(θ) + y1* sin(θ) în spațiul parametric (r, θ).
Puncte colineare, (xi , yi), din imagine, corespund intersecțiilor sinusoidelor din spațiul parametric. Pentru o imagine de N1xN2 pixeli,
.
Discretizarea spațiului parametric :
– intervalul se împarte în n subintervale;
– intervalul [ -D, D ] se împarte in m subintervale, unde
Astfel, diferențiind în raport cu x ambele părți ale ecuației :
r = x* cos(θ) + y* sin(θ)
Direcția frontului in (x , y) este
Rezultă că pentru fiecare pixel B[k][l] = 1, numai un singur element al matricei P trebuie să fie actualizat : P[iθ][jθ]. Complexitatea calculului transformatei Hough se reduce in acest caz de la O(k*N) la O(N).
public Bitmap Hough2(Bitmap IMG, int n, int m, double T)
{
//Bitmap newb = new Bitmap(IMG.Width, IMG.Height, IMG.PixelFormat);
//newb = Sobel(IMG, 10, 100);
Bitmap newimg = new Bitmap(IMG.Width, IMG.Height, IMG.PixelFormat);
//inainte sa aplici Hough, trebuie sa aplici Canny pe imagine
newimg = Canny3(IMG, 20, 150);
Bitmap dest = new Bitmap(IMG.Width, IMG.Height, IMG.PixelFormat);
Rectangle rectimg = new Rectangle(0, 0, IMG.Width, IMG.Height);
Rectangle rectdest = new Rectangle(0, 0, dest.Width, dest.Height);
// ImageLockMode lockmod = ImageLockMode.ReadWrite;
BitmapData ImgData = new BitmapData();
BitmapData destData = new BitmapData();
ImgData = newimg.LockBits(rectimg, ImageLockMode.ReadWrite, IMG.PixelFormat);
int n2, m2, N1, N2;
destData = dest.LockBits(rectdest, ImageLockMode.ReadWrite, IMG.PixelFormat);
int i, j, k, l;
N1 = IMG.Height;
N2 = IMG.Width;
n2 = IMG.Height;
m2 = IMG.Width;
unsafe
{
for (k = 0; k < N1; k++)
for (l = 0; l < N2; l++)
{
byte* p = (byte*)(ImgData.Scan0) + k * ImgData.Stride + l * 3;
byte* dp = (byte*)(destData.Scan0) + k * destData.Stride + l * 3;
dp[0] = p[0];
dp[1] = p[1];
dp[2] = p[2];
}
double[] COS;
double t, r1, y;
double[] SIN;
COS = new Double[n];
SIN = new Double[n];
for (i = 0; i < n; i++)
{
t = (i * Math.PI / (n – 1) – Math.PI / 2);
//COS[i]=new Double();
COS[i] = Math.Cos(t);
SIN[i] = Math.Sin(t);
}
double D = Math.Sqrt((float)N1 * N1 + (float)N2 * N2);
double[,] P;
P = new Double[n2, m2];
for (k = 0; k < n2; k++)
for (l = 0; l < m2; l++)
P[k, l] = 0;
for (k = 0; k < N1; k++)
for (l = 0; l < N2; l++)
{
byte* p = (byte*)(ImgData.Scan0) + k * ImgData.Stride + l * 3;
if (p[0] + p[1] + p[2] > 0)
{
for (i = 0; i < n2; i++)//pentru fiecare
{
r1 = l * COS[i] + k * SIN[i];
j = (int)((r1 + D) * (m2 – 1) / (2 * D) + 0.5);
P[i, j]++;
}
}
}
//Se calculeaza transformata Hough inversa (din spatiul (r , @ în (x , y))
//Pentru fiecare element P[k][l] > T, se înscrie în matricea out, sub forma de
//pixeli = 1, linia dreapta corespunzatoare elementului (k , l)
//Se efectueaza operatia and între rezultatul transformatei si matricea de intrare
for (k = 0; k < n2; k++)
{
for (l = 0; l < m2; l++)
{
// Console.Write("sunt " + n + " " + m);
if (P[k, l] > T)
{
r1 = ((2.0 * l * D) / (m2 – 1));
r1 = r1 – D;
// se marcheaza in matricea out pixelii care satisfac ecuatia
// r = x*COS[k] + y*SIN[k]
for (i = 0; i < N2; i++)
{
if (SIN[k] == 0)
{ y = N1 / 2; }
else
{
y = i * COS[k];
y = r1 – y;
y = y / SIN[k];
}
j = (int)(y + 0.5);
if (j >= 0 && j < N1)
{
byte* p = (byte*)(ImgData.Scan0) + j * ImgData.Stride + i * 3;
byte* dp = (byte*)(destData.Scan0) + j * destData.Stride + i * 3;
if (p[0] + p[1] + p[2] > 0)
{
//Console.WriteLine("sunt " + j + " " + i + " " + p[2]);
//Console.WriteLine("sunt " + n + " " + m);
dp[0] = 128;
dp[1] = 128;
dp[2] = 128;
}
}
}
}
}
}
}
newimg.UnlockBits(ImgData);
dest.UnlockBits(destData);
return dest;
}
public double rad(int angle)
{
//PI .. 180
//x … angle
return (double)angle * 3.14159 / 180.00;
}
Segmentarea prin aplicarea de praguri alese pe baza histogramei
Segmentarea prin divizare bazată pe praguri
1. Se aleg valorile de prag ca puncte de minim local ale funcției histogramă :
Dacă f(x,y) este funcția imagine de intrare, atunci imaginea segmentată este definită:
g(x, y) = Ri , dacă Ti-1 f(x,y) < Ti cu i = 1,N
2. Divizarea în N regiuni distincte, fiecăreia corespunzându-i un domeniu de intensități. f(x, y) – imaginea de intrare; g(x,y) – imaginea segmentată, L nivele de intensitate
g(x,y) =
, i = 0,1…..N-2
Dacă histograma imaginii este concentrată într-un domeniu mic de intensități, atunci segmentarea prin aplicarea unui prag uniform (metoda a 2-a) nu dă rezultate bune deoarece majoritatea pixelilor vor fi repartizați într-un număr foarte mic de regiuni.
În astfel de cazuri se poate folosi un prag neuniform pentru segmentare.
Dacă G(f(x,y)) este transformarea utilizată în egalizarea histogramei:
g(x,y) =
Subtiere si scheletizare
Subtierea
Este o operatie prin care sunt indepartati pixeli selectati din regiunile unei imagini binare.
Operatia de subtiere foloseste un sablon binar numit element de structurare. Acesta este definit printr-o matrice de 3×3 elemente avand valorile 1 si 0. Originea sa este in centrul sablonului. In continuare, pentru simplitatea exprimarii, vom numi elementul de structurare sablon.
Procesul de subtiere este iterativ.
O iteratie consta in translatia originii sablonului peste toti pixelii imaginii si compararea elementelor sablonului cu pixelii acoperiti de el, astfel: daca toate valorile 1 ale sablonului acopera biti egali cu 1 in imagine si daca toate valorile 0 ale sablonului acopera biti egali cu 0 in imagine, atunci pixelul imagine acoperit de originea sablonului este setat la zero, adica devine pixel de fond. Altfel, nu este modificat.
Procesul se incheie atunci cand intr-o iteratie nu se produce nici o modificare a imaginii (pana la convergenta).
Elementul de structurare este ales in functie de aplicatia operatiei de subtiere, deoarece el determina situatiile in care un pixel al unei forme (=1) este transformat in pixel de fond (=0).
Subtierea se utilizeaza pentru scheletizarea regiunilor rezultate din segmentarea imaginilor dar si pentru imbunatatirea rezultatului detectoarelor de frontiere, prin reducerea latimii liniilor la un pixel, fara modificarea lungimii lor. In cel de-al 2-lea caz, subtierea se aplica imaginii binare care se obtine prin aplicarea unui prag asupra matricei marimii fronturilor.
Un algoritm simplu de subtiere:
repeta
gata =1;
pentru fiecare pixel al imaginii
daca este pixel de contur al unei regiuni (are cel putin un vecin in afara regiunii)
atunci
daca are mai mult de un vecin in regiune si prin eliminarea sa nu se sparge regiunea
atunci
Elimina pixelul (se setezaza ca pixel de fond)
gata =0;
cat timp (!gata);
Scheletizarea
Multe forme, mai ales cele subtiri, pot fi descrise prin versiunile lor subtiate, alcatuite din linii conectate, aflate, in mod ideal, de-a lungul axei mediane a formelor.
Desenele compuse din linii sau caractere de text trebuie sa fie digitizate la o rezolutie suficient de mare pentru ca liniile sa nu fie intrerupte sau terminatiile lor sa se piarda. La o astfel de rezolutie este posibil ca pe unele portiuni liniile sa aiba latimea mai mare ca 2 pixeli.
Scheletizarea permite refacerea structurii liniare a figurii digitizate, fara distrugerea conectivitatii.
Axa mediana sau axa schelet a unei regiuni este definita in planul continuu (analogic) astfel:
Fie R o regiune in planul continuu, C conturul regiunii si P un punct din R.
Cel mai apropiat vecin al lui P pe C este un punct M cu proprietatea ca nu exista alt punct in C a carui distanta fata de P sa fie mai mica decat PM. Daca P are mai multi vecini in C cu aceasta proprietate, atunci P este un punct de schelet al lui R.
Din definitie rezulta ca punctele schelet sunt centre ale unor cercuri continute in intregime in R, cu proprietatea ca nu exista alte cercuri cu aceleasi centre, de raza mai mare, continute in R.
Reuniunea punctelor de schelet formeaza axa schelet sau axa mediana a lui R.
Scheletul unei regiuni este util deoarece este o reprezentare simpla si compacta a unei forme, care conserva multe dintre caracteristicile topologice si de dimensiune ale formei. Se utilizeaza pentru recunoasterea formelor( de ex. a caracterelor de text, a scrisului manual) estimand caracteristici ale formelor cum ar fi: lungimea si latimea formei, numarul de puncte de jonctiune (puncte in care se intalnesc cel putin 3 linii) si altele.
Transpunerea conceptului de axa mediana in planul discret este dificila deoarece drumul dintre 2 pixeli nu este unic si nu poate fi aplicata definitia distantei euclidiene dintre 2 pixeli.
Scheletizarea este procesul de determinare a axei schelet a unei forme (regiuni).
Intrarea acestui proces este o imagine binara in care pixelii de fond au valoarea zero iar pixelii regiunilor au valoarea 1.
Iesirea procesului de scheletizare este o imagine binara in care numai pixelii axelor schelet au valoarea 1.
Nu exista o definitie matematica a procesului de scheletizare dar exista numerosi algoritmi. Majoritatea utilizeaza operatori de subtiere prin care se erodeaza treptat marginile regiunilor.
Algoritmi de scheletizare prin subtiere
Scheletizarea prin subtierea poate fi definita euristic ca o succesiune de erodari ale marginilor unei forme pana cand se obtine scheletul formei. Algoritmii de subtiere sunt algoritmi iterativi care indeparteaza pixelii de contur, adica pixelii de tranzitie 0 1 intr-o imagine binara. Totodata, pixelii sunt indepartati astfel incat conectivitatea formei sa fie mentinuta.
Algoritmii de subtiere trebuie sa satisfaca urmatoarele constrangeri:
1. Sa conserve conectivitatea in fiecare iteratie; pentru aceasta, nu sunt eliminati pixelii de contur care ar putea cauza discontinuitati, de exemplu, pixelul marcat in figura (a).
Sa nu scurteze terminatiile formelor alungite; nu trebuie eliminati pixeli ca cel din figura (b)
In fiecare iteratie se viziteaza o singura data fiecare pixel al imaginii, verificandu-se daca poate fi indepartat, cu satisfacerea celor 2 constrangeri.
Pentru aceasta se cerceteaza vecinatatea de 8 pixeli a fiecarui pixel interior regiunii (P=1). Fie urmatoarea notatie:
– se numara pixelii interiori din vecinatatea lui P (inreg);
– daca inreg 2, P nu poate fi eliminat din regiune caci el apartine unei
Terminatii sau conecteaza 2 parti ale unei forme;
– daca inreg = 8, P nu poate fi indepartat, deoarece aceasta ar conduce
la erodarea formei;
– daca 2< inreg<8:
Se numara tranzitiile 0 1 din vecinatatea lui P: P8, P1, P2, P3, P4, P5, P6, P7,P8
Daca nrtranz = 1 inseamna ca in fereastra de 3×3 pixeli exista o singura componenta conectata. In acest caz se poate indeparta pixelul din centru caci indepartarea sa nu afecteaza conectivitatea locala a celorlalti pixeli din fereastra.
Algoritmul se termina atunci cand in ultima iteratie nu s-a mai indepartat nici un pixel.
Dezavantajul algoritmului este ca nu subtiaza regiunile simetric. Daca imaginea este parcursa pe randuri si de la stanga la dreapta liniile regiunii subtiate sunt localizate in partea de sud -– est a marginii obiectului, deoarece pixelii de frontiera din partile de nord – vest sunt eliminati primii. De aceea, rezultatul produs de algoritm nu este satisfacator atunci cand regiunile din imagini sunt relativ mari si convexe.
Se poate obtine o subtiere simetrica aplicand o varianta a algoritmului care opereaza in doi pasi, alternativi.
In primul pas sunt eliminati pixelii care apartin unei frontiere de est sau de sud sau unui colt de Nord – Vest.
In pasul al doilea sunt eliminati cei care apartin unei frontiere de nord sau de vest sau unui colt de sud – est.
Notam cu :
N(P0) numarul de pixeli interiori din vecinatatea de 3 x 3 a pixelului curent, P0
T (P0) numarul de tranzitii 0 1 in secventa de pixeli care formeaza periferia ferestrei: p1, p2, p3, …, p8,p1
Atunci cele 2 conditii de eliminare a pixelilor in cei doi pasi sunt exprimate prin predicatele:
Pas 1 :
( 2 < N (P0) < 8 ) && T ( P0 ) == 1 && ( P3 == 0 || P5 == 0 || (P1 == 0 && P7 == 0))
Pas2 :
( 2 < N (P0) < 8 ) && T ( P0 ) == 1 && ( P1 == 0 || P7 == 0 || (P3 == 0 && P5 == 0))
In fiecare pas, pixelii care indeplinesc conditia de a fi eliminati sunt marcati pentru eliminare si numai dupa parcurgerea intregii imagini sunt eliminati.
Algoritm de scheletizare bazat pe notiunea de pixel multiplu
Scheletul unei regiuni este o regiune liniara deci o regiune alcatuita numai din pixeli multipli.
Algortimii clasici de scheletizare considera drept pixeli schelet pixelii multiplii care satisfac conditia (1) din definitia pixelului multiplu (vezi “Regiuni liniare in spatiul discret”), adica satisfac unul dintre cele 6 sabloane.
Intr-un grup de pixeli marcati cu aceeasi litera cel putin unul are valoarea diferita de zero.
Imaginea de intrare este binara. Se considera ca pixeli de contur pixelii care au un vecin-d egal cu zero. Numai pixelii de contur pot fi pixeli de schelet.
In fiecare iteratie se parcurge imaginea marcand pixelii de contur si pixelii de schelet, pe toate cele 4 laturi ale fiecarei regiuni existente in imagine. Dupa ce toata imaginea a fost traversata, pixelii marcati ca fiind de contur sunt eliminati (li se da valoarea zero).
In descrierea algoritmului se apeleaza functia sablon care primeste coordonatele unui pixel si indicele unuia dintre cele 6 sabloane. Functia intoarce 1 daca vecinatatea pixelului satisface sablonul.
Exemplu:
0 2 0 A A A
0 P 0 satisface sablonul 0 P 0
1 0 0 B B B
Descrierea aplicatiei realizateAplicatie_SPG
Prin programul Aplicatie_SPG, realizat în Visual C# 2008, am implementat algoritmii prezentați în capitolele anterioare. Proiectul reprezintă un material didactic auxiliar, care poate fi folosit la curs, laborator sau seminar. În implementarea algoritmilor am respectat formulele matematice și condițiile asociate acestora pentru fiecare caz în parte și de aceea a fost necesară includerea demonstrațiilor în cadrul lucrării. Pentru implementarea fiecărui algoritm, am realizat câte o funcție publică în Visual C# 2008, cu numele algoritmului, pentru a putea fi ușor identificată.
În vederea implementării, am conceput un sistem de meniuri organizat astfel încât să respecte cât mai fidel organizarea informațiilor la cursul Sisteme de Prelucrare Grafică.
Organizarea meniului – structura opțiunilor în adâncime aleasă pentru implementare:
Operații cu fișierele aplicației
Detectia pixelilor de front
Pe baza gradientului imaginii (detectoare):
Detectorul Roberts,
Detectorul Sobel,
Detectorul Prewitt
Pe baza Laplacianului imaginii
Operatorul Laplace
Operatorul LoG
Extragerea frontierelor
Binarizarea matricei amplitudinilor
Detectorul Canny
Metoda Roberts
Predictia urmatorului punct
Transformata HOUGH
Reducere Zgomot
Filtrul Gauss
Filtrul Median
Scheletizare
Histograma
Prezentarea Interfetei utilizator
Prin 10 imagini (Figura1-Figura10), sugestive, am sintetizat conținutul directorului cu fișierele aplicației, ecranul de lucru pentru aplicație și ecrane din execuția aplicației pentru diferite opțiuni din meniu.
Figura 1: Conținutul directorului cu fișierele aplicației Aplicație_SPG
Figura 2: Ecranul de lucru pentru Aplicație_SPG
Figura 3: Meniul principal al aplicației
Figura 4: Meniul File
Figura 5: Meniul File – opțiunea Open (pentru încărcarea unei imagini)
Figura 6: Meniul Detecția pixelilor de front – opțiunea Pe baza gradientului imaginii.
Figura 7: Meniul Detecția pixelilor de front – opțiunea Pe baza Laplacianului imaginii
Figura 8: Meniul Detecția pixelilor de front – opțiunea Extragerea frontierelor
Figura 9: Meniul Reducere Zgomot
Figura 10: Meniul Histograma
Prezentarea rezultatelor execuției opțiunilor din meniul aplicației.
În imaginile din acest capitol am captat ecranele care prezintă rezultatele aplicării unui algoritm asupra unei imagini selectate.
În partea stângă este imaginea original, iar în dreapta sau dedesupt, se vizualizează efectul asupra pozei inițiale al aplicării algoritmului selectat din meniu.
Detectia pixelilor de front pe baza gradientului imaginii folosind Detectorul Roberts:
Detectia pixelilor de front pe baza gradientului imaginii folosind Detectorul Sobel:
Detectia pixelilor de front pe baza gradientului imaginii folosind Detectorul Prewitt
Detectia pixelilor de front pe baza Laplacianului imaginii folosind operatorul Laplace:
și aplicând opțiunea Reducere Zgomot – Gauss, se obține:
Cu histograma din imaginea urmatoare:
Detectia pixelilor de front pe baza Laplacianului imaginii folosind operatorul LoG:
Extragerea frontierelor prin selectarea opțiunii Binarizarea matricei amplitudinilor:
Extragerea frontierelor prin selectarea opțiunii Detectorul Canny (vizualizare pentru două imagini selectate succesiv):
Extragerea frontierelor folosind Transformata Hough:
În imaginile care urmează, am selectat și alte poze din colecția de poze din directorul Poze al aplicației și am aplicat transformări exemplificate anterior. Înaintea fiecărei imagini captate, am specificat operația efectuată asupra pozei.
Extragerea frontierelor prin selectarea opțiunii Detectorul Canny:
Pe imaginea anterioară, am aplicat Reducerea zgomotului cu filtrul Gauss:
Detectia pixelilor de front pe baza gradientului imaginii folosind Detectorul Sobel:
Am selectat altă poză și am aplicat transformări asupra ei, conform imaginilor care urmează:
Detectia pixelilor de front pe baza gradientului imaginii folosind Detectorul Laplace:
Reducerea zgomotului folosind filtrul Gauss:
Reducerea zgomotului folosind filtrul Median:
și obținerea histogramei imaginii prelucrate prin ultima operație:
Concluzii
Tema proiectului sugerează aplicabilitatea aplicației în scop didactic la orele de SPG – Sisteme de Prelucrare Grafică, Anul IV, Specializarea: C4.
Realizarea modulară a programului oferă studenților câteva facilități pentru fixarea noțiunilor teoretice studiate la Sisteme de Prelucrare Grafică, și anume:
Deslușirea algoritmului prezentat la curs pe baza implementării din Aplicație_SPG, prin studierea funcției care implementează algoritmul în Visual C#.
Vizualizarea rezultatelor aplicării algoritmilor implementați în Aplicație_SPG și oferirea posibilității comparării variantelor de algoritmi pe tipuri de poze. Se poate astfel decide asupra algoritmului optim pentru fiecare caz particular.
Fixarea noțiunilor teoretice prin aplicații practice
Posibilitatea studenților de a propune eventuale optimizări la implementarea realizată
Posibilitatea studenților de a propune dezvoltarea aplicației cu introducerea în meniu a altor opțiuni prin care se implementează și alți algoritmi.
Bibliografie
Prof. Florica Moldoveanu, Note de curs Sisteme de Prelucrare Grafică, Specializarea: SPG, Anul IV
Digital Image Processing – 3rd edition, Rafael C. Gonzalez, Richard E. Woods, Prentice Hall, 2008
Image processing handbook, John Russ, CRC Press 2007.
Florica Moldoveanu, Alin Moldoveanu, Catalin Tudose, Irina Mocanu, Marius Zaharia, Dragos Dobrota – Programarea aplicatiilor grafice in Java (Editura Printech) – indrumar laborator curs EGC la A&C, UPB
Florica Moldoveanu, Zoea Racovita, Gabriel Hera, Serban Petrescu,Marius Zaharia – Grafica pe calculator, Editura Teora, București, ISBN 973-601-290-5
Colojoară I., Lecții de analiză matematică, Facultatea de matematică, Universitatea din București, Tipografia Universității, 1979.
Cristea, V., C. Giumale, E, Kalisz, Al. Paunoiu, Limbajul C standard, Ed. Teora, București, 1992.
Popovici, M. D., Popovici, M. I., C++. Tehnologia orientată spre obiecte. Aplicații, Ed . Teora, București, 2000.
Pop D., Petcu D., Modelarea lumii tridimensionale, Editura Eubeea, Timișoara, ISBN 973- 673-011-5, 2004
Copyright Notice
© Licențiada.org respectă drepturile de proprietate intelectuală și așteaptă ca toți utilizatorii să facă același lucru. Dacă consideri că un conținut de pe site încalcă drepturile tale de autor, te rugăm să trimiți o notificare DMCA.
Acest articol: Metode de Prelucrare ale Imaginilor (ID: 118246)
Dacă considerați că acest conținut vă încalcă drepturile de autor, vă rugăm să depuneți o cerere pe pagina noastră Copyright Takedown.
