Individální projekty MPOA

Mikroprocesory s architekturou ARM

Uživatelské nástroje

Nástroje pro tento web


2018:raspberry-video

Rozdíly

Zde můžete vidět rozdíly mezi vybranou verzí a aktuální verzí dané stránky.

Odkaz na výstup diff

Obě strany předchozí revize Předchozí verze
Následující verze
Předchozí verze
2018:raspberry-video [2019/01/13 14:28]
Tomáš Bravenec
2018:raspberry-video [2019/01/14 13:45] (aktuální)
Tomáš Bravenec
Řádek 11: Řádek 11:
  
 ===== Raspberry Pi 3 Model B ===== ===== Raspberry Pi 3 Model B =====
 +{{ :​2018:​raspberry-video:​raspberry-pi-3-462x322.jpg|}}
 +
 +Pro tvorbu projektu byla zvolen mikropočítač Raspberry Pi 3 Model B, díky tomu, že disponuje více jádry a umožňuje tak urychlit zpracování obrazu paralelizací,​ a také podporou operačních systémů, na kterých se dá aplikace vyvinout i s podporou grafického rozhraní.
 +
 +==== Technické parametry ====
 +
 +  * Quad Core 1.2GHz Broadcom BCM2837 64bit CPU
 +  * 1GB (900 MHz) LPDDR2 RAM 
 +  * 100 Base Ethernet
 +  * 40-pin extended GPIO
 +  * 4 USB 2 ports
 +  * 4 Pole stereo output and composite video port
 +  * Další porty typu HDMI, 3,5mm jack atd...
  
 ====== Implementace ====== ====== Implementace ======
Řádek 91: Řádek 104:
 </​code>​ </​code>​
 Za zmínku stojí také to, že v takto paralelizovaných cyklech není možné využívat proměnné definované jen pro jedno vlákno, jelikož by začalo docházek k souběhu (anglicky Race Condition) a aplikace by nepodávala správné výsledky. Za zmínku stojí také to, že v takto paralelizovaných cyklech není možné využívat proměnné definované jen pro jedno vlákno, jelikož by začalo docházek k souběhu (anglicky Race Condition) a aplikace by nepodávala správné výsledky.
-Knihovna OpenMP obsahuje mnohem více funkcí a direktiv, určených pro paralelizaci,​ pro danou aplikaci ovšem nebyly potřeba.+Knihovna OpenMP obsahuje mnohem více funkcí a direktiv, určených pro paralelizaci,​ pro danou aplikaci ovšem nebyly potřeba. ​Při inicializaci aplikace je zjištěn dostupný počet jader, a tento počet je nastaven jako výchozí pomocí kódu (Pomocí Glade je výchozí počet jader nastaven na 1, proto není potřeba **else** část podmínky):​ 
 + 
 +<code cpp> 
 +int coreCount = std::​thread::​hardware_concurrency();​ 
 +if(coreCount > 0) 
 +
 +    cores->​set_range(1,​ coreCount);​ 
 +    cores->​set_value(coreCount);​ 
 +
 +</​code>​
  
 ===== Prahování ===== ===== Prahování =====
Řádek 133: Řádek 155:
  
 ==== Filtr Sobel ==== ==== Filtr Sobel ====
-Detekce hran pomocí filtru sobel je tvořena výpočtem+Detekce hran pomocí filtru sobel je tvořena výpočtem ​dvou gradientů pomocí 2D konvoluce filtrů s obrazem v odstínech šedi, každý z nich vyjadřuje sílu hrany v horizontálním nebo vertikálním směru. Pro získání jednoho obrazu z těchto gradientů, se využívá odmocniny ze součtu druhých mocnin obou gradientů, a to pro každý pixel. Jelikož je ale odmocnina výpočetně náročná, lze tento výpočet nahradit přibližnou aproximací pomocí součtu absolutních hodnot gradientů. Takto zkombinované horizontální a vertikální hrany poté stačí jen prohnat prahováním pro dosžení výsledku. 
  
 ==== Detektor hran - Canny ==== ==== Detektor hran - Canny ====
Řádek 140: Řádek 163:
 ==== Srovnání fitru Sobel a Cannyho detektoru ==== ==== Srovnání fitru Sobel a Cannyho detektoru ====
 Při porovnání obou přístupů,​ si lze všimnout, silnějších čar, které nebyly utlumeny ani při použití vysokého prahu, na rozdíl od toho, při použití Cannyho detektoru a velmi nízkého prahu jsou v obraze lépe viditelné hrany, a výstup vypadá celkově lépe, ale za cenu náročnějšího zpracování. Při porovnání obou přístupů,​ si lze všimnout, silnějších čar, které nebyly utlumeny ani při použití vysokého prahu, na rozdíl od toho, při použití Cannyho detektoru a velmi nízkého prahu jsou v obraze lépe viditelné hrany, a výstup vypadá celkově lépe, ale za cenu náročnějšího zpracování.
