Perspektivtransformation in OpenCV durchführen, mit Bildern und Koordinaten erklärt

How to Do a Perspective Transform in OpenCV, Explained with Images and Coordinates

Written by

in

Dieser Artikel nutzt Python und OpenCV für eine Perspektivtransformation: die vier Ecken eines Bildes auf vier neue legen. Kurz: getPerspectiveTransform berechnet die Matrix, warpPerspective verzerrt die Pixel. Das Beispiel ist ein 600×600-Gitter mit einer Silhouette. (Die Beschriftungen in den Abbildungen sind englisch.)

OpenCV-Perspektivtransformation, beurteilt an Gitter und Personensilhouette. Zeigt, dass vier Ecken ein Trapez oder ein Parallelogramm ergeben können. Die vier Felder sind Frontal, Scherung, Trapez unten breit und eines nach rechts fliehend.
Dieselbe Eingabe, nur die vier Eckziele geändert

Kernpunkte

HierhinPunkt
Was wir tunLegen Sie fest, wohin die vier Ecken gehen; die Ebene streckt sich zum Trapez
Code und ErgebnisDas Skript unten mit derselben Ordnerlage wie in der Abbildung ausführen
Ordnersource_pics und save_pics neben das Skript legen
KoordinatenOben links ist [0, 0]. Reihenfolge: oben-links → unten-links → unten-rechts → oben-rechts
Die übrigen ZeilenLesen, Größe, Matrix, Verzerrung, Speichern
AusgabegrößePixel außerhalb des dritten Arguments werden abgeschnitten
Ihr eigenes BildNur Punktreihenfolge und Ausgabegröße an das gewünschte Rechteck anpassen
Eingeschränkte KameraAuch schräg liefern die vier Ecken der realen Ebene noch ein Rechteck an Daten

Eine Perspektivtransformation legt fest, wohin die vier Ecken gehen

Eine affine Transformation (warpAffine) lässt parallele Linien parallel. Eine Perspektivtransformation (getPerspectiveTransform und warpPerspective) kann die vier Ecken unabhängig verschieben, also ein Trapez, das vorn breit und hinten schmal ist. Eine Visitenkarte von oben zum Rechteck richten oder einen Boden in Aufsicht betrachten, gehören zu diesen Funktionen.

Übergeben Sie Eingangs- und Ausgangsecken in derselben Reihenfolge. Dieser Artikel geht die Algebra in der Matrix nicht durch. Wenn Sie wissen, wohin die vier Ecken gehen, können Sie die Funktionen nutzen.

Das Skript und Vorher/Nachher

Zuerst ein laufendes Skript. Die nächsten Abschnitte folgen den Koordinaten und den übrigen Zeilen.

test_1.py
import numpy as np
import cv2

image = cv2.imread("source_pics/test_1.png", cv2.IMREAD_COLOR)

height, width, channels = image.shape[:3]

source_points = np.array([[0, 0], [0, height], [width, height], [width, 0]], dtype=np.float32)
target_points = np.array([[200, 0], [0, 600], [600, 600], [400, 0]], dtype=np.float32)

mat = cv2.getPerspectiveTransform(source_points, target_points)

perspective_image = cv2.warpPerspective(image, mat, (width, height))
cv2.imwrite("save_pics/perspective_image.png", perspective_image)
Vorher/Nachher einer Perspektivtransformation, verglichen über Eingabe- und Ausgabedatei. Zeigt, dass Einziehen der oberen Ecken ein oben schmales Trapez ergibt. Links quadratisches test_1.png; rechts perspective_image.png.
test_1.png vorher und perspective_image.png nachher

Dieses Beispiel zieht die Oberkante nach innen, daher ist das Ergebnis ein Trapez, oben schmal und unten breit. Der schwarze Rand ist der Teil der Ausgabefläche außerhalb des verzerrten Vierecks.

Ordner neben das Skript legen

Ablaufordner für das Skript, gezeigt an Lage von Skript sowie Ein- und Ausgabeordner. Ordnet die Pfade im Code dem Lesen und Schreiben zu. test_1.py liegt neben source_pics und save_pics.
source_pics und save_pics neben test_1.py

Pfade im Code sind relativ zu dem test_1.py, das Sie ausführen. Das gelesene Bild ist source_pics/test_1.png; die geschriebene Datei ist save_pics/perspective_image.png. Bei eigenen Dateien die Namen angleichen oder die Pfadzeichenketten ändern.

💡 Tipp

Schlägt imread fehl, ist image None und die nächste Zeile, image.shape, bricht ab. Vor dem Lauf Ordnernamen und das aktuelle Arbeitsverzeichnis prüfen.

Koordinaten beginnen bei [0, 0] oben links

