Table of Contents

Tema 3. Image processing

Responsabili:

Termen de predare: 15.01.2017 23:59

Pentru fiecare zi (24 de ore) de întârziere, se vor scădea 10 puncte din nota acordată. Deadline-ul hard este 18.01.2017 23:59.

Actualizări:

Scopul temei:

Introducere

Procesarea de imagini se refera la aplicarea unor algoritmi specifici pe continutul unei imagini pentru a obtine anumite efecte (blur, sharpening, etc.) sau rezultate (face detection/recognition, etc.). In aceasta tema vom lucra cu unul dintre cele mai simple formate de imagini si anume formatul BMP.

O imagine BMP are următoarea structură:

Header-ele pe care le puteți folosi în implementare se află în scheletul de cod asociat temei.

Urmatiti cu foarte mare atentie exemplul de aici si incercati sa intelegeti cum e reprezentata o imagine BMP inainte de a incepe implementarea. Daca e ceva neclar, puteti intreba oricand pe forum.

Cerinte

Veti avea de rezolvat 4 task-uri. La primele 3 task-uri o sa procesati o singura imagine. Numele acesteia o sa fie citit de pe prima linie din fisierul de intrare (vezi formatul datelor de intrare). La task-ul 4 o sa lucrati cu un format binar special (mai multe detalii in sectiunea corespunzatoare task-ului 4).

Task 1 (10p):

Acest task presupune transformarea unei imagini color intr-o imagine alb-negru. Fisierul .bmp la care se gaseste imaginea se va citi de pe prima linie a fisierului de intrare, numit input.txt. Daca numele imaginii este <nume>.bmp atunci imaginea alb-negru se va scrie in fisierul <nume>_black_white.bmp (de exemplu, daca prima linie din fisierul input.txt este image.bmp atunci imaginea alb negru se va scrie in fisierul image_black_white.bmp). Procedeul prin care o imagine color se transforma intr-o imagine alb-negru este urmatorul: fiecare pixel din imaginea color, fie acesta (R, G, B), va deveni (X, X, X) unde X = [ (R + G + B) / 3] unde prin [] se intelege parte intreaga (inferioara). De exemplu pixelul (3, 2, 2) va deveni (2, 2, 2), iar pixelul (10, 20, 30) va deveni (20, 20, 20).

Exemplu: Daca imaginea color arata astfel:  Imaginea originala

Dupa transformare ar trebui sa arate astfel:  Imaginea alb-negru

Nu descarcati imaginile pentru testare! Acestea nu respecta formatul BMP! Pentru testare folositi numai imaginile puse la dispozitie in arhiva de testare. Imaginile de mai sus au doar caracter informativ.

Task 2 (20p):

In cadrul acestui task va trebui sa aplicati anumite filtre pe o imagine. Un filtru este o matrice (in cazul acestei teme matricea va avea mereu 3 linii si 3 coloane) care este aplicata fiecarui pixel din imagine pentru a obtine noul pixel. In continuare o sa vedem ce inseamna sa aplici o matrice (filtru) unui pixel. Fie $(R_{22},G_{22},B_{22})$ un pixel din imagine care are urmatorii vecini:

$$ \begin{bmatrix} (R_{11},G_{11},B_{11}) & (R_{12},G_{12},B_{12}) & (R_{13},G_{13},B_{13}) \\ (R_{21},G_{21},B_{21}) & (R_{22},G_{22},B_{22}) & (R_{23},G_{23},B_{23}) \\ (R_{31},G_{31},B_{31}) & (R_{32},G_{32},B_{32}) & (R_{33},G_{33},B_{33}) \end{bmatrix} $$

si fie filtrul:

$$ A = \begin{bmatrix} a_{11} & a_{12} & a_{13} \\ a_{21} & a_{22} & a_{23} \\ a_{31} & a_{32} & a_{33} \end{bmatrix} $$

