OpenCV pe STM32F7-Discovery

OpenCV pe STM32F7-Discovery Sunt unul dintre dezvoltatorii de sisteme de operare Embox, iar în acest articol voi vorbi despre cum am reușit să rulez OpenCV pe placa STM32746G.

Dacă tastați ceva de genul „OpenCV pe placa STM32” într-un motor de căutare, puteți găsi o mulțime de oameni care sunt interesați să folosească această bibliotecă pe plăcile STM32 sau alte microcontrolere.
Există mai multe videoclipuri care, judecând după titlu, ar trebui să demonstreze ce este necesar, dar de obicei (în toate videoclipurile pe care le-am văzut) pe placa STM32, doar primind o imagine de la cameră și afișând rezultatul pe ecran, și Procesarea imaginii în sine a fost făcută fie pe un computer obișnuit, fie pe plăci mai puternice (de exemplu, Raspberry Pi).

De ce este greu acest lucru?

Popularitatea interogărilor de căutare se explică prin faptul că OpenCV este cea mai populară bibliotecă de computer vision, ceea ce înseamnă că mai mulți dezvoltatori sunt familiarizați cu ea, iar capacitatea de a rula cod pregătit pentru desktop pe un microcontroler simplifică foarte mult procesul de dezvoltare. Dar de ce nu există încă rețete populare gata făcute pentru a rezolva această problemă?

Problema cu utilizarea OpenCV pe plăci mici este legată de două caracteristici:

  • Dacă compilați biblioteca chiar și cu un set minim de module, pur și simplu nu se va potrivi în memoria flash a aceluiași STM32F7Discovery (chiar fără a lua în considerare sistemul de operare) din cauza codului foarte mare (mai mulți megaocteți de instrucțiuni)
  • Biblioteca în sine este scrisă în C++, ceea ce înseamnă
    • Avem nevoie de suport pentru rulare pozitivă (excepții etc.)
    • Există puțin suport pentru LibC/Posix, care se găsesc de obicei în sistemele de operare pentru sistemele încorporate - aveți nevoie de o bibliotecă standard plus și o bibliotecă de șabloane STL standard (vector etc.)

Portare la Embbox

Ca de obicei, înainte de a porta orice program în sistemul de operare, este o idee bună să încercați să îl compilați în forma în care dezvoltatorii l-au propus. În cazul nostru, nu există probleme cu aceasta - codul sursă poate fi găsit la github, biblioteca este compilată sub GNU/Linux cu cmake obișnuit.

Vestea bună este că OpenCV poate fi construit din cutie ca o bibliotecă statică, ceea ce face portarea mai ușoară. Asamblam biblioteca cu configurația standard și vedem cât spațiu ocupă. Fiecare modul este asamblat î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 puteți vedea din ultima linie, .bss și .data nu ocupă mult spațiu, dar codul este mai mare de 70 MiB. Este clar că dacă aceasta este legată static cu o anumită aplicație, va exista mai puțin cod.

Să încercăm să aruncăm cât mai multe module pentru a crea un exemplu minim (care, de exemplu, va afișa pur și simplu versiunea OpenCV), așa că haideți să aruncăm o privire cmake .. -LA și dezactivați în opțiuni tot ce este 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 de bibliotecă, pe de altă parte, acesta este fără optimizare de către compilator pentru dimensiunea codului (-Os). ~3 MiB de cod este încă destul de mult, dar deja dă speranță pentru succes.

Rulează într-un emulator

Este mult mai ușor să depanați pe un emulator, așa că mai întâi ne vom asigura că biblioteca funcționează pe qemu. Am ales Integrator/CP ca platformă emulată, pentru că... în primul rând, este și ARM și, în al doilea rând, Embbox acceptă ieșirea grafică pentru această platformă.

Embox are un mecanism pentru construirea de biblioteci externe, cu ajutorul lui adăugăm OpenCV ca modul (trecând toate aceleași opțiuni pentru asamblarea „minimală” sub formă de biblioteci statice), după care adaug o aplicație simplă care arată astfel:

version.cpp:

#include <stdio.h>
#include <opencv2/core/utility.hpp>

int main() {
    printf("OpenCV: %s", cv::getBuildInformation().c_str());

    return 0;
}

Asamblam sistemul, îl rulăm și obținem rezultatul așteptat.