Python
source_points = np.array([[0, 0], [0, height], [width, height], [width, 0]], dtype=np.float32)
target_points = np.array([[200, 0], [0, 600], [600, 600], [400, 0]], dtype=np.float32)

Die Arrays stehen in dieser Reihenfolge. Der Ursprung liegt nicht unten, wie auf Millimeterpapier.

Reihenfolgesource_pointstarget_points
Oben links[0, 0][200, 0]
Unten links[0, height][0, 600]
Unten rechts[width, height][600, 600]
Oben rechts[width, 0][400, 0]

Dieses Beispiel ist 600×600, daher sind height und width beide 600. Ausgefüllt sehen die Arrays so aus.

Python
source_points = np.array([[0, 0], [0, 600], [600, 600], [600, 0]], dtype=np.float32)
target_points = np.array([[200, 0], [0, 600], [600, 600], [400, 0]], dtype=np.float32)
Vier-Ecken-Karte einer Perspektivtransformation, bezogen auf source_points und target_points. Mit Ursprung oben links verkürzt Einziehen der Oberkante die Oberseite. [0, 0] und [600, 0] gehen nach [200, 0] und [400, 0].
Die Oberkante nach innen, und oben wird ein schmales Trapez

Die beiden oberen Punkte rücken nach innen, die Oberkante wird kürzer. Die beiden unteren bleiben. Ändern Sie die Zahlen, und dieselbe Vier-Ecken-Zuordnung ergibt ein anderes Trapez.

⚠️ Häufige Falle

Stimmen Eingangs- und Ausgangsreihenfolge nicht überein, verdreht sich die Zuordnung. Beide als oben-links → unten-links → unten-rechts → oben-rechts schreiben. Punkte auf einer mathematischen y-Achse (oben positiv) lassen das Bild auf dem Kopf stehen.

Was die übrigen Zeilen tun

Imports

Python
import numpy as np
import cv2

NumPy hält die Punktarrays. OpenCVs cv2 liest, schreibt und verzerrt Bilder.

Bild lesen

Python
image = cv2.imread("source_pics/test_1.png", cv2.IMREAD_COLOR)

cv2.imread lädt test_1.png aus source_pics, neben dem Skript, in Farbe, in image.

Bildgröße

Python
height, width, channels = image.shape[:3]

shape liefert Höhe, Breite und Kanalzahl. Dieses Beispiel nutzt die Kanäle nicht, aber die Anzahl der entpackten Werte muss passen, daher werden alle drei genommen.

💡 Tipp

Die Zahl der Variablen an die Zahl der entpackten Werte anpassen.

Punkte für die Matrix

Python
source_points = np.array([[0, 0], [0, height], [width, height], [width, 0]], dtype=np.float32)
target_points = np.array([[200, 0], [0, 600], [600, 600], [400, 0]], dtype=np.float32)

Jeweils vier XY-Punkte. Auf diesem Quellbild ist [height, width] [600, 600], daher enthalten die Arrays:

Python
source_points =  [[  0.   0.]
 [  0. 600.]
 [600. 600.]
 [600.   0.]]
target_points =  [[200.   0.]
 [  0. 600.]
 [600. 600.]
 [400.   0.]]

Das sind die Koordinaten aus der Abbildung. Die Verzerrung nutzt diese Zuordnung.

Die Transformationsmatrix

Python
mat = cv2.getPerspectiveTransform(source_points, target_points)

cv2.getPerspectiveTransform berechnet die Matrix aus den vier Eingangsecken und den vier Ausgangsecken. In diesem Beispiel sind die Werte:

Python
mat =  [[ 3.33333333e-01 -3.33333333e-01  2.00000000e+02]
 [ 0.00000000e+00  3.33333333e-01  0.00000000e+00]
 [ 0.00000000e+00 -1.11111111e-03  1.00000000e+00]]

Die Bedeutung jedes Eintrags brauchen Sie hier nicht. Gehen die Ecken dorthin, wo Sie sie wollten, den Rückgabewert an die nächste Zeile übergeben.

Die Verzerrung

Python
perspective_image = cv2.warpPerspective(image, mat, (width, height))

cv2.warpPerspective nimmt das Quellbild, die Matrix sowie Ausgabebreite und -höhe. Dieses Beispiel verkürzt nur eine Kante, daher bleibt die Ausgabegröße (width, height), wie die Quelle. Die echte Größe an den transformierten Koordinaten festlegen.

Speichern

Python
cv2.imwrite("save_pics/perspective_image.png", perspective_image)

cv2.imwrite schreibt perspective_image.png nach save_pics, neben dem Skript. Den Ordner zuerst anlegen; ohne ihn schlägt das Schreiben fehl.

Ist die Ausgabe zu klein, wird der Überstand abgeschnitten

