Dziś zupełnie z innej beczki – post o moich perypetiach z kompilacją OpenCV na Raspberry PI. Przede wszystkim po co mi OpenCV? To taka fajna biblioteka pozwalająca na dużo ciekawych metod przetwarzania obrazu. I do tego jest napisana w C/C++ co powoduje że jest relatywnie szybka. Na tyle szybka że można się pokusić o jakieś mniej-lub-bardziej skomplikowane rozwiązania za pomocą malinki. Na przykład urządzenie które otwiera drzwiczki dla zwierzaków tylko jak zobaczy przed nimi kota, tak by nie wpuszczać węży, szopów praczy, szczurów, psów czy wiewiórek. W moim przypadku problem jest inny – jak pamiętacie próbuję zrobić kamerkę internetową z dużym zoomem do obserwowania co wchodzi i wychodzi z portu. I zakupiłem w tym celu najtańszy obiektyw jaki się dało który da się przymocować do kamerki Pi. Obiektyw ma zasięg ogniskowych 5-100mm i jest bardzo jasny (F/1.8) = za bodajże $30 jest to świetny kawałek szkła. Jednak ma on jedną, poważną wadę – ręczną regulację wszystkiego – przesłony, zooma i ostrości. I tu jest diabeł pogrzebany. Ustawienie ostrości na 100mm ogniskowej jest bardzo czułe – liczą się milimetry. Więc postanowiłem pobawić się w pomysłowego Dobromira i zautomatyzować ten obiektyw. Na początek tylko ostrość bo to największy problem, ale docelowo mam nadzieję dołożyć także regulację zooma i przesłony. W tym celu zakupiłem kilka silniczków krokowych i „czapkę” na Pi która potrafi te silniczki poruszać. Do tego są kółka + gumki i nawet to działa ręcznie – napisałem mały programik pozwalający na zdalną ręczną regulację ostrości. Wszystko fajnie, ale uznałem że jak już się bawić to na całość. Więc czemu by nie napisać algorytmu szukającego ostrości. W końcu prędkość nie jest ważna, więc wystarczy powolutku się posuwać i sprawdzać czy już jest ostro. I tu leży pies pogrzebany. Najprostszą techniką sprawdzania ostrości jest dokonanie transformaty za pomocą transformacji Laplace’a a potem policzenie jej wariancji. Można by to zrobić ręcznie, ale tu na pomoc przychodzi OpenCV – całość to jedna linia kodu:
cv2.Laplacian(image, cv2.CV_64F).var()
Proste, prawda? I potem jedziemy powoli ostrością i co klatkę mierzymy wartość. Tam gdzie jest maximum, tam jest najbardziej ostro. Czyli całość można dosłownie w kilku liniach kodu w Python’ie zrobić. Ale do tego trzeba mieć OpenCV. I tu zaczynają się schody. Na internecie jest 100 rożnych sposobów jak skompilować OpenCV na Raspberry Pi. Próbowałem ich wszystkich. Bez sukcesu. Jednym z problemów jest samo Pi – 9 godzin kompilacji powoduje że mały procesorek się przegrzewa i system się wywala. Pomimo wentylatorów ustawionych na malinkę za nic mi się nie udawało dojść do końca kompilacji. Poza tym każda próba to 9 godzin, a moja cierpliwość jest ograniczona. Więc postanowiłem spróbować coś innego – cross-kompilacji. W tym celu trzeba na Mac’u zainstalować narzędzie „ct-ng” a następnie za jego pomocą skompilować OpenCV. Potem wystarczy już tylko przerzucić wyprodukowane binaria na malinkę i gotowe! Proste, prawda? Oczywiście diabeł siedzi w szczegółach. Pierwszy problem – Mac. Niby Unix, ale nie do końca. Na szczęście jest „Homebrew” które pozwala z Mac’a zrobić prawie 100% Unix.
Zacznijmy od instalacji CrossTool NG:
brew install crosstool-ng --with-grep
To powinno pójść bez problemu. Ale… Właśnie – tu zaczynają się schody. Pierwszy to problem z dużymi literkami – standardowo system plików Mac’a nie rozróżnia dużych i małych literek. Niestety crosstool-ng zakłada że będzie rozróżniał. Więc trzeba mu pomóc – zbudujemy dwa systemy plików – jeden na źródła a drugi na gotowy kompilator:
hdiutil create -type SPARSE -fs 'Case-sensitive Journaled HFS+' -size 20g -volname work ~/work.dmg.sparseimage
hdiutil attach ~/work.dmg.sparseimage
hdiutil create -type SPARSE -fs 'Case-sensitive Journaled HFS+' -size 2g -volname tools ~/tools.dmg.sparseimage
hdiutil attach ~/tools.dmg.sparseimage
W ten sposób mamy dwa systemy plików osiągalne pod /Volumes/work i /Volumes/tools. Przejdźmy na /Volumes/work i wezwijmy na pomoc ct-ng:
ct-ng armv7-rpi2-linux-gnueabihf
Zrobi to plik .config. Niestety nie jest on taki jak trzeba, więc musimy w nim pogmerać. Trzeba pozmieniać:
CT_LOCAL_TARBALLS_DIR="/Volumes/work/src"
CT_WORK_DIR="/Volumes/work/.build"
CT_PREFIX_DIR="/Volumes/tools/${CT_TARGET}"
CT_EXTRA_CFLAGS_FOR_HOST="-fbracket-depth=512 -fno-unroll-loops"
CT_DEBUG_CT_SAVE_STEPS=y
CT_WANTS_STATIC_LINK=n
Jeszcze tylko dajmy kompilatorowi więcej otwartych plików (ulimit -n 1024
) i już możemy odpalić kompilację (która nie zadziała, ale o tym za chwilę). Odpalamy ct-ng build
i przez chwilę wydaje nam się że osiągnęliśmy sukces. Ale po chwili kompilacja się rozwala. Okazuje się że trzeba zmienić dwie linie kodu. Otwieramy plik /Volumes/work/.build/src/binutils*/gold/gold-threads.cc
i znajdujemy:
Once_initialize()
: once_(PTHREAD_ONCE_INIT)
{ }
Trzeba to zmienić na:
Once_initialize()
{once_.__sig = _PTHREAD_ONCE_SIG_init; once_.__opaque[0] = 0;}
Po zmianie odpalamy kompilację i po jakichś 20 minutach mamy gotowy kompilator produkujący kod dla Pi 2. Nieźle, ale to dopiero początek problemów. Teraz musimy skompilować OpenCV. Najpierw trzeba je ściągnąć. Odpalamy:
wget https://github.com/Itseez/opencv/archive/3.1.0.tar.gz
tar xzf 3.1.0.tar.gz
git clone https://github.com/opencv/opencv_contrib.git
(to ostanie ściąga różne przydatne moduły które nie są w podstawowej wersji). Alternatywnie jeżeli czujemy się odważni, to można zamiast ściągać wersji 3.1.0 ściągnąć najnowszą wersję:
git clone https://github.com/opencv/opencv.git
(ja poszedłem na całość i spróbowałem skompilować obie wersje). Włazimy do katalogu opencv (lub opencv-3.1.0) i tworzymy w nim katalog build. A następnie odpalamy:
cmake -D CMAKE_BUILD_TYPE=RELEASE \
-D CMAKE_INSTALL_PREFIX=/usr/local \
-D OPENCV_EXTRA_MODULES_PATH=~/opencv_contrib/modules \
-D INSTALL_C_EXAMPLES=OFF \
-D INSTALL_PYTHON_EXAMPLES=ON
-D BUILD_EXAMPLES=OFF \
-D CMAKE_TOOLCHAIN_FILE=../platforms/linux/arm-gnueabi.toolchain.cmake \
-D CMAKE_C_COMPILER=/Volumes/tools/armv7-rpi2-linux-gnueabihf/bin/armv7-rpi2-linux-gnueabihf-cc \
-D CMAKE_CXX_COMPILER=/Volumes/tools/armv7-rpi2-linux-gnueabihf/bin/armv7-rpi2-linux-gnueabihf-c++ \
-D CMAKE_LINKER=/Volumes/tools/armv7-rpi2-linux-gnueabihf/bin/armv7-rpi2-linux-gnueabihf-ld \
-D CMAKE_AR=/Volumes/tools/armv7-rpi2-linux-gnueabihf/bin/armv7-rpi2-linux-gnueabihf-ar \
-D ARM_LINUX_SYSROOT=/Volumes/tools/armv7-rpi2-linux-gnueabihf \
-D WITH_PNG=OFF \
-D WITH_OPENCL=OFF \
-D WITH_OPENCLAMDFFT=OFF \
-D WITH_OPENCLAMDBLAS=OFF \
..
Cmake powarczy chwilę i wyprodukuje konfigurację dla make. Teraz wystarczy już tylko odpalić make -j4
i za chwilę mamy skompilowane OpenCV na Pi! Po czterech dniach walki…. Dla wyjaśnienia:
- przykłady w C nie chcą się kompilować na Pi
- biblioteka obsługująca format PNG nie chce się kompilować na Pi
- Mój Mac ma zainstalowany OpenCL i cmake go wykrywa i próbuje użyć na Pi więc musiałem to wyłączyć
W kompilowaniu tego sposobu korzystałem z wielu stron internetowych i w sumie nie pamiętam gdzie i co znalazłem poza jednym – jak zainstalować ct-ng na Mac’u – to znalazłem tutaj. W tym samym miejscu znajdziecie info jak zrobić cross compiler dla Raspberry Pi 3, ale to osobna historia. Aha, jeszcze tutaj jak obsługiwać system plików rozróżniający duże i małe literki.