OpenCV sur STM32F7-Discovery

Je suis l'un des développeurs du système d'exploitation Embox , et dans cet article, je parlerai de la façon dont j'ai réussi à exécuter OpenCV sur la carte STM32746G.


Si vous entrez dans un moteur de recherche comme "OpenCV sur carte STM32", vous pouvez en trouver un certain nombre qui sont intéressés à utiliser cette bibliothèque sur des cartes STM32 ou d'autres microcontrôleurs.
Il y a plusieurs vidéos qui, à en juger par le nom, devraient démontrer ce qui est nécessaire, mais généralement (dans toutes les vidéos que j'ai vues) sur la carte STM32, seule l'image a été reçue de la caméra et le résultat a été affiché à l'écran, et le traitement d'image lui-même a été effectué soit sur un ordinateur ordinaire, ou sur des cartes plus puissantes (par exemple, Raspberry Pi).


Pourquoi est-ce difficile?


La popularité des requêtes de recherche s'explique par le fait qu'OpenCV est la bibliothèque de vision par ordinateur la plus populaire, ce qui signifie que davantage de développeurs la connaissent et que la possibilité d'exécuter du code prêt pour le bureau sur le microcontrôleur simplifie considérablement le processus de développement. Mais pourquoi n'y a-t-il toujours pas de recettes prêtes à l'emploi populaires pour résoudre ce problème?


Le problème de l'utilisation d'OpenCV sur de petites cartes est associé à deux fonctionnalités:


  • Si vous compilez la bibliothèque même avec un ensemble minimal de modules, elle ne rentrera tout simplement pas dans la mémoire flash du même STM32F7Discovery (même sans tenir compte du système d'exploitation) en raison du code très volumineux (plusieurs mégaoctets d'instructions)
  • La bibliothèque elle-même est écrite en C ++, ce qui signifie
    • Besoin de support pour un runtime positif (exceptions, etc.)
    • Il y a peu de support pour LibC / Posix, qui se trouve généralement dans le système d'exploitation pour les systèmes embarqués - vous avez besoin d'une bibliothèque standard d'avantages et d'une bibliothèque standard de modèles STL (vecteur, etc.)

Portage vers Embox


Comme d'habitude, avant de porter des programmes sur le système d'exploitation, c'est une bonne idée d'essayer de les assembler sous la forme voulue par les développeurs. Dans notre cas, cela ne pose aucun problème - les sources peuvent être trouvées sur le github , la bibliothèque est construite sous GNU / Linux avec le cmake habituel.


De la bonne nouvelle - OpenCV prêt à l'emploi peut être assemblé comme une bibliothèque statique, ce qui facilite le portage. Nous collectons la bibliothèque avec la configuration standard et voyons combien d'espace ils prennent. Chaque module est assemblé dans une bibliothèque distincte.


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

Comme vous pouvez le voir sur la dernière ligne, .bss et .data ne prennent pas beaucoup de place, mais le code dépasse 70 Mo. Il est clair que si cela est lié statiquement à une application spécifique, le code deviendra plus petit.


Essayons de jeter autant de modules que possible afin qu'un exemple minimal soit construit (qui, par exemple, affiche simplement la version d'OpenCV), alors regardez cmake .. -LA et désactivez tout ce qui est désactivé dans les options.


  -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'une part, ce n'est qu'un module de bibliothèque, d'autre part, il est sans optimisation par le compilateur en termes de taille de code ( -Os ). ~ 3 Mio de code, c'est encore beaucoup, mais cela donne déjà de l'espoir pour le succès.


Exécuter dans l'émulateur


Le débogage sur l'émulateur est beaucoup plus facile, alors assurez-vous d'abord que la bibliothèque fonctionne sur qemu. En tant que plate-forme émulée, j'ai choisi Integrator / CP, car d'une part, il s'agit également d'ARM et, d'autre part, Embox prend en charge la sortie graphique pour cette plate-forme.