Atunci, in imaginea rezultata in urma aplicarii filtrului A, pixel-ul $(R_{22},G_{22},B_{22})$ va fi inlocuit cu $(R'_{22},G'_{22},B'_{22})$ unde:

$$ R'_{22} = \sum_{i=1}^3 \sum_{j=1}^3 R_{ij}a_{ij} $$ $$ G'_{22} = \sum_{i=1}^3 \sum_{j=1}^3 G_{ij}a_{ij} $$ $$ B'_{22} = \sum_{i=1}^3 \sum_{j=1}^3 B_{ij}a_{ij} $$

Daca in urma calculului uneia dintre cele 3 valori de mai sus, suma rezultata este < 0 atunci valoarea se va seta la 0. Asemanator, daca suma este > 255 atunci componenta corespunzatoare din pixel se va seta la 255.

Daca un pixel are vecini in afara imaginii, la calculul sumei acesti vecini vor avea valoarea (0, 0, 0).

Sa presupunem ca avem urmatoarea imagine:

(2 2 2)    (3 3 3)   (0 0 0)   (0 0 0)
(10 10 10) (1 1 1)   (0 0 0)   (0 0 0)
(0 0 0)    (0 0 0)  (50 50 5)  (0 0 0)
(0 0 0)    (0 0 0) (240 0 240) (0 0 0)

Primul pixel (cel de pe prima linie si de pe prima coloana) trebuie sa fie pixelul din coltul stanga sus al imaginii (atunci cand este afisata pe ecran)! Atentie la faptul ca imaginea se citeste rasturnata din fisierul BMP. La aplicarea filtrelor trebuie sa considerati dispunerea de mai sus (nu cea din fisier). Cu alte cuvinte, liniile sunt dispuse crescator de sus in jos, prima linie fiind si prima linie afisata pe ecran, iar coloanele sunt dispuse crescator de stanga la dreapta.

Pentru imaginea de mai sus, fisierul BMP ar arata astfel (ignorand cele 2 headere):

(0 0 0)    (0 0 0) (240 0 240) (0 0 0)      No padding
(0 0 0)    (0 0 0)  (50 50 5)  (0 0 0)      No padding
(10 10 10) (1 1 1)   (0 0 0)   (0 0 0)      No padding
(2 2 2)    (3 3 3)   (0 0 0)   (0 0 0)      No padding

Pe imaginea de mai sus dorim sa aplicam urmatorul filtru:

$$ \begin{bmatrix}0 & 1 & 0\\1 & 1 & 1\\0 & 1 & 0 \end{bmatrix} $$

Imaginea rezultata va fi:

(15 15 15) (6 6 6)     (3 3 3)      (0 0 0)
(13 13 13) (14 14 14)  (51 51 6)    (0 0 0)
(10 10 10) (51 51 6)   (255 50 245) (50 50 5)
(0 0 0)    (240 0 240) (255 50 245) (240 0 240)

Pentru rezolvarea acestui task va trebui sa aplicati 3 filtre de edge-detection. Acestea sunt:

$$ F_1 = \begin{bmatrix}-1 & -1 & -1\\-1 & 8 & -1\\-1 & -1 & -1 \end{bmatrix} F_2 = \begin{bmatrix}0 & 1 & 0\\1 & -4 & 1\\0 & 1 & 0 \end{bmatrix} F_3 = \begin{bmatrix}1 & 0 & -1\\0 & 0 & 0\\-1 & 0 & 1 \end{bmatrix} $$

Aceste filtre se vor aplica pe rand imaginii alb-negru obtinute la Task-ul 1. Imaginea obtinuta prin aplicarea lui $F_1$ imaginii alb-negru se va salva in fisierul <nume>_f1.bmp, imaginea obtinuta prin aplicarea lui $F_2$ imaginii alb-negru se va salva in fisierul <nume>_f2.bmp, iar imaginea obtinuta prin aplicarea lui $F_3$ imaginii alb-negru se va salva in fisierul <nume>_f3.bmp.

Task 3 (30p)

Ne propunem sa realizam o compresie simpla pentru imaginile BMP. La un nivel inalt, algoritmii de compresie se impart in doua categorii: lossless si lossy. Compresia imaginilor este o compresie lossy. Nu se poate aplica o compresie lossless, pe o imagine, care sa aiba un grad ridicat de compresie deoarece algoritmii de compresie lossless se bazeaza pe eliminarea reduntantei statistice, care in cazul unei imagini este mica (deoarece intr-o imagine, putem intalnii, orice valoare cu aceeasi probabilitate). De exemplu, formatul JPEG presupune o compresie de tip loosy. Algoritmul de compresie pe care il vom aplica in aceasta tema este unul minimalist si se bazeaza pe urmatoarea idee: pixeli invecinati care sunt putin diferiti (culoarea este asemanatoare) pot fi grupati in pixeli cu aceeasi culoare fara sa se observe o diferenta majora la vizualizarea imaginii. Ganditi-va ca avem urmatoarea imagine

.

Se observa ca exista apoximativ 3 nuante de culoare: albastru inchis, albastru deschis si verde. In loc sa salvam fiecare pixel separat in fisier putem sa specificam doar cele trei zone si sa spunem ca fiecare are o anumita culoare. Astfel, se va pierde din calitate (compresie loosy) dar se va castiga spatiu. Cat se pierde din calitate este direct proportional cu cat de mare este pragul pentru care vom considera ca doi pixeli fac parte din aceeasi zona.

O zona este o multime de pixeli adiacenti (care se invecineaza pe orizontala sau veriticala, dar nu pe diagonala). O zona nu trebuie sa aiba o forma neaparat regulata.

Algoritmul de compresie pe care o sa il implementati (si descrie formal ceea ce am spus mai sus) este urmatorul:

1) Se alege un pixel din imagine care nu a fost inclus in nicio zona. Fie acest pixel $ (R, G, B) $. Daca toti pixelii au fost inclusi intr-o zona atunci algoritmul s-a terminat;

