OpenCV na STM32F7-Discovery

OpenCV na STM32F7-Discovery Som jedným z vývojárov operačného systému Embox, a v tomto článku vám poviem, ako sa mi podarilo spustiť OpenCV na doske STM32746G.

Ak do vyhľadávača zadáte niečo ako „OpenCV na doske STM32“, môžete nájsť pomerne veľa ľudí, ktorí sa zaujímajú o používanie tejto knižnice na doskách STM32 alebo iných mikrokontroléroch.
Existuje niekoľko videí, ktoré, podľa názvu, by mali demonštrovať to, čo potrebujete, ale zvyčajne (vo všetkých videách, ktoré som videl) sa na doske STM32 iba získavali obrázky z kamery a zobrazoval sa výsledok na obrazovke, pričom samotné spracovanie obrazu sa vykonávalo buď na bežnom počítači, alebo na silnejších doskách (napríklad Raspberry Pi).

Prečo je to ťažké?

Popularita vyhľadávacích dopytov je spôsobená tým, že OpenCV je najobľúbenejšia knižnica počítačového videnia, a teda je s ňou oboznámených viac vývojárov, a navyše možnosť spúšťať kód pripravený pre desktop na mikrokontroléri výrazne zjednodušuje proces vývoja. Ale prečo stále neexistujú nejaké populárne hotové recepty na riešenie tohto problému?

Problém používania OpenCV na malých doskách súvisí s dvoma vlastnosťami:

  • Ak skompilujete knižnicu aj s minimálnym počtom modulov, do flash pamäte STM32F7Discovery sa jednoducho nezmestí (aj bez ohľadu na OS) kvôli veľmi veľkému kódu (niekoľko megabajtov inštrukcií)
  • Samotná knižnica je napísaná v C++, a teda
    • Je potrebná podpora C++ runtime (výnimky a pod.)
    • Malo je podpory LibC/Posix, ktoré sú zvyčajne k dispozícii v OS pre zabudované systémy — potrebná je štandardná knižnica C++ a štandardná knižnica šablón STL (vector a pod.)

Portovanie na Embox

Ako zvyčajne, pred portovaním akýchkoľvek programov do operačného systému nie je zlé pokúsiť sa ho zostaviť v takom formáte, aký zamýšľali vývojári. V našom prípade s tým nie sú žiadne problémy — zdrojové kódy môžete nájsť na github-e, knižnica sa zostavuje pod GNU/Linux pomocou bežného cmake-a.

Z dobrých správ — OpenCV sa dá z vyžaduje zostaviť ako statická knižnica, čo uľahčuje portovanie. Zostavujeme knižnicu so štandardným konfiguráciou a sledujeme, koľko miesta zaberajú. Každý modul sa zostavuje do samostatnej knižnice.

> 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)

Ako vyplýva z posledného riadku, .bss a .data nezaberajú veľa miesta, avšak kód presahuje 70 MiB. Je jasné, že ak by to bolo staticky linkované s konkrétnou aplikáciou, veľkosť kódu by sa zmenšila.

Skúsime odstrániť čo najviac modulov, aby sme vytvorili minimálny príklad (ktorý napríklad jednoducho zobrazí verziu OpenCV), takže sa pozrime cmake .. -LA a v možnostiach vypneme všetko, čo je možné vypnúť.

        -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)

Na jednej strane ide len o jeden modul knižnice, na druhej strane, to je bez optimalizácie kompilátorom podľa veľkosti kódu (-Os). ~3 MiB kódu — to je stále dosť veľa, ale už dáva nádej na úspech.

Spustenie v emulátore

Na emulátore sa odladí oveľa jednoduchšie, takže sa najprv uistíme, že knižnica funguje na qemu. Ako emulovanú platformu som si zvolil Integrator/CP, pretože jednak je to tiež ARM a jednak Embox podporuje grafický výstup pre túto platformu.

V Embox je mechanizmus na zostavenie externých knižníc, pomocou neho pridáme OpenCV ako modul (prevodom všetkých tých istých možností pre „minimálnu“ zostavu vo forme statických knižníc), po tom pridám najjednoduchšiu aplikáciu, ktorá vyzerá takto:

