OpenCV na STM32F7-Discovery

OpenCV na STM32F7-Discovery Jsem jedním z vývojářů operačního systému Embox, a v tomto článku vám povím, jak se mi podařilo spustit OpenCV na desce STM32746G.

Pokud do vyhledávače zadáte něco jako "OpenCV na desce STM32", můžete najít celkem dost těch, kteří se zajímají o použití této knihovny na deskách STM32 nebo jiných mikrořízeních.
Existuje několik videí, která podle názvu mají ukazovat to, co potřebujete, ale obvykle (ve všech videích, která jsem viděl) byla na desce STM32 prováděna pouze akvizice obrazu z kamery a výsledky byly zobrazovány na obrazovce, zatímco samotné zpracování obrazu probíhalo buď na běžném počítači, nebo na výkonnějších deskách (například Raspberry Pi).

Proč je to složité?

Popularita vyhledávacích dotazů se vysvětluje tím, že OpenCV je nejpopulárnější knihovna počítačového vidění, což znamená, že s ní je obeznámeno více vývojářů, a možnost spouštět kód připravený pro desktop na mikrořadiči značně usnadňuje proces vývoje. Ale proč stále neexistují nějaké populární hotové recepty na řešení tohoto problému?

Problém s používáním OpenCV na malých deskách souvisí se dvěma zvláštnostmi:

  • Pokud zkompilujete knihovnu i s minimálním počtem modulů, do flash paměti stejné STM32F7Discovery se jednoduše nevejde (i bez ohledu na OS) kvůli velmi velkému kódu (několik megabajtů instrukcí)
  • Sama knihovna je napsaná v jazyce C++, a to znamená
    • Je potřeba podpora C++ runtime (výjimky atd.)
    • Nízká podpora LibC/Posix, která je obvykle dostupná v OS pro vestavěné systémy — potřebná je standardní knihovna C++ a standardní knihovna šablon STL (vector atd.)

Portování na Embox

Jak už to bývá, před portováním jakýchkoli programů do operačního systému je dobré zkusit ji sestavit ve formátu, v jakém to zamýšleli vývojáři. V našem případě s tím není problém — zdrojové kódy lze najít na githubu, knihovna se sestavuje pod GNU/Linux pomocí běžného cmake.

Z dobrých zpráv — OpenCV lze z krabice sestavit jako statickou knihovnu, což usnadňuje portování. Sestavíme knihovnu se standardním nastavením a zjistíme, kolik místa zabírá. Každý modul se sestavuje do samostatné knihovny.

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

Jak je vidět z posledního řádku, .bss a .data nezabírají příliš mnoho místa, ale kód má více než 70 MiB. Je jasné, že pokud to staticky prolinkujeme s konkrétní aplikací, kód se zmenší.

Zkusíme vyhodit co nejvíce modulů, abychom vytvořili minimální příklad (k který například jen zobrazí verzi OpenCV), takže se díváme cmake .. -LA a vypneme v možnostech všechno, co lze vypnout.

        -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 jedné straně, je to pouze jeden modul knihovny, na druhé straně, to je bez optimalizace kompilátorem na velikost kódu (-Os). ~3 MiB kódu — to je stále dost, ale už to dává naději na úspěch.

Spuštění v emulátoru

Na emulátoru se debuguje mnohem snadněji, proto se nejprve ujistíme, že knihovna funguje na qemu. Jako emulovanou platformu jsem vybral Integrator/CP, protože to je také ARM a Embox podporuje grafický výstup pro tuto platformu.

V Emboxu existuje mechanismus pro kompilaci externích knihoven, s jehož pomocí přidáváme OpenCV jako modul (předávajíc všechny stejné možnosti pro „minimální“ kompilaci jako statické knihovny), poté přidávám nejjednodušší aplikaci, která vypadá takto:

version.cpp:

#include 
#include 

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

    return 0;
}

Sestavíme systém, spustíme — získáme očekávaný výstup.