Embox a un mécanisme pour construire des bibliothèques externes, en l'utilisant nous ajoutons OpenCV en tant que module (en passant toutes les mêmes options pour la construction "minimale" en tant que bibliothèques statiques), après quoi j'ajoute l'application la plus simple qui ressemble à ceci:


 version.cpp: #include <stdio.h> #include <opencv2/core/utility.hpp> int main() { printf("OpenCV: %s", cv::getBuildInformation().c_str()); return 0; } 

Nous assemblons le système, le faisons fonctionner - nous obtenons la conclusion attendue.


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

L'étape suivante consiste à exécuter un exemple, le meilleur de tous les standards de ceux que les développeurs eux-mêmes proposent sur leur site Web . J'ai choisi le détecteur de frontière de Canny .


L'exemple a dû être réécrit un peu afin d'afficher l'image avec le résultat directement dans le tampon de trame. Je devais faire ça parce que la fonction imshow() est capable de dessiner des images via les interfaces QT, GTK et Windows, qui, bien sûr, ne seront certainement pas dans la configuration STM32. En fait, QT peut également être exécuté sur STM32F7Discovery, mais cela sera discuté dans un autre article :)


Après une courte clarification dans quel format le résultat du détecteur de frontière est stocké, nous obtenons une image.



Image originale



Résultat


Exécution sur STM32F7Discovery


Il existe plusieurs partitions matérielles sur le 32F746GDISCOVERY que nous pouvons utiliser de toute façon


  1. 320 Ko de RAM
  2. Flash de 1 Mo pour l'image
  3. 8MiB SDRAM
  4. Lecteur flash NAND QSPI 16 Mo
  5. Emplacement pour carte MicroSD

Une carte SD peut être utilisée pour stocker des images, mais dans le cadre de l'exécution d'un exemple minimal, ce n'est pas très utile.
L'affichage a une résolution de 480x272, ce qui signifie que la mémoire du tampon d'images sera de 522240 octets à une profondeur de 32 bits, c'est-à-dire c'est plus que la taille de la RAM, nous allons donc placer le framebuffer et un tas (qui sera requis pour OpenCV pour stocker les données pour les images et les structures auxiliaires) en SDRAM, tout le reste (mémoire pour les piles et autres besoins du système) ira dans la RAM .


Si nous prenons la configuration minimale pour STM32F7Discovery (jetez tout le réseau, toutes les commandes, faites les piles aussi petites que possible, etc.) et ajoutez OpenCV avec des exemples, avec la mémoire requise, ce qui suit sera:


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

Pour ceux qui ne sont pas très familiers avec les sections qui sont repliées, je vais expliquer: les instructions et les constantes (en gros, données en lecture seule) sont en .text et .rodata , les données sont modifiables en .data , et "zéro" en .bss les variables qui ont cependant besoin d'une place (cette section "ira" en RAM).


La bonne nouvelle est que .data / .bss devrait convenir, mais avec .text problème est qu'il n'y a que 1 Mo de mémoire pour l'image. Vous pouvez .text image de l'exemple du .text et la lire, par exemple, de la carte SD dans la mémoire au démarrage, mais fruits.png pèse environ 330 Ko, donc cela ne résoudra pas le problème: la plupart du .text compose de code OpenCV.


