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 budu mluvit o tom, jak se mi podařilo spustit OpenCV na desce STM32746G.

Pokud zadáte do vyhledávače něco jako „OpenCV on STM32 board“, můžete najít nemálo lidí, kteří mají zájem tuto knihovnu používat na deskách STM32 nebo jiných mikrokontrolérech.
Existuje několik videí, která by podle názvu měla demonstrovat, co je potřeba, ale obvykle (ve všech videích, která jsem viděl) na desce STM32 byl přijat pouze obraz z kamery a výsledek se zobrazil na obrazovce, a 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 těžké?

Popularita vyhledávacích dotazů se vysvětluje skutečností, že OpenCV je nejoblíbenější knihovnou počítačového vidění, což znamená, že ji zná více vývojářů, a možnost spouštět kód připravený pro stolní počítače na mikrokontroléru značně zjednodušuje proces vývoje. Proč ale stále neexistují oblíbené hotové recepty na řešení tohoto problému?

Problém používání OpenCV na malých šálách souvisí se dvěma funkcemi:

  • Pokud zkompilujete knihovnu i s minimální sadou modulů, jednoduše se nevejde do flash paměti stejného STM32F7Discovery (i bez zohlednění OS) kvůli velmi velkému kódu (několik megabajtů instrukcí)
  • Samotná knihovna je napsána v C++, což znamená
    • Potřebujete podporu pro pozitivní běhové prostředí (výjimky atd.)
    • Malá podpora pro LibC/Posix, který se obvykle nachází v OS pro vestavěné systémy - potřebujete standardní knihovnu plus a standardní knihovnu šablon STL (vektor atd.)

Portování do Emboxu

Jako obvykle je před portováním jakýchkoli programů do operačního systému dobré zkusit jej postavit do podoby, v jaké jej vývojáři zamýšleli. V našem případě s tím nejsou žádné problémy - zdrojový kód najdete na githabe, knihovna je postavena pod GNU/Linux s obvyklým cmake.

Dobrou zprávou je, že OpenCV lze sestavit jako statickou knihovnu ihned po vybalení, což usnadňuje přenos. Shromažďujeme knihovnu se standardní konfigurací a vidíme, kolik místa zabírají. Každý modul je shromážděn v samostatné knihovně.

> 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 můžete vidět z posledního řádku, .bss a .data nezabírají mnoho místa, ale kód má více než 70 MiB. Je jasné, že pokud je toto staticky propojeno s konkrétní aplikací, bude kód méně.

Zkusme vyhodit co nejvíce modulů, aby se sestavil minimální příklad (který například jednoduše vydá verzi OpenCV), takže se podíváme cmake .. -LA a v možnostech vypněte vše, co se vypíná.

        -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 jednu stranu se jedná pouze o jeden modul knihovny, na druhou stranu bez optimalizace kompilátoru na velikost kódu (-Os). ~3 MiB kódu je stále poměrně hodně, ale už dává naději na úspěch.

Spusťte v emulátoru

Na emulátoru je mnohem jednodušší ladit, takže se nejprve ujistěte, že knihovna funguje na qemu. Jako emulovanou platformu jsem zvolil Integrator / CP, protože za prvé je to také ARM a za druhé Embox podporuje grafický výstup pro tuto platformu.

Embox má mechanismus pro vytváření externích knihoven, pomocí něj přidáme OpenCV jako modul (předání všech stejných možností pro „minimální“ sestavení ve formě statických knihoven), poté přidám jednoduchou aplikaci, která vypadá takto:

version.cpp:

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

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

    return 0;
}

Sestavíme systém, spustíme jej – dostaneme očeká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
< Дальше идут прочие параметры сборки -- с какими флагами компилировалось,
  какие модули OpenCV включены в сборку и т.п.>

Dalším krokem je spuštění nějakého příkladu, nejlépe jednoho ze standardních nabízených samotnými vývojáři. na vašem webu. Vybírám si hraniční detektor mazaný.

Příklad musel být mírně přepsán, aby se obrázek s výsledkem zobrazil přímo ve frame bufferu. Musel jsem to udělat, protože. funkce imshow() umí kreslit obrázky přes rozhraní QT, GTK a Windows, což samozřejmě v configu pro STM32 rozhodně nebude. Ve skutečnosti lze QT spustit i na STM32F7Discovery, ale o tom bude řeč v jiném článku 🙂

Po krátkém upřesnění, v jakém formátu je výsledek detektoru hran uložen, dostaneme obrázek.

OpenCV na STM32F7-Discovery

originální obrázek

OpenCV na STM32F7-Discovery

Výsledek

Běží na STM32F7Discovery

Na 32F746GDISCOVERY je několik sekcí hardwarové paměti, které můžeme použít tak či onak

  1. 320 kiB RAM
  2. 1MiB blesk pro obrázek
  3. 8MiB SDRAM
  4. 16MiB QSPI NAND Flash
  5. slot na microSD kartu

