Introduction
Les gabarits 3D dans Tulip Vision
Tulip utilise des marqueurs 3D parce que c'est le moyen le plus précis de suivre des objets en haute fidélité dans un environnement très bruyant comme l'atelier. Les gabarits marqueurs sont faciles à coller aux objets. On peut les placer sur une poubelle, un chariot élévateur, un semi-remorque ou un minuscule outil d'horloger - ils fonctionnent à n'importe quelle échelle, pour autant que la caméra soit capable de les repérer. Le cas classique d'utilisation des gabarits est, bien entendu, le suivi des objets dans lesquels la pièce à usiner s'insère, mais avec les capacités 3D, nous ajoutons la possibilité de suivre des objets complexes, avec des marqueurs collés sur différentes surfaces de l'objet.
Les gabarits sont constitués de nombreux marqueurs 3D regroupés. Les marqueurs 3D indiquent un emplacement sur l'objet 3D qui est fixé dans le "cadre" (un cadre est constitué des 3 axes utilisés pour définir la position et l'orientation, les axes X, Y et Z) de l'objet, en supposant que l'objet est un corps rigide. Chaque fois que nous voyons le marqueur, nous savons qu'il est fixé au même point sur l'objet. Le groupe de marqueurs de gabarit - définit un objet complet. Les gabarits peuvent définir des formes 3D complexes, avec certains des marqueurs visibles et d'autres cachés. Les marqueurs visibles nous aident à trouver un emplacement et une orientation pour l'objet. Lorsque les marqueurs cachés sont visibles, ils compensent les autres marqueurs qui sont maintenant cachés.
Optimisation de l'ajustement des paquets
Lorsque nous définissons un gabarit à partir de marqueurs, nous capturons plusieurs vues de l'objet marqué sous différents angles par rapport à la caméra. Chaque image capturée par la caméra fournit une autre "vue" de l'objet avec ses marqueurs. Et dans chaque image, nous calculons la position des marqueurs sur l'objet et leurs relations (transformations). Après avoir capturé suffisamment de vues, nous les combinons pour obtenir un modèle 3D holistique de l'objet, c'est-à-dire le processus d'"enregistrement". Cependant, pour plusieurs raisons liées à l'optique et à la stabilité numérique des calculs, les vues autour de l'objet ne sont pas toujours parfaitement alignées. En fait, plus on prend de vues de l'objet, plus l'erreur cumulée d'enregistrement augmente jusqu'à ce que l'enregistrement final soit inutile. C'est là qu'intervient le "Bundle Adjustment".
L'ajustement de l'ensemble (BA) est un processus d'optimisation numérique qui combat l'erreur cumulée résultant de l'enregistrement de plusieurs vues de caméra pour reconstruire une géométrie. Dans le BA traditionnel, presque tous les paramètres de la reconstruction sont optimisés, y compris la modélisation optique de la caméra. Mais avant d'expliquer le processus de BA, nous devons définir les paramètres en jeu qui nécessitent une optimisation. Nous vous recommandons vivement de vous référer au merveilleux livre du professeur Richard Szeliski "Computer Vision : Algorithms and Applications", Springer press, 2011 (chapitre 7 pp320).
Pose caméra-objet avec marqueurs 3D
Lorsqu'une caméra regarde un marqueur, qui est un objet plat, il est possible de calculer l'orientation du marqueur par rapport à l'origine de la caméra. Considérons le schéma suivant :
Le marqueur est visible dans la vue de la caméra et projeté sur le plan de l'image - une construction conceptuelle qui aide à formuler la traduction entre les coordonnées 3D et 2D des pixels. Cependant, lorsque nous prenons une photo de la scène avec le marqueur, nous ne connaissons pas les paramètres du point 3D, nous pouvons seulement détecter où ces points 3D ont été projetés sur l'image 2D. Cette projection peut être capturée par l'équation suivante :
Les coordonnées X, Y et Z sont les coordonnées 3D du centre du marqueur, par exemple, tandis que x et y sont les positions en pixels 2D des coins sur l'image. Le marque l'ambiguïté des paramètres, une information manquante, causée par le fait qu'un point 3D dans le monde peut apparaître n'importe où sur le rayon entre le centre de la caméra et le point 3D réel (voir les points orange délavés dans le diagramme). En d'autres termes, des objets de n'importe quelle échelle arbitraire peuvent apparaître sur l'image dans n'importe quelle taille arbitraire, tout dépend de leur distance par rapport à la caméra. Nous avons également dans cette équation la rotation 3D (paramètres r) et la translation (paramètres t) de l'objet ou inversement de la caméra, sans perte de généralité. Les paramètres f et c sont les "paramètres intrinsèques" qui modélisent l'optique de la caméra (de manière très approximative dans cet exemple de jouet).
Néanmoins, il existe une relation linéaire entre les points 3D et les points 2D, et si nous connaissions tous les paramètres de cette équation, nous pourrions calculer : (1) la position 3D réelle du marqueur à partir des coordonnées des pixels 2D, et (2) la rotation ri et la translation tx,y,z du marqueur par rapport à la caméra. On notera que pour travailler avec des coordonnées 2D, nous ne pouvons pas simplement supprimer le paramètre dans notre équation, et en fait, pour obtenir les points de pixels, il faudrait diviser par la dernière entrée du vecteur : x'=λx, x =x'/λ, y'=λy, y=y'/λ.
Étant donné un nombre suffisant de points correspondants de la 2D à la 3D, nous pouvons réarranger l'équation ci-dessus en un ensemble d'équations linéaires (homogènes) permettant de récupérer R et t. En utilisant les marqueurs 3D, nous pouvons obtenir au moins 4 paires de points 2D-3D correspondants pour chaque marqueur. Les points 2D sont obtenus en regardant l'image et en trouvant les coins. Les points 3D sont donnés par la disposition du marqueur, qui est également sous notre contrôle (puisque nous avons imprimé le marqueur). Le processus global de récupération de la pose est connu sous le nom de Perspective-n-Point et il existe de nombreuses approches et algorithmes pour le résoudre. Par exemple, voici comment on peut trouver la pose d'une caméra en Python avec OpenCV à partir d'un ensemble de points 2D-3D alignés :
_, R, t = cv2.solvePnP(aligned_3d, aligned_2d, K, dc)
Le problème d'optimisation
Annotons la dernière opération de "projection" comme suit :
$$P_{2D}=\mathrm{Proj}([R|t], P_{3D})$$
Autrement dit, nous obtenons la position du pixel 2D (P2D) à partir de la projection du point 3D P3D et de la rotation R et de la translation t entre la caméra et l'objet. Le principal problème de ce régime de projection est qu'il est basé sur des calculs effectués sur les points 2D en coordonnées pixel, qui ne sont pas très précis et également quantifiés sur la grille de pixels. Si nous reprojetons les points 3D (les projetons en 2D sur l'image) après avoir trouvé la pose de l'objet [R|t], nous trouvons souvent les positions 2D décalées par rapport à leurs positions dans l'image. L'image suivante montre les décalages, qui sont généralement plus contrastés dans des situations extrêmes telles qu'un angle important par rapport à la caméra, ou en présence de flou.
Notre objectif est de trouver les paramètres de position de la caméra de sorte que tous ces décalages 2D soient aussi petits que possible. Pour exprimer cela par une formule, nous souhaitons résoudre le problème de minimisation suivant, qui recherche les [R|t] optimaux qui minimisent les résidus :
\hat{[R|t]} = \mathop{\arg\min}_{[R|t]} \sum_i \Vert \mathrm{Proj}([R|t],P_i^{\mathrm{3D}}) - P_i^{\mathrm{2D}} \Vert^2
La différence entre le point 3D reprojeté et le point 2D est appelée le résidu. Et en général, nous appelons ce problème un problème de moindres carrés puisque nous élevons le résidu au carré. Ce cas particulier est un problème de moindres carrés non linéaires, puisque l'opérateur Proj(.) est non linéaire. Avec cette formulation en place, nous pouvons également introduire dans le problème d'optimisation, par exemple, les paramètres intrinsèques de la caméra et trouver des valeurs optimales pour ceux-ci également :
\hat{[R|t]},\hat{\{P^\mathrm{3D}\}},\hat{K} = \mathop{\arg\min}_{[R|t],\{P^\mathrm{3D}\},K} \sum_i \Vert \mathrm{Proj}([R|t],P_i^{\mathrm{3D}},K) - P_i^{\mathrm{2D}} \Vert^2
Voici un exemple de calcul des résidus en Python avec OpenCV à partir de paires de points 2D-3D correspondants et de sortie d'une liste de résidus :
def calcResiduals(Rt) : projPts2d,_ = cv2.projectPoints(pts3d, Rt[:3], Rt[3 :], K, None) return (np.squeeze(projPts2d2) - pts2d21).ravel()
Heureusement, il existe de nombreux algorithmes et logiciels pour résoudre les problèmes de moindres carrés non linéaires, tels que le solveur Ceres, diverses méthodes MATLAB, SciPy de Python, et bien d'autres. Par exemple, avec SciPy et OpenCV, on peut résoudre le problème de la manière suivante :
res = scipy.optimize.least_squares(calcResiduals, np.hstack([ cv2.Rodrigues(R)[0], t[np.newaxis] ]).ravel())
Résoudre la BA pour les gabarits 3D
Jusqu'à présent, nous avons abordé l'AB en termes généraux, mais nos objectifs d'optimisation pour les gabarits 3D sont un peu différents. Lorsque nous construisons nos gabarits 3D, nous construisons essentiellement une carte 3D. La cartographie (et la localisation) est un problème bien connu, par exemple dans le domaine de la navigation autonome et de l'odométrie, où un véhicule doit s'orienter dans le monde à partir des observations des caméras. Notre technique de cartographie par gabarit est similaire aux algorithmes SLAM (Simultaneous Localization and Mapping), en ce sens qu'elle construit une carte du monde observé de manière incrémentielle et qu'elle effectue occasionnellement un BA sur celle-ci afin de réduire l'erreur résiduelle des différents algorithmes d'estimation linéaire.
Comme mentionné dans la première section, dans une image donnée, nous pouvons voir certains marqueurs mais pas d'autres, et au fur et à mesure que la cartographie progresse, nous avons plus d'indices sur la position des marqueurs les uns par rapport aux autres. Nous commençons par les premiers marqueurs visibles et notons leur structure 3D, en supposant que cette structure ne changera jamais. Par exemple, la transformation entre le marqueur 1 et le marqueur 2 est notée T12. Dans une image ultérieure, nous ne voyons plus le marqueur 1 mais le marqueur 3 est révélé, tandis que le marqueur 2 reste visible. On note la transformation de 2 en 3 avec T23, et de 1 en 3 en concaténant les transformations : T13 = T12T23.
Le processus de mise en correspondance introduit d'autres erreurs dans la carte, auxquelles s'ajoute l'erreur intrinsèque de récupération de la pose 3D du marqueur dont nous avons parlé précédemment. La concaténation des transformations aggrave les erreurs, à tel point que des cas dégénérés peuvent se produire. Nous devons appliquer la BA pour atténuer les erreurs composées, sinon le processus de mappage de gabarit échouera.
Une option d'optimisation consiste à fixer les transformations obtenues à partir de l'estimation de la pose de la caméra, ce qui ressemblerait à la formulation BA précédente. Nous recherchons une CamP qui minimise les résidus lorsque les points 3D sont donnés :
\hat{\mathrm{CamP}} = \mathop{\arg\min}_{\mathrm{CamP}} \sum_i \Vert \mathrm{Proj}(\mathrm{CamP},P_i^{\mathrm{3D}}) - P_i^{\mathrm{2D}} \Vert^2
Cependant, nous notons que la pose de la caméra est dérivée des points 3D (par correspondance 2D-3D). Par conséquent, nous pourrions optimiser les coordonnées des points 3D eux-mêmes, et recalculer la pose de la caméra à partir de ces points. Nous fixons la pose de la caméra et la minimisons sur la base des points 3D, en recherchant les points 3D optimaux qui minimisent le résidu de la reprojection 2D :
\hat{\{P^{\mathrm{3D}}\}} = \mathop{\arg\min}_{\{P^{\mathrm{3D}}\}} \sum_i \Vert \mathrm{Proj}(\mathrm{CamP},P_i^{\mathrm{3D}}) - P_i^{\mathrm{2D}} \Vert^2
Cette astuce nous aide principalement à obtenir un ensemble optimal de points 3D qui se trouvent sur l'objet et dont les erreurs par rapport aux points 2D d'origine des images sont minimes. Nous maintenons la relation entre les points de la carte 3D et l'ID de leurs marqueurs, de sorte qu'en cours d'exécution, nous pouvons trouver des correspondances 2D-3D et récupérer la pose de l'objet, avec solvePnP. Sur une nouvelle image entrante, nous localisons les positions 2D des coins des marqueurs et les faisons correspondre aux points 3D de la carte, de sorte que, globalement, nous pouvons trouver la pose de l'objet à partir de nombreux points 2D-3D ensemble, en faisant la moyenne de l'erreur.
Nous pouvons clairement voir qu'après avoir effectué le BA sur la carte de gabarit 3D, les décalages 2D sont réduits et l'estimation de la pose de l'objet sera bien meilleure.
Conclusions
Les gabarits dans Tulip Vision offrent un large éventail de cas d'utilisation pour la détection des opérations dans l'atelier. Grâce aux nouvelles fonctionnalités des gabarits 3D, de nouveaux cas d'utilisation sont possibles, tels que le suivi d'outils complexes qui seront visibles sous différents angles, comme les outils portatifs. En utilisant le mappage de gabarits et l'ajustement de faisceaux, nous sommes en mesure de produire des cartes d'objets complexes avec un minimum d'erreurs et une géométrie optimisée. Les jigs sont immédiatement utilisables dans Tulip, avec l'optimisation intégrée. Utilisez-les pour suivre vos outils, les équipements de votre poste de travail et même les matériaux.