Comment faire une transformation de perspective dans OpenCV, expliqué avec des images et des coordonnées

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

Written by

in

Cet article utilise Python et OpenCV pour une transformation de perspective : déplacer les quatre coins d’une image vers quatre nouveaux. En résumé, getPerspectiveTransform calcule la matrice et warpPerspective déforme les pixels. L’exemple est une grille 600×600 avec une silhouette. (Les libellés des figures sont en anglais.)

Figure de perspective OpenCV, d’après une grille et une silhouette. Montre qu’apparier quatre coins fait un trapèze ou un parallélogramme. Les panneaux sont face, cisaillement, trapèze large en bas, et un qui fuit à droite.
La même entrée, seuls les destins des quatre coins changent

Points clés

RegarderIdée
Ce que l’on faitFixer où vont les quatre coins, et le plan s’étire en trapèze
Code et résultatExécuter le script ci-dessous avec la même arborescence que la figure
DossiersPlacer source_pics et save_pics à côté du script
CoordonnéesLe haut gauche est [0, 0]. Ordre : haut-gauche → bas-gauche → bas-droit → haut-droit
Le reste des lignesLecture, taille, matrice, déformation, enregistrement
Taille de sortieLes pixels hors du troisième argument sont coupés
Votre propre imageNe changer que l’ordre des points et la taille de sortie vers le rectangle voulu
Caméra contrainteMême en biais, apparier les quatre coins du plan réel donne encore un rectangle de données

Une transformation de perspective fixe où vont les quatre coins

Une transformée affine (warpAffine) laisse les parallèles parallèles. Une de perspective (getPerspectiveTransform et warpPerspective) peut bouger les quatre coins séparément, donc un trapèze large devant et étroit derrière. Remettre une carte de visite en rectangle vue de dessus, ou regarder un sol en plan, relèvent de ces fonctions.

Passez les coins d’entrée et les coins de sortie dans le même ordre. Cet article ne suit pas l’algèbre de la matrice. Si vous savez où vont les quatre coins, vous pouvez utiliser les fonctions.

Le script et l’avant/après

D’abord un script qui tourne. Les sections suivantes suivent les coordonnées et le reste des lignes.

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)
Avant/après d’une transformation de perspective, comparé par fichier d’entrée et de sortie. Montre qu’approcher les coins du haut fait un trapèze étroit en haut. Gauche : test_1.png carré ; droite : perspective_image.png.
test_1.png avant, perspective_image.png après

Cet exemple rapproche le bord supérieur, donc le résultat est un trapèze étroit en haut et large en bas. La marge noire est la partie du canevas de sortie hors du quadrilatère déformé.

Placer les dossiers à côté du script

Arborescence pour lancer le script, d’après la place du script et des dossiers d’entrée et de sortie. Relie les chemins du code à où l’on lit et écrit. test_1.py est à côté de source_pics et save_pics.
source_pics et save_pics à côté de test_1.py

Les chemins du code sont relatifs au test_1.py que vous lancez. L’image lue est source_pics/test_1.png ; le fichier écrit est save_pics/perspective_image.png. Pour vos fichiers, alignez ces noms ou changez les chaînes de chemin.

💡 Astuce

Si imread échoue, image vaut None et la ligne suivante, image.shape, s’arrête. Avant de lancer, vérifiez l’orthographe des dossiers et le répertoire de travail courant.

Les coordonnées commencent à [0, 0] en haut à gauche

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)

Les tableaux suivent cet ordre. L’origine n’est pas en bas, comme sur du papier millimétré.

Ordresource_pointstarget_points
Haut gauche[0, 0][200, 0]
Bas gauche[0, height][0, 600]
Bas droit[width, height][600, 600]
Haut droit[width, 0][400, 0]

Cet exemple fait 600×600, donc height et width valent 600. Remplis, les tableaux ressemblent à ceci.

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)
Carte des quatre coins pour une transformation de perspective, d’après source_points et target_points. Avec origine en haut à gauche, rapprocher le bord supérieur raccourcit le haut. [0, 0] et [600, 0] vont vers [200, 0] et [400, 0].
Rapprochez le bord supérieur : le haut devient un trapèze étroit

Les deux points du haut se rapprochent, donc le bord supérieur raccourcit. Les deux du bas ne bougent pas. Changez les nombres et le même appariement des quatre coins fait un autre trapèze.

⚠️ Piège courant

Si l’ordre d’entrée et l’ordre de sortie ne correspondent pas, la correspondance se tord. Écrivez les deux en haut-gauche → bas-gauche → bas-droit → haut-droit. Si vous prenez les points sur un axe y mathématique (le haut est positif), l’image paraît à l’envers.

Ce que font les autres lignes

Imports

Python
import numpy as np
import cv2

NumPy tient les tableaux de points. Le cv2 d’OpenCV lit, écrit et déforme les images.

Lecture de l’image

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

cv2.imread charge test_1.png depuis source_pics, à côté du script, en couleur, dans image.

Taille de l’image

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

