Essayez de mesurer la position d'un objet sur le bureau (système de coordonnées réel) à partir de l'image de la caméra avec Python + OpenCV

introduction

Le but est de trouver la position de tout objet placé sur un plan (bureau) avec un marqueur de référence dans le système de coordonnées réel comme suit **. Il est implémenté en Python et utilise le module ** ArUco ** d'OpenCV. 概要.png

Environnement d'exécution

Utilisez GoogleColab (Python 3.6.9). La version du module utilisé est la suivante.

Différentes versions de modules


%pip list | grep -e opencv -e numpy
numpy                    1.17.5     
opencv-contrib-python    4.1.2.30   
opencv-python            4.1.2.30   

Créer des marqueurs avec ArUco

Commencez par charger les modules nécessaires au traitement.

Dans l'environnement GoogleColab, cv2.imshow (...) n'est pas disponible, mais en faisant ce qui suit, l'image (np.ndarray) peut être affichée dans la cellule de résultat d'exécution avec cv2_imshow (...). Il peut être produit.

Importation de module


import cv2
import numpy as np
from google.colab.patches import cv2_imshow

ArUco a ** un ensemble de marqueurs prédéfinis **. Cette fois, nous allons l'utiliser. ʻAruco.getPredefinedDictionary (...) récupère le dictionnaire contenant les marqueurs prédéfinis. L'argument ʻaruco.DICT_4X4_50 sélectionne un dictionnaire avec jusqu'à 50 $ marqueurs avec un motif de remplissage $ 4 \ fois 4 $ à l'intérieur du carré.

Enregistrez les marqueurs prédéfinis (0-3) sous forme de fichier image


aruco = cv2.aruco
p_dict = aruco.getPredefinedDictionary(aruco.DICT_4X4_50)
marker =  [0] * 4 #Initialisation
for i in range(len(marker)):
  marker[i] = aruco.drawMarker(p_dict, i, 75) # 75x75 px
  cv2.imwrite(f'marker{i}.png', marker[i])

L'exécution de ce qui précède produira des fichiers $ 4 $ (75 $ \ fois 75 $ de taille de pixel) de "marker0.png " à "marker3.png ".

Dans l'environnement GoogleColab, le marqueur créé peut être affiché et confirmé dans la cellule de sortie avec cv2_imshow (...). Vous pouvez voir qu'un marqueur avec un motif de remplissage $ 4 \ fois 4 $ est créé à l'intérieur du carré. cv2_show_marker.png Si vous donnez DICT_5X5_100 à l'argument de ʻaruco.getPredefinedDictionary (...)`, vous pouvez obtenir un dictionnaire avec jusqu'à 100 $ de marqueurs définis dans le modèle de $ 5 \ times 5 $. cv2_show_marker_5x5.png

Détection de marqueurs par ArUco

Imprimer "marker0.png ", c'est-à-dire la sortie du 0ème marqueur de ʻaruco.DICT_4X4_50 sur papier. Ensuite, appelons-le "m0-photo.jpg " pris avec un appareil photo. Des marqueurs sont détectés pour cette cible, et l'image ʻimg_marked qui recouvre les résultats de détection est sortie.

Détecté à partir de photos contenant le marqueur 0


img = cv2.imread('m0-photo.jpg')
corners, ids, rejectedImgPoints = aruco.detectMarkers(img, p_dict) #détection
img_marked = aruco.drawDetectedMarkers(img.copy(), corners, ids)   #Résultats de détection de superposition
cv2_imshow(img_marked) #afficher

m0_photo_marked.png

Le marqueur entier est ** carré vert ** </ font>, le coin supérieur gauche du marqueur est ** carré rouge ** </ font>, et l'ID du marqueur (numéro) ) Est dessiné avec ** caractères bleus </ font> ** superposés sur l'image d'origine. À partir de là, vous pouvez voir que la détection des marqueurs est effectuée correctement.

Regardons de plus près le ** return ** de ʻaruco.detectMarkers (...) `.

coins stocke les ** coordonnées d'image d'angle (quatre coins) ** de chaque marqueur trouvé dans l'image dans une liste de np.ndarray.

py:aruco.detectMarkers(...)Coins de valeur de retour


#■ Coins de valeur de retour
# print(type(corners)) # -> <class 'list'>
# print(len(corners))  # -> 1
# print(type(corners[0]))  # -> <class 'numpy.ndarray'>
# print(corners[0].shape)  # -> (1, 4, 2)
print(corners)

Résultat d'exécution


[array([[[265., 258.],
        [348., 231.],
        [383., 308.],
        [297., 339.]]], dtype=float32)]

Les coordonnées des coins sont stockées dans le sens antihoraire dans l'ordre supérieur gauche, supérieur droit, inférieur droit et inférieur gauche du marqueur. Autrement dit, cela ressemble à ceci: m0-photo1_.png

ʻIds` stocke le ** ID de marqueur ** trouvé dans l'image au format numpy.ndarray.

py:aruco.detectMarkers(...)ID de valeur de retour


#■ Identifiants de retour
# print(type(ids)) # -> <class 'numpy.ndarray'>
# print(ids.shape) # -> (1, 1)
print(ids)

Le résultat sera quelque chose comme «[[0]]» (notez que ce n'est pas «[0]»).

rejetéImgPoints stocke les coordonnées des carrés trouvés dans l'image qui ne contiennent pas le modèle approprié (https://docs.opencv.org/4.1.2). /d9/d6a/group__aruco.html#gab9159aa69250d8d3642593e508cb6baa) contient les imgPoints des carrés dont le code interne n'a pas une codification correcte. Utile à des fins de débogage.).

Je pense qu'il est plus facile de comprendre cela si vous regardez un exemple concret. rejetéImgPoints stocke les coordonnées du carré comme indiqué par la flèche dans la figure. rejectedImgPoints.png

Détection de marqueurs par ArUco (lorsqu'il y a plusieurs marqueurs)

Vérifiez le résultat lorsqu'il y a plusieurs marqueurs dans l'image (y compris le cas où il y a deux ou plus du même marqueur) comme suit. ms-photo.jpg Le code de détection est le même que ci-dessus. La superposition des résultats de détection ressemble à ceci: Peu importe qu'il y ait au moins deux marqueurs identiques. ms_photo_marked.png

Les valeurs de retour de ʻaruco.detectMarkers (...) ʻids et corners sont les suivantes: Notez qu'ils ne sont pas triés par ID.

print(ids)Résultat de


[[1]
 [1]
 [0]
 [2]]

print(corners)Résultat de


[array([[[443., 288.],
        [520., 270.],
        [542., 345.],
        [464., 363.]]], dtype=float32), array([[[237., 272.],
        [313., 255.],
        [331., 328.],
        [254., 346.]]], dtype=float32), array([[[ 64., 235.],
        [140., 218.],
        [154., 290.],
        [ 75., 307.]]], dtype=float32), array([[[333., 113.],
        [404.,  98.],
        [424., 163.],
        [351., 180.]]], dtype=float32)]

Mesure de position dans le système de coordonnées réel

Préparez une feuille de papier dans laquelle les quatre marqueurs «marker0.png» à «marker3.png» créés en premier sont disposés dans le sens des aiguilles d'une montre comme indiqué ci-dessous. Ici, la distance entre les marqueurs est fixée à 150 $ \ mathrm {mm} $ (cette création papier sera inexacte si elle n'est pas faite strictement). table.png

Placez l'objet "chien" dont vous souhaitez détecter la position sur ce papier, et tirez depuis ** une position appropriée au-dessus ** (la position et l'angle de prise de vue à ce moment ne doivent pas être exacts). Le but est de trouver la position de ce "chien" dans le ** système de coordonnées réel basé sur des marqueurs **. inu.png

Convertir en une image vue directement au-dessus

L'image de la caméra prise à partir d'une position et d'un angle appropriés au-dessus est convertie en ** l'image vue directement au-dessus **. Ici, l'image convertie est de 500 $ \ fois 500 , \ mathrm {px} $.

Convertir en une image vue directement au-dessus


aruco = cv2.aruco
p_dict = aruco.getPredefinedDictionary(aruco.DICT_4X4_50)
img = cv2.imread('inu.jpg')
corners, ids, rejectedImgPoints = aruco.detectMarkers(img, p_dict) #détection

#Stockez les "coordonnées centrales" du marqueur en m dans l'ordre à partir du coin supérieur gauche dans le sens des aiguilles d'une montre
m = np.empty((4,2))
for i,c in zip(ids.ravel(), corners):
  m[i] = c[0].mean(axis=0)

width, height = (500,500) #Taille de l'image après transformation

marker_coordinates = np.float32(m)
true_coordinates   = np.float32([[0,0],[width,0],[width,height],[0,height]])
trans_mat = cv2.getPerspectiveTransform(marker_coordinates,true_coordinates)
img_trans = cv2.warpPerspective(img,trans_mat,(width, height))
cv2_imshow(img_trans)

Le résultat de l'exécution est le suivant. Vous pouvez voir que le ** centre ** de chaque marqueur est transformé pour qu'il devienne les quatre coins de l'image. inu_trans.png Même si vous utilisez une image prise à partir d'une position plus oblique, vous pouvez la convertir en une image vue directement d'en haut comme suit. coin.png

Par souci de clarté, le ** centre ** de chaque marqueur correspond désormais aux quatre coins de l'image convertie. Cependant, il n'est pas pratique de calculer la position du "chien" (il y a aussi des fragments de marqueur aux quatre coins ...).

Par conséquent, modifiez le programme de sorte que les coins de chaque marqueur touchant le carré $ 150 \ times 150 \ mathrm {mm} $ sur le papier soient les quatre coins de l'image convertie.

Converti en une image vue directement au-dessus (version améliorée)


aruco = cv2.aruco
p_dict = aruco.getPredefinedDictionary(aruco.DICT_4X4_50)
img = cv2.imread('inu.jpg')
corners, ids, rejectedImgPoints = aruco.detectMarkers(img, p_dict) #détection

#Changer ici
corners2 = [np.empty((1,4,2))]*4
for i,c in zip(ids.ravel(), corners):
  corners2[i] = c.copy()
m[0] = corners2[0][0][2]
m[1] = corners2[1][0][3]
m[2] = corners2[2][0][0]
m[3] = corners2[3][0][1]

width, height = (500,500) #Taille de l'image après transformation
marker_coordinates = np.float32(m)
true_coordinates   = np.float32([[0,0],[width,0],[width,height],[0,height]])
trans_mat = cv2.getPerspectiveTransform(marker_coordinates,true_coordinates)
img_trans = cv2.warpPerspective(img,trans_mat,(width, height))
cv2_imshow(img_trans)

Le résultat de l'exécution est le suivant. inu_trans2.png La taille de cette image est de 500 $ \ fois 500 , \ mathrm {px} $, et la taille correspondante sur papier est de 150 $ \ fois 150 , \ mathrm {mm} $. Par conséquent, si les coordonnées sur l'image sont $ (160 \ mathrm {px}, , 200 \ mathrm {px}) $, les coordonnées réelles correspondantes sont $ (160 \ times \ frac {150} {500} = 48. Il peut être calculé comme suit: \ mathrm {mm}, , 200 \ times \ frac {150} {500} = 60 \ mathrm {mm}) $.

Détection d'objets à partir d'images

Utilisez les différentes fonctions fournies par OpenCV pour trouver la position du chien à partir de l'image ci-dessus. «Cv2.connectedComponentsWithStats (...)» est particulièrement important.

Sortie de position après détection d'objet et conversion de coordonnées réelles


tmp = img_trans.copy()

# (1)Conversion de l'échelle de gris
tmp = cv2.cvtColor(tmp, cv2.COLOR_BGR2GRAY)
#cv2_imshow(tmp)

# (2)Traitement du flou
tmp = cv2.GaussianBlur(tmp, (11, 11), 0)
#cv2_imshow(tmp)

# (3)Processus de binarisation
th = 130 #Seuil de binarisation(Ajustement requis)
_,tmp = cv2.threshold(tmp,th,255,cv2.THRESH_BINARY_INV) 
#cv2_imshow(tmp)

# (4)Détection de blob (= masse)
n, img_label, data, center = cv2.connectedComponentsWithStats(tmp)

# (5)Organiser les résultats de détection
detected_obj = list() #Destination de stockage du résultat de la détection
tr_x = lambda x : x * 150 / 500 #Coordonnées de l'image sur l'axe X → coordonnées réelles
tr_y = lambda y : y * 150 / 500 #Axe Y 〃
img_trans_marked = img_trans.copy()
for i in range(1,n):
  x, y, w, h, size = data[i]
  if size < 300 : #Ignorer les zones de moins de 300 pixels
    continue
  detected_obj.append( dict( x = tr_x(x),
                              y = tr_y(y),
                              w = tr_x(w),
                              h = tr_y(h),
                              cx = tr_x(center[i][0]),
                              cy = tr_y(center[i][1])))  
  #Vérification
  cv2.rectangle(img_trans_marked, (x,y), (x+w,y+h),(0,255,0),2)
  cv2.circle(img_trans_marked, (int(center[i][0]),int(center[i][1])),5,(0,0,255),-1)

# (6)Voir les résultats
cv2_imshow(img_trans_marked)
for i, obj in enumerate(detected_obj,1) :
  print(f'■ Objet détecté{i}Position centrale X={obj["cx"]:>3.0f}mm Y={obj["cy"]:>3.0f}mm ')

Le résultat de l'exécution est le suivant. inu_trans_marked.png

Résultat d'exécution


■ Position centrale X de l'objet détecté 1=121mm Y= 34mm

Placez une règle et vérifiez si les résultats ci-dessus sont appropriés. Il semble qu'ils le recherchent correctement. inu_pos.png

Expérience supplémentaire

Mettons plus d'objets et vérifions. m4-photo2.jpg J'avais besoin "d'ajuster le seuil" lors de la binarisation, mais cela a fonctionné. L'objet détecté 1 est "chien (marron)", l'objet détecté 2 est "chien (gris)", l'objet détecté 3 est "lapin" et l'objet détecté 4 est "ours". m4-result.png

L'étape suivante

  • Essayez de combiner avec l'apprentissage automatique (classification d'image) (classez chaque objet de l'image en «chien», «lapin» et «ours»).
  • Essayez de créer une démo en combinaison avec le bras du robot.

robot_arm.jpg

Entrées connexes

Recommended Posts