niedziela, 29 marca 2009

OpenCV cz. 3. Otwieranie i zapis obrazu wideo

OpenCV posiada możliwość odczytywania i zapisywania obrazu w wielu popularnych formatach. Funkcjonalność ta udostępniona jest w zestawie HighGui. Dopuszczalne są (podano rozszerzenia plików):bmp, dib, jpeg, jpg, jpe, png, pbm, pgm, ppm, sr, ras, tiff, tif, exr oraz jp2. Funkcje, które pozwalają przeprowadzić te operacje pojawiały się już w poprzednich wpisach. Dla przypomnienia poniżej zaprezentowano przykładowy kawałek kodu:
// wczytanie obrazu w odcieniach szarosci
IplImage*img=cvLoadImage("/home/lukasz/Pulpit/rysowanie.png",CV_LOAD_IMAGE_GRAYSCALE);
// zapisanie obrazu do pliku
cvSaveImage("/home/lukasz/Pulpit/rysowanie.png",img);

Trochę bardziej złożonym zagadnieniem jest odczyt i zapis strumienia wideo. OpenCV pozawala domyślnie na obsługę plików dvix (patrz: źródło). Z obsługą obrazu wideo związana jest analogiczna do IplImage struktura CvCapture. Wyznaję zasadę, że lepszy okomentowany kod niż długie opisy działania ;-) Poniżej przykład pokazujący odczyt i wyświetlanie pliku avi:
// odczytanie pliku avi
CvCapture* vid = cvCreateFileCapture("/home/lukasz/Pulpit/sample.avi");

// tworzymy okno wyswietlajace obraz
cvNamedWindow("plik wideo", 0);

// odczytanie pierwszej klatki - niezbedne do prawidlowego odczytania wlasciwosci pliku
// przy uzyciu funkcji cvGetCaptureProperty
cvQueryFrame(vid);

// odczytujemy z wlasciwosci pliku liczbe klatek na sekunde
double fps =  cvGetCaptureProperty(vid, CV_CAP_PROP_FPS);

// wyliczamy czas potrzebny do odtwarzania pliku z prawidlowa prędkoscia
int odstep_miedzy_klatkami = 1000 / fps;

while (true)
{
// pobranie kolejnej ramki
IplImage* ramka = cvQueryFrame(vid);

// jezeli nie jest pusta to wyswietlamy
if (ramka != 0)
cvShowImage("plik wideo", ramka);
else
break;

// czekamy przez okreslony czas
int c = cvWaitKey(odstep_miedzy_klatkami);

// jezeli nacisnieto klawisz 'k', konczymy wyswietlanie
if (c == 'k')
break;

}

// zwolnienie zasobów
cvDestroyAllWindows();
cvReleaseCapture(&vid);
Kolejnym poruszanym zagadnieniem jest odczyt i zapis obrazu z kamery. Zasadniczo nie różni się on bardzo od obsługi plików dvix. Dalej mamy do czynienia ze strukturą CvCapture. Zmienia się jedynie metoda zwracająca do niej wskaźnik. Przykład:
// pobranie zasobu kamery, parametr mowi o tym ktora kamere wybieramy
// w przypadku gdy w systemie dostepna jest wiecej niz jedna
CvCapture* vid = cvCreateCameraCapture(0);

// tworzymy okno wyswietlajace obraz
cvNamedWindow("plik wideo", CV_WINDOW_AUTOSIZE);

// odczytanie pierwszej klatki - niezbedne do prawidlowego odczytania wlasciwosci pliku
// przy uzyciu funkcji cvGetCaptureProperty
cvQueryFrame(vid);

// ustalamy wartosc fps - najlepiej wieksza niz maksymalna deklarowana przez producenta
// Jezeli wartosc bedzie za mala, obraz bedzie pojawial sie z opoznieniem
double fps =  50;

int odstep_miedzy_klatkami = 1000 / fps;

while (true)
{
// pobranie kolejnej klatki
IplImage* ramka = cvQueryFrame(vid);

// jezeli nie jest pusta to wyswietlamy
if (ramka != 0)
cvShowImage("plik wideo", ramka);
else
break;

// czekamy przez okreslony czas
int c = cvWaitKey(odstep_miedzy_klatkami);

// jezeli nacisnieto klawisz 'k', konczymy wyswietlanie
if (c == 'k')
break;

}

