This is an old revision of the document!


Tema 1 PP 2023

DEADLINE 7 aprilie
  • Temele trebuie submise pe curs.upb.ro
  • Temele ce nu sunt acceptate de validatorul de arhive NU vor fi puncate
  • Va sugeram ca dupa ce ati incarcat o arhiva, sa o descarcati si sa o testati cu validatorul de arhive

În cadrul acestei teme veți lucra cu fișiere imagine, mai exact cu formatul PPM din pachetul Netpbm. Le veți primi sub formă de listă de charactere.

Folosiți un stil de programare funcțional. NU aveți voie cu:

- Efecte laterale (de exemplu modificarea parametrilor dați ca input la funcție)

- var (val este ok!)

Scheletul se poate găsi la: tema1_2023_pp-skel.zip

Exemplu fișier PPM (cu explicații)

Exemplu fișier PPM (cu explicații)

P3                        (este mereu așa, format PPM din NetPBM)
3 2                       (lungimea și înălțimea imaginii)
255                       (culoarea maximă care va fi mereu 255)
255 0 0                   (primul pixel de pe prima linie este rosu)
0 255 0                   (al doilea pixel de pe prima linie este verde)
0 0 255                   (al treilea pixel de pe prima linie este albastru)
255 255 0                 (primul pixel de pe a doua linie este galben)
255 255 255               (al doilea pixel este alb)
0 0 0                     (al treilea pixel este negru)

Vizualizare:

Coloana 1 Coloana 2 Coloana 3
Linia 1 roșu verde albastru
Linia 2 galben alb negru

Primele 3 linii vor fi mereu aproape la fel, formatul P3 pe prima linie, lungimea și înălțimea pe a doua, culoarea maximă pe a treia, care va fi mereu 255. Apoi fiecare pixel va fi pe o linie, mai intâi valoarea de roșu (de la 0 la 255) apoi verde și albastru, urmat direct de un \n.

La ieșirea voastră va fi un singur format, la fel ca în exemplu (după valoarea albastru a culorii va fi mereu \n, fără spații în plus).
Nu schimbați funcțiile din scheletul de cod. Puteți, însă, adăuga oricâte în plus.
Pentru a vedea ușor imaginea generată de voi, puteți folosi https://0xc0de.fr/webppm/

Pentru a reprezenta imaginea, se va folosi tipul Image definit mai jos:

type Image = List[List[Pixel]]
type GrayscaleImage = List[List[Double]]

unde Pixel este o clasă in care se rețin valorile celor 3 culori (roșu, verde, albastru) sub forma de Integer. Clasa Pixel ce se găsește in folderul util din schelet.

1. Alăturare de imagini pe verticală (10p)

Se dau două imagini de aceeași lungime. Să se alăture vertical astfel încât prima imagine să fie sus și a doua jos.

Exemplu

Exemplu

Dacă prima imagine arată așa:

P1 P2
P3 P4

și a doua așa:

D1 D2

Alăturarea lor pe verticală ar fi așa:

P1 P2
P3 P4
D1 D2

Se va completa pentru asta funcția

def verticalConcat(image1: Image, image2: Image): Image = ???

2. Alăturare de imagini pe orizontală (5p)

Se dau două imagini de aceeași înălțime. Să se alăture orizontal astfel încât prima imagine să fie în stânga și a doua în dreapta.

Exemplu

Exemplu

Dacă prima imagine arată așa:

P1 P2 P3
P4 P5 P6

și a doua așa:

D1
D2

Alăturarea lor pe orizontală ar fi așa:

P1 P2 P3 D1
P4 P5 P6 D2

Se va completa pentru asta funcția

def horizontalConcat(image1: Image, image2: Image): Image = ???

3. Rotire de imagini (5p)

La această cerință se va implementa o rotație în sens trigonometric cu multipli de 90 de grade (90, 180, …).

Exemplu

Exemplu

Pentru o matrice

P1 P2
P3 P4

dacă o rotim în sens trigonometric cu 90 de grade, vom avea:

P2 P4
P1 P3