2) Pornind de la acest pixel se detemina zona de pixeli care satisface simultan urmatoarele conditii:

3) Toti pixelii din zona determinata vor avea culoarea pixelului de la pasul 1) adica $ (R, G, B) $.

4) Dupa deteminarea noii zone se reia algoritmul incepand cu pasul 1).

Modul in care alegem pixelul la pasul 1) poate inflenta calitatea algoritmului. Pentru aceasta tema vom aplica urmatoarea metoda de selectie: daca exista mai multi pixeli care nu au fost deja inclusi intr-o zona, se va alege acel pixel care se afla pe linia mai mica, iar in caz de egalitate (pixeli pe aceeasi linie) se va alege cel care se afla pe coloana mai mica. Atentie! La fel ca si la Task-ul 1 prima linie este linia cea mai de sus din imagine (adica cea mai de jos din fisier) iar prima coloana este coloana cea mai la stanga in imagine.

Sa consideram urmatoarea imagine (si threshold = 10):

(10 10 10) (12 10 13) (10 10 10) (30 30 30) (30 29 31)
(10 10 10) (12 15 10) (10 10 10) (30 28 30) (12 10 13)
(11 11 11) (11 12 11) (10 10 10) (30 33 30) (12 11 13)
(12 12 12) (12 10 12) (10 10 10) (30 30 31) (12 10 13)

Initial nici un pixel nu este inclus in vreo zona. Se alege pixelul din coltul stanga-sus de valoare (10 10 10) deoarece acesta se afla pe cea mai mica linie (si pe cea mai mica coloana). Se determina zona maximala din care acesta face parte:

 (10 10 10) (12 10 13) (10 10 10) (30 30 30) (30 29 31)
 (10 10 10) (12 15 10) (10 10 10) (30 28 30) (12 10 13)
 (11 11 11) (11 12 11) (10 10 10) (30 33 30) (12 11 13)
 (12 12 12) (12 10 12) (10 10 10) (30 30 31) (12 10 13)

Alegem din nou un pixel care nu a fost inclus intr-o zona conform algoritmului de selectie. Acest pixel este cel de pe linia 1 si coloana 4 (indexarea incepe la 1) de valoare (30 30 30). Se determina zona maximala din care acesta face parte (colorata cu verde):

 (10 10 10) (12 10 13) (10 10 10) (30 30 30) (30 29 31)
 (10 10 10) (12 15 10) (10 10 10) (30 28 30) (12 10 13)
 (11 11 11) (11 12 11) (10 10 10) (30 33 30) (12 11 13)
 (12 12 12) (12 10 12) (10 10 10) (30 30 31) (12 10 13)

Alegem din nou un pixel care nu a fost inclus intr-o zona conform algoritmului de selectie. Acest pixel este cel de pe linia 2 si coloana 5 (indexarea incepe la 1) de valoare (12 10 13). Se determina zona maximala din care acesta face parte (colorata cu albastru):

 (10 10 10) (12 10 13) (10 10 10) (30 30 30) (30 29 31)
 (10 10 10) (12 15 10) (10 10 10) (30 28 30) (12 10 13)
 (11 11 11) (11 12 11) (10 10 10) (30 33 30) (12 11 13)
 (12 12 12) (12 10 12) (10 10 10) (30 30 31) (12 10 13)