// zwolnienie zasobow
cvDestroyAllWindows();
cvReleaseCapture(&vid);
W powyższych kodach pojawia się funkcja cvQueryFrame. Jak wspomniano, służy ona do pobierania ramek z CvCapture. Ten sam efekt można osiągnąć posługując się kolejno funkcjami cvGrabFrame i cvRetrieveFrame. Pierwsza z nich ściąga ramkę z kamery i pozostawia ją w stanie surowym w pamięci, a druga pobiera taką ramkę i zamienia ją na IplImage.

Ostatnią funkcjonalnością dzisiaj opisaną będzie zapis wideo. Zapisywać możemy kolejne obrazy IplImage. Nie musi być to zatem obraz z kamery czy przetworzony plik wideo. Przykład:

// pobranie zasobu kamery, parametr mowi o tym ktora kamere wybieramy
// w przypadku gdy w systemie dostepna jest wiecej niz jedna
CvCapture* vid = cvCreateCameraCapture(0);

// tworzymy okno wyswietlajace obraz
// jako parametr przekazujemy CV_WINDOW_AUTOSIZE, ktory utworzy okno o wielkości
// odpowiadajacej wyswietlanemu obrazowi
cvNamedWindow("plik wideo", CV_WINDOW_AUTOSIZE);

// odczytanie pierwszej klatki - niezbedne do prawidlowego odczytania wlasciwosci pliku
// przy uzyciu funkcji cvGetCaptureProperty
cvQueryFrame(vid);

// ustalamy wartosc fps
double fps =  30;
int odstep_miedzy_klatkami = 1000 / fps;

// tworzymy strukture zapisujaca klatki do pliku
// kolejno podajemy: sciezke do pliku, format, fps i rozmiar ramki (opcjonalnie czy obraz ma byc w kolorze)
CvVideoWriter* vr = cvCreateVideoWriter("/home/lukasz/Pulpit/zapis.mpg",CV_FOURCC_DEFAULT,fps,cvSize(640,480));

while (true)
{
// pobranie kolejnej klatki
IplImage* ramka = cvQueryFrame(vid);

// jezeli nie jest pusta to wyswietlamy
if (ramka != 0)
{
cvShowImage("plik wideo", ramka);

// zapisujemy ramke do pliku
cvWriteFrame(vr,ramka);
}
else
break;

// czekamy przez okreslony czas
int c = cvWaitKey(odstep_miedzy_klatkami);

// jezeli nacisnięto klawisz 'k', konczymy wyswietlanie
if (c == 'k')
break;

}

// zwolnienie zasobow
cvDestroyAllWindows();
cvReleaseCapture(&vid);
cvReleaseVideoWriter(&vr);

Jakieś wady? Niestety tak. Nie da się ukryć, że kamerki komputerowe mają zmienny (zależny np. od oświetlenia) współczynnik fps. W CvVideoWriter nie zmienimy tego po utowrzeniu, więc film może zachowywać się nieprzewidywalnie w trakcie nagrywania. Problem nie dotyczy oczywiście zapisu wcześniej przygotowanych statycznych obrazów. Jeżeli ktoś posiada lepszy pomysł na synchronizację obrazu z kamery z nagrywaniem to zapraszam do komentarzy ;-)

poniedziałek, 23 lutego 2009

OpenCV cz. 2. Rysujemy i piszemy

Do rysowania prostych figur przygotowano odpowiednie funkcje. Podobnie ma się rzecz z wyświetlaniem tekstu. Aby wyświetlić linię prostą skorzystamy z funkcji cvLine. Jako parametry przyjmuje ona wskaźnik na CvArr, dwa punkty oznaczające końce linii oraz kolor. Ponadto można ustawić kilka parametrów opcjonalnych pozwalających ustawić grubość linii czy sposób jej wyświetlania (np. z antyaliasingiem). Przy ustawionym ROI, linia zostanie wyświetlona tylko w jego obszarze.
// tworzy linię o końcach [1,1] oraz [100,100], koloru białego, o grubości 3 pikseli
cvLine(img,cvPoint(1,1),cvPoint(100,100),cvScalarAll(255.0),3);

Jak widać, jest ona podobna w nazewnictwie do innych funkcji tej biblioteki. Idąc dalej mamy możliwość umieszczania innych obiektów geometrycznych: prostokąta, elipsy itd. Przykładowy kod na końcu notki (aby lepiej zrozumieć działanie funkcji cvEllipse zachęcam do zapoznania się z tym obazkiem). Inne funkcje wysujące (np. wielokąty) działają podobnie, w razie potrzeby odsyłam, a jakże inaczej, do dokumentacji.

