Vision du robot Raspberry Pi: carte de profondeur

image

Aujourd'hui, toute la technologie de «construction de drones» devient activement moins chère. Sauf pour un: obtenir une carte de l'espace environnant. Il y a deux extrêmes: soit des lidars coûteux (milliers de dollars) et des solutions optiques pour construire une carte de profondeur (plusieurs centaines de dollars), soit des solutions assez penny comme les télémètres à ultrasons.
Par conséquent, une idée a surgi sur la base d'un Raspberry Pi bon marché avec une caméra pour faire une solution qui serait dans une niche vide et vous permettrait d'obtenir une carte de profondeur "pour peu coûteux". Et pour ce faire dans un langage de programmation simple tel que Python, afin qu'il soit disponible pour les débutants à expérimenter. En fait, je voulais parler de mes résultats. Les scripts résultants avec des exemples de photos peuvent également être exécutés sur le bureau.



Carte de profondeur d'une caméra.



Tout d'abord, quelques mots sur la partie optique. Deux images sont toujours utilisées pour construire une carte de profondeur - à partir des caméras gauche et droite. Et nous avons des framboises avec une seule caméra. Par conséquent, un séparateur optique a été développé, qui donne en conséquence une paire stéréo à la caméra.
En termes simples - si vous regardez la photo, à partir de la boîte noire, deux yeux de l'appareil photo vous regardent. Mais en réalité, la caméra en est une. Juste un peu de magie optique.

image

La photo montre la douzième itération de l'appareil. Il a fallu beaucoup de temps pour obtenir une conception stable et fiable, qui en même temps serait peu coûteuse. La partie la plus difficile est les miroirs internes, qui ont été fabriqués sur commande par dépôt d'aluminium sous vide. Si vous utilisez des miroirs standard, dans lesquels la couche réfléchissante est située sous le verre et non au-dessus, alors à la jonction, ils forment un espace qui gâche radicalement l'image entière.

Sur quoi allons-nous travailler



L'image de framboise de Raspbian Wheezy a été prise comme base, Python 2.7 et OpenCV 2.4 ont été installés, ainsi, les packages requis pour les petites choses - matplotlib, numpy et autres. Toutes sortes et un lien vers l'image finie de la carte sont disposés à la fin de l'article. La description des scripts sous forme de leçons est disponible sur le site web du projet

Préparer une image pour construire une carte de profondeur



Notre solution n'étant pas en métal et sans optique ultra-précise, de petits écarts par rapport à la géométrie idéale sont possibles du fait de l'assemblage. De plus, la caméra est fixée à l'appareil avec des vis, de sorte que sa position peut ne pas être idéale. Le problème avec l'emplacement de la caméra est résolu manuellement, et la compensation de la "courbure" de l'assemblage de la structure se fera par logiciel.

Script One - Alignement de la caméra



La jonction des miroirs dans l'image devrait idéalement être verticale et centrée. C'est difficile à faire à l'œil nu, donc le premier script a été fait. Il capture simplement l'image de l'appareil photo en mode d'aperçu en direct, l'affiche à l'écran et au centre en superposition dessine une bande blanche le long de laquelle l'alignement a lieu. Une fois que la caméra est correctement orientée, nous resserrons les vis et l'assemblage est terminé.



Ce qui est intéressant dans le code du premier script
  • – , cv.imshow() , . , . , , .
  • – , .. . «» , camera.hflip = True



Le deuxième script - nous obtenons une paire stéréo "propre"