Dans l'ensemble, il ne reste qu'une chose: charger une partie du code sur un lecteur flash QSPI (il dispose d'un mode de fonctionnement spécial pour mapper la mémoire sur le bus système, afin que le processeur puisse accéder directement à ces données). Dans ce cas, un problème survient: d'une part, la mémoire d'un lecteur flash QSPI n'est pas disponible immédiatement après le redémarrage de l'appareil (vous devez initialiser séparément le mode mappé en mémoire), et d'autre part, vous ne pouvez pas flasher cette mémoire avec le chargeur de démarrage habituel.


En conséquence, il a été décidé de lier tout le code dans QSPI et de le flasher avec un chargeur de démarrage, qui recevra le binaire nécessaire via TFTP.


Résultat


L'idée de porter cette bibliothèque sur Embox est née il y a environ un an, mais elle a été retardée à maintes reprises pour diverses raisons. L'un d'eux est le support de libstdc ++ et de la bibliothèque de modèles standard. Le problème de la prise en charge de C ++ dans Embox est au-delà de la portée de cet article, donc ici, je dirai simplement que nous avons réussi à obtenir cette prise en charge de la bonne quantité pour que cette bibliothèque fonctionne :)


Au final, ces problèmes ont été résolus (au moins suffisamment pour que l'exemple OpenCV fonctionne), et l'exemple a démarré. 40 longues secondes permettent au tableau de rechercher des limites par le filtre Canny. Ceci, bien sûr, est trop long (il y a des considérations sur la façon d'optimiser cette question, il sera possible d'écrire un article séparé à ce sujet en cas de succès).




Néanmoins, l'objectif intermédiaire était de créer un prototype qui montrera la possibilité fondamentale d'exécuter OpenCV sur STM32, respectivement, cet objectif a été atteint, bravo!

tl; dr: instructions étape par étape


0: Téléchargez les sources d'Embox, par exemple comme ceci:


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

1: Commençons par construire un chargeur de démarrage qui «flashera» le lecteur flash QSPI.


  make confload-arm/stm32f7cube 

Vous devez maintenant configurer le réseau, car Nous téléchargerons l'image via TFTP. Afin de définir les adresses IP de la carte et de l'hôte, vous devez modifier le fichier conf / rootfs / network.


Exemple de configuration:


 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 est l'adresse hôte à partir de laquelle l'image sera chargée, address est l'adresse de la carte.


Après cela, récupérez le chargeur de démarrage:


  make 

2: Chargement normal du chargeur de démarrage (désolé pour le jeu de mots) sur la carte - il n'y a rien de spécifique ici, vous devez le faire comme pour toute autre application pour STM32F7Discovery. Si vous ne savez pas comment procéder, vous pouvez en lire plus ici .
3: Compilation de l'image avec la configuration pour OpenCV.


  make confload-platform/opencv/stm32f7discovery make 

4: Extraire des sections ELF qui doivent être écrites sur QSPI, dans 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 

Le répertoire conf contient un script qui fait cela, vous pouvez donc l'exécuter


  ./conf/qspi_objcopy.sh #   -- build/base/bin/qspi.bin 

5: À l'aide de tftp, chargez qspi.bin.bin sur un lecteur flash QSPI. Pour ce faire, sur l'hôte, copiez qspi.bin dans le dossier racine du serveur tftp (généralement c'est / srv / tftp / ou / var / lib / tftpboot /; les packages pour le serveur correspondant sont dans les distributions les plus populaires, généralement appelés tftpd ou tftp-hpa, parfois, vous devez faire systemctl start tftpd.service pour démarrer).


  #   tftpd sudo cp build/base/bin/qspi.bin /srv/tftp #   tftp-hpa sudo cp build/base/bin/qspi.bin /var/lib/tftpboot 

Sur Embox (c'est-à-dire dans le chargeur de démarrage), vous devez exécuter la commande suivante (nous supposons que le serveur a l'adresse 192.168.2.1):


  embox> qspi_loader qspi.bin 192.168.2.1 

6: En utilisant la commande goto , vous devez "sauter" dans la mémoire QSPI. L'emplacement spécifique variera en fonction de la façon dont l'image est liée, vous pouvez voir cette adresse avec mem 0x90000000 (l'adresse de début tient dans le deuxième mot d'image 32 bits); vous devez également définir la pile avec l'indicateur -s , l'adresse de la pile est à 0x90000000, par exemple:


  embox>mem 0x90000000 0x90000000: 0x20023200 0x9000c27f 0x9000c275 0x9000c275 ↑ ↑        embox>goto -i 0x9000c27f -s 0x20023200 #  -i         <      ,    OpenCV > 

7: Courir


  embox> edges 20 

et profitez d'une recherche de frontière de 40 secondes :)


Si quelque chose ne va pas - écrivez le problème dans notre référentiel , ou dans la liste de diffusion embox-devel@googlegroups.com, ou dans les commentaires ici.

Source: https://habr.com/ru/post/fr457724/


All Articles