Pisanie jest czynnością podobną do rysowania, ale wcześniej należy zainicjować czcionkę.
// zainicjowanie czcionki
CvFont font = cvFont(1.0,1);
cvInitFont(&font,CV_FONT_HERSHEY_SIMPLEX,1.0,1.0);

a same napisy tworzymy funkcją cvPutText.
Poniżej kod na podsumowanie:
#include "cv.h"
#include "cxcore.h"
#include "cxtypes.h"
#include "highgui.h"

/*
* Rysowanie i pisanie
* autor: ratixu.blogspot.com
*/
int main(int argc, char** argv)
{
IplImage* img = cvCreateImage(cvSize(700, 700), IPL_DEPTH_32F, 3);

// tworzenie linii o końcach w punktach [5,1] oraz [200,100]
// o kolorze białym, grubości 5 pkseli z antyaliasingiem
cvLine(img,cvPoint(5,1),cvPoint(200,100),cvScalarAll(255.0),5,CV_AA);

// tworzenie prostokąta o przekątnej o końcach w punktach [5,110]
// oraz [200,210] grubości 4 pksele
cvRectangle(img,cvPoint(5,110),cvPoint(200, 210),cvScalar(122,121,43),4);

// tworzenie elipsy - jedna z metod
CvPoint srodek = cvPoint(200,350);
CvSize osie = cvSize(200,100);
double kat = 360.0;
double katStartowy = 0.0;
double katKoncowy = kat;
CvScalar kolor = cvScalarAll(255.0);
int grubosc = 5;
cvEllipse(img,srodek,osie,kat,katStartowy,katKoncowy,kolor,grubosc);

// tworzenie okręgu o promieniu 75 pikseli i środku w punkcie [200,600]
// oraz grubości 5 pikseli
cvCircle(img,cvPoint(200,600),75,cvScalarAll(255.0),5);

// zainicjowanie czcionki:
// skala rozciągnięcia, przyjumjąc 1 za 100%
double skala = 2.0;
// pochylenie czcionki, przyjmując 1.0 za 45 stopni
double kursywa = 0.2;
CvFont font = cvFont(skala,grubosc);
// włączamy antyalliasing, CV_FONT_XXX to typy dostępnych czcionek
cvInitFont(&font,CV_FONT_HERSHEY_SIMPLEX,skala,skala,kursywa,grubosc,CV_AA);


//podpisujemy figury
cvPutText(img,"Odcinek",cvPoint(300,100),&font,cvScalarAll(255.0));
cvPutText(img,"Prostokat",cvPoint(300,200),&font,cvScalarAll(255.0));
cvPutText(img,"Elipsa",cvPoint(450,350),&font,cvScalarAll(255.0));
cvPutText(img,"Okrag",cvPoint(300,650),&font,cvScalarAll(255.0));

cvNamedWindow("img",0);
cvShowImage("img", img);
while(true)
{
if(cvWaitKey(100) == 'k') break;
}

// zapisujemy wynik do pliku
cvSaveImage("/home/lukasz/Desktop/rysowanie.png",img);
cvDestroyWindow("img");
cvReleaseImage(&img);
return (EXIT_SUCCESS);
}

a tutaj zapisany obraz:

piątek, 13 lutego 2009

OpenCV cz. 1. Podstawy

Dzisiaj będzie trochę o strukturach w OpenCV. Podstawą jest struktura o nazwie CvArr. Jak sama nazwa wskazuje jest to jednowymiarowa tablica przechowująca dane określonego typu. Dalej znajdziemy CvMat czyli tablicę wielowymiarową oraz IplImage który jest odzwierciedleniem obrazu. Struktury wymieniono w takiej kolejności nie bez powodu: można ich generalnie używać jakby istniał schemat dziedziczenia Arr -> Mat -> Image. Sama biblioteka to w większości funkcje napisane w C i kilka klas w C++. Mamy tu zatem do czynienia z klasycznymi strukturami i ciężko mówić o dziedziczeniu obiektowym. Piszę o tym ponieważ w większości funkcji bibliotecznych które przyjmują jako parametr CvArr możemy spokojnie posłać w tym miejscu CvMat lub IplImage i dalej, jeżeli przyjmuje CvMat to przyjmuje także IplImage. Nie dotyczy to jednak metod napisanych samodzielnie... W skład tych struktur, oprócz wskaźnika na dane, wchodzą inne przydatne informacje takie jak liczba kanałów, wysokość, szerokość itp. Poniżej trochę kodu:
// macierz o 30 wierszach i 40 kolumnach, składająca się z elementów 32 bitowych zmiennoprzecinkowych
CvMat* mat = cvCreateMat(30,40,CV_32F);

