LinkedIn YouTube Facebook
Szukaj

Newsletter

Proszę czekać.

Dziękujemy za zgłoszenie!

Wstecz
SoM / SBC

Pierwsze kroki z Raspberry Pi: aplikacje wideo bazujące na OpenCV

Podstawowe przekształcenia obrazów

W kolejnej aplikacji przedstawione zostaną proste operacje przekształceń obrazów: progowanie oraz filtracja dolno- i górnoprzepustowa, odpowiadające rozmyciu i wyostrzaniu obrazów. Aby móc w prosty sposób porównać wyniki różnych przekształceń, w naszym kolejnym programie utworzymy klika okien z nazwami odpowiadającymi wykonanym operacjom. Kod kolejnego programu został przedstawiony na listingu 2.

List. 2.

#include "cv.h"
#include "highgui.h"
#include "math.h"

int main(){
	 IplImage* obraz_zrodlowy;
	 IplImage* obraz_szarosc;
	 IplImage* obraz_progowanie;
	 IplImage* obraz_wygladzenie;
	 IplImage* obraz_wyostrzenie;

	 obraz_zrodlowy = cvLoadImage("linux.jpg", 1);
	 obraz_szarosc = cvCreateImage (cvSize(obraz_zrodlowy->width, obraz_zrodlowy->height), IPL_DEPTH_8U, 1);
	 cvCvtColor (obraz_zrodlowy, obraz_szarosc, CV_BGR2GRAY);

	 obraz_progowanie = cvCloneImage (obraz_szarosc);
	 obraz_wygladzenie = cvCloneImage (obraz_zrodlowy);
	 obraz_wyostrzenie = cvCloneImage (obraz_zrodlowy);

	 cvNamedWindow ("Zrodlowy", 1);
	 cvNamedWindow ("Szarosc", 1);
	 cvNamedWindow ("Progowanie", 1);
	 cvNamedWindow ("Wygladzenie", 1);
	 cvNamedWindow ("Wyostrzenie", 1);

	 cvThreshold(obraz_szarosc, obraz_progowanie, 150, 255, CV_THRESH_BINARY);

	 CvMat *dolnoprzepustowa = cvCreateMat(3,3,CV_32FC1);
	 cvZero(dolnoprzepustowa);
	 dolnoprzepustowa->data.fl[0] = 1.0;
	 dolnoprzepustowa->data.fl[1] = 1.0;
	 dolnoprzepustowa->data.fl[2] = 1.0;
	 dolnoprzepustowa->data.fl[3] = 1.0;
	 dolnoprzepustowa->data.fl[4] = 1.0;
	 dolnoprzepustowa->data.fl[5] = 1.0;
	 dolnoprzepustowa->data.fl[6] = 1.0;
	 dolnoprzepustowa->data.fl[7] = 1.0;
	 dolnoprzepustowa->data.fl[8] = 1.0;
	 cvFilter2D(obraz_zrodlowy,obraz_wygladzenie,dolnoprzepustowa,cvPoint(1,1));

	 CvMat *gornoprzepustowa = cvCreateMat(3,3,CV_32FC1);
	 cvZero(gornoprzepustowa);
	 gornoprzepustowa->data.fl[0] = -1.0;
	 gornoprzepustowa->data.fl[1] = -1.0;
	 gornoprzepustowa->data.fl[2] = -1.0;
	 gornoprzepustowa->data.fl[3] = -1.0;
	 gornoprzepustowa->data.fl[4] = 9.0;
	 gornoprzepustowa->data.fl[5] = -1.0;
	 gornoprzepustowa->data.fl[6] = -1.0;
	 gornoprzepustowa->data.fl[7] = -1.0;
	 gornoprzepustowa->data.fl[8] = -1.0;
	 cvFilter2D(obraz_zrodlowy,obraz_wyostrzenie,gornoprzepustowa,cvPoint(1,1));

	 cvShowImage ("Zrodlowy", obraz_zrodlowy);
	 cvShowImage ("Szarosc", obraz_szarosc);
	 cvShowImage ("Progowanie", obraz_progowanie);
	 cvShowImage ("Wygladzenie", obraz_wygladzenie);
	 cvShowImage ("Wyostrzenie", obraz_wyostrzenie);

	 cvWaitKey(0);

	 cvDestroyWindow ("Zrodlowy");
	 cvDestroyWindow ("Szarosc");
	 cvDestroyWindow ("Progowanie");
	 cvDestroyWindow ("Wygladzenie");
	 cvDestroyWindow ("Wyostrzenie");

	 cvReleaseImage (&obraz_zrodlowy);
	 cvReleaseImage (&obraz_szarosc);
	 cvReleaseImage (&obraz_progowanie);
	 cvReleaseImage (&obraz_wygladzenie);
	 cvReleaseImage (&obraz_wyostrzenie);

	 cvReleaseMat(&dolnoprzepustowa);
	 cvReleaseMat(&gornoprzepustowa);

	 return 0;
}

