Sono uno sviluppatore del sistema operativo , e in questo articolo spiegherò come sono riuscito a far funzionare OpenCV sulla scheda STM32746G.
Cercando su un motore di ricerca qualcosa come «OpenCV su scheda STM32», si possono trovare molte persone interessate all'uso di questa libreria su schede STM32 o altri microcontrollori.
Ci sono alcuni video che, a giudicare dai titoli, dovrebbero dimostrare ciò che serve, ma di solito (in tutti i video che ho visto) sulla scheda STM32 si otteneva solo un'immagine dalla telecamera e si mostrava il risultato sullo schermo, mentre l'elaborazione delle immagini avveniva o su un computer normale o su schede più potenti (ad esempio, Raspberry Pi).
Perché è complicato?
La popolarità delle ricerche si spiega con il fatto che OpenCV è la libreria di computer vision più popolare, quindi è conosciuta da un maggior numero di sviluppatori, e la possibilità di eseguire codice destinato al desktop su un microcontrollore semplifica notevolmente il processo di sviluppo. Ma perché non ci sono ancora ricette pronte popolari per risolvere questo problema?
Il problema di utilizzare OpenCV su piccole schede è legato a due caratteristiche:
- Anche compilando la libreria con un set minimo di moduli, semplicemente non entrerà nella memoria flash della stessa STM32F7Discovery (anche senza considerare il sistema operativo) a causa di un codice molto grande (una serie di megabyte di istruzioni).
- La libreria stessa è scritta in C++, il che implica.
- È necessaria la supporto per il runtime C++ (eccessioni, ecc.).
- C'è poca supporto per LibC/Posix, tipico dei sistemi operativi per sistemi embedded — è necessaria la libreria standard C++ e la libreria standard dei template STL (vector, ecc.).
Porting su Embox.
Come al solito, prima di portare qualsiasi programma su un sistema operativo, è una buona pratica provare a compilarlo nella forma in cui è stato progettato dagli sviluppatori. Nel nostro caso non ci sono problemi — il codice sorgente può essere trovato su. , la libreria si compila su GNU/Linux con un normale cmake.
Dalla buona notizia — OpenCV può essere compilato come libreria statica, il che semplifica il porting. Compiliamo la libreria con la configurazione standard e vediamo quanto spazio occupa. Ogni modulo viene compilato 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 non molto spazio, ma il codice supera i 70 MiB. È chiaro che se questo viene collegato staticamente con un'app specifica, il codice diminuirà.
Proveremo a rimuovere il maggior numero possibile di moduli per creare un esempio minimo (che, ad esempio, mostrerà semplicemente la versione di OpenCV), quindi guardiamo cmake .. -LA e disabilitiamo nelle opzioni tutto ciò che può essere disabilitato.
-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 lato, ciò avviene senza ottimizzazione da parte del compilatore per le dimensioni del codice (-Os). ~3 MiB di codice — è comunque abbastanza, ma già dà speranza di successo.
Avvio nell'emulatore
È molto più facile eseguire il debug nell'emulatore, quindi prima verifichiamo che la libreria funzioni su qemu. Come piattaforma emulata ho scelto Integrator/CP, poiché, da un lato, è sempre ARM, e dall'altro Embox supporta l'output grafico per questa piattaforma.
In Embox c'è un meccanismo per la costruzione di librerie esterne; usandolo aggiungiamo OpenCV come modulo (passando tutte le stesse opzioni per la ‘costruzione minima’ sotto forma di 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, avviamo — otteniamo l'output previsto.
root@embox:/#opencv_version
OpenCV:
Configurazione generale per OpenCV 4.0.1 =====================================
Controllo della 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?: NOIl passo successivo è eseguire un esempio, meglio se uno standard proposto dagli sviluppatori stessi . Ho scelto .
L'esempio ha dovuto essere leggermente modificato per visualizzare direttamente l'immagine con il risultato nel framebuffer. Questo è stato necessario poiché la funzione imshow() può visualizzare immagini tramite le interfacce QT, GTK e Windows, che, ovviamente, non saranno presenti nella configurazione per STM32. In realtà, QT può anche essere eseguito su STM32F7Discovery, ma di questo si parlerà in un altro articolo 🙂
Dopo un breve chiarimento sul formato in cui viene memorizzato il risultato del rilevatore di bordi, otteniamo l'immagine.

Immagine originale

