![]() |
Pełny kod źródłowy |
Pierwszym problemem podczas przejścia ze struktury IplImage do używania klasy Mat będzie zapewne to, jak odczytać i zmodyfikować zawartość poszczególnych składowych piksela. Do tej pory było kilka sposobów, żeby sobie z tym poradzić (polecam wpis na oficjalnym Wiki pod hasłem: How to access image pixels). Teraz podobnie, mamy kilka możliwości, aby dostać się do piksela, jednak dokumentacja opisuje to bardzo pobieżnie. W rozdziale Introduction → Fast Element Access zostały pokazane 3 różne sposoby operowania na pikselach obrazu, jednak dla mnie wydają się one kłopotliwe w użyciu. To właśnie było powodem przygotowania niniejszego poradnika, w którym chciałem przedstawić własne i znalezione w Internecie wygodne sposoby dostępu do składowych piksela.
Obrazy w skali szarości
Na początek, aby nabrać wprawy najlepiej zrobić proste testy z wykorzystaniem obrazów 1-kanałowych. W tym celu obrazy pozyskane z kamery można przekonwertować na obraz szary i na nim wykonywać dalsze operacje.
Poniżej został pokazany fragment kodu, który zawiera 3 sposoby dostępu do piksela, począwszy od tego najbardziej tradycyjnego, gdzie operujemy bezpośrednio na tablicy z danymi (metoda 1), poprzez użycie funkcji at(), której przekazujemy współrzędne interesującego nas punktu (metoda 2), a skończywszy na zastosowaniu wskaźnika do każdego analizowanego wiersza obrazu (metoda 3). Z tych 3 metod podejście obiektowe jest reprezentowane przez sposób 2, podczas gdy pozostałe 2 przypadki działają bardziej tradycyjnie.
Aby skupić się na tym co istotne, poniższy kod zawiera jedynie najważniejszą część programu, z deklaracją używanych struktur reprezentujących obraz, oraz środkiem pętli przetwarzającej obraz z kamery. Aby uruchomić ten program można skorzystać z szablonu, który podałem w poprzednim wpisie OpenCV - instalacja i pierwszy przykład w Ubuntu w sekcji Ten sam przykład po nowemu.
Mat cam_frame, img_gray_v1, img_gray_v2, img_gray_v3; // LOOP FOR GRABBING IMAGE FROM WEBCAM cap >> cam_frame; // Method 1: Old style method operating on data array cvtColor(cam_frame, img_gray_v1, CV_BGR2GRAY); for(int i = 0; i < img_gray_v1.rows; i++) for(int j = 0; j < img_gray_v1.cols; j++) { img_gray_v1.data[img_gray_v1.step*i + j] = 255 - img_gray_v1.data[img_gray_v1.step*i + j]; } // Method 2: Assign pixel using .at() function cvtColor(cam_frame, img_gray_v2, CV_BGR2GRAY); for(int i = 0; i < img_gray_v2.rows; i++) for(int j = 0; j < img_gray_v2.cols; j++) img_gray_v2.at(i,j) = 255 - img_gray_v2.at (i,j); // Method2: Use plain C operator []. More efficient than method 2 // if you need to process a whole row of a 2d array cvtColor(cam_frame, img_gray_v3, CV_BGR2GRAY); for(int i = 0; i < img_gray_v3.rows; i++) { uchar* img_gray_v3_i = img_gray_v3.ptr (i); for(int j = 0; j < img_gray_v3.cols; j++) img_gray_v3_i[j] = 255 - img_gray_v3_i[j]; } // END LOOP
Obrazy wielokanałowe
Większy problem aniżeli obrazy 1-kanałowe sprawiają obrazy kolorowe, domyślnie uzyskiwane z tradycyjnych kamer cyfrowych. Należy tutaj pamiętać nie tylko o współrzędnych punktu, który jest analizowany, ale również o jego składowej koloru. na szczęście jest na to kilka sposobów, które można prześledzić poniżej.
Podejście tradycyjne
Podobnie jak w przypadku obrazów szarych, dla obrazów wielokanałowych można również skorzystać z podejścia znanego z obróbki struktur IplImage i przeliczać wskaźnik do elementu, który nas interesuje. Przykład poniżej:
Mat frame, result; // LOOP FOR GRABBING IMAGE FROM WEBCAM cap >> frame; result = frame.clone(); // invert the image for(i=0; i < frame.rows; i++) for(j=0; j < frame.cols; j++) for(k=0; k < channels; k++) { uchar* temp_ptr = &((uchar*)(result.data + result.step*i))[j*3]; temp_ptr[0] = 255 - temp_ptr[0]; temp_ptr[1] = 255 - temp_ptr[1]; temp_ptr[2] = 255 - temp_ptr[2]; } // END LOOP
Sposoby opisane w oficjalnej dokumentacji
Tak jak wspomniałem na początku tego wpisu, w oficjalnej dokumentacji we wpisie Introduction → Fast Element Access pokazano 3 sposoby na to jak dostać się do składowych piksela. Dla mnie osobiście dziwne wydaje się rozbijanie obrazu wielokanałowego na obrazy zawierające jeden kanał i po wykonaniu obliczeń ponowne ich scalanie. Nie dość, że wydłuża to zapis algorytmu obróbki obrazu, to mam wrażenie, że powoduje dodatkowy narzut obliczeniowy. Kod z drobnymi poprawkami względem wersji oryginalnej znajduje się poniżej:
Mat img_frame, img_resultA, img_resultB, img_resultC; vectorplanesA,planesB,planesC; // LOOP FOR GRABBING IMAGE FROM WEBCAM cap >> img_frame; // Method 1. process Y plane using an iterator split(img_frame, planesA); // split the image into separate color planes MatIterator_ it1 = planesA[0].begin (), it1_end = planesA[0].end (), it2 = planesA[1].begin (), it3 = planesA[2].begin (); for(; it1 != it1_end; ++it1,++it2,++it3 ) { *it1 = 255 - *it1; *it2 = 255 - *it2; *it3 = 255 - *it3; } merge(planesA, img_resultA); // Method 2. process the first chroma plane using pre-stored row pointer. split(img_frame, planesB); for( int y = 0; y < img_frame.rows; y++ ) { uchar* Uptr1 = planesB[0].ptr (y); uchar* Uptr2 = planesB[1].ptr (y); uchar* Uptr3 = planesB[2].ptr (y); for( int x = 0; x < img_frame.cols; x++ ) { Uptr1[x] = 255 - Uptr1[x]; Uptr2[x] = 255 - Uptr2[x]; Uptr3[x] = 255 - Uptr3[x]; } } merge(planesB, img_resultB); // Method 3. process the second chroma plane using // individual element access operations split(img_frame, planesC); for( int y = 0; y < img_frame.rows; y++ ) { for( int x = 0; x < img_frame.cols; x++ ) { uchar& Vxy1 = planesC[0].at (y, x); uchar& Vxy2 = planesC[1].at (y, x); uchar& Vxy3 = planesC[2].at (y, x); Vxy1 = 255 - Vxy1; Vxy2 = 255 - Vxy2; Vxy3 = 255 - Vxy3; } } merge(planesC, img_resultC); // END LOOP
Ostatnia deska ratunku
Powyżej opisane sposoby dla obrazów wielokanałowych nie wydają się nazbyt wygodne i raczej zniechęcają, a nie zachęcają do stosowania klasy Mat. Na szczęście światełkiem w tunelu jest klasa pochodna po cv::Mat o niemal identycznej nazwie: cv::Mat_. Klasa ta ma opracowane operatory dostępu do składowych piksela poprzez zastosowanie konstrukcji ( x , y ). Możemy w ten sposób uzyskać wskaźnik do konkretnego punktu obrazu, i dalej stosując zapis tablicowy [] możemy dostać się do konkretnej wartości danego kanału.
Przykład 1: Operowanie na oryginalnym obrazie:
Mat frame; // LOOP FOR GRABBING IMAGE FROM WEBCAM cap >> frame; Mat_& frame_ = (Mat_ &)frame; for(int i = 0; i < frame_.rows; i++) for(int j = 0; j < frame_.cols; j++) for(int k = 0; k < 3; k++) frame_(i,j)[k] = 255 - frame_(i,j)[k]; // END LOOP
Przykład 2: Operowanie na obrazie pomocniczym:
Mat frame; Mat_rgb; // LOOP FOR GRABBING IMAGE FROM WEBCAM cap >> frame; cvtColor(frame, rgb, CV_BGR2RGB); for(int i = 0; i < rgb.rows; i++) for(int j = 0; j < rgb.cols; j++) for(int k = 0; k < 3; k++) rgb(i,j)[k] = 255 - rgb(i,j)[k]; // END LOOP
Podsumowując zebrane w tym poradniku sposoby dostępu do składowych piksela, najwygodniejsze wydaje się użycie operatora at() dla obrazów jednokanałowych, natomiast dla obrazów wielokanałowych najprostszy i najbardziej czytelny zapis daje klasa cv::Mat_.
2 komentarze:
Obrazy wielokanałowe->Podejście tradycyjne->kod: trzecia pętla for jest zbędna. Dzięki za wpis, zaoszczędziłem czas na szukaniu, pozdrawiam serdecznie
Jeżeli weżmiesz pod uwagę czasy dostępu to można zauwazyc, że najprostsze oraz najwygodniejsze sposoby są niestety zarazem najwolniejsze...
Najefektywniej działa:
/ Method 2. process the first chroma plane using pre-stored row pointer.
Prześlij komentarz