Ziel ist es, die Position eines Objekts auf einer Ebene (Schreibtisch) mit einer Referenzmarkierung im realen Koordinatensystem wie folgt zu ermitteln **. Es ist in Python implementiert und verwendet das ** ArUco ** -Modul von OpenCV.
Verwenden Sie GoogleColab (Python 3.6.9). Die Version des verwendeten Moduls lautet wie folgt.
Verschiedene Modulversionen
%pip list | grep -e opencv -e numpy
numpy 1.17.5
opencv-contrib-python 4.1.2.30
opencv-python 4.1.2.30
Laden Sie zunächst die für die Verarbeitung erforderlichen Module.
In der GoogleColab. -Umgebung ist "cv2.imshow (...)" nicht verfügbar. Auf folgende Weise kann das Bild (np.ndarray) in der Ausführungsergebniszelle mit "cv2_imshow (...)" angezeigt werden. Es kann ausgegeben werden.
Modulimport
import cv2
import numpy as np
from google.colab.patches import cv2_imshow
ArUco hat ** eine Reihe vordefinierter Marker **. Dieses Mal werden wir es verwenden. Verwenden Sie "aruco.getPredefinedDictionary (...)", um das Wörterbuch abzurufen, das die vordefinierten Markierungen enthält. Das Argument aruco.DICT_4X4_50
wählt ein Wörterbuch mit bis zu $ 50 $ Markierungen mit einem $ 4 \ mal 4 $ Füllmuster innerhalb eines Quadrats aus.
Speichern Sie die vordefinierten Markierungen (0-3) als Bilddatei
aruco = cv2.aruco
p_dict = aruco.getPredefinedDictionary(aruco.DICT_4X4_50)
marker = [0] * 4 #Initialisieren
for i in range(len(marker)):
marker[i] = aruco.drawMarker(p_dict, i, 75) # 75x75 px
cv2.imwrite(f'marker{i}.png', marker[i])
Wenn Sie die obigen Schritte ausführen, werden $ 4 $ -Dateien ($ 75 \ mal 75 $ Pixelgröße) von "marker0.png " bis "marker3.png " ausgegeben.
In der GoogleColab. -Umgebung kann der erstellte Marker in der Ausgabezelle mit "cv2_imshow (...)" angezeigt und bestätigt werden. Sie können sehen, dass innerhalb des Quadrats ein Marker mit einem Füllmuster von $ 4 \ times 4 $ erstellt wird. Wenn Sie dem Argument von "aruco.getPredefinedDictionary (...)" DICT_5X5_100 "geben, können Sie ein Wörterbuch mit bis zu 100 $ Markierungen erhalten, die im Muster von $ 5 \ times 5 $ definiert sind.
Drucken Sie "marker0.png ", dh die Ausgabe des 0. Markers von "aruco.DICT_4X4_50" auf Papier. Nennen wir es dann "m0-photo.jpg ", aufgenommen mit einer Kamera. Für dieses Ziel werden Marker erkannt, und das Bild "img_marked", das die Erkennungsergebnisse überlagert, wird ausgegeben.
Erkannt von Fotos mit Marker 0
img = cv2.imread('m0-photo.jpg')
corners, ids, rejectedImgPoints = aruco.detectMarkers(img, p_dict) #Erkennung
img_marked = aruco.drawDetectedMarkers(img.copy(), corners, ids) #Ergebnisse der Überlagerungserkennung
cv2_imshow(img_marked) #Anzeige
Der gesamte Marker ist ** grünes Quadrat ** </ font>, die obere linke Ecke des Markers ist ** rotes Quadrat ** </ font> und die Marker-ID (Nummer) ) Wird mit ** blauen Zeichen </ font> ** gezeichnet, die dem Originalbild überlagert sind. Daraus können Sie ersehen, dass die Markererkennung ordnungsgemäß durchgeführt wurde.
Schauen wir uns den ** Rückgabewert ** von aruco.detectMarkers (...)
genauer an.
Ecken
speichert die Bildkoordinaten ** der Ecke (vier Ecken) ** jedes im Bild gefundenen Markers in einer Liste von np.ndarray.
py:aruco.detectMarkers(...)Rückgabewertecken
#■ Rückgabewertecken
# 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)
Ausführungsergebnis
[array([[[265., 258.],
[348., 231.],
[383., 308.],
[297., 339.]]], dtype=float32)]
Die Eckkoordinaten werden gegen den Uhrzeigersinn in der Reihenfolge oben links, oben rechts, unten rechts und unten links in der Markierung gespeichert. Das heißt, es sieht so aus:
ids
speichert die im Bild gefundene ** Marker-ID ** im Format numpy.ndarray.
py:aruco.detectMarkers(...)Rückgabewert-IDs
#■ IDs zurückgeben
# print(type(ids)) # -> <class 'numpy.ndarray'>
# print(ids.shape) # -> (1, 1)
print(ids)
reverseImgPoints
speichert die Koordinaten der im Bild gefundenen Quadrate, die nicht das entsprechende Muster enthalten (https://docs.opencv.org/4.1.2). /d9/d6a/group__aruco.html#gab9159aa69250d8d3642593e508cb6baa) enthält die imgPoints der Quadrate, deren innerer Code keine korrekte Codierung aufweist. Nützlich für Debugging-Zwecke.).
Ich denke, es ist einfacher, dies zu verstehen, wenn Sie sich ein konkretes Beispiel ansehen. reverseImgPoints
speichert die Koordinaten des Quadrats, wie durch den Pfeil in der Abbildung gezeigt.
Überprüfen Sie das Ergebnis wie folgt, wenn das Bild mehrere Markierungen enthält (einschließlich des Falls, in dem zwei oder mehr derselben Markierung vorhanden sind). Der Erkennungscode ist der gleiche wie oben. Das Überlagern der Erkennungsergebnisse sieht folgendermaßen aus: Es spielt keine Rolle, ob zwei oder mehr der gleichen Marker vorhanden sind.
Die Rückgabewerte "ids" und "Ecken" von "aruco.detectMarkers (...)" lauten wie folgt: Beachten Sie, dass sie nicht nach ID sortiert sind.
print(ids)Ergebnis von
[[1]
[1]
[0]
[2]]
print(corners)Ergebnis von
[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)]
Bereiten Sie ein Blatt Papier vor, in dem die vier zuerst erstellten Markierungen "marker0.png " bis "marker3.png " wie unten gezeigt im Uhrzeigersinn angeordnet sind. Hier wird der Abstand zwischen den Markierungen auf $ 150 \ mathrm {mm} $ festgelegt (diese Papiererstellung ist ungenau, wenn sie nicht streng durchgeführt wird).
Platzieren Sie das Objekt "Hund", dessen Position Sie erkennen möchten, auf diesem Papier und schießen Sie von ** einer geeigneten Position über ** (die Schussposition und der Schusswinkel müssen zu diesem Zeitpunkt nicht genau sein). Der Zweck ist es, die Position dieses "Hundes" im ** marker-basierten realen Koordinatensystem ** zu finden.
Das von einer geeigneten Position und einem geeigneten Winkel oben aufgenommene Kamerabild wird in ** das direkt von oben gesehene Bild ** konvertiert. Hier ist das konvertierte Bild $ 500 \ times 500 , \ mathrm {px} $.
Konvertieren Sie in ein Bild von oben
aruco = cv2.aruco
p_dict = aruco.getPredefinedDictionary(aruco.DICT_4X4_50)
img = cv2.imread('inu.jpg')
corners, ids, rejectedImgPoints = aruco.detectMarkers(img, p_dict) #Erkennung
#Speichern Sie die Markierung "Mittelkoordinaten" in m in der Reihenfolge von links oben im Uhrzeigersinn
m = np.empty((4,2))
for i,c in zip(ids.ravel(), corners):
m[i] = c[0].mean(axis=0)
width, height = (500,500) #Bildgröße nach der 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)
Das Ausführungsergebnis ist wie folgt. Sie können sehen, dass die ** Mitte ** jedes Markers so transformiert wird, dass sie zu den vier Ecken des Bildes wird. Selbst wenn Sie ein Bild verwenden, das von einer schrägeren Position aus aufgenommen wurde, können Sie es wie folgt von oben nach oben in ein Bild konvertieren.
Aus Gründen der Klarheit ist die ** Mitte ** jedes Markers jetzt die vier Ecken des konvertierten Bildes. Es ist jedoch unpraktisch, die Position des "Hundes" zu berechnen (Markerfragmente können sich in den vier Ecken widerspiegeln ...).
Ändern Sie daher das Programm so, dass die Ecken jeder Markierung, die das Quadrat $ 150 \ times 150 \ mathrm {mm} $ auf dem Papier berühren, die vier Ecken des konvertierten Bildes sind.
Konvertiert in ein Bild von oben (verbesserte Version)
aruco = cv2.aruco
p_dict = aruco.getPredefinedDictionary(aruco.DICT_4X4_50)
img = cv2.imread('inu.jpg')
corners, ids, rejectedImgPoints = aruco.detectMarkers(img, p_dict) #Erkennung
#Hier ändern
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) #Bildgröße nach der 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)
Das Ausführungsergebnis ist wie folgt. Die Größe dieses Bildes beträgt $ 500 \ mal 500 , \ mathrm {px} $, und die entsprechende Größe auf Papier beträgt $ 150 \ mal 150 , \ mathrm {mm} $. Wenn daher die Koordinaten auf dem Bild $ (160 \ mathrm {px}, , 200 \ mathrm {px}) $ sind, sind die entsprechenden reellen Koordinaten $ (160 \ times \ frac {150} {500} = 48. Es kann berechnet werden als \ mathrm {mm}, , 200 \ times \ frac {150} {500} = 60 \ mathrm {mm}) $.
Verwenden Sie die verschiedenen Funktionen von OpenCV, um die Position des Hundes anhand des obigen Bildes zu ermitteln. Besonders wichtig ist "cv2.connectedComponentsWithStats (...)".
Positionsausgabe nach Objekterkennung und Realkoordinatenkonvertierung
tmp = img_trans.copy()
# (1)Graustufenumwandlung
tmp = cv2.cvtColor(tmp, cv2.COLOR_BGR2GRAY)
#cv2_imshow(tmp)
# (2)Unschärfeverarbeitung
tmp = cv2.GaussianBlur(tmp, (11, 11), 0)
#cv2_imshow(tmp)
# (3)Binarisierungsprozess
th = 130 #Binarisierungsschwelle(Anpassung erforderlich)
_,tmp = cv2.threshold(tmp,th,255,cv2.THRESH_BINARY_INV)
#cv2_imshow(tmp)
# (4)Blob (= Masse) Erkennung
n, img_label, data, center = cv2.connectedComponentsWithStats(tmp)
# (5)Erkennungsergebnisse organisieren
detected_obj = list() #Speicherziel des Erkennungsergebnisses
tr_x = lambda x : x * 150 / 500 #X-Achsen-Bildkoordinaten → reelle Koordinaten
tr_y = lambda y : y * 150 / 500 #Y-Achse 〃
img_trans_marked = img_trans.copy()
for i in range(1,n):
x, y, w, h, size = data[i]
if size < 300 : #Ignorieren Sie Bereiche mit weniger als 300 Pixel
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])))
#Bestätigung
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)Ergebnisse anzeigen
cv2_imshow(img_trans_marked)
for i, obj in enumerate(detected_obj,1) :
print(f'■ Erkanntes Objekt{i}Mittelstellung X.={obj["cx"]:>3.0f}mm Y={obj["cy"]:>3.0f}mm ')
Das Ausführungsergebnis ist wie folgt.
Ausführungsergebnis
■ Mittelposition X des erkannten Objekts 1=121mm Y= 34mm
Platzieren Sie ein Lineal und prüfen Sie, ob die obigen Ergebnisse angemessen sind. Es scheint, dass sie es richtig suchen.
Lassen Sie uns mehr Objekte setzen und überprüfen. Ich musste beim Binärisieren "den Schwellenwert anpassen", aber es hat funktioniert. Das erkannte Objekt 1 ist "Hund (braun)", das erkannte Objekt 2 ist "Hund (grau)", das erkannte Objekt 3 ist "Kaninchen" und das erkannte Objekt 4 ist "Bär".
--Versuchen Sie die Kombination mit maschinellem Lernen (Bildklassifizierung) (klassifizieren Sie jedes Objekt im Bild in "Hund", "Kaninchen" und "Bär"). --Versuchen Sie, eine Demo in Kombination mit dem Roboterarm zu erstellen.