root@embox: /#opencv_version                                                     
OpenCV: 
Obecná konfigurace pro OpenCV 4.0.1 =====================================
  Kontrola verze:               bd6927bdf-dirty

  Platforma:
    Časová značka:               2019-06-21T10:02:18Z
    Hostitel:                    Linux 5.1.7-arch1-1-ARCH x86_64
    Cíl:                        Obecné arm-unknown-none
    CMake:                       3.14.5
    Generátor CMake:             Unix Makefiles
    Nástroj pro sestavení CMake: 
    /usr/bin/make
    Konfigurace:                 Debug

  CPU/HW funkce:
    Základ:
      požadováno:                 DETEKOVAT
      zakázáno:                  VFPV3 NEON

  C/C++:
    Sestaveno jako dynamické knihovny?:      NE

Další krok – spustit nějaký příklad, nejlepší je použít nějaký standardní, který nabízejí sami vývojáři na svých stránkách. Vybral jsem si detektor hranic Canny.

Příklad jsem musel trochu přepracovat, aby se obrázek s výsledkem zobrazil přímo ve framebufferu. Bylo to nutné, protože funkce imshow() umí vykreslovat obrázky prostřednictvím rozhraní QT, GTK a Windows, které v konfiguraci pro STM32 samozřejmě nebudou. Ve skutečnosti lze QT také spustit na STM32F7Discovery, ale o tom bude řeč v jiném článku 🙂

Po krátkém vyjasnění, v jakém formátu se ukládá výstup z detektoru hranic, získáváme obrázek.

OpenCV na STM32F7-Discovery

Originální obrázek

OpenCV na STM32F7-Discovery

Výsledek

Spuštění na STM32F7Discovery

Na 32F746GDISCOVERY je několik bloků paměti, které můžeme různými způsoby použít

  1. 320KiB operační paměti
  2. 1MiB flash paměti pro obraz
  3. 8MiB SDRAM
  4. 16MiB QSPI NAND-flash
  5. Konektor pro microSD kartu

SD kartu lze použít pro ukládání obrázků, ale v kontextu spouš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 framebuffer bude činit 522 240 bajtů při hloubce 32 bitů, tedy je to více, než je velikost operační paměti, takže framebuffer a halda (která bude potřebná i pro OpenCV, aby uchovávala data pro obrázky a pomocné struktury) budou umístěny v SDRAM, všechno ostatní (paměť pro zásobníky a další systémové potřeby) půjde do RAM.

Pokud vezmeme minimální konfiguraci pro STM32F7Discovery (vyhodíme celou síť, všechny příkazy, uděláme zásobníky co nejmenší atd.) a přidáme k tomu OpenCV s příklady, s požadovanou pamětí to bude vypadat takto:

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

Pro ty, kdo nejsou příliš obeznámeni s tím, jaké sekce kam patří, vysvětlím: v .text a .rodata jsou uloženy instrukce a konstanty (zjednodušeně řečeno, readonly data), v .data jsou uložena měnitelná data, v .bss jsou "nulované" proměnné, kterým však přesto potřebujeme místo (tato sekce se "převede" do RAM).

Dobrou zprávou je, že .data/.bss by měly být umístěny, ale problém je s .text bohužel — pro obraz je k dispozici pouze 1MiB paměti. Můžeme sice vyhodit obrázek z příkladu a číst ho například z SD karty do paměti při spuštění, ale fruits.png váží přibližně 330KiB, takže tento problém to nevyřeší: většina .text se skládá právě z kódu OpenCV. .text Z čistě technického hlediska zůstává jen jedno — načíst část kódu na QSPI-flash (má speciální režim pro mapování paměti na systémovou sběrnici, takže procesor k těmto datům může přistupovat přímo). Při tom nastává problém: zaprvé, paměť QSPI-flash není dostupná ihned po restartování zařízení (je třeba zvlášť inicializovat režim memory-mapped), a zadruhé, tuto paměť nelze „nahrát“ obvyklým bootloaderem.

Nakonec bylo rozhodnuto slinkovat celý kód do QSPI a nahrávat ho vlastním bootloaderem, který bude získávat potřebný binární soubor přes TFTP.

Nápad portovat tuto knihovnu na Embox se objevil přibližně před rokem, ale opakovaně byl odkládán z různých důvodů. Jedním z nich je podpora libstdc++ a standardní šablonové knihovny. Problém s podporou C++ v Embox přesahuje rámec tohoto článku, proto zde jen povím, že se nám podařilo dosáhnout této podpory v potřebném rozsahu pro fungování této knihovny 🙂

Výsledek

Nakonec byly tyto problémy překonány (alespoň v dostatečné míře pro fungování příkladu OpenCV) a příklad byl spuštěn. 40 dlouhých sekund trvá desce nalezení hranic pomocí Cannyho filtru. To je samozřejmě příliš dlouho (existují úvahy o tom, jak by se to dalo optimalizovat, o tom bychom mohli napsat samostatný článek v případě úspěchu).

Nicméně mezitím bylo cílem vytvoření prototypu, který prokáže principielní možnost spuštění OpenCV na STM32, a tedy tento cíl byl splněn, hurá!

OpenCV na STM32F7-Discovery

tl;dr: podrobný návod

0: Stáhněte si zdrojové kódy Embox, například takto:

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

    1: Začneme se sestavením bootloaderu, který "nahrává" QSPI-flash.

make confload-arm/stm32f7cube

    vytvořit confload-arm/stm32f7cube

Nyní je třeba nastavit síť, protože obraz budeme načítat přes TFTP. Abyste nastavili IP adresy pro desku a hostitele, musíte upravit soubor conf/rootfs/network.

Příklad konfigurace:

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 hostitele, ze které bude obraz načítán, address — adresa desky.

Poté sestavíme bootloader:

    make

2: Běžné načtení bootloaderu (omlouváme se za hříčku) na desku — zde není nic specifického, je to třeba provést jako u jakékoli jiné aplikace pro STM32F7Discovery. Pokud nevíte, jak na to, můžete si o tom přečíst. tady.
3: Kompilace obrazu s konfigurací pro OpenCV.

    make confload-platform/opencv/stm32f7discovery
    make

4: Extrakce z ELF sekcí, které je třeba zapisovat do QSPI, do souboru 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

Ve složce conf se nachází skript, který to provedete, takže jej můžete spustit.

    ./conf/qspi_objcopy.sh # Požadovaný binární soubor -- build/base/bin/qspi.bin

5: Pomocí tftp načtěte qspi.bin na QSPI flash paměť. Na hostiteli je třeba zkopírovat qspi.bin do kořenového adresáře TFTP serveru (obvykle je to /srv/tftp/ nebo /var/lib/tftpboot/; balíčky pro odpovídající server jsou dostupné v většině populárních distribucí, obvykle se nazývá tftpd nebo tftp-hpa, někdy je třeba provést systemctl start tftpd.service pro zahájení).

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

Na Emboxu (tj. v bootloaderu) je třeba provést následující příkaz (předpokládáme, že adresa serveru je 192.168.2.1):

    embox> qspi_loader qspi.bin 192.168.2.1

6: Pomocí příkazu goto je třeba „skočit“ do paměti QSPI. Konkrétní adresa se bude lišit v závislosti na tom, jak se obraz slepuje, tuto adresu můžete zjistit příkazem mem 0x90000000 (adresa startu je uložena ve druhém 32-bitovém slově obrazu); také bude třeba nastavit zásobník flagem -s, adresa zásobníku je na adrese 0x90000000, příklad:

    embox>mem 0x90000000
    0x90000000:     0x20023200  0x9000c27f  0x9000c275  0x9000c275
                      ↑           ↑
              to je adresa    to je  adresa 
                zásobníku    první
                           instrukce

    embox>goto -i 0x9000c27f -s 0x20023200 # Flag -i potřebný k tomu, aby se během inicializace systému zakázala přerušení

7: Spustíme

    embox> edges 20

a užíváme si 40 sekund vyhledávání hran 🙂

Pokud něco neproběhne správně — napište problém do našeho repozitáře, nebo na mailing list embox-devel@googlegroups.com, nebo sem do komentářů.

Zdroj: habr.com

Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery 🔥 Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery | ProHoster