Nos images gauche et droite sont jointes au centre de l'image. Le joint sur la photo a une largeur non nulle - vous ne pouvez vous en débarrasser qu'en retirant les miroirs de l'appareil, ce qui augmente la taille de la structure. La caméra de la framboise a une mise au point définie sur l'infini, et les objets proches (dans notre cas, c'est une jonction) «flouent» simplement. Par conséquent, il vous suffit d'indiquer le script, qui, à notre avis, est une «mauvaise» zone, de sorte que la paire stéréo soit nette en images. Un deuxième script a été créé qui affiche une image et vous permet d'utiliser les touches pour indiquer la zone à couper.
Voici à quoi ressemble le processus:



Ce qui est intéressant dans le code du deuxième script
  • , , cv2.rectangle(). , , . , , Enter .
  • , . , .
  • JSON. , , .
  • . , , , . , ./src . . pf_1280_720.txt – , .
  • , . . :
    loadImagePath = ""
    # loadImagePath = "./src/scene_1280x720_1.png"



Le troisième script - une série de photos pour l'étalonnage



La science fondamentale dit que pour réussir à construire une carte de profondeur, la paire stéréo doit être calibrée. À savoir, tous les points clés de l'image de gauche doivent être à la même hauteur et dans l'image de droite. Dans cette situation, la fonction StereoBM, qui est notre seul temps réel, peut réussir son travail.
Pour l'étalonnage, nous devons imprimer une image de référence, faire une série de photos et la donner à l'algorithme d'étalonnage, qui calculera toutes les distorsions et enregistrera les paramètres pour ramener les images à la normale.
Alors, imprimez «l' échiquier » et collez-le sur une surface plane et dure. Pour plus de simplicité, la photo de série a été réalisée sous la forme d'un script avec un compte à rebours, qui s'affiche en haut de la vidéo.
Voici à quoi ressemble le script de photographie en série au travail:



Ce qui est intéressant dans le 3e code de script
  • . , . , , «» . , – , . camera.capture () , use_video_port=True.
  • – camera.annotate_text() . 5 – .
  • -, , ./src



Il convient de noter que la «justesse» de la série réalisée est critique pour les résultats d'étalonnage. Un peu plus tard, nous examinerons le résultat obtenu avec des photographies mal prises.

Script 4 - couper des photos sur des paires stéréo



Après qu'une série de photos a été prise, nous allons créer un autre script de service qui prend toute la série de photos prises et les coupe en paires d'images - gauche et droite, et enregistre les paires dans un dossier. zones au centre de l'image. Le script est assez ordinaire, donc j'ai caché la vidéo sous un spoiler.

Un exemple de travail et un lien vers les sources du 4ème script


La chose la plus intéressante est l'étalonnage, le cinquième script


Le script d'étalonnage alimente toutes les paires stéréo du dossier ./src vers la fonction d'étalonnage et plonge dans la pensée. Après son travail difficile (pour 15 images 1280x720 sur la première framboise cela prend environ 5 minutes), il prend la dernière paire stéréo, "corrige" les images (rectifie) et montre des versions déjà corrigées par lesquelles vous pouvez construire une carte de profondeur.
Voici à quoi ressemble le script au travail:



Ce qui est intéressant dans le code du 5ème script
  • StereoVision. , , , «» .
  • , . . «» , ,
  • calibrator.add_corners((imgLeft, imgRight), True)
    True False – , .



"Quelque chose s'est mal passé."


Il y a des moments où les résultats d'étalonnage sont inattendus.
Voici quelques exemples frappants:

Mauvaise carte de disparité

En fait, l'étalonnage est un moment déterminant. Ce que nous obtenons au stade de la construction d'une carte de profondeur dépend directement de sa qualité. Après un grand nombre d'expériences, la liste suivante des exigences de prise de vue se profile:
  • L'image d'échecs ne doit pas être parallèle au plan de la photo - toujours sous des angles différents. Mais même sans fanatisme - si vous tenez le tableau presque perpendiculaire au plan de la photo, le script ne trouvera tout simplement pas les échecs dans l'image.
  • Lumière, bonne lumière. En cas de faible éclairage de la pièce, et même lorsque vous retirez des images de la vidéo, la qualité de l'image diminue. Dans mon cas, la lumière dans 90% des cas a immédiatement corrigé la situation.
  • Écrivez sur Internet que la carte doit occuper autant que possible l'espace maximum dans l'image. Aide vraiment.


Voici à quoi ressemble une paire stéréo «fixe» avec de bons résultats d'étalonnage:

image

Script 6 - la première tentative de construction d'une carte de profondeur


Comme tout est prêt, vous pouvez déjà créer une carte de profondeur.
Nous chargeons les résultats de l'étalonnage, prenons une photo et construisons audacieusement une carte de profondeur en utilisant cv2.StereoBM Nous obtenons quelque chose comme ceci:

image

Le résultat n'est pas très impressionnant, évidemment nous devons resserrer quelque chose. Eh bien, passons à un réglage plus fin dans le prochain, 7ème script. Là, nous n'utiliserons pas 10 paramètres pour la construction, comme dans StereoBM (), mais presque 10, ce qui est beaucoup plus intéressant.

Voici les sources du 6ème script

Script 7 - carte de profondeur avec paramètres avancés


Lorsque les paramètres ne sont pas 2 mais 10, le tri de leurs options avec un redémarrage constant des scripts est incorrect. Par conséquent, un script a été créé pour un réglage interactif pratique de la carte de profondeur. La tâche n'était pas de compliquer le code avec l'interface, donc tout a été fait sur matplotlib. Le dessin de l'interface dans matplotlib sur les framboises est assez lent, donc je transfère généralement le dossier de travail des framboises à l'ordinateur portable et sélectionne les paramètres là-bas. Voici comment fonctionne le script:



Après avoir sélectionné les paramètres, le script par le bouton Enregistrer enregistre le résultat dans le fichier 3dmap_set.txt au format JSON.

Ce qui est intéressant dans le code du 7ème script
  • 7-
  • . , - , .
  • , . , numOfDisparities 16, . update(val), . numOfDisparities 65.57 64. , .
  • matplotlib , .



Un travail pratique avec la carte de profondeur a montré que, tout d'abord, vous devez sélectionner le paramètre minDisparity, et conjointement avec numOfDisparities. Eh bien, rappelez-vous que numOfDisparities change en fait discrètement, à l'étape 16.
Après avoir configuré cette paire, vous pouvez jouer avec d'autres paramètres.

Les caractéristiques des paramètres de la carte dépendent déjà du goût de l'utilisateur et dépendent de la tâche à résoudre. Vous pouvez apporter la carte à un grand nombre de petites parties ou afficher des zones agrandies. Pour un simple évitement d'obstacles par des robots, le second est plus adapté. Pour un nuage de points, le premier, mais des problèmes de performances se posent ici (nous y reviendrons).

Que voulons-nous voir?


Eh bien, l'un des points les plus importants est peut-être le réglage de la "clairvoyance" de notre appareil. Lors de la configuration de la carte de profondeur, je place généralement un objet à une distance d'environ 30 cm, le second sur un mètre et le reste sur deux mètres. Et lors de la configuration des deux premiers paramètres (minDisparity et numOfDisparities) dans le 7e script, j'obtiens ce qui suit:
  • Objet le plus proche (30 cm) - rouge
  • Objet dans un demi-mètre - jaune ou vert
  • Objets 2-3 mètres - vert ou bleu clair

En conséquence, nous obtenons un système configuré pour reconnaître la zone «proche» d'obstacles dans un rayon de 5 à 10 mètres.

Travailler avec la vidéo à la volée - script 8th, final


Eh bien, nous avons maintenant un système personnalisé prêt à l'emploi et nous devons obtenir un résultat pratique. Nous essayons de construire une carte de profondeur en temps réel en utilisant la vidéo de notre caméra et de la montrer en temps réel lors de sa mise à jour.



Ce qui est intéressant dans le code du 8ème script
– . , , overlay. , :

  • Overlay R, G B, - . grayscale
  • disparity_color = cv2.applyColorMap(disparity_grayscale, cv2.COLORMAP_JET)
  • overlay RGB, BGR – cv2.cvtColor()
  • Overlay 16. 16 — .



Dans la course à la vitesse


Ainsi, la première mesure a été effectuée sur le premier Raspberry avec un processeur monocœur.
- 4 secondes - construire une carte sur l'image de 1280x720 C'est beaucoup.
- 2,5 secondes - sur le Raspberry Pi 2, déjà mieux.
L'analyse a montré que dans ce cas, un seul noyau est utilisé sur la deuxième framboise. Mess! J'ai reconstruit OpenCV en utilisant la bibliothèque de parallélisation TBB.
- 1,5 seconde - lancement sur la deuxième framboise en utilisant le multicœur. En fait, il s'est avéré que seuls 2 cœurs sont utilisés - cela doit encore être bricolé. Il s'est avéré que non seulement j'ai rencontré ce problème , donc il y a encore de la place pour bouger.
A en juger par l'algorithme, la vitesse de fonctionnement devrait dépendre linéairement de la taille des données traitées. Par conséquent, si vous réduisez la résolution de 2 fois, tout devrait théoriquement fonctionner 4 fois plus rapidement.
- 0,3 seconde , soit environ 3-4 FPS - avec une résolution semi-réduite de 640x360. La théorie a été confirmée.

Plans futurs


Tout d'abord, je voulais tirer le meilleur parti du multicœur de la deuxième framboise. Je vais regarder de plus près les sources de la fonction StereoBM et essayer de comprendre pourquoi le travail ne va pas jusqu'au bout.

La prochaine étape promet beaucoup plus d'aventures - c'est l'utilisation du GPU framboises pour accélérer les calculs.
Trois chemins possibles sont tracés ici:



Si vous aviez de l'expérience avec TBB pour Raspberry OpenCV, ou si vous vous occupiez du codage pour les GPU framboise, je vous serais reconnaissant pour des conseils supplémentaires . J'ai réussi à trouver un certain nombre de développements prêts à l'emploi pour une raison simple - les framboises avec deux caméras sont rares. Si vous connectez deux webcams via USB, de gros freins viennent, et seul le Raspberry Pi Compute peut fonctionner avec deux caméras natives, qui ont également besoin d'un devboard lourd avec des lacets et des adaptateurs.

Liens utiles:


Scripts de travail:

Configuration d'OpenCV et de Python sur les framboises:

Bibliothèque StereoVision:

Travail GPU

Eh bien, un article intéressant sur le habr sur " Pour reconnaître les images, vous n'avez pas besoin de reconnaître les images "

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


All Articles