Sunt unul dintre dezvoltatorii sistemului de operare , iar în acest articol vă voi povesti despre cum am reușit să lansez OpenCV pe placa STM32746G.
Dacă căutați ceva de genul „OpenCV pe placa STM32”, puteți găsi destul de mulți care sunt interesați de utilizarea acestei biblioteci pe plăcile STM32 sau pe alte microcontrolere.
Există câteva videoclipuri care, judecând după titlu, ar trebui să demonstreze ceea ce este necesar, dar de obicei (în toate videoclipurile pe care le-am văzut) pe placa STM32 s-a realizat doar captarea imaginii de la cameră și afișarea rezultatului pe ecran, iar procesarea imaginii a fost realizată fie pe un computer obișnuit, fie pe plăci mai puternice (de exemplu, Raspberry Pi).
De ce este complicat?
Popularitatea căutărilor este explicată prin faptul că OpenCV este cea mai populară bibliotecă de viziune computerizată, ceea ce înseamnă că mai mulți dezvoltatori sunt familiarizați cu aceasta, iar posibilitatea de a rula codul pregătit pentru desktop pe un microcontroler simplifică semnificativ procesul de dezvoltare. Dar de ce nu există până acum rețete populare gata pentru a rezolva această problemă?
Problema utilizării OpenCV pe plăci mici este legată de două caracteristici:
- Dacă se compilează biblioteca chiar și cu un set minim de module, în memoria flash a aceeași STM32F7Discovery pur și simplu nu va încăpea (chiar fără a lua în considerare OS-ul) din cauza codului foarte mare (câteva megabyte de instrucțiuni)
- Însuși biblioteca este scrisă în C++, ceea ce înseamnă
- Este necesară suportul runtime pentru C++ (excepții și altele).
- Este puțin suport pentru LibC/Posix, care sunt de obicei disponibile în sistemele de operare pentru sisteme încorporate — este nevoie de o bibliotecă standard pentru C++ și o bibliotecă standard de șabloane STL (vector etc.).
Portarea pe Embox
Așa cum se întâmplă de obicei, înainte de a porta orice program în sistemul de operare, este bine să încercați să-l construiți în forma în care l-au gândit dezvoltatorii. În cazul nostru, nu sunt probleme cu acest lucru — sursele pot fi găsite pe , biblioteca se compilează pe GNU/Linux cu un cmake obișnuit.
Din veștile bune — OpenCV poate fi compilat direct ca o bibliotecă statică, ceea ce face portarea mai ușoară. Compilăm biblioteca cu configurația standard și vedem cât spațiu ocupă. Fiecare modul este compilat într-o bibliotecă separată.
> size lib/*so --totals
text data bss dec hex filename
1945822 15431 960 1962213 1df0e5 lib/libopencv_calib3d.so
17081885 170312 25640 17277837 107a38d lib/libopencv_core.so
10928229 137640 20192 11086061 a928ed lib/libopencv_dnn.so
842311 25680 1968 869959 d4647 lib/libopencv_features2d.so
423660 8552 184 432396 6990c lib/libopencv_flann.so
8034733 54872 1416 8091021 7b758d lib/libopencv_gapi.so
90741 3452 304 94497 17121 lib/libopencv_highgui.so
6338414 53152 968 6392534 618ad6 lib/libopencv_imgcodecs.so
21323564 155912 652056 22131532 151b34c lib/libopencv_imgproc.so
724323 12176 376 736875 b3e6b lib/libopencv_ml.so
429036 6864 464 436364 6a88c lib/libopencv_objdetect.so
6866973 50176 1064 6918213 699045 lib/libopencv_photo.so
698531 13640 160 712331 ade8b lib/libopencv_stitching.so
466295 6688 168 473151 7383f lib/libopencv_video.so
315858 6972 11576 334406 51a46 lib/libopencv_videoio.so
76510375 721519 717496 77949390 4a569ce (TOTALS)După cum se poate observa din ultima linie, .bss și .data nu ocupă mult spațiu, dar codul este de peste 70 MiB. Este clar că, dacă se leagă static cu o aplicație specifică, dimensiunea codului va scădea.
Să încercăm să eliminăm cât mai multe module, pentru a crea un exemplu minim (care, de exemplu, va afișa versiunea OpenCV), așa că ne uităm cmake .. -LA și dezactivăm în opțiuni tot ce poate fi dezactivat.
-DBUILD_opencv_java_bindings_generator=OFF
-DBUILD_opencv_stitching=OFF
-DWITH_PROTOBUF=OFF
-DWITH_PTHREADS_PF=OFF
-DWITH_QUIRC=OFF
-DWITH_TIFF=OFF
-DWITH_V4L=OFF
-DWITH_VTK=OFF
-DWITH_WEBP=OFF> size lib/libopencv_core.a --totals
text data bss dec hex filename
3317069 36425 17987 3371481 3371d9 (TOTALS)Pe de o parte, acesta este doar un modul al bibliotecii, pe de altă parte, acest lucru este fără optimizarea compilatorului pentru dimensiunea codului (-Os). ~3 MiB de cod — totuși este încă mult, dar oferă deja speranța succesului.
Rulează în emulator
Debugging-ul pe emulator este mult mai ușor, așa că întâi ne asigurăm că biblioteca funcționează pe qemu. Ca platformă emulată am ales Integrator/CP deoarece, pe de o parte, este tot ARM, iar pe de altă parte, Embox suportă ieșirea grafică pentru această platformă.
În Embox există un mecanism pentru a construi biblioteci externe, folosind care adăugăm OpenCV ca modul (transmițând aceleași opțiuni pentru «construirea minimă» sub formă de biblioteci statice), după care adaug o aplicație simplă care arată astfel:
version.cpp:
#include
#include
int main() {
printf("OpenCV: %s", cv::getBuildInformation().c_str());
return 0;
}Construim sistemul, îl lansăm — și obținem rezultatul așteptat.
root@embox:\/ #opencv_version
OpenCV:
Configurarea generală pentru OpenCV 4.0.1 =====================================
Controlul versiunii: bd6927bdf-dirty
Platformă:
Data și ora: 2019-06-21T10:02:18Z
Gazdă: Linux 5.1.7-arch1-1-ARCH x86_64
Țintă: Generic arm-unknown-none
CMake: 3.14.5
Generator CMake: Unix Makefiles
Unelte de construire CMake: \/usr\/bin\/make
Configurare: Debug
Funcții CPU\/HW:
Bază:
cerut: DETECT
dezactivat: VFPV3 NEON
C\/C++:
Compilat ca biblioteci dinamice?: NUUrmătorul pas este să rulăm un exemplu, cel mai bine un standard oferit de dezvoltatori . Am ales .
Exemplul a trebuit să fie rescris ușor, pentru a afișa imaginea cu rezultatul direct în buffer-ul de cadru. A fost necesar să facem acest lucru, deoarece funcția imshow() poate reda imagini prin interfețele QT, GTK și Windows, care, desigur, nu vor fi disponibile în configurația pentru STM32. De fapt, QT poate fi rulat și pe STM32F7Discovery, dar despre asta vom vorbi într-un articol viitor 🙂
După o scurtă clarificare a formatului în care este stocat rezultatul detectorului de margini, obținem imaginea.

Imaginea originală

Rezultatul
Rularea pe STM32F7Discovery
Pe 32F746GDISCOVERY există mai multe secțiuni de memorie hardware, pe care le putem utiliza în diverse moduri
- 320KiB de memorie RAM
- 1MiB de memorie flash pentru imagine
- 8MiB SDRAM
- 16MiB de NAND flash QSPI
- Slot pentru card microSD
Cardul SD poate fi utilizat pentru stocarea imaginilor, dar în contextul rulării unui exemplu minim nu este foarte util.
Display-ul are o rezoluție de 480×272, ceea ce înseamnă că memoria pentru buffer-ul de cadru va fi de 522240 de byte la o adâncime de 32 de biți, adică aceasta este mai mare decât dimensiunea memoriei RAM, așa că buffer-ul de cadre și heap-ul (care va fi necesar și pentru OpenCV, pentru a stoca datele pentru imagini și structurile auxiliare) vor fi amplasate în SDRAM, tot restul (memoria pentru stive și alte nevoi sistemice) va merge în RAM.
Dacă luăm configurația minimă pentru STM32F7Discovery (eliminând întreaga rețea, toate comenzile, făcând stivele cât mai mici și alte ajustări) și adăugăm OpenCV cu exemple, cerințele de memorie vor fi următoarele:
text data bss dec hex filename
2876890 459208 312736 3648834 37ad42 build\/base\/bin\/emboxPentru cei care nu sunt foarte familiarizați cu secțiunile în care se încadrează, voi explica: în .text și .rodata se află instrucțiunile și constantele (în termeni simplificați, datele readonly), în .data se află datele care pot fi modificate, în .bss se află variabilele „resetate” care, totuși, au nevoie de spațiu (această secțiune „se va duce” în RAM).
Vestea bună este că .data/.bss trebuie să se încadreze, dar cu .text problema este că sub imagine există doar 1MiB de memorie. Poate fi eliminată imaginea din exemplu și citită, de exemplu, de pe un card SD în memorie la pornire, dar fruits.png cântărește aproximativ 330KiB, așa că această problemă nu se rezolvă: cea mai mare parte .text constă chiar în codul OpenCV. .text În esență, rămâne o singură soluție - încărcarea unei părți din cod pe un chip QSPI (acesta are un mod special de operare pentru maparea memoriei pe busul de sistem, astfel încât procesorul să poată accesa aceste date direct). Totuși, apare o problemă: pe de o parte, memoria chipului QSPI nu este accesibilă imediat după repornirea dispozitivului (trebuie să inițializăm separat modul_memory-mapped), pe de altă parte, nu se poate „programa” această memorie cu un bootloader obișnuit.
În cele din urmă, s-a decis să se leagă tot codul în QSPI, iar programarea acestuia se va face cu un bootloader personalizat, care va obține binarul necesar prin TFTP.
Ideea de a porta această bibliotecă pe Embox a apărut acum aproximativ un an, dar a fost amânată din diferite motive. Unul dintre ele este suportul pentru libstdc++ și biblioteca standard de șabloane. Problema suportului C++ în Embox depășește sfera acestui articol, așa că aici voi spune doar că am reușit să obținem acest suport în volumul necesar pentru a face această bibliotecă să funcționeze 🙂
Rezultatul
În cele din urmă, aceste probleme au fost depășite (cel puțin, într-o măsură suficientă pentru a rula exemplul OpenCV), iar exemplul s-a pornit. Căutarea marginilor cu filtrul Canny durează 40 de secunde lungi. Acest lucru, desigur, este prea mult (există sugestii despre cum să optimizăm acest proces, despre care s-ar putea scrie un articol separat în cazul unui succes).
Cu toate acestea, scopul intermediar a fost crearea unui prototip care să demonstreze posibilitatea fundamentală a rularii OpenCV pe STM32, prin urmare, acest obiectiv a fost atins, hurrah!

tl;dr: instrucțiuni pas cu pas
0: Descărcăm sursele Embox, de exemplu, astfel:
git clone https://github.com/embox/embox && cd ./embox
1: Să începem cu compilarea bootloader-ului, care „va programa” chipul QSPI.make confload-arm/stm32f7cube
face confload-arm/stm32f7cubeAcum trebuie să configurăm rețeaua, deoarece vom încărca imaginea prin TFTP. Pentru a seta adresele IP ale plăcii și ale gazdei, trebuie să modificăm fișierul conf/rootfs/network.
Exemplu de configurare:
iface eth0 inet static
address 192.168.2.2
netmask 255.255.255.0
gateway 192.168.2.1
hwaddress aa:bb:cc:dd:ee:02gateway — adresa gazdei de unde va fi încărcată imaginea, address — adresa plăcii.
După aceasta, compilăm încărcătorul:
make2: Încărcare obișnuită a încărcătorului (scuze pentru jocul de cuvinte) pe placă — aici nu este nimic specific, trebuie să facem asta ca pentru orice altă aplicație pentru STM32F7Discovery. Dacă nu știți cum se face, puteți citi despre asta .
3: Compilarea imaginii cu configurația pentru OpenCV.
make confload-platform/opencv/stm32f7discovery
make4: Extracția din secțiunile ELF care trebuie scrise în QSPI, în qspi.bin
arm-none-eabi-objcopy -O binary build/base/bin/embox build/base/bin/qspi.bin
--only-section=.text --only-section=.rodata
--only-section='.ARM.ex*'
--only-section=.dataÎn directorul conf se află un script care face asta, așa că se poate rula
.\/conf\/qspi_objcopy.sh # Binariul necesar -- build/base/bin/qspi.bin5: Folosind tftp, încărcăm qspi.bin.bin pe memoria flash QSPI. Pe gazdă, pentru asta trebuie să copiati qspi.bin în directorul rădăcină al serverului tftp (de obicei este \/srv\/tftp\/ sau \/var\/lib\/tftpboot\/; pachetele pentru serverul respectiv sunt disponibile în majoritatea distribuțiilor populare, de obicei se numește tftpd sau tftp-hpa, uneori trebuie să rulați systemctl start tftpd.service pentru a porni).
# вариант для tftpd
sudo cp build/base/bin/qspi.bin /srv/tftp
# вариант для tftp-hpa
sudo cp build/base/bin/qspi.bin /var/lib/tftpbootPe Embox (adică în încărcător) trebuie să executați următoarea comandă ( presupunând că adresa serverului este 192.168.2.1):
embox> qspi_loader qspi.bin 192.168.2.16: Folosind comanda goto trebuie să "săriți" în memoria QSPI. Locația specifică va varia în funcție de modul în care imaginea este conectată, acest adresă poate fi văzută cu comanda mem 0x90000000 (adresa de start se află în al doilea cuvânt de 32 de biți al imaginii); va fi necesar să setați stack-ul cu un flag -s, adresa stack-ului se află la 0x90000000, exemplu:
embox>mem 0x90000000
0x90000000: 0x20023200 0x9000c27f 0x9000c275 0x9000c275
↑ ↑
acesta este adresa acesta este adresa
stack-ului primei
instrucțiuni
embox>goto -i 0x9000c27f -s 0x20023200 # Flag-ul -i este necesar pentru a interzice întreruperile în timpul inițializării sistemului
< Începând de aici, va fi afișarea imaginii cu OpenCV >7: Rulăm
embox> edges 20și ne bucurăm de o căutare a marginilor de 40 de secunde 🙂
Dacă ceva nu decurge conform planului — scrieți o problemă în , sau pe lista de discuții embox-devel@googlegroups.com, sau în comentarii aici.
Sursa: habr.com
