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 ;-)