Drugi program dotyczący przetwarzania statycznych obrazów jest bardziej rozbudowany, jednak większość kodu to znane już deklaracje tworzenia i usuwania okien oraz wyświetlania obrazów. Po teoretyczne informacje dotyczące wykonanych przekształceń, z którymi warto się zapoznać przed analizą programu, odsyłam m.in. na stronę. Przejdźmy więc do omówienia nowo wprowadzonych funkcji. Przed przystąpieniem do operacji binaryzacji obraz źródłowy zostaje przekonwertowany do skali szarości. W tym celu funkcją cvCreateImage() utworzyliśmy nowy obraz oraz dokonaliśmy alokacji pamięci. Funkcja ta jako parametry przyjmuje odpowiednio: rozmiar obrazu, liczbę bitów na kanał oraz liczbę kanałów. Zwracaną wartością jest wskaźnik do utworzonego obrazu. Po wykonaniu funkcji mamy już przydzielone miejsce na nasze nowe dane. Umieśćmy więc tam przekonwertowany do skali szarości obraz źródłowy. Konwersji dokonamy poprzez wywołanie cvCvtColor(). Zadaniem tej funkcji jest zmiana przestrzeni barw. Parametry przekazywane do cvCvtColor() to obraz źródłowy, obraz docelowy oraz rodzaj przeprowadzanej konwersji. Kolejną niepoznaną dotychczas funkcją jest cvCloneImage() dzięki której tworzymy, jak sama nazwa wskazuje – klony istniejących już obrazów. Taki zabieg pozwala nam zachować bez zmian wczytane oryginały w celu porównania efektów przed i po działaniu naszego algorytmu, a także zwalania z konieczności wywoływania funkcji cvCreateImage() i przepisywania danych. Po przeanalizowaniu pierwszych linijek kodu przechodzimy teraz do głównych operacji wykonywanych przez nasz program. Pierwszym rodzajem przetwarzania, jakiemu poddamy nasz przykładowy obraz linux.jpg jest progowanie (a właściwie binaryzacja). Operację progowania realizuje funkcja cvThreshold(). Funkcja ta ma następującą składnię:

  double cvThreshold(const CvArr* src,CvArr* dst, double threshold, double maxValue, int thresholdType)

Parametr threshold określa wartość progu, maxValue maksymalną wartość jaką przypisujemy pojedynczemu pikselowi, natomiast thresholdType rodzaj przeprowadzanego progowania. Obraz wynikowy przeprowadzonej operacji umieszczamy w strukturze obraz_progowanie. Dwa ostatnie przekształcenia na obrazie to filtracja dolno- i górnoprzepustowa. Filtracje w dziedzinie przestrzennej odpowiadają operacji splotu obrazu z macierzą filtru. Tak więc przed operacją wygładzenia lub wyostrzenia obrazu niezbędne jest zadeklarowanie odpowiednich macierzy. Utworzenie macierzy jest realizowane poprzez funkcję cvCreateMat(). Funkcja ta alokuje miejsce na nagłówek i dane oraz zwraca wskaźnik do nowo utworzonej macierzy. Poprzez wywołanie funkcji cvZero() zerujemy wszystkie elementy macierzy, a następnie odwołując się do odpowiednich indeksów uzupełniamy macierz odpowiednimi danymi, np.:

  dolnoprzepustowa->data.fl[5] = 1.0;

Po wypełnieniu macierzy ostatnim niezbędnym krokiem jest wykonanie splotu. Operację tą realizujemy poprzez wywołanie funkcji cvFilter2D(). Parametry przekazywane do funkcji to obraz źródłowy, obraz docelowy, maska filtru oraz współrzędne punktu środka dla filtru maski.

Ostatnie linijki kodu to znane już nam zwolnienie zaalokowanej pamięci na obrazy i macierze. Powyższy kod programu skompilujemy analogicznym poleceniem jak dla przykładu pierwszego. Wynik działania aplikacji został przedstawiony na rysunku 2.

Rys. 2. Wynik działania aplikacji z listingu 2

Rys. 2. Wynik działania aplikacji z listingu 2

 

Wybrane funkcje biblioteczne OpenCV