// utworzenie obrazka o rozdzielczości 800x600, głębi 32 bitowej i 4 kanałach
IplImage* img = cvCreateImage(cvSize(800,600),IPL_DEPTH_32F,4);

// wierna kopia obrazka, macierz posiada analogiczną metodę cvCloneMat
IplImage* img2 = cvCloneImage(img);
CvMat* mat2 = cvCloneMat(mat);

// oczywiście sprzątamy po sobie
cvReleaseImage(&img);
cvReleaseImage(&img2);
cvReleaseMat(&mat);
cvReleaseMat(&mat2);

Kolejnymi strukturami o których warto wspomnieć są: CvSize, CvScalar i CvRect. Pojawiają się one dosyć często do określania parametrów funkcji (jak np. w w/w konstruktorach). CvSize służy do określenia wysokości i szerokości, CvScalar do przetrzymywania 4 wartości typu dobule (np. jako kolor w formacie RGBA) a CvRect do określania prostokątów (wysokość,szerokość,x,y).
// prostokąt o wymiarach 40x30 kórego pozycja startowa (lewy górny róg)
// jest w punkcie [0,0]
CvRect rect = cvRect(0,0,40,30);
// podobne do CvRect ale bez punktu zaczepienia
CvSize size = cvSize(40,30);
// Prosty skalar
CvScalar scal = cvScalar(23.2,34.2,45.7,23.5);
// odpowiada to funkcji cvScalar(0.3,0.3,0.3,0.3);
CvScalar scal2 = cvScalarAll(0.3);

Takich struktur jest jeszcze trochę. W razie problemów najlepiej sięgnąć do dokumentacji i API (znajdziecie je w dziale linki - wizja komputerowa). Pisanie ułatwia oczywiście dobre IDE. Podpowiada one składnię i można szybko zobaczyć jakie parametry posiada funkcja. Wiele struktur ma gotowe funkcje tworzące, które ułatwiają poszukiwania ;-)

Czas wrócić do macierzy i obrazów. Wypadałoby jeszcze wspomnieć jak dostać się do ich elementów. Na początek najlepiej posłużyć się cvGet2D przyjmującym dwie współrzędne i, oczywiście, wskaźnik do CvArr dzięki czemu działa na macierzach i obrazkach. Podobnie przedstawia się ustawianie wartości za pomocą funkcji cvSet2D
// pobranie wartości spod komórki [1,1]
// przyjęte parametry to: wskaźnik na strukturę, współrzędna Y, współrzędna X
CvScalar s1 = cvGet2D(img,1,1);
// działa tak jak powyższa metoda, ale zwraca tylko wartość double
double s2 = cvGetReal2D(mat,1,1);

// ustawianie wartości
// przyjęte parametry to: wskaźnik na strukturę, współrzędna Y, współrzędna X,
// CvScalar z wartością
cvSet2D(mat,1,1,cvScalarAll(0.5));
cvSet2D(img,1,1,,cvScalarAll(0.5));

Oczywiście sami musimy zadbać w tym przypadku czy nie pobieramy danych spoza zakresu. Opisana metoda nie jest najszybszą (można wręcz powiedzieć że najwolnieszją) metodą dostępu to pikseli. Później poznamy szybsze ;-)

Inną użyteczną rzeczą są tzw. ROI (Region of Interest) oraz COI (Color of Interest). Pozwalają one ograniczyć obszar (lub kolor) który jest przetwarzany. Wiele funkcji sprawdza czy podany obraz ma ustawiony ROI i stosuje algorytm tylko na nim. Z COI jest już trochę gorzej ale powiedzmy że także działa :)

// ustawienie ROI na kwadrat o boku 10, zaczynający się w [0,0]
cvSetImageROI(img,cvRect(0,0,10,10));
// ustawienie kanału COI
cvSetImageCOI(img,1);

Podsumowując:
#include "cxtypes.h"
#include "cxcore.h"
#include "highgui.h"

/*
* Manipulowanie danymi
* autor: ratixu.blogspot.com
*/
int main(int argc, char** argv)
{

IplImage* img = cvCreateImage(cvSize(300,300),IPL_DEPTH_32F,3);

for(int i = 0; i < img->width; i++)
for(int j = 0; j < img->height; j++)
{
if(i==j)
cvSet2D(img,i,j,cvScalar(255,0,255,0));
else if (i < j)
cvSet2D(img,i,j,cvScalar(0,255,255,0));
else
cvSet2D(img,i,j,cvScalar(255,255,0,0));
}

cvNamedWindow("okienko", 1);
// metoda ta wyświetla obraz. Sama dostosuje rozmiar okna
cvShowImage("okienko",img);
while(true)
{
if(cvWaitKey(100) == 'q') break;
}
cvDestroyWindow("okienko");
cvReleaseImage(&img);
return (EXIT_SUCCESS);
}

i efekt wywołania:

niedziela, 8 lutego 2009

OpenCV cz. 0. Wprowadzenie

Tym wpisem rozpoczynam uproszczony kurs OpenCV. Przyznaję bez bicia, że prawdopodobnie będzie dość chaotyczny i nieregularny, ale przynajmniej będzie można przeglądać wpisy w dowolnej kolejności ;-) Używany przeze mnie zestaw to Linux Mint + Netbeans. Konfiguracja jest zapewne podobna na innych systemach i środowiskach więc zaczynamy.

Rzeczy poniżej opisane mogą wydawać się oczywiste dla wprawionego programisty, więc takowy może spokojnie przejść do przykładowego kodu.

Gdy posiadamy już Netbeansa (ściągnąć można stąd, należy zwrócić uwagę by posiadał moduł C/C++), tworzymy nowy projekt. Wybieramy nową aplikację C++
Dalej możemy zmienić takie rzeczy jak nazwa, czy ścieżka lub po prostu kliknąć "Finish". Następnie wybieramy opcje nowo utworzonego projektuNastępnie w części Build->C++ Compiler wskazujemy ścieżkę do plików nagłówkowych (u mnie będzie to /usr/include/opencv)
Kolejnym krokiem jest wskazanie katalogu dodatkowych bibliotek dynamicznych (Additional Library Directories w części Linker). Są to pliki .so w linuksie lub .dll w windowsie, u mnie ten katalog to /usr/lib. Dalej dołączenie bibliotek statycznych (pliki .a w linuksie i bodajże .lib w windowsie, u mnie w katalogu /usr/lib), przykładowy zestaw na załączonym poniżej zrzucie.

Kolejną czynnością jest dodanie plików nagłówkowych do projektu. Klikamy prawym przyciskiem na katalog Headers a następnie na Add Exsisting Items from Folder i wskazujemy odpowiednią ścieżkę (u mnie /usr/include/opencv). Można zamiast katalogu podać bezpośrenio pliki poleceniem Add Exsisting Items, wtedy podczas includowania w kodzie wymagana będzie jedynie ich nazwa



To by było na tyle z przygotowań, można zacząć pisać kod. Tworzymy nowy plik i wpisujemy poniższy kod:
#include "opencv/highgui.h"
int main(int argc, char** argv)
{

cvNamedWindow("okienko", 1);
while(true)
{
if(cvWaitKey(100) == 'q') break;
}
cvDestroyWindow("okienko");
return 0;
}

Program ten tworzy nowe okno, następnie czeka na wciśnięcie klawisza q, po czym kończy działanie. Dalsza część kursu wkrótce ;-)

sobota, 7 lutego 2009

Linux Mint 6 64-bit oficjalnie wydany

Wczoraj wydana została wersja 64-bitowa szóstej edycji Linux Minta. Piszę o tym nie bez powodu: uważam go za najlepszy system z rodziny linuksów i sam od pewnego czasu używam go na co dzień.
Nie mam zamiaru przekonywać nikogo do wyższości Linuksa nad wiadomo czym, już mnie to nie bawi - oba systemy mają wady i zalety i każdy niech sam zdecyduje co dla niego lepsze.

Linux Mint to wersja zbudowana w oparciu o Debiana i Ubuntu. Wyróżnia się jednak tym, że bardzo luźno traktuje sprawy otwartości, przez co już na starcie ma wiele udogodnień których Ubuntu nie posiada i w które należy ten system wyposażyć własnoręcznie (np. sterowniki do kart nVidia czy flash). Ponadto przy większości problemów można korzystać z porad udzielanych w ogromnej społeczności Ubuntu, lub specjalnych forach dedykowanych Mintowi.
Zachęcam do pobrania i przetestowania:
Linux Mint 6 Download