OpenCV a STM32F7-Discovery

OpenCV a STM32F7-Discovery Sóc un dels desenvolupadors del sistema operatiu Embox, i en aquest article parlaré de com vaig aconseguir executar OpenCV a la placa STM32746G.

Si escriviu alguna cosa com "OpenCV a la placa STM32" en un motor de cerca, podeu trobar moltes persones interessades a utilitzar aquesta biblioteca en plaques STM32 o altres microcontroladors.
Hi ha diversos vídeos que, a jutjar pel nom, haurien de demostrar què es necessita, però normalment (en tots els vídeos que vaig veure) a la placa STM32, només es va rebre la imatge de la càmera i el resultat es mostrava a la pantalla, i el processament d'imatges en si es va fer en un ordinador normal o en taulers més potents (per exemple, Raspberry Pi).

Per què és difícil?

La popularitat de les consultes de cerca s'explica pel fet que OpenCV és la biblioteca de visió per ordinador més popular, la qual cosa significa que més desenvolupadors la coneixen i la capacitat d'executar codi preparat per a l'escriptori en un microcontrolador simplifica molt el procés de desenvolupament. Però, per què encara no hi ha receptes ja fetes populars per resoldre aquest problema?

El problema d'utilitzar OpenCV en mantons petits està relacionat amb dues característiques:

  • Si compileu la biblioteca fins i tot amb un conjunt mínim de mòduls, simplement no s'adaptarà a la memòria flaix del mateix STM32F7Discovery (fins i tot sense tenir en compte el sistema operatiu) a causa d'un codi molt gran (diversos megabytes d'instruccions)
  • La pròpia biblioteca està escrita en C++, és a dir
    • Necessiteu suport per a un temps d'execució positiu (excepcions, etc.)
    • Poc suport per a LibC/Posix, que normalment es troba al sistema operatiu per a sistemes incrustats: necessiteu una biblioteca estàndard plus i una biblioteca estàndard de plantilles STL (vector, etc.)

Portada a Embox

Com és habitual, abans de portar qualsevol programa al sistema operatiu, és una bona idea intentar construir-lo en la forma en què ho pretenen els desenvolupadors. En el nostre cas, no hi ha problemes amb això: el codi font es pot trobar a github, la biblioteca està construïda sota GNU/Linux amb el cmake habitual.

La bona notícia és que OpenCV es pot crear com una biblioteca estàtica fora de la caixa, cosa que facilita la portabilitat. Recollim una biblioteca amb una configuració estàndard i veiem quant espai ocupen. Cada mòdul es recull en una biblioteca separada.

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

Com podeu veure a l'última línia, .bss i .data no ocupen gaire espai, però el codi supera els 70 MiB. És evident que si això està enllaçat estàticament a una aplicació concreta, el codi es reduirà.

Intentem llençar tants mòduls com sigui possible de manera que es munti un exemple mínim (que, per exemple, simplement sortirà la versió OpenCV), així que mirem cmake .. -LA i desactiva a les opcions tot el que s'apaga.

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

D'una banda, aquest és només un mòdul de la biblioteca, d'altra banda, això és sense optimització del compilador per a la mida del codi (-Os). ~3 MiB de codi encara són bastants, però ja donen esperança d'èxit.

Executeu a l'emulador

És molt més fàcil depurar a l'emulador, així que primer assegureu-vos que la biblioteca funcioni amb qemu. Com a plataforma emulada, vaig triar Integrator / CP, perquè en primer lloc, també és ARM i, en segon lloc, Embox admet la sortida de gràfics per a aquesta plataforma.

Embox té un mecanisme per construir biblioteques externes, utilitzant-lo afegim OpenCV com a mòdul (passant totes les mateixes opcions per a la compilació "mínima" en forma de biblioteques estàtiques), després d'això afegeixo una aplicació senzilla que sembla així:

version.cpp:

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

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

    return 0;
}

Muntem el sistema, l'executem: obtenim la sortida esperada.

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

El següent pas és executar algun exemple, preferiblement un dels estàndards que ofereixen els mateixos desenvolupadors. al vostre lloc. Vaig triar detector de fronteres canny.

L'exemple s'ha hagut de reescriure lleugerament per mostrar la imatge amb el resultat directament a la memòria intermèdia. Vaig haver de fer això, perquè. funció imshow() pot dibuixar imatges a través de les interfícies QT, GTK i Windows, que, per descomptat, definitivament no estarà a la configuració per a STM32. De fet, QT també es pot executar a STM32F7Discovery, però això es discutirà en un altre article 🙂

Després d'un breu aclariment en quin format s'emmagatzema el resultat del detector de vora, obtenim una imatge.

OpenCV a STM32F7-Discovery

imatge original

OpenCV a STM32F7-Discovery

Resultat

S'executa amb STM32F7Discovery

A 32F746GDISCOVERY hi ha diverses seccions de memòria de maquinari que podem utilitzar d'una manera o altra

  1. 320 KiB de RAM
  2. Flash d'1 MiB per a la imatge
  3. 8 MiB de SDRAM
  4. Flash QSPI NAND de 16 MiB
  5. ranura per a targeta microSD

Es pot utilitzar una targeta SD per emmagatzemar imatges, però en el context d'executar un exemple mínim, això no és gaire útil.
La pantalla té una resolució de 480×272, el que significa que la memòria del framebuffer serà de 522 bytes a una profunditat de 240 bits, és a dir. això és més que la mida de la RAM, de manera que el framebuffer i el munt (que serà necessari, inclòs per a OpenCV, per emmagatzemar dades per a imatges i estructures auxiliars) es trobaran a SDRAM, tota la resta (memòria per a piles i altres necessitats del sistema). ) anirà a la memòria RAM.