Lassen Sie die Ausgabe bei 600×600 und nehmen Sie Punkte wie diese, dann verlässt der gedehnte obere Teil die Fläche.

Python
source_points = np.array([[200, 0], [0, 600], [600, 600], [400, 0]], dtype=np.float32)
target_points = np.array([[0, 0], [0, 600], [600, 600], [600, 0]], dtype=np.float32)
Zuschnitt bei zu kleiner Ausgabefläche, gezeigt an Punkten außerhalb des Rahmens. Erklärt, dass das dritte Argument von warpPerspective die geschriebene Bildgröße ist. Die ursprünglichen [0, 0] und [600, 0] liegen außerhalb 600×600, oben wird geschnitten.
Bleibt die Ausgabe 600×600, wird der gedehnte obere Teil abgeschnitten

Das dritte Argument ist die Größe des Bildes, das Sie schreiben, nicht die Größe der verzerrten Welt. Pixel, deren Punkte außerhalb dieses Rechtecks liegen, sind weg. Um sie zu behalten, die Ausgabe zuerst vergrößern oder target-Punkte wählen, die alle hineinpassen.

⚠️ Häufige Falle

Liegt ein target-x oder -y jenseits von Ausgabebreite oder -höhe und bleibt das dritte Argument bei der Quellgröße, wird abgeschnitten. Sieht es geschnitten aus, zuerst Punkte und Ausgabegröße vergleichen.

Dieselben Schritte gelten für eigene Fotos

Bleibt die Vier-Ecken-Reihenfolge, arbeiten dieselben zwei Funktionen auch auf Fotos, die kein Gitter sind. Nur die target-Zahlen und die Ausgabegröße an das gewünschte Rechteck anpassen.

Auch wenn die Kamera schräg sitzt, liefern vier Ecken noch ein Rechteck

Sie haben eine kurze Kette gemacht: Eingangsecken den Ausgangsecken zuordnen, die Matrix lösen, auf die Ausgabefläche schreiben. Die Funktionen sind auf dem Gitter und auf einem Foto vom Boden dieselben. Vor Ort ändern sich nur die Punktwahl und die Ausgabegröße.

Wenn die Kameraposition von der Maschine festliegt

An Anlagen und Prüfständen können Sie die Kamera oft nicht senkrecht über dem Teil platzieren. Abdeckungen, Leitungen, Schutze und Roboterreichweite stehen zuerst; das Objektiv wird danach hineingezwängt. Messen oder lesen Sie das Bild so, bleibt ein Trapez, vorn breit und hinten schmal, und Längen und Lagen sind verzerrt.

Ordnen Sie die vier Ecken zu, und Sie gewinnen das Rechteck der gewünschten Ebene zurück, ohne den Rahmen neu zu bauen. Datenerfassung bleibt möglich, wenn die Kamerapose strukturell eingeschränkt ist. Der eigentliche Kniff ist, die Punkte auf die vier Ecken der physischen Ebene zu legen, nicht auf die vier Ecken des Bildschirms. Eine Werkstückecke, eine Vorrichtungsbohrung, eine Bodenfuge — jeder Punkt, den Sie später wieder zeigen können — und die Schritte dieses Artikels gelten unverändert.

Sie müssen die Mathematik nicht selbst schreiben

OpenCV ist eine Implementierung für diese Aufgabe. Die Punkte mit NumPy zu legen und an OpenCVs getPerspectiveTransform und warpPerspective zu übergeben, heißt eine gepflegte Implementierung zu nutzen statt die Algebra selbst zu schreiben. Diese Wahl ist nicht auf OpenCV beschränkt. Rund um Bildverarbeitung, Numerik, Kommunikation und Steuerung gibt es für dieselbe Arbeit bereits Implementierungen, die andere pflegen und versionieren. Diesen Code zu lesen und zu nutzen bringt Sie schneller zu den Maschinengrenzen zurück, als die projektive Mathematik von Grund auf abzuleiten.

Nutzen reicht. Treffen Sie auf ein Loch oder etwas Unklares, können Sie es melden, die Doku richten oder einen Patch schicken. Wie viel Sie zurückgeben, unterscheidet sich. Auch null stellt Sie noch auf die Seite, die diese Arbeit am Laufen hält, indem Sie auf die nächste Version warten. Das Werkzeug an Ihre Maschine anpassen und bei Bedarf etwas zurückgeben. Diese Schleife nutzt Ingenieurzeit besser, als eine vollständige Eigenentwicklung zu tragen.

Auch mit schräger Kamera machen vier Ecken noch ein Rechteck an Daten. Danach ist die eigentliche Arbeit, zu messen, zu lesen und zu behalten, was auf diesem Rechteck liegt.

Comments

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert