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.
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
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é.
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 $.
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
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:
ʻ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.
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. 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.
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)]
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).
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 **.
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. 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.
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. 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}) $.
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.
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.
Mettons plus d'objets et vérifions. 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".
Recommended Posts