Si agafem la configuració mínima per a STM32F7Discovery (llenceu tota la xarxa, totes les ordres, fem les piles el més petites possibles, etc.) i hi afegim OpenCV amb exemples, la memòria requerida serà la següent:

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

Per a aquells que no estiguin molt familiaritzats amb quines seccions van a on, els explicaré: in .text и .rodata les instruccions i constants (en termes generals, dades de només lectura) es troben .data les dades són mutables, .bss hi ha variables "nul·lades", que, tanmateix, necessiten un lloc (aquesta secció "anirà" a la memòria RAM).

La bona notícia és que .data/.bss hauria d'encaixar, però amb .text el problema és que només hi ha 1 MiB de memòria per a la imatge. Es pot llençar fora .text la imatge de l'exemple i llegiu-la, per exemple, de la targeta SD a la memòria a l'inici, però fruit.png pesa uns 330 KiB, de manera que això no solucionarà el problema: la majoria .text consisteix en el codi OpenCV.

En general, només queda una cosa: carregar una part del codi en un flaix QSPI (té un mode de funcionament especial per assignar la memòria al bus del sistema, de manera que el processador pugui accedir directament a aquestes dades). En aquest cas, sorgeix un problema: en primer lloc, la memòria d'una unitat flaix QSPI no està disponible immediatament després de reiniciar el dispositiu (cal inicialitzar per separat el mode de mapa de memòria) i, en segon lloc, no podeu "flash" aquesta memòria amb un carregador d'arrencada conegut.

Com a resultat, es va decidir enllaçar tot el codi a QSPI i flashejar-lo amb un carregador escrit per si mateix que rebrà el binari requerit mitjançant TFTP.

Resultat

La idea de portar aquesta biblioteca a Embox va aparèixer fa aproximadament un any, però una i altra vegada es va ajornar per diversos motius. Un d'ells és el suport per a libstdc++ i la biblioteca de plantilles estàndard. El problema del suport de C++ a Embox està fora de l'abast d'aquest article, així que aquí només diré que hem aconseguit aquest suport en la quantitat adequada perquè aquesta biblioteca funcioni 🙂

Al final, aquests problemes es van superar (almenys prou perquè funcionés l'exemple d'OpenCV) i l'exemple es va executar. El tauler triga 40 segons llargs a cercar límits mitjançant el filtre Canny. Això, per descomptat, és massa llarg (hi ha consideracions sobre com optimitzar aquest tema, serà possible escriure un article separat sobre això en cas d'èxit).

OpenCV a STM32F7-Discovery

No obstant això, l'objectiu intermedi era crear un prototip que mostrés la possibilitat fonamental d'executar OpenCV a STM32, respectivament, aquest objectiu es va aconseguir, hurra!

tl;dr: instruccions pas a pas

0: Baixeu les fonts d'Embox, com aquesta:

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

1: Comencem per muntar un carregador d'arrencada que "flash" una unitat flaix QSPI.

    make confload-arm/stm32f7cube

Ara cal configurar la xarxa, perquè. Penjarem la imatge via TFTP. Per configurar les adreces IP del tauler i de l'amfitrió, heu d'editar el conf/rootfs/network.

Exemple de configuració:

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 - adreça d'amfitrió des d'on es carregarà la imatge, address - adreça de la junta.

Després d'això, recollim el carregador d'arrencada:

    make

2: La càrrega habitual del carregador d'arrencada (perdoneu el joc de paraules) al tauler: aquí no hi ha res específic, heu de fer-ho com per a qualsevol altra aplicació per a STM32F7Discovery. Si no saps com fer-ho, pots llegir-ne aquí.
3: compilació d'una imatge amb una configuració per a OpenCV.

    make confload-platform/opencv/stm32f7discovery
    make

4: Extracte de les seccions ELF per escriure a QSPI a 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

Hi ha un script al directori conf que fa això, de manera que el podeu executar

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

5: Mitjançant tftp, descarregueu qspi.bin.bin a una unitat flaix QSPI. A l'amfitrió, per fer-ho, copieu qspi.bin a la carpeta arrel del servidor tftp (normalment /srv/tftp/ o /var/lib/tftpboot/; els paquets per al servidor corresponent estan disponibles a les distribucions més populars, normalment anomenades tftpd o tftp-hpa, de vegades cal fer-ho systemctl start tftpd.service començar).

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

A Embox (és a dir, al carregador d'arrencada), heu d'executar l'ordre següent (suposem que el servidor té l'adreça 192.168.2.1):

    embox> qspi_loader qspi.bin 192.168.2.1

6: Amb comandament goto heu de "saltar" a la memòria QSPI. La ubicació específica variarà en funció de com estigui enllaçada la imatge, podeu veure aquesta adreça amb l'ordre mem 0x90000000 (l'adreça inicial s'adapta a la segona paraula de 32 bits de la imatge); també haureu de marcar la pila -s, l'adreça de pila és 0x90000000, exemple:

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

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

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

7: Llançament

    embox> edges 20

i gaudeix de la cerca de fronteres de 40 segons 🙂

Si alguna cosa va malament, escriviu un problema el nostre repositori, o a la llista de correu [protegit per correu electrònic], o en un comentari aquí.

Font: www.habr.com

Afegeix comentari