OpenCV az STM32F7-Discovery-n

OpenCV az STM32F7-Discovery-n Az operációs rendszer egyik fejlesztője vagyok Embox, és ebben a cikkben arról fogok beszélni, hogyan sikerült az OpenCV-t futtatnom az STM32746G kártyán.

Ha valami olyasmit ír be a keresőbe, hogy „OpenCV az STM32 kártyán”, akkor jó néhány embert találhat, aki érdeklődik a könyvtár STM32 kártyákon vagy más mikrokontrollereken való használata iránt.
Több videó is van, aminek a névből ítélve bemutatnia kell, hogy mire van szükség, de általában (az összes videóban, amit láttam) az STM32 táblán csak a kép érkezett a kamerából, és az eredmény megjelent a képernyőn, maga a képfeldolgozás pedig vagy normál számítógépen, vagy erősebb táblákon (például Raspberry Pi) történt.

Miért nehéz?

A keresési lekérdezések népszerűségét az magyarázza, hogy az OpenCV a legnépszerűbb számítógépes látáskönyvtár, ami azt jelenti, hogy több fejlesztő ismeri, és az asztali számítógépre alkalmas kód mikrokontrolleren való futtatása nagyban leegyszerűsíti a fejlesztési folyamatot. De miért nincsenek még mindig népszerű kész receptek a probléma megoldására?

Az OpenCV kis kendőkön való használatának problémája két jellemzőhöz kapcsolódik:

  • Ha a könyvtárat még minimális modulkészlettel is összeállítja, egyszerűen nem fog beleférni ugyanazon STM32F7Discovery flash memóriájába (még az operációs rendszer figyelembevétele nélkül sem) a nagyon nagy kód miatt (több megabájt utasítás)
  • Maga a könyvtár C++ nyelven van írva, ami azt jelenti
    • Támogatás szükséges a pozitív futásidőhöz (kivételek stb.)
    • Kevés támogatás a LibC/Posix-hoz, amely általában megtalálható a beágyazott rendszerek operációs rendszerében – szüksége van egy szabványos plusz könyvtárra és egy szabványos STL sablonkönyvtárra (vektor stb.)

Portolás az Emboxba

Szokás szerint, mielőtt bármilyen programot portolnánk az operációs rendszerre, érdemes megpróbálni a fejlesztők által tervezett formában felépíteni. A mi esetünkben ezzel nincs probléma - a forráskód megtalálható github, a könyvtár GNU/Linux alatt épül fel a szokásos cmake-vel.

A jó hír az, hogy az OpenCV statikus könyvtárként is felépíthető a dobozból, ami megkönnyíti a portolást. Összegyűjtünk egy könyvtárat egy szabványos konfigurációval, és megnézzük, mennyi helyet foglalnak el. Minden modul külön könyvtárban van gyűjtve.

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

Ahogy az utolsó sorból is látszik, a .bss és a .data nem foglal sok helyet, de a kód több mint 70 MiB. Nyilvánvaló, hogy ha ez statikusan kapcsolódik egy adott alkalmazáshoz, akkor a kód kevesebb lesz.

Próbáljunk meg minél több modult kidobni, hogy egy minimális példa kerüljön összeállításra (ami például egyszerűen kiadja az OpenCV verziót), így nézzük cmake .. -LA és kapcsoljon ki mindent, ami kikapcsol.

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

Ez egyrészt csak egy modulja a könyvtárnak, másrészt ez a fordítóprogram kódméretre való optimalizálása nélkül (-Os). ~3 MiB kód még elég sok, de már reményt ad a sikerre.

Futtassa az emulátort

Sokkal egyszerűbb a hibakeresés az emulátoron, ezért először győződjön meg arról, hogy a könyvtár működik qemu-n. Emulált platformként az Integrator / CP-t választottam, mert egyrészt ez is ARM, másrészt az Embox támogatja a grafikus kimenetet ezen a platformon.

Az Embox rendelkezik egy külső könyvtárak felépítésének mechanizmusával, ennek használatával az OpenCV-t modulként adjuk át (a "minimális" felépítéshez ugyanazokat a lehetőségeket adjuk át statikus könyvtárak formájában), majd hozzáadok egy egyszerű alkalmazást, amely így néz ki:

version.cpp:

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

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

    return 0;
}

Összeállítjuk a rendszert, lefuttatjuk - megkapjuk a várt kimenetet.

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 включены в сборку и т.п.>

A következő lépés egy példa futtatása, lehetőleg a fejlesztők által kínált szabványos példák egyike. a webhelyén. én választok ravasz határdetektor.

A példát kissé át kellett írni, hogy a kép közvetlenül a keretpufferben jelenjen meg az eredménnyel. Ezt meg kellett tennem, mert. funkció imshow() képes képeket rajzolni a QT, GTK és Windows felületeken keresztül, ami természetesen az STM32 konfigurációjában biztosan nem lesz benne. Valójában a QT STM32F7Discovery-n is futtatható, de erről egy másik cikkben lesz szó 🙂

Rövid tisztázás után, hogy az éldetektor eredményét milyen formátumban tároljuk, képet kapunk.

OpenCV az STM32F7-Discovery-n

eredeti kép

OpenCV az STM32F7-Discovery-n

Eredmény

STM32F7Discovery-n fut

A 32F746GDISCOVERY-n több hardveres memóriarész található, amelyeket így vagy úgy használhatunk

  1. 320KiB RAM
  2. 1 MiB vaku a képhez
  3. 8 MiB SDRAM
  4. 16 MiB QSPI NAND vaku
  5. microSD kártya nyílás

Az SD-kártya használható képek tárolására, de egy minimális példa futtatása kapcsán ez nem túl hasznos.
A kijelző felbontása 480×272, ami azt jelenti, hogy a framebuffer memória 522 240 bájt lesz 32 bites mélységben, pl. ez több, mint a RAM mérete, így a framebuffer és a kupac (amelyre szükség lesz, beleértve az OpenCV-t is, a képek és a segédstruktúrák adatainak tárolására) az SDRAM-ban lesz, minden más (memória a veremekhez és egyéb rendszerigényekhez) ) a RAM-ba kerül.

Ha vesszük az STM32F7Discovery minimális konfigurációját (kidobjuk a teljes hálózatot, az összes parancsot, a veremeket a lehető legkisebbre állítjuk stb.) és hozzáadjuk az OpenCV-t példákkal, akkor a szükséges memória a következő lesz:

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

Azok számára, akik nem nagyon ismerik, hogy melyik szakasz hova megy, elmagyarázom: be .text и .rodata utasítások és konstansok (nagyjából szólva csak olvasható adatok) rejlenek .data az adatok változtathatók, .bss vannak "nullált" változók, amelyeknek azonban hely kell (ez a rész "megy" a RAM-ba).

A jó hír az .data/.bss illeszkednie kell, de azzal .text az a baj, hogy csak 1MiB memória van a képhez. Ki lehet dobni .text a képet a példából és olvassa be például az SD kártyáról a memóriába indításkor, de a fruits.png körülbelül 330KiB súlyú, így ez nem oldja meg a problémát: a legtöbb .text OpenCV kódból áll.

Összességében csak egy dolog van hátra - a kód egy részének betöltése egy QSPI flash-re (speciális működési módja van a memória rendszerbuszhoz való hozzárendeléséhez, így a processzor közvetlenül hozzáférhet ezekhez az adatokhoz). Ebben az esetben egy probléma adódik: egyrészt a QSPI flash meghajtó memóriája nem érhető el azonnal az eszköz újraindítása után (külön kell inicializálni a memória-leképezett módot), másodszor pedig ezt a memóriát nem tudja „flash” egy ismerős bootloader.

Ennek eredményeként úgy döntöttek, hogy az összes kódot QSPI-ben kapcsolják össze, és egy saját írású betöltővel frissítik, amely megkapja a szükséges bináris fájlt TFTP-n keresztül.

Eredmény

Körülbelül egy éve jelent meg az ötlet, hogy ezt a könyvtárat áthelyezzük az Emboxra, de különféle okok miatt újra és újra elhalasztották. Az egyik a libstdc++ és a szabványos sablonkönyvtár támogatása. Az Embox C++ támogatásának problémája túlmutat ennek a cikknek a keretein, így itt csak annyit mondok el, hogy ezt a támogatást sikerült elérni a megfelelő mennyiségben ahhoz, hogy ez a könyvtár működjön 🙂

Végül ezeket a problémákat sikerült megoldani (legalább ahhoz, hogy az OpenCV példa működjön), és a példa futott. 40 hosszú másodpercig tart, amíg a tábla a Canny szűrővel határokat keres. Ez persze túl hosszú (vannak megfontolások, hogyan lehet ezt az ügyet optimalizálni, erről lehet majd külön cikket írni, ha sikerül).

OpenCV az STM32F7-Discovery-n

A köztes cél azonban egy olyan prototípus létrehozása volt, amely megmutatja az OpenCV STM32-n való futtatásának alapvető lehetőségét, illetve ez a cél teljesült, hurrá!

tl;dr: lépésről lépésre

0: Töltse le az Embox-forrásokat, például:

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

1: Kezdjük egy bootloader összeállításával, ami "flash" egy QSPI flash meghajtót.

    make confload-arm/stm32f7cube

Most konfigurálnia kell a hálózatot, mert. A képet TFTP-n keresztül töltjük fel. Az alaplap és a gazdagép IP-címének beállításához módosítania kell a conf/rootfs/network fájlt.

Konfigurációs példa:

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 - a gazdagép címe, ahonnan a kép betöltődik, address - a testület címe.

Ezt követően összegyűjtjük a rendszerbetöltőt:

    make

2: A rendszerbetöltő szokásos betöltése (elnézést a szójátékért) a fórumon - itt nincs semmi konkrét, ezt úgy kell megtenni, mint bármely más STM32F7Discovery alkalmazásnál. Ha nem tudod, hogyan kell csinálni, akkor olvashatsz róla itt.
3: Kép fordítása OpenCV konfigurációval.

    make confload-platform/opencv/stm32f7discovery
    make

4: Kivonat az ELF szakaszokból a QSPI-be írandó qspi.bin fájlba

    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

A conf könyvtárban van egy szkript, ami ezt megteszi, így futtatható

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

5: Tftp használatával töltse le a qspi.bin.bin fájlt egy QSPI flash meghajtóra. A gazdagépen ehhez másolja a qspi.bin fájlt a tftp szerver gyökérmappájába (általában /srv/tftp/ vagy /var/lib/tftpboot/; a megfelelő kiszolgálóhoz csomagok elérhetők a legtöbb népszerű disztribúcióban, általában ún. tftpd vagy tftp-hpa, néha meg kell tennie systemctl start tftpd.service kezdeni).

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

Az Emboxon (azaz a rendszerbetöltőben) a következő parancsot kell végrehajtania (feltételezzük, hogy a szerver címe 192.168.2.1):

    embox> qspi_loader qspi.bin 192.168.2.1

6: paranccsal goto be kell "ugrani" a QSPI memóriába. Az adott hely a kép hivatkozási módjától függően változik, ezt a címet láthatja a paranccsal mem 0x90000000 (a kezdőcím a kép második 32 bites szavába illeszkedik); meg kell jelölnie a verem is -s, a verem címe 0x90000000, például:

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

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

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

7: Indítás

    embox> edges 20

és élvezze a 40 másodperces határkeresést 🙂

Ha valami elromlik - írja be a problémát tárházunk, vagy a levelezőlistára [e-mail védett], vagy itt kommentben.

Forrás: will.com

Hozzászólás