shape donne la hauteur, la largeur et le nombre de canaux. Cet exemple n’utilise pas les canaux, mais le nombre de valeurs dépaquetées doit correspondre, donc on prend les trois.

💡 Astuce

Alignez le nombre de variables sur le nombre de valeurs que vous dépaquetez.

Points pour la matrice

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)

Quatre points XY de chaque côté. Sur cette image source, [height, width] vaut [600, 600], donc les tableaux contiennent :

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

Ce sont les coordonnées de la figure. La déformation utilise cet appariement.

La matrice de transformation

Python
mat = cv2.getPerspectiveTransform(source_points, target_points)

cv2.getPerspectiveTransform calcule la matrice à partir des quatre coins d’entrée et des quatre de sortie. Dans cet exemple, les valeurs sont :

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

Le sens de chaque coefficient n’est pas utile ici. Si les coins vont où vous vouliez, passez la valeur de retour à la ligne suivante.

La déformation

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

cv2.warpPerspective prend l’image source, la matrice, et la largeur et la hauteur de sortie. Cet exemple ne raccourcit qu’un bord, donc la taille de sortie reste (width, height), comme la source. Choisissez la vraie taille en regardant les coordonnées transformées.

Enregistrement

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

cv2.imwrite écrit perspective_image.png dans save_pics, à côté du script. Créez d’abord le dossier ; l’écriture échoue s’il manque.

Si la sortie est trop petite, le débordement est coupé

Gardez la sortie à 600×600 et prenez des points comme ceux-ci : le haut élargi sort du canevas.

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)
Coupe quand le canevas de sortie est trop petit, d’après des points hors du cadre. Explique que le troisième argument de warpPerspective est la taille écrite. Les [0, 0] et [600, 0] d’origine sont hors de 600×600, donc le haut est coupé.
Si la sortie reste 600×600, le haut élargi est coupé

Le troisième argument est la taille de l’image que vous écrivez, pas la taille du monde déformé. Les pixels dont les points sortent de ce rectangle disparaissent. Pour les garder, agrandissez d’abord la sortie, ou choisissez des points target qui tiennent tous dedans.

⚠️ Piège courant

Si un x ou un y de target dépasse la largeur ou la hauteur de sortie, et que vous laissez le troisième argument à la taille source, ça coupe. Quand ça a l’air coupé, comparez d’abord les points à la taille de sortie.

Les mêmes étapes marchent sur vos propres photos

Gardez l’ordre des quatre coins et les deux mêmes fonctions marchent sur des photos qui ne sont pas une grille. Ne changez que les nombres de target et la taille de sortie vers le rectangle voulu.

Même si la caméra est en biais, apparier quatre coins donne encore un rectangle

Vous avez fait une chaîne courte : apparier les coins d’entrée aux coins de sortie, résoudre la matrice, écrire sur le canevas de sortie. Les fonctions sont les mêmes sur la grille et sur une photo du sol. Sur site, seuls le choix des points et la taille de sortie changent.

Quand la position de la caméra est fixée par la machine

Sur un équipement ou un banc d’inspection, on ne peut souvent pas poser la caméra pile au-dessus de la pièce. Capots, tuyaux, protecteurs et zone robot se décident d’abord ; l’objectif se glisse après. Mesurer ou lire cette image telle quelle laisse un trapèze large devant et étroit derrière, donc longueurs et positions sont faussées.

Appariez les quatre coins et vous récupérez le rectangle du plan voulu, sans reconstruire le bâti. La collecte de données reste possible quand la pose de la caméra est contrainte par la structure. L’astuce réelle est de poser les points sur les quatre coins du plan physique, pas les quatre coins de l’écran. Un coin de pièce, un trou de montage, un joint de sol — tout point que vous pourrez re-désigner plus tard — et les étapes de cet article s’appliquent telles quelles.

Vous n’avez pas à écrire les maths vous-même

OpenCV est une implémentation pour ce travail. Ranger les points avec NumPy et les passer à getPerspectiveTransform et warpPerspective d’OpenCV, c’est choisir une implémentation maintenue plutôt que d’écrire l’algèbre soi-même. Ce choix n’est pas limité à OpenCV. Autour du traitement d’image, du calcul numérique, des communications et de la commande, le même travail a déjà des implémentations, tenues et versionnées par d’autres. Lire et utiliser ce code vous ramène plus vite aux contraintes de la machine que dériver les maths projectives depuis zéro.

S’en servir suffit. Si vous tombez sur un trou ou quelque chose d’obscur, vous pouvez signaler, corriger la doc ou envoyer un patch. La quantité rendue varie. Même zéro vous met encore du côté qui fait durer ce travail, en attendant la version suivante. Adaptez l’outil à votre machine, et rendez un peu si besoin. Cette boucle dépense mieux le temps d’ingénierie que de porter une réécriture interne complète.

Même avec une caméra en biais, quatre coins font encore un rectangle de données. Ensuite, le vrai travail est de mesurer, lire et garder ce qui est sur ce rectangle.

Comments

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *