niedziela, 20 lutego 2011

Splot 2

Na blogu pojawił się już kiedyś post o splocie. Odsyłam do niego, jeżeli chcecie przypomnieć sobie jak to działa. Poniżej przedstawię trochę bardziej zaawansowane techniki dokonywania splotu. Ich celem jest zwiększenie prędkości wykonania. Jest to ważne, gdyż taka operacja jest w niektórych sytuacjach wykorzystywana często, chociażby wyostrzając każdą klatkę obrazu z kamery. Każde zaoszczędzone cykle procesora w wizji się przydadzą :) 
FFT
FFT (Fast Fourier Transform, szybka transformacja Fouriera) jest operacją matematyczną pozwalającą przyspieszyć proces uzyskania sygnału w dziedzinie częstotliwościowej. Operację splotu można opisać następująco: wyznaczenie transformat Fouriera obrazu i maski, wymnożenie odpowiednich częstotliwości, wyznaczenie transformaty odwrotnej obrazu. Samo wyznaczenie sygnału w dziedzinie częstotliwościowej ma podobną złożoność obliczeniową co splot. Dzięki FFT można przyspieszyć ten uciążliwy proces. Dochodzi jednak mnożenie częstotliwości. Aby splot z FFT przyspieszył wykonywany kod, maska musi mieć odpowiednio duży rozmiar. Inną sprawą jest fakt, że do FFT najefektywniejsze są obrazy o rozmiarach będących potęgą dwójki. Niestety wykorzystanie FFT nie pozostaje bez wpływu na wyniki, które różnią się od tych uzyskanych stosując wprost wzór splotu. Są tutaj trochę inne problemy niż przy klasycznym splocie, zainteresowanych odsyłam do stosownej literatury. Kod można znaleźć w dokumentacji OpenCV.
Rozbicie operacji na mniejsze
Inną operacją pozwalającą zmniejszyć liczbę operacji jest robicie ich na mniejsze. Pewna grupa filtrów umożliwia rozbicie maski na dwa wektory. Przykładem jest filtr Sobela, którego maska wygląda następująco:
Macierz ta jest wynikiem mnożenia dwóch wektorów:

W takim przypadku można najpierw zastosować splot z jednym z wektorów a następnie na jego wyniku wykonać splot z drugim wektorem znacząco redukując przy tym liczbę mnożeń i dodawań. Do tego celu przeznaczona jest w OpenCV specjalna funkcja o nazwie sepFilter2D, bardzo podobna do filter2D z tym, że przyjmuje dwie maski, kolejno poziomą i pionową. Trzeba jednak wziąć po uwagę ewentualne pośrednie wyniki. Przy klasycznej masce Sobela mogą wystąpić wartości ujemne, natomiast w przypadku wektora [1,2,1] wykroczyć poza dopuszczalny zakres.
GPU
Inną metodą przyspieszenia splotu jest zaprzęgnięcie do obliczeń karty graficznej. OpenCV w wersji 2.2 wykorzystuje możliwości udostępnione przez firmę NVDIA w CUDA. Na marginesie sama NVIDIA zadeklarowała pomoc w rozwijaniu OpenCV. Aby dowiedzieć się więcej o programowaniu GPU polecam artykuł na wikipedii. W skrócie chodzi o fakt, że obliczenia te są mocno równoległe co znacznie przyspiesza pewien zakres operatorów matematycznych. Poza tym, odciąża ona CPU, który można wykorzystać do innych obliczeń. Samo programowanie GPU nie należy do łatwych, trzeba przestawić się na trochę inne myślenie, inaczej projektować algorytmy. Na szczęście OpenCV udostępnia uproszczone metody do działania na karcie graficznej. Znajdują się one w przestrzeni nazw gpu a zadeklarowane są w pliku nagłówkowym gpu.h. I tak mamy dwie metody dokonujące splotu:
void filter2D(const GpuMat& src,
GpuMat& dst,
int ddepth,
const Mat& kernel,
Point anchor=Point(-1,-1));
void sepFilter2D(const GpuMat& src,
GpuMat& dst,
int ddepth,
const Mat& kernelX,
const Mat& kernelY,
Point anchor = Point(-1,-1),
int rowBorderType = BORDER DEFAULT,
int columnBorderType = -1);
Najistotniejsza zmiana to wykorzystanie GpuMat zamiast Mat. Jest to spowodowane innym rodzajem pamięci dostępnej w GPU. Dla nas najważniejsze jest to, że można łatwo zamieniać jedną na drugą. Dostępny jest konstruktor GpuMat przyjmujący jako parametr referencję do Mat, oraz dwie metody pozwalające na przesyłanie danych i pobieranie ich:
void upload(const cv::Mat& m); // pobranie wartości z m
void download(cv::Mat& m) const; // zapisanie wartości do m
Same metody fiter2d oraz sepFilter2D działają tak samo jak ich "normalne" odpowiedniki. Trzeba tylko pamiętać, że na karcie graficznej dostępne są jedynie tablice floatów, więc przesłanie w GpuMat wartości typu uchar spowoduje błąd. Podobno autorzy mają usunąć to ograniczenie w przyszłości. Ponadto samo przesyłanie danych, ich odczyt a nawet samo zainicjowanie korzystania z GPU ma pewien narzut, więc przydaje się ono w często wykorzystywanych splotach, lub tych z wykorzystaniem wielki obrazów i masek. Na koniec małe porównanie czasu wykonania stu splotów z obrazem 800 na 600 pikseli  pomiędzy CPU a GPU (testowano na Intel C2D 2,4 GHz i GeForce 9600 GT)
CPU:
                filter2D   sepFilter2D
maska 3x3:       1,04s        0,40s
maska 15x15:     3,90s        2,99s  
GPU:
                filter2D   sepFilter2D
maska 3x3:       0,01s        0,28s
maska 15x15:     0,06s        0,69s 

sobota, 5 lutego 2011

Instalacja OpenCV 2.2

Zaczynając nowy cykl powrót do korzeni, czyli instalacja OpenCV :) Tym razem skupię się na najnowszej wersji tej biblioteki w wersji 2.2. Przynosi ona kilka istotnych zmian. Pierwszą z nich jest nowy podział biblioteki na moduły. Tym razem jest ich znacznie więcej i są bardziej wyspecjalizowane.:
  • core - podstawowe struktury, algebra, obsługa XML/YAML,
  • imgproc - operacje na obrazach (filtracje, konwersje kolorów, zmiana rozmiarów itp.),
  • highgui - obsługa I/O i GUI,
  • ml - uczenie maszynowe (sztuczne sieci neuronowe i inne statystyczne),
  • features2d - detektory punktów i obszarów charakterystycznych na płaskich obrazach,
  • video - analiza ruchu, śledzenie obiektów,
  • objdetect - detekcja obiektów (np. twarzy),
  • calib3d - kalibracja kamer, stereowizja,
  • flann - po prostu FLANN ;-),
  • contrib - kod wstępnie zatwierdzony, ale nie stabilny na rozwinięty na tyle by stanowił integralną część biblioteki,
  • legacy - zapewnia wsteczną kompatybilność,
  • gpu - część algorytmów z wykorzystaniem wsparcia obliczeń na kartach graficznych.
Jak widać, trochę się to rozrosło, ale będzie chyba łatwiej ograniczyć się podczas tworzenia własnego projektu do niezbędnych części OpenCV.
Linux
OpenCV na linuksie zainstalować można na dwa sposoby. Pierwszy banalny (na przykładzie Ubuntu):
Uruchamiamy menadżer pakietów Synaptic, szukamy "opencv", zaznaczamy i instalujemy (niestety obecnie jest to wersja 2.1, trzeba poczekać na aktualizację).
Metoda druga pozwala na większą kontrolę nad tym co i jak instalujemy. Pobieramy źródła opencv, rozpakowujemy, a następnie uruchamiamy cmake (można doinstalować w synapticu, polecam wersję z interfejsem graficznym ;-).  Uruchamiamy cmake-gui i wybieramy katalog ze źródłami oraz miejsce do zbudowania wersji binarnej (przydatne także do usuwania OpenCV):
Klikamy przycisk Configure i dostajemy wynik pierwszego przebiegu
Na czerwono podświetlone są pozycje które wymagają weryfikacji (na początku wszystkie), tutaj można zaznaczyć ewentualne opcje których potrzebujemy (np. obsługa okien z wykorzystaniem Qt)
W ten sposób można też wybrać wiele innych rzeczy, np. czytanie niektórych formatów pilków, które wymaga dodatkowych bibliotek w wersji dev do zainstalowania z synaptica. Tutaj można wskazać też takie dodatkowe rzeczy jak wykorzystanie GPU czy IPP. Kiedy już wszystko zostanie poprawione ponownie klikamy Configure. Na czerwono zostaną zaznaczone tylko te pozycje, które wymagają ponownej weryfikacji lub mają niespełnione zależności:
Tutaj tylko weryfikujemy i klikamy ponownie Configure (dodam tylko, że Qt musi być doinstalowane ręcznie, np. z Synaptica). Teraz pozostaje tylko kliknięcie Generate i jeżeli wszysko było ok, otrzymaliśmy wersję gotową do kompilacji.
Uruchamiamy konsolę i wchodzimy do katalogu w którym została ona utworzona. Wpisujemy make (z parametrem -j 2 jeżeli posiadamy dwurdzeniowy procesor, -j 4 jeżeli cztery rdzenie itd.) a następnie jeżeli wszystko przebiegło bez problemów sudo make install i cieszymy się nowym OpenCV :) W ten sposób można też instalować najświeższą wersję z SVNa. Trzeba pamiętać by odinstalować starą wersję przed instalacją innej poleceniem w konsoli sudo make uninstall.

Przykładowy program na linuksie
Do pisania programów w C++ używam ostatnio Qt Creatora. Tworzymy nowy projekt, wybieramy aplikację konsolową, ustawiamy nazwę projektu i jego położenie.
Początkowo mamy dwa pliki: *.pro z opcjami projektu oraz *.cpp z kodem. Plik pro przetrzymuje opcje projektu, takie jak dołączone biblioteki. Zmieniamy jego zawartość na następującą 
TARGET = pierwszy
CONFIG   += console
CONFIG   -= app_bundle
TEMPLATE = app
SOURCES += main.cpp
INCLUDEPATH += /usr/local/include/opencv2
LIBS += -L/ur/local/lib \
    -lopencv_highgui
gdzie  to nazwa pliku pro będąca też nazwą projektu. Zwrócić uwagę należy na INCLUDEPAH gdzie wskazano katalog z plikami nagłówkowymi OpenCV oraz LIBS gdzie deklaruje się użyte biblioteki. Znacznikiem -L wskazujemy katalog z bibliotekami (należy zwrócić uwagę na brak odstępu pomiędzy -L a początkiem ścieżki). Znak \ wskazuje, że kontynuujemy zawartość LIBS w kolejnej linii. Bibliotekę wskazujemy znacznikiem -l (także bez spacji) zgodnie z konwencją uniksową. Jeżeli plik biblioteki nazywa się libXXX.so to omijamy przedrostek lib. Do main.cpp wsawiamy przykładowy kod użyty w dokumentacji
#include <highgui/highgui.hpp>
#include <opencv.hpp>

using namespace cv;

int main(int, char**)
{
    VideoCapture cap(0);
    if(!cap.isOpened())
        return -1;
    Mat edges;
    namedWindow("edges",1);
    for(;;)
    {
        Mat frame;
        cap >> frame;
        cvtColor(frame, edges, CV_BGR2GRAY);
        GaussianBlur(edges, edges, Size(7,7), 1.5, 1.5);
        Canny(edges, edges, 0, 30, 3);
        imshow("edges", edges);
        if(waitKey(30) >= 0) break;
    }
 
    return 0;
}
Klikamy Uruchom (skrót klawiszowy Ctrl+R) i jeżeli wszystko jest ok, oglądamy obraz z kamery z wykrytymi krawędziami

Windows
W Windowsie ściągamy plik instalacyjny z wersją binarną (OpenCV-2.2.0-win32-vs2010.exe) i instalujemy w standardowy windowsowy sposób pamiętając o zaznaczeniu po drodze o dodaniu do PATHa
Inną metodą jest instalacja ze źródła. Podobnie jak w linuksie wykorzystujemy do tego cmake, do ściągnięcia ze strony autorów. Należy także zaznaczyć dodanie CMake do PATHa i uruchamiania go z uprawnieniami administratora. Do instalacji ze źródeł ściągamy plik OpenCV-2.2.0-win.zip. Dalej postępujemy tak jak w przypadku linuksa, z tym że wybieramy po drodze odpowiedni kompilator (u mnie Visual Studio 10):
Dzięki takiemu wyborowi dostajemy odpowiednie pliki projektów do Visual Studio 10 (np. INSTALL).
Przykładowy program na windowsie
Uruchamiamy Visual Studio 10 i tworzymy nowy projekt C++
Przechodzimy do opcji. W zakładce C/C++ dodajemy folder z plikami nagłówkowymi
Następnie w zakładce Linker/General należy dodać katalog z bibliotekami
a w Linker/Input biblioteki z których korzystamy
Dopisujemy kod programu i uruchamiamy :) Na windowsie miałem z tym masę problemów, do teraz nie wiem czemu to raz działa, raz nie (bo 64-bity, bo sterownik do kamery dziwny itd.), więc tymczasowo podziękuję systemowi Microsoftu...

środa, 29 grudnia 2010

Ankieta

Blog został trochę odświeżony. Mam nadzieję, że znajdę teraz trochę więcej czasu do pisania notek :) Jak można zauważyć, po prawej dodałem BLIPa, na którym przekazywał będę co ciekawsze linki do artykułów czy blogów z okolic wizji komputerowej i jej zastosowań. Ponadto pojawiły się ankiety, zachęcam do głosowania. Zauważyłem, że najczęściej na bloga wchodzi się z wyszukiwarek pod hasłem "kurs opencv" oraz "opencv". Najpopularniejsze posty także dotyczą wyłącznie OpenCV, stąd pomysł ankiety i poznania preferencji czytelników :)
Przez te ponad pół roku od ostatniego wpisu dostałem ogromną liczbę maili z prośbą o pomoc. Nie wszystkim udało mi się odpisać (nawet nie połowie...), co wynikało z braku czasu. Bardzo dużo osób pisało w sprawie kompilacji biblioteki jak i samych programów. Widzę, że to poważny problem, postaram się go trochę pociągnąć na bardzo podstawowym poziomie.

wtorek, 6 kwietnia 2010

OpenCV 2.1

Dzisiaj pojawiła się nowa wersja OpenCV oznaczona numerem 2.1. Najważniejsze zmiany od poprzedniej wersji 2.0 to:
  • poprawa ogromnej ilości błędów (o których już kiedyś przy okazji 2.0 wspominałem...)
  • zamiana OpenMP na TBB
  • nowy interfejs dla Pythona
  • lepsze wsparcie 64-bitowych aplikacji dla systemów Windows i MacOS
  • całkowite przejście na obsługiwanie błędów poprzez wyjątki
  • całkowite przejście na CMake dla wszystkich systemów
  • implementacja algorytmu GrabCut
  • poprawa algorytmów związanych ze stereografią
Pozostaje pobrać i przetestować ;-)

niedziela, 4 kwietnia 2010

Intel IPP i OpenCV

Ostatnio mam potrzebę zastosowania innej wizyjnej biblioteki - Intel® Integrated Performance Primitives (w skrócie IPP). Jak nazwa wskazuje, podobnie jak OpenCV, powstała ona przy udziale firmy Intel. Jest to biblioteka o wysokiej optymalizacji dzięki czemu osiąga ona niskie czasy wykonania. Istnieje możliwość przyspieszenia OpenCV gdy wcześniej zainstalowano IPP. Obecnie ma to już marginalne znaczenie. Wspieranych jest jedynie kilka funkcji, m.in. klasyfikator Harra czy DFT. Więcej o bibliotece powinno pojawić się na blogu w przyszłości, teraz skupię się na jednym aspekcie.

Pomimo że w IPP zawarto pokaźną liczbę niskopoziomowych funkcji, brakuje w niej odpowiednika highgui z OpenCV. Powoduje to, że wszystkie dane musimy alokować samodzielnie, samodzielnie też przetwarzać odpowiednie formaty plików graficznych. Z pomocą może nam jednak przyjść OpenCV ;-). Możemy wykorzystać cvLoadImage i operować na IplImage. Spójrzmy na przykładowy kod wyznaczający laplasjan obrazu:
#include < highgui.h >
#include < ippi.h >

int main() {
const char * str = "{ścieżka do pliku}";
IplImage * wczytanyObraz = cvLoadImage(str,CV_LOAD_IMAGE_GRAYSCALE);

IppiSize size;
size.height = wczytanyObraz->height;
size.width = wczytanyObraz->width;
int step = 0;

Ipp8u *poFiltracji = ippiMalloc_8u_C1(size.width,size.height,&step);
ippiFilterLaplace_8u_C1R((Ipp8u*)wczytanyObraz->imageData,wczytanyObraz->widthStep,poFiltracji,step,size,ippMskSize5x5);
IplImage * doWyswietlenia = cvCreateImageHeader(cvGetSize(wczytanyObraz),8,1);
cvSetData(doWyswietlenia,(void*)poFiltracji,step);

cvShowImage("okno",doWyswietlenia);
while(true)
{
int key = cvWaitKey(100);
if(key == (int) ' ')
break;
}
ippiFree(poFiltracji);
cvReleaseImage(&wczytanyObraz);
return 0;
}


W linii 6. wczytujemy z dysku plik. Robimy to z użyciem HighGui. w liniach 8-11 przygotowujemy dane potrzebne w funkcji IPP obliczającej laplasjan. W linii 13. alokujemy pamięć na obraz wynikowy. Funkcja ta alokuje obraz 8-bitowy o jednym kanale na co wskazuje jej nazwa (8u i C1). Więcej o nazewnictwie IPP w przyszłości. W linii 14. obliczamy właściwy wynik. I tutaj najważniejsze: jako źródło (w tym przypadku wskaźnik na Ipp8u) używamy imageData z IplImage, z kolei jako przeznaczenie używamy naszego zaalokowanego Ipp8u. Druga ważna rzecz dzieje się w liniach 15-16. Zamiast tworzyć obraz tworzymy tylko jego nagłówek. Dane natomiast ustawiamy korzystając ze wskaźnika na Ipp8u. Można jeszcze zwrócić uwagę na linię 25., gdzie zwalniamy zaalokowaną przez IPP strukturę.

Powyżej pokazano jak mieszać OpenCV i IPP. Przyznam, że powyższy kod to efekt moich prób jak uzyskać płynne przejście między OpenCV i IPP bez kopiowania danych między strukturami. Metoda może więc nie być idealna, lub co gorzej, mieć jakieś ukryte błędy... Jak ktoś zna lepsze podejście to proszę o podzielenie się w komentarzach :)