OpenCV su STM32F7-Discovery

OpenCV su STM32F7-Discovery Sono uno degli sviluppatori del sistema operativo Embox, e in questo articolo racconterò come sono riuscito a far funzionare OpenCV sulla scheda STM32746G.

Se si cerca su un motore di ricerca qualcosa come «OpenCV su scheda STM32», si possono trovare diverse persone interessate all'uso di questa libreria su schede STM32 o su altri microcontrollori.
Ci sono diversi video che, a giudicare dal titolo, dovrebbero dimostrare ciò che serve, ma di solito (in tutti i video che ho visto) sulla scheda STM32 veniva effettuata solo l'acquisizione dell'immagine dalla telecamera e la visualizzazione del risultato sullo schermo, mentre il trattamento dell'immagine avveniva o su un comune computer o su schede più potenti (ad esempio, Raspberry Pi).

Perché è complicato?

La popolarità delle ricerche è dovuta al fatto che OpenCV è la libreria di visione artificiale più popolare, il che significa che più sviluppatori hanno familiarità con essa, e la possibilità di eseguire codice pronto per desktop su un microcontrollore semplifica notevolmente il processo di sviluppo. Ma perché non ci sono ancora delle ricette pronte e popolari per risolvere questo problema?

Il problema dell'utilizzo di OpenCV su schede di piccole dimensioni è legato a due peculiarità:

  • Se si compila la libreria anche con un insieme minimale di moduli, nella memoria flash della stessa STM32F7Discovery semplicemente non ci sta (anche senza considerare il sistema operativo) a causa di un codice molto grande (diversi megabyte di istruzioni)
  • La libreria stessa è scritta in C++, il che significa
    • È necessaria la supporto del runtime C++ (eccezioni, ecc.)
    • Poca supporto per LibC/Posix, che di solito è presente nei sistemi operativi per sistemi embedded — è necessaria una libreria standard C++ e una libreria standard di template STL (vector, ecc.)

Porting su Embox

Come al solito, prima di portare qualsiasi programma in un sistema operativo è meglio provare a compilarlo nella forma in cui è stato concepito dagli sviluppatori. Nel nostro caso non ci sono problemi — il codice sorgente può essere trovato su GitHub, la libreria si compila su GNU/Linux usando il normale cmake.

Dalle buone notizie — OpenCV può essere assemblato come libreria statica, il che rende il porting più semplice. Compiliamo la libreria con la configurazione standard e vediamo quanto spazio occupano. Ogni modulo viene assemblato in una libreria separata.

> dimensione lib/*so --totali
   testo    dati     bss     dec     esadecimale nomefile
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 (TOTALE)

Come si può vedere dall'ultima riga, .bss e .data occupano poco spazio, mentre il codice supera i 70 MiB. È chiaro che se ciò viene collegato staticamente a un'applicazione specifica, il codice diminuirà.

Cerchiamo di rimuovere quanti più moduli possibile, in modo da ottenere un esempio minimo (che, ad esempio, semplicemente visualizzerà la versione di OpenCV), quindi vediamo. cmake .. -LA e disattiviamo tutte le opzioni che possono essere disattivate.

        -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

> dimensione lib/libopencv_core.a --totali
   testo    dati     bss     dec     esadecimale nomefile
3317069   36425   17987 3371481  3371d9 (TOTALE)

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 è comunque piuttosto tanto, ma offre già speranza di successo.

Esecuzione in emulatore

È molto più semplice eseguire il debug in un emulatore, quindi prima ci assicuriamo che la libreria funzioni su qemu. Come piattaforma emulata ho scelto Integrator/CP, perché, da un lato, è anch'essa ARM, e dall'altro, Embox supporta l'output grafico per questa piattaforma.

In Embox c'è un meccanismo per compilare librerie esterne, con il quale aggiungiamo OpenCV come modulo (passando tutte le stesse opzioni per la 'compilazione minima' come librerie statiche), dopo di che aggiungo una semplice applicazione che appare così:

version.cpp:

#include 
#include 

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

    return 0;
}

Compiliamo il sistema, lo avviamo e otteniamo l'output previsto.

root@embox: /#opencv_version                                                     
OpenCV: 
Configurazione generale per OpenCV 4.0.1 =====================================
  Controllo versione:               bd6927bdf-dirty

  Piattaforma:
    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
    Generatore CMake:             Unix Makefiles
    Strumento di build CMake:     /usr/bin/make
    Configurazione:               Debug

  Caratteristiche CPU/HW:
    Baseline:
      richiesto:                 DETECT
      disabilitato:              VFPV3 NEON

  C/C++:
    Costruito come librerie dinamiche?:      NO
< Дальше идут прочие параметры сборки -- с какими флагами компилировалось,
  какие модули OpenCV включены в сборку и т.п.>

Il passo successivo è eseguire un esempio, preferibilmente uno degli standard proposti dagli stessi sviluppatori sul loro sito. Ho scelto il rilevatore di bordi Canny.

Ho dovuto modificare leggermente l'esempio per visualizzare l'immagine con il risultato direttamente nel framebuffer. Questo è stato necessario perché la funzione imshow() può visualizzare immagini tramite le interfacce QT, GTK e Windows, che naturalmente non ci saranno nella configurazione per STM32. In realtà, QT può anche essere eseguito su STM32F7Discovery, ma di questo parleremo in un altro articolo 🙂

Dopo aver scoperto rapidamente in quale formato viene memorizzato il risultato del rilevatore di bordi, otteniamo l'immagine.

OpenCV su STM32F7-Discovery

L'immagine originale

OpenCV su STM32F7-Discovery

Risultato

Esecuzione su STM32F7Discovery

Su 32F746GDISCOVERY ci sono diverse sezioni di memoria hardware che possiamo utilizzare in vari modi

  1. 320KiB di RAM
  2. 1MiB di memoria flash per l'immagine
  3. 8MiB di SDRAM
  4. 16MiB di chiavetta QSPI NAND
  5. Slot per scheda microSD

La scheda SD può essere utilizzata per memorizzare immagini, ma nel contesto dell'esecuzione di un esempio minimo ciò non è molto utile.
Il display ha una risoluzione di 480×272, il che significa che la memoria per il framebuffer sarà di 522 240 byte con una profondità di 32 bit, cioè è più grande della dimensione della RAM, così il framebuffer e la heap (necessaria anche per OpenCV, per memorizzare i dati per le immagini e le strutture ausiliarie) saranno collocati nella SDRAM, mentre tutto il resto (memoria per stack e altre necessità di sistema) andrà nella RAM.

Se prendiamo la configurazione minima per STM32F7Discovery (eliminando tutta la rete, tutti i comandi, riducendo al minimo gli stack, ecc.) e aggiungiamo OpenCV con esempi, 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 sono molto familiari con le sezioni in cui si suddivide il progetto, spiego: in .text e .rodata si trovano istruzioni e costanti (in termini semplici, dati readonly), in .data si trovano dati modificabili, in .bss si trovano variabili "azzerate", che comunque necessitano di spazio (questa sezione "andrà" in RAM).

La buona notizia è che .data/.bss devono essere memorizzate, ma c'è un .text problema: sotto l'immagine c'è solo 1MiB di memoria. Si può rimuovere .text l'immagine dall'esempio e leggerla, ad esempio, da una scheda SD in memoria all'avvio, ma fruits.png pesa circa 330KiB, quindi questo non risolverà il problema: gran parte .text è composta proprio dal codice di OpenCV.

In sostanza, rimane solo una possibilità: caricare parzialmente il codice su un flash QSPI (questo ha una modalità di funzionamento speciale per mappare la memoria sul bus di sistema, in modo che il processore possa accedere direttamente a questi dati). Tuttavia, si presenta un problema: innanzitutto, la memoria della flash QSPI non è accessibile subito dopo il riavvio del dispositivo (è necessario inizializzare separatamente la modalità di memory-mapping); in secondo luogo, non è possibile "flashare" questa memoria con un bootloader standard.

Pertanto, è stato deciso di linkare tutto il codice nella QSPI e di flasharlo con un bootloader personalizzato, che riceverà il binary necessario tramite TFTP.

Risultato

L'idea di portare questa libreria su Embox è emersa circa un anno fa, ma di volta in volta è stata rimandata per vari motivi. Uno di questi è il supporto per libstdc++ e la standard template library. Il problema del supporto C++ in Embox va oltre lo scopo di questo articolo, quindi dirò solo che siamo riusciti a ottenere questo supporto nella misura necessaria per il funzionamento di questa libreria 🙂

Alla fine, questi problemi sono stati superati (almeno in misura sufficiente per far funzionare l'esempio di OpenCV), e l'esempio è stato eseguito. Al microcontrollore servono 40 lunghe secondi per cercare i bordi con il filtro di Canny. È, ovviamente, troppo tempo (ci sono idee su come ottimizzare questa procedura, di questo si potrà scrivere un articolo separato nel caso in cui si ottenga successo).

OpenCV su STM32F7-Discovery

Tuttavia, l'obiettivo intermedio era creare un prototipo che mostrasse la possibilità principe di far funzionare OpenCV su STM32, quindi questo obiettivo è stato raggiunto, evviva!

tl;dr: istruzioni passo dopo passo

0: Scarica i sorgenti di Embox, ad esempio in questo modo:

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

1: Iniziamo con la compilazione del bootloader, che "flashera" la flash QSPI.

    make confload-arm/stm32f7cube

Ora è necessario configurare la rete, poiché 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 — l'indirizzo dell'host da cui verrà caricata l'immagine, address — l'indirizzo della scheda.

Dopo di che compiliamo il bootloader:

    make

2: Caricamento normale del bootloader (scusate il gioco di parole) sulla scheda — non c'è nulla di specifico, è necessario farlo come per qualsiasi altra applicazione per STM32F7Discovery. Se non sai come farlo, puoi leggere al riguardo qui.
3: Compilazione dell'immagine con la configurazione per OpenCV.

    make confload-platform/opencv/stm32f7discovery
    make

4: Estrazione delle 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

Nella directory conf si trova uno script che lo fa, quindi puoi eseguirlo

    ./conf/qspi_objcopy.sh # Il binario necessario -- build/base/bin/qspi.bin

5: Con tftp carichiamo qspi.bin sul QSPI-flash. Sul host è necessario copiare qspi.bin nella cartella principale del server tftp (di solito questa è /srv/tftp/ o /var/lib/tftpboot/; i pacchetti per il server corrispondente sono disponibili nella maggior parte delle distribuzioni popolari, di solito si chiama tftpd o tftp-hpa, a volte è necessario fare systemctl start tftpd.service per avviarlo).

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

Sull'Embox (cioè nel bootloader) è necessario eseguire questo comando (presumendo che l'indirizzo del server sia 192.168.2.1):

    embox> qspi_loader qspi.bin 192.168.2.1

6: Con il comando goto è necessario "saltare" nella memoria QSPI. La posizione specifica varierà a seconda di come l'immagine è aggregata, per visualizzare questo indirizzo è possibile utilizzare il comando mem 0x90000000 (l'indirizzo di avvio si trova nel secondo word a 32 bit dell'immagine); sarà inoltre necessario impostare lo stack con il flag -s, l'indirizzo dello stack si trova all'indirizzo 0x90000000, esempio:

    embox>mem 0x90000000
    0x90000000:     0x20023200  0x9000c27f  0x9000c275  0x9000c275
                      ↑           ↑
              questo è l'indirizzo    questo è l'indirizzo 
                dello stack        della prima
                           istruzione

    embox>goto -i 0x9000c27f -s 0x20023200 # Il flag -i è necessario per disabilitare le interruzioni durante l'inizializzazione del sistema

    

7: Avviamo

    embox> edges 20

e godiamoci una ricerca dei bordi di 40 secondi 🙂

Se qualcosa non funziona — scrivi un problema nel nostro repository, o nella mailing list embox-devel@googlegroups.com, o nei commenti qui.

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, server VPS VDS 🔥 Acquista hosting affidabile per siti web con protezione DDoS, server VPS VDS - ProHoster