OpenCV su STM32F7-Discovery

OpenCV su STM32F7-Discovery Sono uno degli sviluppatori del sistema operativo Emboxe in questo articolo parlerò di come sono riuscito a eseguire OpenCV sulla scheda STM32746G.

Se digiti qualcosa come "OpenCV su scheda STM32" in un motore di ricerca, puoi trovare alcune persone interessate a utilizzare questa libreria su schede STM32 o altri microcontrollori.
Ci sono diversi video che, a giudicare dal nome, dovrebbero dimostrare cosa serve, ma solitamente (in tutti i video che ho visto) sulla scheda STM32 veniva ricevuta solo l'immagine dalla telecamera e il risultato veniva visualizzato sullo schermo, e l'elaborazione delle immagini stessa è stata eseguita su un normale computer o su schede più potenti (ad esempio, Raspberry Pi).

Perché è difficile?

La popolarità delle query di ricerca è spiegata dal fatto che OpenCV è la libreria di visione artificiale più popolare, il che significa che più sviluppatori la conoscono e la possibilità di eseguire codice pronto per il desktop su un microcontrollore semplifica notevolmente il processo di sviluppo. Ma perché non ci sono ancora ricette già pronte popolari per risolvere questo problema?

Il problema dell'utilizzo di OpenCV su piccoli scialli è legato a due caratteristiche:

  • Se compili la libreria anche con un set minimo di moduli, semplicemente non entrerà nella memoria flash dello stesso STM32F7Discovery (anche senza tener conto del sistema operativo) a causa di un codice molto grande (diversi megabyte di istruzioni)
  • La libreria stessa è scritta in C++, il che significa
    • Hai bisogno di supporto per un runtime positivo (eccezioni, ecc.)
    • Poco supporto per LibC/Posix, che di solito si trova nei sistemi operativi per sistemi embedded: è necessaria una libreria standard plus e una libreria di modelli STL standard (vettore, ecc.)

Trasferimento su Embox

Come al solito, prima di eseguire il porting di qualsiasi programma sul sistema operativo, è una buona idea provare a compilarlo nella forma in cui lo intendevano gli sviluppatori. Nel nostro caso, non ci sono problemi con questo: il codice sorgente può essere trovato su githabe, la libreria è compilata sotto GNU/Linux con il solito cmake.

La buona notizia è che OpenCV può essere creato come una libreria statica pronta all'uso, il che semplifica il porting. Raccogliamo una libreria con una configurazione standard e vediamo quanto spazio occupano. Ogni modulo è raccolto in una libreria separata.

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

Come puoi vedere dall'ultima riga, .bss e .data non occupano molto spazio, ma il codice supera i 70 MiB. È chiaro che se questo è legato staticamente a una specifica applicazione, il codice diventerà meno.

Proviamo a eliminare quanti più moduli possibile in modo da assemblare un esempio minimo (che, ad esempio, produrrà semplicemente la versione OpenCV), quindi guardiamo cmake .. -LA e spegni nelle opzioni tutto ciò che si spegne.

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

Da un lato, questo è solo un modulo della libreria, dall'altro è senza ottimizzazione del compilatore per la dimensione del codice (-Os). ~3 MiB di codice sono ancora parecchi, ma fanno già sperare in un successo.

Esegui nell'emulatore

È molto più semplice eseguire il debug sull'emulatore, quindi assicurati prima che la libreria funzioni su qemu. Come piattaforma emulata, ho scelto Integrator / CP, perché in primo luogo, è anche ARM e, in secondo luogo, Embox supporta l'output grafico per questa piattaforma.

Embox ha un meccanismo per costruire librerie esterne, usandolo aggiungiamo OpenCV come modulo (passando tutte le stesse opzioni per la build "minima" sotto forma di librerie statiche), dopodiché aggiungo una semplice applicazione che assomiglia a questa:

version.cpp:

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

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

    return 0;
}

Montiamo il sistema, lo eseguiamo: otteniamo l'output previsto.

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

Il passaggio successivo consiste nell'eseguire qualche esempio, preferibilmente uno di quelli standard offerti dagli stessi sviluppatori. sul tuo sito. Ho scelto rivelatore di confini astuto.

L'esempio è stato leggermente riscritto per visualizzare l'immagine con il risultato direttamente nel frame buffer. Ho dovuto farlo, perché. funzione imshow() può disegnare immagini tramite le interfacce QT, GTK e Windows, che, ovviamente, non saranno sicuramente nella configurazione per STM32. In effetti, QT può essere eseguito anche su STM32F7Discovery, ma questo sarà discusso in un altro articolo 🙂

Dopo un breve chiarimento in quale formato è memorizzato il risultato del rilevatore di bordi, otteniamo un'immagine.

OpenCV su STM32F7-Discovery

immagine originale

OpenCV su STM32F7-Discovery

risultato

In esecuzione su STM32F7Discovery

Su 32F746GDISCOVERY ci sono diverse sezioni di memoria hardware che possiamo usare in un modo o nell'altro

  1. RAM da 320 KiB
  2. Flash da 1 MiB per l'immagine
  3. SDRAM da 8 MB
  4. Flash NAND QSPI da 16 MiB
  5. slot per schede microSD

Una scheda SD può essere utilizzata per memorizzare le immagini, ma nel contesto dell'esecuzione di un esempio minimo, questo non è molto utile.
Il display ha una risoluzione di 480×272, il che significa che la memoria del framebuffer sarà di 522 byte a una profondità di 240 bit, ovvero questo è più della dimensione della RAM, quindi il framebuffer e l'heap (che saranno richiesti, anche per OpenCV, per memorizzare i dati per immagini e strutture ausiliarie) si troveranno nella SDRAM, tutto il resto (memoria per stack e altre esigenze di sistema ) andrà alla RAM .

