Sono uno degli sviluppatori del sistema operativo , 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 , 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 . Ho scelto .
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.

L'immagine originale

Risultato
Esecuzione 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 QSPI NAND
- 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/emboxPer 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).

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 ./embox1: Iniziamo con la compilazione del bootloader, che "flashera" la flash QSPI.
make confload-arm/stm32f7cubeOra è 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:02gateway — l'indirizzo dell'host da cui verrà caricata l'immagine, address — l'indirizzo della scheda.
Dopo di che compiliamo il bootloader:
make2: 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 .
3: Compilazione dell'immagine con la configurazione per OpenCV.
make confload-platform/opencv/stm32f7discovery
make4: 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=.dataNella directory conf si trova uno script che lo fa, quindi puoi eseguirlo
./conf/qspi_objcopy.sh # Il binario necessario -- build/base/bin/qspi.bin5: 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/tftpbootSull'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.16: 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 20e godiamoci una ricerca dei bordi di 40 secondi 🙂
Se qualcosa non funziona — scrivi un problema nel , o nella mailing list embox-devel@googlegroups.com, o nei commenti qui.
Fonte: habr.com