Risultato
Avvio su STM32F7Discovery
Su 32F746GDISCOVERY ci sono diverse sezioni di memoria hardware che possiamo utilizzare in vari modi.
- 320KiB di RAM
- 1MiB di memoria flash per l'immagine
- 8MiB di SDRAM
- 16MiB di chiavetta NAND QSPI
- Fessura per scheda microSD
La scheda SD può essere utilizzata per memorizzare immagini, ma nel contesto di avviare un esempio minimo 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è è maggiore della dimensione della RAM, quindi il framebuffer e lo heap (che sarà necessario anche per OpenCV, per memorizzare dati per immagini e strutture ausiliarie) verranno posizionati in SDRAM, mentre tutto il resto (memoria per stack e altre necessità di sistema) andrà in RAM.
Se consideriamo la configurazione minima per STM32F7Discovery (eliminare tutta la rete, tutti i comandi, rendere gli stack il più piccoli possibile, ecc.) e aggiungiamo OpenCV con esempi, la memoria necessaria sarà la seguente:
testo dati bss dec hex nomefile
2876890 459208 312736 3648834 37ad42 build/base/bin/emboxPer chi non è molto familiare con dove vanno le varie sezioni, spiego: .text e .rodata contiene istruzioni e costanti (in parole povere, dati di sola lettura), in .data si trovano i dati modificabili, in .bss ci sono variabili "azzerate" che, tuttavia, necessitano di spazio (questa sezione "andrà" in RAM).
La buona notizia è che .data/.bss devono essere allocati, ma la .text brutta notizia è che c'è solo 1MiB di memoria per l'immagine. Si può eliminare 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 l'immagine dell'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: la maggior parte .text è composta proprio dal codice OpenCV.
In sostanza, rimane solo una possibilità — caricare parte del codice su una flash QSPI (ha una modalità speciale di funzionamento per la mappatura della memoria sul bus di sistema, permettendo al processore di accedere a questi dati direttamente). Tuttavia, sorge un problema: innanzitutto, la memoria della flash QSPI non è accessibile subito dopo il riavvio del dispositivo (è necessario inizializzare separatamente la modalità di mappatura della memoria), e in secondo luogo, non si può "flashare" questa memoria con un bootloader convenzionale.
Alla fine è stato deciso di collegare tutto il codice in QSPI e di programmarlo con un bootloader personalizzato, che riceverà il binario necessario via TFTP.
Risultato
L'idea di portare questa libreria su Embox è emersa circa un anno fa, ma è stata continuamente rinviata per vari motivi. Uno di questi è il supporto di libstdc++ e della standard template library. Il problema del supporto di C++ in Embox esula da questo articolo, quindi qui dirò solo che siamo riusciti a ottenere questo supporto nel volume necessario per far funzionare questa libreria 🙂
Alla fine questi problemi sono stati superati (almeno in misura sufficiente per far funzionare l'esempio di OpenCV) e l'esempio è stato avviato. La scheda impiega 40 lunghissimi secondi per trovare i bordi con il filtro di Canny. Questo è, ovviamente, troppo lungo (ci sono considerazioni su come ottimizzare questo processo, su cui si potrebbe scrivere un articolo separato in caso di successo).

Tuttavia, l'obiettivo intermedio era creare un prototipo che mostrasse la possibilità di eseguire OpenCV su STM32, quindi questo obiettivo è stato raggiunto, evviva!
tl;dr: istruzioni passo passo
0: Scarica il codice sorgente di Embox, ad esempio così:
git clone https://github.com/embox/embox && cd ./embox1: Iniziamo con la costruzione del bootloader che "flasherà" la memoria QSPI.
make confload-arm/stm32f7cubeOra dobbiamo configurare la rete, dato che 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:02gateway — indirizzo dell'host da cui verrà caricata l'immagine, address — indirizzo della scheda.
Dopo di ciò compiliamo il bootloader:
make2: Caricamento normale del bootloader (scusate il gioco di parole) sulla scheda — qui non c'è nulla di specifico, bisogna farlo come per qualsiasi altra applicazione per STM32F7Discovery. Se non sapete come fare, potete leggere al riguardo. .
3: Compilazione dell'immagine con la configurazione per OpenCV.
make confload-platform/opencv/stm32f7discovery
make4: Estrazione 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=.dataNella directory conf si trova uno script che fa questo, quindi è possibile eseguirlo
./conf/qspi_objcopy.sh # Il binario necessario -- build/base/bin/qspi.bin5: Utilizzando tftp, carichiamo qspi.bin.bin sulla memoria flash QSPI. Sul host, è necessario copiare qspi.bin nella cartella radice del server tftp (di solito si trova in /srv/tftp/ o /var/lib/tftpboot/; i pacchetti per il server appropriato sono disponibili nella maggior parte delle distribuzioni popolari e di solito si chiamano 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/tftpbootSu Embox (cioè nel bootloader), è necessario eseguire il seguente comando (supponiamo che l'indirizzo del server sia 192.168.2.1):
embox> qspi_loader qspi.bin 192.168.2.16: Con il comando goto è necessario «saltare» nella memoria QSPI. La posizione specifica varierà a seconda di come l'immagine è linkata, è possibile visualizzare questo indirizzo con il comando mem 0x90000000 (l'indirizzo di partenza è memorizzato nel secondo word a 32 bit dell'immagine); sarà inoltre necessario impostare il flag dello stack -s, l'indirizzo dello stack si trova all'indirizzo 0x90000000, esempio:
embox>mem 0x90000000
0x90000000: 0x20023200 0x9000c27f 0x9000c275 0x9000c275
↑ ↑
questo indirizzo questo indirizzo
stack prima
istruzione
embox>goto -i 0x9000c27f -s 0x20023200 # Il flag -i serve per vietare le interruzioni durante l'inizializzazione del sistema7: Avviamo
embox> edges 20e goditi una ricerca di 40 secondi ai confini 🙂
Se qualcosa va storto, scrivi un'issue nel , oppure alla mailing list embox-devel@googlegroups.com, o nei commenti qui.
Fonte: habr.com
