Zde můžete vidět rozdíly mezi vybranou verzí a aktuální verzí dané stránky.
Obě strany předchozí revize Předchozí verze Následující verze | Předchozí verze | ||
2018:raspberry-video [2019/01/13 14:43] Tomáš Bravenec |
2018:raspberry-video [2019/01/14 13:45] (aktuální) Tomáš Bravenec |
||
---|---|---|---|
Řádek 104: | Řá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 146: | Řá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 153: | Řá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.1 ^ | ||
|{{ :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 161: | Řá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 167: | Řá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 172: | Řá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 ====== |