root@embox:/#opencv_version                                                     
OpenCV: 
General configuration for OpenCV 4.0.1 =====================================
  Version control:               bd6927bdf-dirty

  Platform:
    Timestamp:                   2019-06-21T10:02:18Z
    Host:                        Linux 5.1.7-arch1-1-ARCH x86_64
    Target:                      Generic arm-unknown-none
    CMake:                       3.14.5
    CMake generator:             Unix Makefiles
    CMake build tool:            /usr/bin/make
    Configuration:               Debug

  CPU/HW features:
    Baseline:
      requested:                 DETECT
      disabled:                  VFPV3 NEON

  C/C++:
    Built as dynamic libs?:      NO
< Дальше идут прочие параметры сборки -- с какими флагами компилировалось,
  какие модули OpenCV включены в сборку и т.п.>

Următorul pas este să rulați un exemplu, de preferință unul standard pe care dezvoltatorii înșiși îl oferă pe site-ul dvs. Am ales Detector de frontieră Canny.

Exemplul a trebuit să fie rescris puțin pentru a afișa imaginea rezultată direct în memoria tampon. A trebuit să fac asta pentru că... funcţie imshow() poate desena imagini prin interfețele QT, GTK și Windows, care, desigur, nu vor fi cu siguranță în configurația pentru STM32. De fapt, QT poate fi rulat și pe STM32F7Discovery, dar acest lucru va fi discutat într-un alt articol :)

După ce ne dăm seama pe scurt în ce format este stocat rezultatul detectorului de margini, obținem o imagine.

OpenCV pe STM32F7-Discovery

Poza originala

OpenCV pe STM32F7-Discovery

Rezultat

Rulează pe STM32F7Discovery

32F746GDISCOVERY are mai multe secțiuni de memorie hardware pe care le putem folosi într-un fel sau altul

  1. 320 KiB RAM
  2. Memorie flash de 1 MiB pentru imagine
  3. 8 MiB SDRAM
  4. Unitate flash QSPI NAND de 16 MiB
  5. Slot pentru card MicroSD

Un card SD poate fi folosit pentru a stoca imagini, dar în contextul rulării unui exemplu minim, acest lucru nu este foarte util.
Afișajul are o rezoluție de 480x272, ceea ce înseamnă că memoria pentru framebuffer va fi de 522 de octeți cu o adâncime de 240 de biți, adică. aceasta este mai mare decât dimensiunea RAM, astfel încât framebuffer-ul și heap-ul (care vor fi necesare, printre altele, pentru ca OpenCV să stocheze date pentru imagini și structuri auxiliare) vor fi localizate în SDRAM, orice altceva (memorie pentru stive și alte sisteme). nevoi) va merge la RAM.

Dacă luăm configurația minimă pentru STM32F7Discovery (aruncăm întreaga rețea, toate comenzile, facem stive cât mai mici posibil, etc.) și adăugăm OpenCV cu exemple acolo, memoria necesară va fi următoarea:

   text    data     bss     dec     hex filename
2876890  459208  312736 3648834  37ad42 build/base/bin/embox

Pentru cei care nu sunt foarte familiarizați cu ce secțiuni merg unde, permiteți-mi să vă explic: în .text и .rodata conține instrucțiuni și constante (în linii mari, date numai în citire), în .data există date modificabile în .bss există variabile „la zero”, care, totuși, au nevoie de spațiu (această secțiune va „merge” în RAM).

Vestea bună este că .data/.bss ar trebui să se potrivească, dar cu .text Problema este că există doar 1 MiB de memorie pentru imagine. Poate fi aruncat .text imaginea din exemplu și citiți-o, de exemplu, de pe un card SD în memorie la pornire, dar fruit.png cântărește aproximativ 330 KiB, așa că acest lucru nu va rezolva problema: majoritatea .text constă tocmai din codul OpenCV.

În general, mai rămâne un singur lucru - încărcarea unei părți a codului pe o unitate flash QSPI (are un mod de operare special pentru maparea memoriei la magistrala de sistem, astfel încât procesorul să poată accesa aceste date direct). În acest caz, apare o problemă: în primul rând, memoria unei unități flash QSPI nu este disponibilă imediat după repornirea dispozitivului (modul mapat cu memorie trebuie inițializat separat) și, în al doilea rând, este imposibil să „flash” această memorie cu bootloader obișnuit.

Drept urmare, s-a decis să se conecteze tot codul în QSPI și să-l flash cu un bootloader auto-scris, care va primi binarul necesar prin TFTP.