Am determinat astfel 3 zone in imagine. Dupa ce se inlocuieste in fiecare zona culoarea pixelilor cu cea a pixelului initial (determinat la pasul 1) obtinem imaginea:

 (10 10 10) (10 10 10) (10 10 10) (30 30 30) (30 30 30)
 (10 10 10) (10 10 10) (10 10 10) (30 30 30) (12 10 13)
 (10 10 10) (10 10 10) (10 10 10) (30 30 30) (12 10 13)
 (10 10 10) (10 10 10) (10 10 10) (30 30 30) (12 10 13)

Aceasta imagine nu o vom scrie intr-un fiser .bmp, ci intr-un fisier binar dupa urmatorul format:

1) Cele doua header-e din formatul BMP se scriu nemodificate;

2) Incepand de la valoarea byte-ul de offset din header (adica unde ar fi trebuit sa inceapa matricea de pixeli din fomatul BMP) se for scrie tupluri (nr_linie, nr_coloana, R, G, B) cu urmatoarea semnificatie:

In fisier se vor scrie doar pixelii care sunt la granita unei zone cu alta zona. Adica se vor scrie doar pixelii de pe marginea fiecarei zone. Ordinea in care acestia se vor scrie in fisier este crescator pe linii, iar in caz de egalitate crescator pe coloane.

Pixelii de pe linia cea mai mica/cea mai mare si pixelii de pe coloana cea mai mica/cea mai mare (adica marginile imaginii) se vor scrie intotdeauna in fisier.

Pentru exemplul de mai sus ceea ce se va scrie in fiserul binar dupa ce se scriu cele doua header-e BMP este:

(1, 1, 10, 10, 10)
(1, 2, 10, 10, 10)
(1, 3, 10, 10, 10)
(1, 4, 30, 30, 30)
(1, 5, 30, 30, 30)
(2, 1, 10, 10, 10)
(2, 3, 10, 10, 10)
(2, 4, 30, 30, 30)
(2, 5, 12, 10, 13)
(3, 1, 10, 10, 10)
(3, 3, 10, 10, 10)
(3, 4, 30, 30, 30)
(3, 5, 12, 10, 13)
(4, 1, 10, 10, 10)
(4, 2, 10, 10, 10)
(4, 3, 10, 10, 10)
(4, 4, 30, 30, 30)
(4, 5, 12, 10, 13)

Urmariti cu atentie exemplul si observati ce pixeli nu au fost/au fost scrisi in fisierul binar.

Aplicarea algoritmului de compresie se realizeaza pe imaginea al carei nume se gaseste pe primul rand in fisierul input.txt (cea pe care am lucrat si la primul task). Valoarea de prag (threshold) care se va utiliza in cadrul algoritmului se va citi de pe linia 2 din fisierul input.txt. Datele in format binar, rezultate in urma compresiei, se vor scrie in fisierul compressed.bin.

Task 4 (30p)

Se da un fisier binar in formatul celui de la Task-ul 3 (imagine comprimata). Sa se aplice algoritmul de decompresie. Numele fisierului din care cititi datele comprimate se gaseste in fisierul input.txt pe linia a treia. Imagina decomprimata se va scrie in format BMP in fisierul decompressed.bmp. Practic, trebuie sa delimitati zonele si sa atribuiti fiecarui pixel dintr-o zona culoarea marginii zonei (toata marginea va avea aceeasi culoare, deoarece asa am definit compresia in Task-ul 3). Daca o imagine este comprimata si apoi decomprimata, imaginea rezultata nu va fi egala cu imaginea initiala (loosy compression) ci va pierde din calitate. Pe de alta parte, un BMP de 6MBytes dupa compresie poate ocupa si 300KBytes (vezi fisierele din arhiva de testare).

Formatul datelor de intrare si iesire

Fisierul de intrare se numeste input.txt. Acesta are urmatoarea structura:

Exemplu:

image.bmp
25
arhive.bin

Ca output pentru exemplul de mai sus se vor scrie fisierele: image_black_white.bmp, image_f1.bmp, image_f2.bmp, image_f3.bmp, compressed.bin si decompressed.bmp.

Cand scrieti imagini in format BMP, intre ultimul byte din header si byte-ul la care incepe matricea de pixeli (adica pana la byte-ul de offset) trebuie sa scrieti peste tot byte-ul 0. Aceeasi observatie se aplica si in cazul formatului comprimat.

Restrictii si precizari

Fiti foarte atenti la erori de tipul stack overflow. Pe vmchecker dimensiunea stivei este de 1MB. Daca aveti functii recursive aproximati cata memorie se ocupa pe stiva la un apel al functiei respective.

Resurese si checker-ul local

Resursele pentru tema se pot descarca de aici. Sunt prezente: