OpenCV pe STM32F7-Discovery

OpenCV pe STM32F7-Discovery Sunt unul dintre dezvoltatorii sistemului de operare Embox, 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 GitHub, 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?:      NU

Următorul pas este să rulăm un exemplu, cel mai bine un standard oferit de dezvoltatori pe site-ul lor. Am ales detectorul de margini Canny.

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.

OpenCV pe STM32F7-Discovery

Imaginea originală

OpenCV pe STM32F7-Discovery

Rezultatul

Rularea pe STM32F7Discovery

Pe 32F746GDISCOVERY există mai multe secțiuni de memorie hardware, pe care le putem utiliza în diverse moduri

  1. 320KiB de memorie RAM
  2. 1MiB de memorie flash pentru imagine
  3. 8MiB SDRAM
  4. 16MiB de NAND flash QSPI
  5. 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\/embox

Pentru 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!

OpenCV pe STM32F7-Discovery

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/stm32f7cube

Acum 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:02

gateway — adresa gazdei de unde va fi încărcată imaginea, address — adresa plăcii.

După aceasta, compilăm încărcătorul:

    make

2: Î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 aici.
3: Compilarea imaginii cu configurația pentru OpenCV.

    make confload-platform/opencv/stm32f7discovery
    make

4: 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.bin

5: 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/tftpboot

Pe 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.1

6: 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 repository-ul nostru, sau pe lista de discuții embox-devel@googlegroups.com, sau în comentarii aici.

Sursa: habr.com

Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS 🔥 Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS | ProHoster