Rezultat

Ideea de a porta această bibliotecă pe Embox a apărut în urmă cu aproximativ un an, dar din nou și din nou a fost amânată din diverse motive. Unul dintre ele este suportul pentru libstdc++ și biblioteca standard de șabloane. Problema suportului C++ în Embbox depășește sfera acestui articol, așa că aici voi spune doar că am reușit să obținem acest suport în măsura necesară pentru ca această bibliotecă să funcționeze :)

În cele din urmă, aceste probleme au fost depășite (cel puțin suficient pentru ca exemplul OpenCV să funcționeze), iar exemplul a rulat. Placa durează 40 de secunde lungi pentru a căuta granițele folosind filtrul Canny. Acest lucru, desigur, este prea lung (există considerații cu privire la modul de optimizare a acestei chestiuni; un articol separat ar putea fi scris despre asta dacă are succes).

OpenCV pe STM32F7-Discovery

Totuși, scopul intermediar a fost crearea unui prototip care să arate posibilitatea fundamentală de a rula OpenCV pe STM32, așa că acest obiectiv a fost atins, hai!

tl;dr: instrucțiuni pas cu pas

0: Descărcați sursele Embbox, de exemplu astfel:

    git clone https://github.com/embox/embox && cd ./embox

1: Să începem prin a asambla un bootloader care va flash unitatea flash QSPI.

    make confload-arm/stm32f7cube

Acum trebuie să configurați rețeaua, deoarece... Vom descărca imaginea prin TFTP. Pentru a seta adresele IP ale plăcii și ale gazdei, trebuie să editați 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 bordului.

După aceea, asamblam bootloader-ul:

    make

2: Încărcarea normală a bootloader-ului (scuți jocul de cuvinte) pe placă - nimic specific aici, trebuie să o faci ca pentru orice altă aplicație pentru STM32F7Discovery. Dacă nu știi cum să faci asta, poți citi despre asta aici.
3: Compilarea unei imagini cu o configurație pentru OpenCV.

    make confload-platform/opencv/stm32f7discovery
    make

4: Extragerea secțiunilor din 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

Există un script în directorul conf care face acest lucru, așa că îl puteți rula

    ./conf/qspi_objcopy.sh # Нужный бинарник -- build/base/bin/qspi.bin

5: Folosind tftp, încărcați qspi.bin.bin pe o unitate flash QSPI. Pe gazdă, pentru a face acest lucru, trebuie să copiați qspi.bin în folderul rădăcină al serverului tftp (de obicei /srv/tftp/ sau /var/lib/tftpboot/; pachetele pentru serverul corespunzător sunt disponibile în cele mai populare distribuții , numit de obicei tftpd sau tftp-hpa, uneori trebuie să faci systemctl start tftpd.service a începe).

    # вариант для tftpd
    sudo cp build/base/bin/qspi.bin /srv/tftp
    # вариант для tftp-hpa
    sudo cp build/base/bin/qspi.bin /var/lib/tftpboot

Pe Embbox (adică în bootloader) trebuie să rulați următoarea comandă (presupunem că serverul are o adresă 192.168.2.1):

    embox> qspi_loader qspi.bin 192.168.2.1

6: Folosind o comandă goto trebuie să „săriți” în memoria QSPI. Locația specifică va varia în funcție de modul în care imaginea este legată, puteți vizualiza această adresă cu comanda mem 0x90000000 (adresa de început se încadrează în al doilea cuvânt de 32 de biți al imaginii); va trebui, de asemenea, să setați steag-ul stivei -s, adresa stivei este 0x90000000, exemplu:

    embox>mem 0x90000000
    0x90000000:     0x20023200  0x9000c27f  0x9000c275  0x9000c275
                      ↑           ↑
              это адрес    это  адрес 
                стэка        первой
                           инструкции

    embox>goto -i 0x9000c27f -s 0x20023200 # Флаг -i нужен чтобы запретить прерывания во время инициализации системы

    < Начиная отсюда будет вывод не загрузчика, а образа с OpenCV >

7: Lansare

    embox> edges 20

și bucurați-vă de căutarea de limite de 40 de secunde :)

Dacă ceva nu merge bine, scrieți o problemă la depozitul nostru, sau la buletinul informativ [e-mail protejat], sau într-un comentariu aici.

Sursa: www.habr.com

Adauga un comentariu