Se va completa pentru asta funcția

def rotate(image: Image, degrees: Integer): Image = ???

4. Detectare de muchii cu detectorul Sobel (40p)

O operatie care apare destul de des in procesarea imaginilor este detectia obiectelor, iar o parte importanta din acest proces complex este detectia frontierelor (marginilor) obiectelor. Un algoritm simplu pentru detectia frontierelor este detectorul/operatorul Sobel.

Acesta se bazeaza pe conceptul de convolutie, care este folosit extrem de des in prelucrarea imaginilor. Convolutia reprezinta o transformare a imaginii in care fiecare pixel din imaginea noua este obtinut prin insumarea valorilor ponderate ale unui pixel si ale vecinilor lui. Ponderile cu care se inmultesc fiecare dintre vecinii pixelului sunt reprezentate de obicei ca o matrice numita nucleu sau kernel de convolutie. Variind kernelul, putem obtine diverse efecte asupra imaginii.

Presupunem ca vrem sa aplicam detectia frontierelor pe aceasta imagine:

imagine originala

imagine originala

Pași detectorului Sobel sunt următorii:

1. Se va aplica funcția grayscale

 def toGrayScale(pixel: Pixel) : Double = ???

din Util pe fiecare pixel (detectorul functioneaza doar pe imagini alb-negru)

imagine alb-negru

imagine alb-negru

2. Se va aplica un blur gaussian prin convolutie cu kernelul:

Kernel pentru blur gaussian

Kernel pentru blur gaussian

$\begin{matrix} \frac{1}{273} & \frac{4}{273} & \frac{7}{273} & \frac{4}{273} & \frac{1}{273} \\
\frac{4}{273} & \frac{16}{273} & \frac{26}{273} & \frac{16}{273} & \frac{4}{273} \\
\frac{7}{273} & \frac{26}{273} & \frac{41}{273} & \frac{26}{273} & \frac{7}{273} \\
\frac{4}{273} & \frac{16}{273} & \frac{26}{273} & \frac{16}{273} & \frac{4}{273} \\
\frac{1}{273} & \frac{4}{273} & \frac{7}{273} & \frac{4}{273} & \frac{1}{273} \end{matrix} $

Cod Scala pentru Kernel

Cod Scala pentru Kernel

val gaussianBlurKernel: GrayscaleImage = List[List[Double]](
    List( 1, 4, 7, 4, 1),
    List( 4,16,26,16, 4),
    List( 7,26,41,26, 7),
    List( 4,16,26,16, 4),
    List( 1, 4, 7, 4, 1)
  ).map(_.map(_ / 273))

(acest pas nu face parte din detector propriu-zis, dar are rolul de a elimina zgomotul din imagine, care ar fi foarte vizibil in rezultat)

imagine blurred

imagine blurred

3. Se vor aplica apoi kernelurile Gx și Gy pentru a genera Mx și My

Gx

Gx

val Gx : GrayscaleImage = List(
  List(-1, 0, 1),
  List(-2, 0, 2),
  List(-1, 0, 1)
)

Gy

Gy

val Gy : GrayscaleImage = List(
  List( 1, 2, 1),
  List( 0, 0, 0),
  List(-1,-2,-1)
)

(acestea detecteaza variatii bruste ale intensitatii pe orizontala si, respectiv, pe veriticala)

Mx

Mx

pixelii cu valori negative sunt reprezentati cu rosu, iar cei cu valori pozitive, cu albastru

My

My

pixelii cu valori negative sunt reprezentati cu rosu, cei cu valori pozitive, cu albastru

Pentru pasii 2 si 3, se va implementa functia

def applyConvolution(image: GrayscaleImage, kernel: GrayscaleImage) : GrayscaleImage = ???

4. Se vor combina Mx și My, adunându-se (element cu element) fiecare pixel în modul (astfel, obtinem o aproximare destul de buna a schimbarii de intensitate in jurul fiecarui pixel)

'imagine' combinata

'imagine' combinata

5. Se va pune un prag (threshold) T, astfel încât fiecare pixel cu o valoare mai mica decat T va fi negru (rgb 0 0 0) și fiecare pixel deasupra T va fi alb (rgb 255 255 255)

rezultat final

rezultat final

Se va completa pentru asta funcția

def edgeDetection(image: Image, threshold : Double): Image = ???

5. Triunghi pascal cu resturi - colorare (40p)

Se calculează Triunghiul lui Pascal. Acesta este o reprezentare 2-dimensionala, a unor valori care apar in foarte multe aplicatii de combinatorica dar si algebra. Triunghiul lui Pascal cu 4 linii este ilustrat mai jos:

       1
      1 1
     1 2 1
    1 3 3 1
   1 4 6 4 1
   ...     
  • Cea mai simpla modalitate de a descrie o linie l din triunghiul lui Pascal este urmatoarea: primul element $ l_0$ (si ultimul) este intotdeauna 1; Pt $ i > 0$ , $ l_i = lp_{i-1} + lp_{i}$ .
  • Cu un pic de atentie, observam ca o valoare de pe linia n si coloana k este $ C_n^k$ (combinari de $ n$ luate cate $ k$ ), si atunci relatia de mai sus devine: $ C_n^k = C_{n-1}^k + C_{n-1}^{k-1} $. Preferam in general sa implementam calculul $ C_n^k$ folosind aceasta relatie de recurenta, pentru ca este mai eficientă și evită mai bine riscul de overflow față de formula cu factoriale.
  • Asadar, triunghiul lui Pascal este format la fiecare linie de $ C_n^0 C_n^1 \ldots C_n^n $ unde n e numărul liniei (>= 0). Linia $ n$ din triunghiul lui Pascal descrie coeficienții din $ (a + b)^n $ după cum se poate vedea aici:

Vizualizare Coeficienți binomiali

Vizualizare Coeficienți binomiali

Nu folosiți formula combinăriilor cu factorial pentru acest exercițiu!

Așadar, limitându-ne la 5 x 5, triunghiul va arăta așa:

Click to display ⇲

Click to hide ⇱

1 0 0 0 0
1 1 0 0 0
1 2 1 0 0
1 3 3 1 0
1 4 6 4 1

Prin triunghiul lui Pascal se pot genera diferite șabloane (patterns), a căror reprezentare grafică este influențata de modul de colorare al matricii rezultate. Vom alege să folosim M culori, fiecare numar x din matrice avand asociată culoarea conform functiei urmatoare ce primește o valoare a restului impărțirii la M ( $ M \le 5 $ ):

def pickColor(i: Integer) : Pixel = {
  if (i == 0) Pixel(255, 0, 0)
  else if (i == 1) Pixel(0, 0, 255)
  else if (i == 2) Pixel(0, 255, 0)
  else if (i == 3) Pixel(255, 255, 255)
  else Pixel(0, 0, 0)
}

Pentru un număr M ales, se calculează resturile (modulo) acestor numere la împărțirea cu M. Formula recursivă ne ajută mai mult deoarece putem să aplicăm modulo la fiecare pas. Așadar nu contează de câte ori și unde aplicăm % M. Vom folosi formula finală: $ C_n^k = (C_{n-1}^k + C_{n-1}^{k-1}) \% M $ la fiecare pas.

Sugestia de rezolvare ar fi generare linie cu linie a triunghiului Pascal începând de la $ C_0^0 = 1 $ pe prima linie. Aceasta este o abordare de programare dinamică.

Exemplul anterior pentru M = 4

Exemplul anterior pentru M = 4

1 0 0 0 0
1 1 0 0 0
1 2 1 0 0
1 3 3 1 0
1 0 2 0 1

Apoi, după o funcție dată ca input (argumentul funct), fiecărui număr i se va acorda o culoare. Deasupra diagonalei (unde sunt zero-uri) va rămâne negru și nu se va aplica funcția!

Acest triunghi fiind infinit, se va limita la $ size^2 $ (size > 2).

Se va completa pentru asta funcția

def moduloPascal(m: Integer, funct: Integer => Pixel, size: Integer): Image = ???