W dotychczas zrealizowanych przykładach wykorzystaliśmy zaledwie kilka funkcji z bardzo obszernej listy dostarczanej przez bibliotekę OpenCV. Ponadto użyte funkcje miały charakter bardziej „niskopoziomowy”, tzn. przy odrobinie wytrwałości moglibyśmy w krótkim czasie napisać od podstaw własne procedury realizujące analogiczne zadania np. splot, a tym samym procesy filtracji. Biblioteka OpenCV dostarcza jednak bardziej zawansowane funkcje, realizujące kilka przekształceń obrazu w jednym wywołaniu. Przykładem mogą być funkcje realizujące np. algorytmy śledzenia ruchu. Nie wybiegając jednak zbyt daleko powróćmy do przetwarzania obrazów statycznych. W kolejnym przykładzie zrealizujmy filtrację dolnoprzepustową (eliminacja szumu) oraz detekcję konturów w obrazie. Obydwie z wymienionych operacji można oczywiście wykonać poprzez dobór odpowiednich macierzy i wykonanie splotu, jak miało to miejsce w poprzednim przykładzie. Tym razem jednak skorzystamy z dedykowanych funkcji, co znacznie uprości budowę naszego programu. Kod programu sample_3 został przedstawiony na listingu 3.

List. 3.

#include "cv.h"
#include "highgui.h"
#include "math.h"

int main(){

  IplImage* obraz_zrodlowy;
  IplImage* obraz_szarosc;
  IplImage* obraz_progowanie;
  IplImage* obraz_filtracja;
  IplImage* obraz_kontury;
  IplImage* obraz_kontury_2;

  obraz_zrodlowy = cvLoadImage("arm.jpg", 1);
  obraz_szarosc = cvCreateImage (cvSize(obraz_zrodlowy->width, obraz_zrodlowy->height), IPL_DEPTH_8U, 1);

  cvCvtColor (obraz_zrodlowy, obraz_szarosc, CV_BGR2GRAY);
  obraz_progowanie = cvCloneImage (obraz_szarosc);
  obraz_filtracja = cvCloneImage (obraz_zrodlowy);

  CvMemStorage * pamiec = cvCreateMemStorage(0);
  CvSeq * kontur = 0;

  cvNamedWindow ("Zrodlowy", 1);
  cvNamedWindow ("Progowanie", 1);
  cvNamedWindow ("Filtracja", 1);
  cvNamedWindow ("Kontury", 1);
  cvNamedWindow ("Wynik", 1);

  //FILTRACJA
  cvSmooth(obraz_zrodlowy, obraz_filtracja,CV_GAUSSIAN,5,5);

  //PROGOWANIE
  cvThreshold(obraz_szarosc, obraz_progowanie, 150, 255, CV_THRESH_BINARY);

  //KONTURY
  obraz_kontury = cvCreateImage(cvGetSize(obraz_progowanie), IPL_DEPTH_8U, 3);
  obraz_kontury = cvCloneImage (obraz_progowanie);
  obraz_kontury_2 = cvCreateImage(cvGetSize(obraz_progowanie), IPL_DEPTH_8U, 3);
  obraz_kontury_2 = cvCloneImage (obraz_zrodlowy);

  cvFindContours(obraz_kontury, pamiec, &kontur, sizeof(CvContour), CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE, cvPoint(0,0));
  cvDrawContours(obraz_kontury_2, kontur, CV_RGB(0, 255, 0), CV_RGB(255, 0, 0), 2, 2, 8);

  cvShowImage ("Zrodlowy", obraz_zrodlowy);
  cvShowImage ("Progowanie", obraz_progowanie);
  cvShowImage ("Filtracja", obraz_filtracja);
  cvShowImage ("Kontury", obraz_kontury);
  cvShowImage ("Wynik", obraz_kontury_2);

  cvWaitKey(0);

  cvDestroyWindow ("Zrodlowy");
  cvDestroyWindow ("Progowanie");
  cvDestroyWindow ("Filtracja");
  cvDestroyWindow ("Kontury");
  cvDestroyWindow ("Wynik");

  cvReleaseImage (&obraz_zrodlowy);
  cvReleaseImage (&obraz_szarosc);
  cvReleaseImage (&obraz_progowanie);
  cvReleaseImage (&obraz_filtracja);
  cvReleaseImage (&obraz_kontury);
  cvReleaseImage (&obraz_kontury_2);

  return 0;
} 

Do pobrania

Łukasz Skalski - absolwent Politechniki Gdańskiej, miłośnik FLOSS, autor książki "Linux. Podstawy i aplikacje dla systemów embedded" oraz szeregu artykułów dotyczących programowania systemów wbudowanych. Zawodowo związany z firmą Samsung. Wszystkie wolne chwile poświęca projektowaniu i programowaniu urządzeń wyposażonych w mikroprocesory 8-/16- i 32-bitowe. Szczególnym zainteresowaniem obejmuje tematykę systemu Linux w aplikacjach na urządzenia embedded.