Edit this page Backlinks This page is read only. You can view the source, but not change it. Ask your administrator if you think this is wrong. ====== Tema 1 PP 2023 ====== <note important> **DEADLINE 7 aprilie ** * Temele trebuie submise pe curs.upb.ro, in assignment-ul numit ''Tema 1''. * 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. </note> În cadrul acestei teme veți lucra cu fișiere imagine, mai exact cu formatul PPM din pachetul [[https://en.wikipedia.org/wiki/Netpbm|Netpbm]]. Le veți primi sub formă de listă de charactere. <note warning> **Folosiți un stil de programare funcțional. NU se vor accepta:** * **Efecte laterale** (de exemplu modificarea parametrilor dați ca input la funcție) * **var** (**val** este ok!) </note> <note>Scheletul se poate găsi la: {{:pp:2023:tema1_2023_pp-skel.zip|}}</note> <note> Validatorul de arhive se poate găsi la: {{:pp:2023:archive_validator_hw1.zip|}} \\ Formatul arhivelor este: * util: Util.scala, Pixel.scala * Solution.scala Numele arhivelor trebuie sa fie de forma **<Nume>_<Prenume>_<Grupa>_T1.zip** (daca aveti mai multe prenume sau nume, le puteti separa prin '-') </note> <hidden Exemplu fișier PPM (cu explicații)> <code> 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) </code> Vizualizare: ^ ^ Coloana 1 ^ Coloana 2 ^ Coloana 3 ^ | Linia 1 | roșu | verde | albastru | | Linia 2 | galben | alb | negru | </hidden> 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. <note important>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).</note> <note important>Nu schimbați funcțiile din scheletul de cod. Puteți, însă, adăuga oricâte în plus. </note> <note tip>Pentru a vedea ușor imaginea generată de voi, puteți folosi [[https://0xc0de.fr/webppm/]] </note> **Pentru a reprezenta imaginea, se va folosi tipul Image definit mai jos:** <code scala> type Image = List[List[Pixel]] type GrayscaleImage = List[List[Double]] </code> **unde** ''Pixel'' **este o clasă in care se rețin valorile celor 3 culori (roșu, verde, albastru) sub forma de Integer. Clasa Pixel 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. <hidden Exemplu> Dacă prima imagine arată așa: <code> P1 P2 P3 P4 </code> și a doua așa: <code> D1 D2 </code> Alăturarea lor pe verticală ar fi așa: <code> P1 P2 P3 P4 D1 D2 </code> </hidden> Se va completa pentru asta funcția <code scala> def verticalConcat(image1: Image, image2: Image): Image = ??? </code> ====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. <hidden Exemplu> Dacă prima imagine arată așa: <code> P1 P2 P3 P4 P5 P6 </code> și a doua așa: <code> D1 D2 </code> Alăturarea lor pe orizontală ar fi așa: <code> P1 P2 P3 D1 P4 P5 P6 D2 </code> </hidden> Se va completa pentru asta funcția <code scala> def horizontalConcat(image1: Image, image2: Image): Image = ??? </code> ====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, ...). <hidden Exemplu> Pentru o matrice <code> P1 P2 P3 P4 </code> dacă o rotim în sens trigonometric cu 90 de grade, vom avea: <code> P2 P4 P1 P3 </code> </hidden> Se va completa pentru asta funcția <code scala> def rotate(image: Image, degrees: Integer): Image = ??? </code> ====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 [[https://en.wikipedia.org/wiki/Sobel_operator|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. {{ :pp:2023:convolution.gif?500 |}} Presupunem ca vrem sa aplicam detectia frontierelor pe aceasta imagine: <hidden imagine originala>{{ :pp:2023:hw1_original.jpg?300 |}}</hidden> Pași detectorului Sobel sunt următorii: 1. Se va aplica funcția grayscale <code scala> def toGrayScale(pixel: Pixel) : Double = ??? </code> din Util pe fiecare pixel (detectorul functioneaza doar pe imagini alb-negru) <hidden imagine alb-negru>{{ :pp:2023:hw1_grayscale.jpg?300 |}}</hidden> 2. Se va aplica un [[https://en.wikipedia.org/wiki/Gaussian_blur|blur gaussian]] prin convolutie cu kernelul: <hidden 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} $ </hidden> <hidden Cod Scala pentru Kernel> <code scala> 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)) </code></hidden> (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) <hidden imagine blurred> {{ :pp:2023:hw1_blurred.jpg?300 |}} </hidden> 3. Se vor aplica apoi, prin convolutie, kernelurile Gx și Gy pentru a genera Mx și My <hidden Gx><code scala> val Gx : GrayscaleImage = List( List(-1, 0, 1), List(-2, 0, 2), List(-1, 0, 1) ) </code></hidden> <hidden Gy><code scala> val Gy : GrayscaleImage = List( List( 1, 2, 1), List( 0, 0, 0), List(-1,-2,-1) ) </code></hidden> (acestea detecteaza variatii bruste ale intensitatii pe orizontala si, respectiv, pe veriticala) <hidden Mx> {{ :pp:2023:hw1_mx.jpg?300 |}} pixelii cu valori negative sunt reprezentati cu rosu, iar cei cu valori pozitive, cu albastru </hidden> <hidden My> {{ :pp:2023:hw1_my.jpg?300 |}} pixelii cu valori negative sunt reprezentati cu rosu, cei cu valori pozitive, cu albastru </hidden> Pentru pasii 2 si 3, se va implementa functia <code scala> def applyConvolution(image: GrayscaleImage, kernel: GrayscaleImage) : GrayscaleImage = ??? </code> 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) <hidden 'imagine' combinata> {{ :pp:2023:hw1_combined.jpg?300 |}} </hidden> 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) <hidden rezultat final> {{ :pp:2023:hw1_result.jpg?300 |}} </hidden> Se va completa pentru asta funcția <code scala> def edgeDetection(image: Image, threshold : Double): Image = ??? </code> <note tip>**Hint:** la acest exercitiu este indicat sa folositi functia ''getNeighbors'' din fisierul Util.scala</note> ====5. Triunghi pascal cu resturi - colorare (40p)==== [[https://en.wikipedia.org/wiki/Pascal%27s_triangle| Triunghiul lui Pascal]] este o reprezentare 2-dimensionala a unor valori care apar in foarte multe aplicatii de combinatorica dar si algebra. Triunghiul lui Pascal cu 5 linii este ilustrat mai jos: <code> 1 1 1 1 2 1 1 3 3 1 1 4 6 4 1 ... </code> * Cea mai simpla modalitate de a descrie o linie ''l'' din triunghiul lui Pascal este urmatoarea: primul element $math[l_0] (si ultimul) este intotdeauna 1; * Pentru restul elementelor din triunghi, se foloseste recurenta $math[i > 0], $math[l_i = lp_{i-1} + lp_{i}], unde $math[l] este linia curenta, si $math[lp] este linia precedenta. * Cu puțină atentie, observam ca o valoare de pe linia ''n'' si coloana ''k'' este $math[C_n^k] (combinari de $math[n] luate cate $math[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 $math[C_n^k] folosind aceasta relatie de recurenta, pentru ca este mai eficientă din punct de vedere al complexității temporale față de formula cu factoriale. * Asadar, triunghiul lui Pascal este format pe fiecare linie din $ C_n^0 C_n^1 ... C_n^n $ unde ''n'' este numărul liniei ( $ n \ge 0 $). /* Linia $math[n] din triunghiul lui Pascal descrie coeficienții din $ (a + b)^n $ după cum se poate vedea mai jos: <hidden Vizualizare Coeficienți binomiali> {{ :pp:2023:pascal_dimension_visualisation.png?300 |}} </hidden> */ <hidden Triunghiul lui Pascal size = 5 ; reprezentare ca matrice> <code> 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 </code> </hidden> * In cele ce urmeaza, nu vom construi triunghiul lui Pascal cu valori absolute, ca mai sus, ci in locul fiecarei valori $math[l_i] vom construi valoarea $math[l_i \% M]. Deoarece numerele apărute in recurența mentionata anterior cresc foarte rapid, ar putea aparea un overflow la adunare, ducand la rezultate eronate. Asadar, pentru a obtine valoarea $math[l_i % M], vom folosi tot relatia de recurenta anterioara: $ Cmodulo_n^k = (Cmodulo_{n-1}^k + Cmodulo_{n-1}^{k-1}) \% M $. Observati ca $math[Cmodulo_n^k = C_n^k \% M = (C_{n-1}^k + C_{n-1}^{k-1}) \% M = (C_{n-1}^k \% M + C_{n-1}^{k-1} \% M) \% M = (Cmodulo_{n-1}^k + Cmodulo_{n-1}^{k-1}) \% M] * * Din acest motiv, pentru un număr ''M'' ales, se calculează resturile (modulo) acestor numerelor de pe fiecare linie 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ă: la fiecare pas. * Prin triunghiul lui Pascal se pot genera diferite șabloane (patterns), a căror reprezentare grafică este influențată de modul de colorare al matricii rezultate. Vom alege să folosim ''M'' culori. * * Sugestia de rezolvare ar fi generarea linie cu linie a triunghiului Pascal începând de la $ C_0^0 = 1 $ pe prima linie. Aceasta este o abordare de **programare dinamică**. * O culoare este, de fapt, un Pixel. **Colorarea** inseamna ca fiecarei valori din triunghiul lui Pascal i se asociaza un Pixel cu anumite valori pentru rosu, verde si albastru, valori ce urmeaza regula prezentata in functia ''pickColor''. * Fiecare intreg ''i'' (rest la impartirea cu M) din triunghiul lui Pascal prezent in matricea generata va avea asociată o culoare conform functiei de mai jos( $ M \le 5 $ ). Tot ce este deasupra diagonalei principale va rămâne negru și nu se va aplica funcția! <code scala> 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) } </code> <hidden Triunghiul lui Pascal size = 5 ; M = 4> <code> 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 </code> </hidden> <hidden Triunghiul lui Pascal size = 5 ; M = 4 DUPA COLORARE>{{:pp:2023:size5m4.png?250|}}</hidden> * Se va completa pentru acest task functia: <code scala> def moduloPascal(m: Integer, funct: Integer => Pixel, size: Integer): Image = ??? </code> <note warning>Nu folosiți formula combinăriilor cu factorial pentru acest exercițiu!</note> <note important>Triunghiul lui Pascal fiind infinit, va avea o marime limitata la $ size^2 $ (size > 2).</note> {{ :pp:2023:hw1_pascal_visualization.png?600 | }}