Versuchen Sie, die Position eines Objekts auf dem Schreibtisch (reales Koordinatensystem) anhand des Kamerabilds mit Python + OpenCV zu messen

Einführung

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. 概要.png

Ausführungsumgebung

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   

Erstellen von Markern mit ArUco

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. cv2_show_marker.png 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. cv2_show_marker_5x5.png

Markererkennung durch ArUco

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

m0_photo_marked.png

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: m0-photo1_.png

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. rejectedImgPoints.png

Markererkennung durch ArUco (wenn mehrere Marker vorhanden sind)

Ü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). ms-photo.jpg 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. ms_photo_marked.png

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)]

Positionsmessung im realen Koordinatensystem

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). table.png

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. inu.png

Konvertieren Sie in ein Bild von oben

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. inu_trans.png 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. coin.png

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. inu_trans2.png 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}) $.

Objekterkennung aus Bildern

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. inu_trans_marked.png

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. inu_pos.png

Zusätzliches Experiment

Lassen Sie uns mehr Objekte setzen und überprüfen. m4-photo2.jpg 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". m4-result.png

Nächster Schritt

--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.

robot_arm.jpg

Verwandte Einträge

Recommended Posts