Pro ukládání snímků lze použít SD kartu, 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ěť framebufferu bude 522 240 bytů při hloubce 32 bitů, tzn. to je více než velikost RAM, takže framebuffer a halda (která bude vyžadována, včetně OpenCV, pro ukládání dat pro obrázky a pomocné struktury) budou umístěny v SDRAM, vše ostatní (paměť pro zásobníky a další systémové potřeby ) přejde do paměti 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 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

Pro ty, kteří nejsou příliš obeznámeni s tím, které úseky kam jdou, vysvětlím: v .text и .rodata instrukce a konstanty (zhruba řečeno data pouze pro čtení) leží v .data data jsou proměnlivá, .bss existují "nulované" proměnné, které však potřebují místo (tato sekce "přejde" do RAM).

Dobrá zpráva je, že .data/.bss by měl pasovat, ale s .text problém je v tom, že pro obraz je pouze 1MiB paměti. Lze vyhodit .text obrázek z příkladu a načíst ho např. z SD karty do paměti při startu, ale fruits.png váží cca 330KiB, takže to problém nevyřeší: většina .text se skládá z kódu OpenCV.

Celkově zbývá jediné - načtení části kódu na QSPI flash (má speciální režim provozu pro mapování paměti na systémovou sběrnici, takže procesor má k těmto datům přímý přístup). V tomto případě nastává problém: za prvé, paměť flash disku QSPI není k dispozici ihned po restartu zařízení (je třeba samostatně inicializovat režim mapování paměti) a za druhé nemůžete tuto paměť „flashovat“ pomocí známý bootloader.

V důsledku toho bylo rozhodnuto propojit veškerý kód v QSPI a flashovat jej pomocí samonapsaného zavaděče, který obdrží požadovaný binární soubor přes TFTP.

Výsledek

Myšlenka portovat tuto knihovnu na Embox se objevila asi před rokem, ale znovu a znovu byla z různých důvodů odkládána. Jedním z nich je podpora pro libstdc++ a standardní knihovnu šablon. Problém podpory C++ v Emboxu je nad rámec tohoto článku, takže zde řeknu pouze to, že se nám podařilo dosáhnout této podpory ve správné míře, aby tato knihovna fungovala 🙂

Nakonec byly tyto problémy překonány (alespoň natolik, aby příklad OpenCV fungoval) a příklad běžel. Desce trvá dlouhých 40 sekund, než pomocí filtru Canny vyhledá hranice. To je samozřejmě příliš dlouhé (existují úvahy, jak tuto záležitost optimalizovat, o tom bude možné v případě úspěchu napsat samostatný článek).

OpenCV na STM32F7-Discovery

Prozatímním cílem však bylo vytvořit prototyp, který ukáže zásadní možnost provozování OpenCV na STM32, respektive tohoto cíle bylo dosaženo, hurá!

tl;dr: pokyny krok za krokem

0: Stáhněte si zdroje Embox, jako je tento:

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

1: Začněme sestavením bootloaderu, který bude „flashovat“ QSPI flash disk.

    make confload-arm/stm32f7cube

Nyní musíte nakonfigurovat síť, protože. Obrázek nahrajeme přes TFTP. Chcete-li nastavit adresu IP desky 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, odkud bude obrázek načten, address - adresa představenstva.

Poté shromáždíme bootloader:

    make

2: Obvyklé načítání bootloaderu (omlouvám se za slovní hříčku) na desku - zde není nic konkrétního, musíte to udělat jako u každé jiné aplikace pro STM32F7Discovery. Pokud nevíte, jak na to, můžete si o tom přečíst zde.
3: Kompilace obrázku s konfigurací pro OpenCV.

    make confload-platform/opencv/stm32f7discovery
    make

4: Výpis ze sekcí ELF, které mají být zapsány 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áři conf je skript, který to dělá, takže ho můžete spustit

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

5: Pomocí tftp stáhněte qspi.bin.bin na QSPI flash disk. Chcete-li to provést, zkopírujte na hostiteli soubor qspi.bin do kořenové složky serveru tftp (obvykle /srv/tftp/ nebo /var/lib/tftpboot/; balíčky pro odpovídající server jsou dostupné ve většině oblíbených distribucí, obvykle nazvaných tftpd nebo tftp-hpa, někdy musíte udělat systemctl start tftpd.service začít).

    # вариант для 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) musíte provést následující příkaz (předpokládáme, že server má adresu 192.168.2.1):

    embox> qspi_loader qspi.bin 192.168.2.1

6: S příkazem goto musíte "skočit" do paměti QSPI. Konkrétní umístění se bude lišit v závislosti na tom, jak je obrázek propojen, tuto adresu můžete vidět pomocí příkazu mem 0x90000000 (počáteční adresa se vejde do druhého 32bitového slova obrázku); budete také muset označit zásobník -s, adresa zásobníku je 0x90000000, příklad:

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

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

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

7: Spuštění

    embox> edges 20

a užijte si 40sekundové hledání hranic 🙂

Pokud se něco pokazí - napište problém naše úložištěnebo do mailing listu [chráněno e-mailem], nebo v komentáři zde.

Zdroj: www.habr.com

Přidat komentář