version.cpp:

#include 
#include 

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

    return 0;
}

Zostavíme systém, spustíme — a dostaneme očakávaný výstup.

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

Dalším krokem je spustit nějaký příklad, nejlépe nějaký standardní od samotných vývojářů na jejich webu. Vybral jsem si detektor hranic Canny.

Příklad jsem musel mírně přepsat, abych mohl zobrazit obrázek s výsledkem přímo ve frame bufferu. To bylo nutné, protože funkce imshow() dokáže vykreslovat obrázky přes rozhraní QT, GTK a Windows, které samozřejmě v konfiguraci pro STM32 určitě nebude. Ve skutečnosti je také možné spustit QT na STM32F7Discovery, ale to bude popsáno v jiném článku 🙂

Po krátkém zkoumání, v jakém formátu je uchováván výstup detektoru hranic, získáme obrázek.

OpenCV na STM32F7-Discovery

Originální obrázek

OpenCV na STM32F7-Discovery

Výsledok

Spuštění na STM32F7Discovery

Na 32F746GDISCOVERY je několik hardwarových oddílů paměti, které můžeme tak či onak využít

  1. 320KiB RAM
  2. 1MiB flash paměti pro image
  3. 8MiB SDRAM
  4. 16MiB QSPI NAND flash
  5. Slot pro microSD kartu

SD kartu lze použít pro ukládání obrázků, ale v kontextu spuštění minimálního příkladu to není příliš užitečné.
Displej má rozlišení 480×272, což znamená, že paměť pro frame buffer bude činit 522 240 bajtů při hloubce 32 bitů, t.j. to je více než velikost RAM, takže frame buffer a haldu (která bude potřebná, včetně pro OpenCV, aby uchovala data pro obrázky a pomocné struktury) umístíme do SDRAM, zbytek (paměť pro stacky a další systémové potřeby) půjde do RAM.

Pokud vezmeme minimální konfiguraci pro STM32F7Discovery (vyhodit celou síť, všechny příkazy, udělat stacky co nejmenší atd.) a přidáme tam OpenCV s příklady, bude požadovaná paměť následující:

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

Pre tých, ktorí nie sú veľmi oboznámení s tým, do ktorých sekcií čo patrí, vysvetlím: v .text a .rodata sú uložené inštrukcie a konštanty (hrubým spôsobom readonly-dáta), v .data sú uložené modifikovateľné dáta, v .bss sú umiestnené „nulové“ premenné, ktoré však potrebujú miesto (táto sekcia sa „presunie“ do RAM).

Dobrá správa je, že .data/.bss musí sa zmestiť, ale s .text problém — pre obrazec je len 1MiB pamäte. Môžete odstrániť z .text obrázka z príkladu a načítať ho, napríklad, z SD karty do pamäte pri spustení, ale fruits.png váži približne 330KiB, takže to problém nevyrieši: väčšia časť .text sa skladá práve z kódu OpenCV.

V podstate ostáva len jedno — nahratie časti kódu na QSPI-flash (má špeciálny režim práce pre mapovanie pamäte na systémovú zbernicu, takže procesor k týmto dátam môže pristupovať priamo). Pri tomto sa objavuje problém: po prvé, pamäť QSPI-flash nie je dostupná okamžite po reštartovaní zariadenia (je potrebné samostatne inicializovať memory-mapped-režim), po druhé, nie je možné „napáliť“ túto pamäť obvyklým nabíjačom.

Nakoniec sme sa rozhodli zlúčiť celý kód do QSPI a napáliť ho vlastným nabíjačom, ktorý získa potrebný binárny súbor pomocou TFTP.

Výsledok

Nápad portovať túto knižnicu na Embox sa objavil pred približne rokom, ale opakovane to bolo odložené z rôznych dôvodov. Jedným z nich je podpora libstdc++ a štandardnej šablónovej knižnice. Problém podpory C++ v Embox presahuje rámec tohto článku, takže tu len poviem, že sa nám podarilo dosiahnuť túto podporu v potrebnom rozsahu na prácu tejto knižnice 🙂

Nakoniec boli tieto problémy prekonané (aspoň v dostatočnej miere na fungovanie príkladu OpenCV) a príklad sa spustil. Hľadanie okrajov pomocou Cannyho filtra trvá dosť dlhých 40 sekúnd. To je samozrejme príliš dlho (existujú úvahy, ako tento proces optimalizovať, o tom by sa mohla napísať samostatná článok v prípade úspechu).

OpenCV na STM32F7-Discovery

Napriek tomu, medzičasovým cieľom bolo vytvorenie prototypu, ktorý preukáže zásadnú možnosť spustenia OpenCV na STM32, a preto bol tento cieľ dosiahnutý, hurá!

tl;dr: krok-za-krokom návod

0: Stiahneme zdrojové kódy Embox, napríklad takto:

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

1: Začneme so zostavením nabíjača, ktorý „napáli“ QSPI-flash.

    make confload-arm/stm32f7cube

Teraz je potrebné nastaviť sieť, pretože budeme načítavať obraz cez TFTP. Aby bolo možné nastaviť IP adresy dosky a hostiteľa, je potrebné zmeniť súbor conf/rootfs/network.

Príklad konfigurácie:

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 hostiteľa, z ktorého sa bude načítavať obraz, address — adresa dosky.

Po tomto zostavíme zavádzač:

    make

2: Bežné načítanie zavádzača (prepáčte za slovnú hračku) na dosku — tu nie je nič špecifické, treba to urobiť ako pre akúkoľvek inú aplikáciu pre STM32F7Discovery. Ak neviete, ako to urobiť, môžete si o tom prečítať. tu.
3: Kompilácia obrazu s konfiguráciou pre OpenCV.

    make confload-platform/opencv/stm32f7discovery
    make

4: Extrakcia z ELF sekcií, ktoré je potrebné zapísať do QSPI, do 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

V adresári conf je skript, ktorý to robí, takže ho môžete spustiť

    ./conf/qspi_objcopy.sh # Potrebný binárny súbor -- build/base/bin/qspi.bin

5: Pomocou tftp načítame qspi.bin na QSPI flash disk. Na hostiteľovi je potrebné skopírovať qspi.bin do koreňového priečinka tftp servera (obe lokácie sú zvyčajne /srv/tftp/ alebo /var/lib/tftpboot/; balíky pre príslušný server sú v väčšine populárnych distribúcií, zvyčajne sa nazývajú tftpd alebo tftp-hpa, niekedy je potrebné systemctl start tftpd.service na spustenie).

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

Na Emboxe (t.j. v zavádzači) je potrebné vykonať tento príkaz (predpokladáme, že adresa servera je 192.168.2.1):

    embox> qspi_loader qspi.bin 192.168.2.1

6: Pomocou príkazu goto je potrebné "preskočiť" do QSPI pamäte. Konkrétna lokalita sa bude líšiť v závislosti od toho, ako sa obraz prepojí, tento adresu môžete skontrolovať príkazom mem 0x90000000 (adresa začiatku sa nachádza v druhom 32-bitovom slove obrazu); tiež bude potrebné nastaviť zásobník s praporkom -s, adresa zásobníka je na adrese 0x90000000, príklad:

    embox>mem 0x90000000
    0x90000000:     0x20023200  0x9000c27f  0x9000c275  0x9000c275
                      ↑           ↑
              to je adresa    to je adresa 
                zásobníka        prvej
                           inštrukcie

    embox>goto -i 0x9000c27f -s 0x20023200 # Prapor -i je potrebný na zakázanie prerušenia počas inicializácie systému

7: Spúšťame

    embox> edges 20

a užívame si 40-sekundové hľadanie hraníc 🙂

Ak sa niečo pokazí — napíšte issue do nášho repozitára, alebo do mailing listu embox-devel@googlegroups.com, alebo do komentárov tu.

Zdroj: habr.com

Kúpte si spoľahlivý hosting pre webové stránky s ochranou proti DDoS, VPS VDS servery 🔥 Kúpte si spoľahlivý hosting pre webové stránky s ochranou proti DDoS, VPS VDS servery | ProHoster