-^ Vstupní obraz ^ Filtr Sobel, Práh = 1.0 ^ Cannyho detektor, Práh = 0.15 ^+ 
 +^ Vstupní obraz ^ Filtr Sobel, Práh = 1.0 ^ Cannyho detektor, Práh = 0.^
 |{{ :​2018:​raspberry-video:​sparrow.jpg?​250 |}}|{{ :​2018:​raspberry-video:​sparrow_edge_sobel_1.png?​250 |}}|{{ :​2018:​raspberry-video:​sparrow_edge_canny.png?​250 |}}| |{{ :​2018:​raspberry-video:​sparrow.jpg?​250 |}}|{{ :​2018:​raspberry-video:​sparrow_edge_sobel_1.png?​250 |}}|{{ :​2018:​raspberry-video:​sparrow_edge_canny.png?​250 |}}|
 |{{ :​2018:​raspberry-video:​lenna.jpg?​250 |}}|{{ :​2018:​raspberry-video:​lena_sobel.png?​250 |}}|{{ :​2018:​raspberry-video:​lena_canny.png?​250 |}}| |{{ :​2018:​raspberry-video:​lenna.jpg?​250 |}}|{{ :​2018:​raspberry-video:​lena_sobel.png?​250 |}}|{{ :​2018:​raspberry-video:​lena_canny.png?​250 |}}|
Řádek 148: Řádek 172:
  
 Pro snadné pochopení jak K-Means funguje v případě obrazů, si lze představit trojrozměrné pole se všemi stranami o délce 256 prvků (každá z os představuje jednu z barevných složek RGB). Pro začátek se náhodně zvolí K bodů (clusterů) z tohoto pole, načež se pro každý pixel vstupního obrazu vypočítá euklidovská vzdálenost ke každému z těchto bodů. Následně se zprůměrují hodnoty barevných složek které jsou nejblíže k jednomu z bodů a tento průměr se nastaví jako nový bod do další iterace. Toto se provede pro všech K bodů, a poté stále dokola v dalších iteracích dokud se body budou v daném trojrozměrném poli posouvat (barva všech bodů nebude stejná, jako v předchozí iteraci). Pro snadné pochopení jak K-Means funguje v případě obrazů, si lze představit trojrozměrné pole se všemi stranami o délce 256 prvků (každá z os představuje jednu z barevných složek RGB). Pro začátek se náhodně zvolí K bodů (clusterů) z tohoto pole, načež se pro každý pixel vstupního obrazu vypočítá euklidovská vzdálenost ke každému z těchto bodů. Následně se zprůměrují hodnoty barevných složek které jsou nejblíže k jednomu z bodů a tento průměr se nastaví jako nový bod do další iterace. Toto se provede pro všech K bodů, a poté stále dokola v dalších iteracích dokud se body budou v daném trojrozměrném poli posouvat (barva všech bodů nebude stejná, jako v předchozí iteraci).
 +
 +Kód provádějící jednu iteraci je níže. Z kódu je opět vidět nastavení OpenMP, následované opět **for** cykly procházející každý řádek a sloupec, tentokrát ovšem obraz není v odstínech šedi, je proto nutné krokovat vždy po třech bytech (RGB), dále je potřeba při indexaci místo počtu sloupců využít **step**, cž je počet Bytů v jednom řádku, který je často o jeden až 3 Byty větší než počet sloupců * 3, jedná se o zarovnání řádků na násobek 32 bitů. Euklidovská vzdálenost je počítána bez odmocniny, která nemá vliv na srovnání vzdálenosti,​ a sníží se tak výpočetní náročnost. Vzdálenost pixelu k prvnímu bodu je vždy nejkratší,​ proto není potřeba žádná další kontrola. Pro následující body je již nutné vzdálenost zkontrolovat a pokud bude pixel blíž jinému než prvnímu bodu, změní se jeho přidělení. Pro přepočet bodů se poté přičtou jednotlivé barevné složky do bodu a zvedne se počítadlo pixelů které k tomuto bodu patří. Posledním krokem je nastavení pixelů výstupního obrazu na barvy bodů pro případ, že by již nedošlo k další iteraci. ​
 +
 +<code cpp>
 +void KMeans::​iterate()
 +{
 +    omp_set_dynamic(0);​
 +    omp_set_num_threads(threadCount);​
 +
 +    unsigned char *imageData = image.data;
 +    unsigned char *clusteredData = clustered.data;​
 +
 +#pragma omp parallel for
 +    for (int i = 0; i < image.rows; i++)
 +    {
 +        for (int j = 0; j < image.cols * 3; j += 3)
 +        {
 +            int shortestDist = 0, shortestCluster = 0;
 +            for (int k = 0; k < clusterCount;​ k++)
 +            {
 +                if (k == 0)
 +                {
 +                    shortestDist = (pow(imageData[i * image.step + j + 0] - newCluster[k].centroidRed,​ 2) +
 +                                    pow(imageData[i * image.step + j + 1] - newCluster[k].centroidGreen,​ 2) +
 +                                    pow(imageData[i * image.step + j + 2] - newCluster[k].centroidBlue,​ 2));
 +                    shortestCluster = k;
 +                }
 +                if ((pow(imageData[i * image.step + j + 0] - newCluster[k].centroidRed,​ 2) +
 +                     ​pow(imageData[i * image.step + j + 1] - newCluster[k].centroidGreen,​ 2) +
 +                     ​pow(imageData[i * image.step + j + 2] - newCluster[k].centroidBlue,​ 2)) < shortestDist)
 +                {
 +                    shortestDist = (pow(imageData[i * image.step + j + 0] - newCluster[k].centroidRed,​ 2) +
 +                                    pow(imageData[i * image.step + j + 1] - newCluster[k].centroidGreen,​ 2) +
 +                                    pow(imageData[i * image.step + j + 2] - newCluster[k].centroidBlue,​ 2));
 +                    shortestCluster = k;
 +                }
 +            }
 +
 +            newCluster[shortestCluster].addRedValue(imageData[i * image.step + j + 0]);
 +            newCluster[shortestCluster].addGreenValue(imageData[i * image.step + j + 1]);
 +            newCluster[shortestCluster].addBlueValue(imageData[i * image.step + j + 2]);
 +            newCluster[shortestCluster].increaseCount();​
 +
 +            clusteredData[i * image.step + j + 0] = newCluster[shortestCluster].centroidRed;​
 +            clusteredData[i * image.step + j + 1] = newCluster[shortestCluster].centroidGreen;​
 +            clusteredData[i * image.step + j + 2] = newCluster[shortestCluster].centroidBlue;​
 +        }
 +    }
 +}
 +</​code>​
  
 Výstupem metody K-Means Clustering může být například:​ Výstupem metody K-Means Clustering může být například:​