Se prendiamo la configurazione minima per STM32F7Discovery (eliminiamo l'intera rete, tutti i comandi, rendiamo gli stack il più piccoli possibile, ecc.) e aggiungiamo OpenCV con esempi lì, la memoria richiesta sarà la seguente:

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

Per coloro che non hanno molta familiarità con quali sezioni vanno dove, spiegherò: in .text и .rodata istruzioni e costanti (in parole povere, dati di sola lettura) si trovano dentro .data i dati sono mutabili, .bss ci sono variabili "annullate", che tuttavia necessitano di un posto (questa sezione "andrà" alla RAM).

La buona notizia è che .data/.bss dovrebbe adattarsi, ma con .text il problema è che c'è solo 1MiB di memoria per l'immagine. Può essere buttato fuori .text l'immagine dall'esempio e leggerla, ad esempio, dalla scheda SD nella memoria all'avvio, ma fruits.png pesa circa 330 KiB, quindi questo non risolverà il problema: la maggior parte .text è costituito dal codice OpenCV.

In generale, rimane solo una cosa: caricare una parte del codice su un flash QSPI (ha una modalità operativa speciale per mappare la memoria sul bus di sistema, in modo che il processore possa accedere direttamente a questi dati). In questo caso, sorge un problema: in primo luogo, la memoria di un'unità flash QSPI non è disponibile immediatamente dopo il riavvio del dispositivo (è necessario inizializzare separatamente la modalità mappata in memoria) e, in secondo luogo, non è possibile eseguire il "flash" di questa memoria con un bootloader familiare.

Di conseguenza, è stato deciso di collegare tutto il codice in QSPI e di eseguirne il flashing con un caricatore scritto da sé che riceverà il binario richiesto tramite TFTP.

risultato

L'idea di portare questa libreria su Embox è apparsa circa un anno fa, ma più e più volte è stata rinviata per vari motivi. Uno di questi è il supporto per libstdc++ e la libreria di modelli standard. Il problema del supporto C++ in Embox va oltre lo scopo di questo articolo, quindi qui dirò solo che siamo riusciti a ottenere questo supporto nella giusta quantità per far funzionare questa libreria 🙂

Alla fine, questi problemi sono stati superati (almeno abbastanza per far funzionare l'esempio OpenCV) e l'esempio è stato eseguito. Il tabellone impiega 40 lunghi secondi per cercare i confini utilizzando il filtro Canny. Questo, ovviamente, è troppo lungo (ci sono considerazioni su come ottimizzare questo argomento, sarà possibile scrivere un articolo a parte su questo in caso di successo).

OpenCV su STM32F7-Discovery

Tuttavia, l'obiettivo intermedio era creare un prototipo che mostrasse la possibilità fondamentale di eseguire OpenCV su STM32, rispettivamente, questo obiettivo è stato raggiunto, evviva!

tl; dr: istruzioni passo passo

0: scarica i sorgenti di Embox, come questo:

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

1: Iniziamo assemblando un bootloader che "lampeggia" un'unità flash QSPI.

    make confload-arm/stm32f7cube

Ora devi configurare la rete, perché. Caricheremo l'immagine tramite TFTP. Per impostare gli indirizzi IP della scheda e dell'host, è necessario modificare il file conf/rootfs/network.

Esempio di configurazione:

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 - indirizzo host da cui verrà caricata l'immagine, address - indirizzo del consiglio.

Successivamente, raccogliamo il bootloader:

    make

2: Il solito caricamento del bootloader (scusate il gioco di parole) sulla scheda - non c'è niente di specifico qui, è necessario farlo come per qualsiasi altra applicazione per STM32F7Discovery. Se non sai come farlo, puoi leggerlo qui.
3: Compilazione di un'immagine con una configurazione per OpenCV.

    make confload-platform/opencv/stm32f7discovery
    make

4: Estratto dalle sezioni ELF da scrivere in QSPI in 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

C'è uno script nella directory conf che fa questo, quindi puoi eseguirlo

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

5: Utilizzando tftp, scarica qspi.bin.bin su un'unità flash QSPI. Sull'host, per fare ciò, copia qspi.bin nella cartella principale del server tftp (di solito /srv/tftp/ o /var/lib/tftpboot/; i pacchetti per il server corrispondente sono disponibili nelle distribuzioni più popolari, solitamente chiamati tftpd o tftp-hpa, a volte devi fare systemctl start tftpd.service iniziare).

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

Su Embox (ovvero nel bootloader), è necessario eseguire il seguente comando (supponiamo che il server abbia l'indirizzo 192.168.2.1):

    embox> qspi_loader qspi.bin 192.168.2.1

6: Con comando goto devi "saltare" nella memoria QSPI. La posizione specifica varierà a seconda di come l'immagine è collegata, puoi vedere questo indirizzo con il comando mem 0x90000000 (l'indirizzo iniziale rientra nella seconda parola a 32 bit dell'immagine); dovrai anche contrassegnare lo stack -s, l'indirizzo dello stack è 0x90000000, ad esempio:

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

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

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

7: Lancio

    embox> edges 20

e goditi la ricerca del confine di 40 secondi 🙂

Se qualcosa va storto, scrivi un problema il nostro deposito, o alla mailing list [email protected], o in un commento qui.

Fonte: habr.com

Aggiungi un commento