Jsem jedním z vývojářů operačního systému , 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 , 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?: NEDalší krok – spustit nějaký příklad, nejlepší je použít nějaký standardní, který nabízejí sami vývojáři . Vybral jsem si .
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.

Originální obrázek

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
- 320KiB operační paměti
- 1MiB flash paměti pro obraz
- 8MiB SDRAM
- 16MiB QSPI NAND-flash
- 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/emboxPro 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á!

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/stm32f7cubeNyní 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:02gateway — adresa hostitele, ze které bude obraz načítán, address — adresa desky.
Poté sestavíme bootloader:
make2: 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. .
3: Kompilace obrazu s konfigurací pro OpenCV.
make confload-platform/opencv/stm32f7discovery
make4: 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=.dataVe 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.bin5: 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/tftpbootNa 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.16: 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 20a užíváme si 40 sekund vyhledávání hran 🙂
Pokud něco neproběhne správně — napište problém do , nebo na mailing list embox-devel@googlegroups.com, nebo sem do komentářů.
Zdroj: habr.com