Řádek 154: Řádek 228:
  
 ====== Video ukázka ====== ====== Video ukázka ======
 +Ve video ukázce je předveden výstup zpracování jednoho obrazu všemi implementovanými metodami. K Raspberry Pi je připojení realizováno pomocí VNC, následné nahrávání obrazu je prováděno z windows, aby nedocházelo k zatížení procesoru při výpočtech enkódováním nahrávaného obrazu.
 +
 +{{youtube>​vo03sZ9O2X4?​medium}}
 +
 +V dolní části pod aplikací je otevřen terminál se spuštěnou konzolovou aplikací **htop** pro sledování využití jader a paměti. V druhém terminálu je spuštěna smyčka odečítající současný takt a teplotu jádra procesoru. Příkaz kterým je tohoto odečítání dosaženo je zde:
 +
 +<code bash>
 +while true; do vcgencmd measure_clock arm; vcgencmd measure_temp;​ sleep 1; done
 +</​code>​
  
 ====== Zdrojový kód ====== ====== Zdrojový kód ======
Řádek 159: Řádek 242:
  
 https://​gitlab.com/​tbravenec/​image_processing_on_rpi https://​gitlab.com/​tbravenec/​image_processing_on_rpi
 +
 +Nebo poslední verze git repozitáře před odevzdáním:​
 +
 +{{ :​2018:​raspberry-video:​image_processing_on_rpi-master.zip |}}
 +
 +====== Závěr ======
 +Aplikace má implementovány všechny metody zpracování obrazu zmíněné v zadání, prahování a detekce hran pomocí filtru sobel, mají identický výsledek jako implementace pomocí OpenCV, cannyho detektor hran implementovaný manuálně vyžaduje pro podobný výsledek k OpenCV nižší práh, to může být dáno odlišnou implementací funkce pro potlačení nemaximálních pixelů a nebo odlišně navrženým prahováním s hysterezí. K-Means clustering podává mírně odlišné výsledky, což je ale očekávatelné,​ když jsou počáteční podmínky generovány náhodně.
 +
 +Neúmyslným vedlejším efektem psaní aplikace pro Raspbian, je že je aplikace kompatibilní nejen s Raspberry Pi, ale i jakýmkoliv jiným procesorem a operačním systémem, pro který existuje implementace GTK, OpenMP a OpenCV.
  
 ====== Reference ====== ====== Reference ======
2018/raspberry-video.1547386087.txt.gz · Poslední úprava: 2019/01/13 14:28 autor: Tomáš Bravenec