Explication détaillée de la transformation de la perspective d'image basée sur OpenCV (de la théorie à la mise en œuvre à la pratique)

1. Transformation affine et transformation perspective

         J'ai été incapable de comprendre la différence entre les deux transformations affines et les transformations de perspective, j'ai donc étudié en détail les détails des deux transformations, réécrit les formules et donné certaines de mes propres opinions.

1. Transformation affine

        On peut considérer que la transformation affine est un cas particulier de transformation perspective .

        La transformation affine est une transformation linéaire entre des coordonnées bidimensionnelles et des coordonnées bidimensionnelles , c'est-à-dire une transformation linéaire impliquant uniquement des graphiques bidimensionnels dans un plan .

La translation , la rotation , le décalage et la mise à l'échelle         des graphiques peuvent être exprimés par la matrice de transformation de la transformation affine .

        Il conserve deux propriétés des graphiques bidimensionnels :

       ① « Rectitude » : Les lignes droites sont toujours des lignes droites après transformation. Une ligne droite est toujours une ligne droite après avoir été translatée , pivotée , décalée et mise à l'échelle .

        ② "Parallélisme": Les lignes parallèles sont toujours des lignes parallèles après transformation, et l'ordre de position des points sur la ligne reste inchangé.

        Le sentiment intuitif est que lorsque nous faisons glisser, retournons, étirons, etc. une image sur l'ordinateur, l'angle de vue de l'image ne changera pas.

        Toute transformation affine peut être exprimée sous la forme d'un vecteur de coordonnées multiplié par une matrice Voici les formes matricielles de plusieurs transformations affines.

        Agrandir :

\begin{equation} \left[ \begin{array}{c} x'\\ y'\\ \end{array} \right ] = \left[ \begin{array}{c} T_{x}x\ \ T_{y}y\\ \end{array} \right ] = \left[ \begin{array}{ccc} T_{x}& 0 \\ 0& T_{y} \\ \end{array} \right ] \left[ \begin{tableau}{c} x\\ y\\ \end{tableau} \right ] \end{équation}

        Rotation :

\begin{equation}\left[\begin{array}{c}x'\\y'\\\end{array}\right] = \left[\begin{array}{c}xcos\theta-ysin\ theta\\xsin\theta+ycos\theta\\end{tableau}\right] = \left[\begin{tableau}{cc}cos\theta& -sin\theta\\sin\theta& cos\theta\\\ end {tableau} \right ] \left[ \begin{tableau}{c} x\\ y\\ \end{tableau} \right ] \end{équation}

        Erreur de coupe :

\begin{equation} \left[ \begin{array}{c} x'\\ y'\\ \end{array} \right ] = \left[ \begin{array}{c} x+ytan\phi\ \ y+xtan\varphi\\ \end{tableau} \right ] = \left[ \begin{tableau}{cc} 1& tan\phi \\ tan\varphi& 1 \\ \end{tableau} \right ] \left [ \begin{tableau}{c} x\\ y\\ \end{tableau} \right ] \end{équation}

        Les transformations ci-dessus peuvent être directement transformées avec seulement une matrice 2x2, mais la traduction ne peut pas être effectuée, car quelle que soit la multiplication dans une matrice 2x2, une quantité constante ne peut pas être transformée. Par conséquent, il est nécessaire de changer le vecteur de coordonnées bidimensionnel d'origine en une coordonnée homogène , c'est-à-dire d'utiliser un vecteur tridimensionnel pour représenter un vecteur bidimensionnel.

        \left[ \begin{tableau}{c} x\\ y\\ \end{tableau} \right ] => \left[ \begin{tableau}{c} x\\ y\\ 1\\ \end{ tableau} \right ]

        Poêle:

        \begin{equation} \left[ \begin{array}{c} x'\\ y'\\ \end{array} \right ] = \left[ \begin{array}{c} x+T_{x} \\ y+T_{y}\\ \end{array} \right ] = \left[ \begin{array}{ccc} 1& 0 &T_{x}\\ 0& 1 &T_{y}\\ \end{array } \right ] \left[ \begin{tableau}{c} x\\ y\\ 1\\ \end{tableau} \right ] \end{équation}

        Après être devenues des coordonnées homogènes, afin de réaliser la transformation de la matrice 2x2 d'origine , telle que la mise à l'échelle, la rotation et l'échelonnementT_{x}=T_{y}=0 , seul l'ordre est requis.

        Les transformations ci-dessus sont toutes des transformations linéaires, de sorte que la transformation affine peut être exprimée par la formule générale suivante , qui est la forme courante sur Internet :

        \begin{equation} \left[ \begin{array}{c} x'\\ y'\\ \end{array} \right ] = \left[ \begin{array}{c} a_{11}x+ a_{12}y+a_{13}\\ a_{21}x+a_{22}y+a_{23}\\ \end{tableau} \right ] = \left[ \begin{tableau}{ccc} a_{11}& a_{12}&a_{13}\\ a_{21}& a_{22} &a_{23}\\ \end{array} \right ] \left[ \begin{array}{c} x \\ y\\ 1\\ \end{tableau} \right ] \end{équation}

A ce moment, la matrice de transformation         de la transformation affine T= \left[ \begin{array}{ccc} a_{11}& a_{12}&a_{13}\\ a_{21}& a_{22} &a_{23}\\ \end{array} \right ]est une matrice 2x3 .

        Ainsi, les équations de transformation de coordonnées sont les suivantes :

        \begin{equation} \left\{ \begin{matrice}{} x'=a_{11}x+a_{12}y+a_{13} \\ y'=a_{21}x+a_{22} y+a_{23} \\ \end{matrice} \right.  \end{équation}

        On peut voir qu'il y a 6 coefficients inconnus, qui nécessitent 3 paires de points de cartographie (à condition qu'ils soient indépendants les uns des autres) pour être résolus. Il n'est pas difficile de comprendre que 6 variables nécessitent naturellement au moins 6 équations pour être calculées, et 1 paire de points cartographiques peut fournir 2 équations .

        En même temps, les trois points déterminent de manière unique un plan, et les trois autres points de cartographie doivent être dans le même plan en raison de la transformation linéaire, on peut donc dire que la transformation affine est une transformation graphique dans le plan.

2. Transformation du point de vue

        La transformation de perspective consiste à projeter l'image sur un nouveau plan de visualisation, également appelé mappage de projection.

        C'est une application de deux dimensions(x,y) à trois dimensions(X,Y,Z) , puis à un autre espace à deux dimensions(x',y') .

        Contrairement à la transformation affine, il ne s'agit pas simplement d'une transformation linéaire. Il offre plus de flexibilité pour mapper une région quadrilatérale à une autre région quadrilatérale.

        La transformation de perspective est également mise en œuvre par multiplication matricielle, en utilisant une matrice 3x3 , les deux premières lignes de la matrice sont les mêmes que la matrice affine, ce qui signifie que toutes les transformations de la transformation de perspective de transformation affine peuvent également être réalisées. La troisième ligne est utilisée pour implémenter la transformation de perspective.

        Les transformations de perspective utilisent également des coordonnées homogènes pour représenter les vecteurs 2D :

        \begin{equation} \left[ \begin{array}{c} x'\\ y'\\ z'\\ \end{array} \right ] = \left[ \begin{array}{ccc} a_{ 11}& a_{12} &a_{13}\\ a_{21}& a_{22} &a_{23}\\ a_{31}& a_{32} &a_{33}\\ \end{tableau} \right ] \left[ \begin{tableau}{c} x\\ y\\ 1\\ \end{tableau} \right ] \end{équation}

        

À ce moment, la matrice de transformation         de la transformation de perspective T= \left[ \begin{array}{ccc} a_{11}& a_{12} &a_{13}\\ a_{21}& a_{22} &a_{23}\\ a_{31}& a_{ 32} &a_{33}\\ \end{tableau} \right ]est une matrice 3x3

        La transformation de perspective \left[ \begin{array}{c} x'\\ y'\\ z'\\ \end{array} \right ]n'est pas les coordonnées finales, et une transformation supplémentaire est nécessaire :

\begin{equation} \left[ \begin{array}{c} x'\\ y'\\ z'\\ \end{array} \right ] = z' \left[ \begin{array}{c} \frac{x'}{z'}\\ \frac{y'}{z'}\\ 1\\ \end{tableau} \right ] \end{équation}

        \left[ \begin{array}{c} \frac{x'}{z'}\\ \frac{y'}{z'}\\ 1\\ \end{array} \right ]est les coordonnées finales transformées, à savoir :

\begin{equation} \left[ \begin{array}{c} x''\\ y''\\ 1\\ \end{array} \right ] = \left[ \begin{array}{c} \ frac{x'}{z'}\\ \frac{y'}{z'}\\ 1\\ \end{tableau} \right ] \end{équation}

        En fait, ici vous pouvez comprendre pourquoi la transformation affine est un cas particulier de transformation de perspective . Car si le vecteur de coordonnées après transformation affine est aussi représenté par des coordonnées homogènes \left[ \begin{array}{c} x'\\ y'\\ 1\\ \end{array} \right ], la conversion de matrice 2x3 en matrice 3x3 peut être considérée comme :

T_{affine}= \left[ \begin{array}{ccc} a_{11}& a_{12} &a_{13}\\ a_{21}& a_{22} &a_{23}\\ 0& 0 &1\ \ \end{tableau} \right ]

        À ce stade, les formes de transformation affine et de transformation de perspective sont unifiées et le processus de transformation affine est considéré comme suit :

\begin{equation} \left[ \begin{array}{c} x'\\ y'\\ 1\\ \end{array} \right ] = \left[ \begin{array}{ccc} a_{11 }& a_{12} &a_{13}\\ a_{21}& a_{22} &a_{23}\\ 0& 0 &1\\ \end{array} \right ] \left[ \begin{array}{c } x\\ y\\ 1\\ \end{tableau} \right ] \end{équation}

La transformation affine n'est         donc qu'un cas particulier de la troisième ligne . À l'heure actuelle, il a été étendu aux vecteurs de dimension 3. En fait, nous pouvons également comprendre que la transformation affine est une transformation d'image dans le plan effectuée sur le plan du système de coordonnées spatiales , c'est-à-dire la valeur dans la direction z est 1, peu importe comment il change, et il est toujours limité à dans le plan.\left[ \begin{matrice} 0&0&1\\ \end{matrice} \right ]Z=1Z=1

        Pour en revenir à la transformation de perspective, l'ensemble du processus de transformation de perspective est le suivant :

\begin{equation} \begin{aligned} \left[ \begin{array}{c} x''\\ y''\\ 1\\ \end{array} \right ] = \left[ \begin{array }{c} \frac{x'}{z'}\\ \frac{y'}{z'}\\ 1\\ \end{array} \right ] = \frac{1}{z'} \ gauche[ \begin{array}{c} x'\\ y'\\ z'\\ \end{array} \right ] = \frac{1}{z'} \left[ \begin{array}{ccc } a_{11}& a_{12} &a_{13}\\ a_{21}& a_{22} &a_{23}\\ a_{31}& a_{32} &a_{33}\\ \end{tableau } \right ] \left[ \begin{array}{c} x\\ y\\ 1\\ \end{array} \right ] \\= \frac{1}{a_{31}x+a_{32 }y+a_{33}} \left[ \begin{array}{ccc} a_{11}& a_{12} &a_{13}\\ a_{21}& a_{22} &a_{23}\\ a_ {31}& a_{32} &a_{33}\\ \end{tableau} \right ] \left[ \begin{tableau}{c} x\\ y\\ 1\\ \end{tableau} \right ] \end{aligné} \end{équation}

        Ainsi, les équations de transformation de coordonnées sont les suivantes :

\begin{equation} \left\{ \begin{matix}{} x''=\frac{x'}{z'}=\frac{a_{11}x+a_{12}y+a_{13} }{a_{31}x+a_{32}y+a_{33}} \\ y''=\frac{y'}{z'}=\frac{a_{21}x+a_{22}y +a_{23}}{a_{31}x+a_{32}y+a_{33}} \\ \end{matix} \right.  \end{équation}

        Il y a 9 paramètres inconnus au total, qui peuvent être encore simplifiés en 8 paramètres inconnus, ce qui est la forme courante sur Internet.

        Mon idée ici est de prouver que la transformation représentée par la matrice de transformation T= \left[ \begin{array}{ccc} a_{11}& a_{12} &a_{13}\\ a_{21}& a_{22} &a_{23}\\ a_{31}& a_{ 32} &a_{33}\\ \end{tableau} \right ]et la matrice de transformation (k est une constante) sont équivalentes.T'=kT= \left[ \begin{array}{ccc} à_{11}& à_{12} &à_{13}\\ à_{21}& à_{22} &à_{23}\\ à_{31} &to_{32} &to_{33}\\\end{array}\right]

        Définissez la matrice de transformation sur T'et remplacez-la par l'opération :

\begin{equation} \left[ \begin{array}{c} x'\\ y'\\ z'\\ \end{array} \right ] =T' \left[ \begin{array}{c} x\\ y\\ 1\\ \end{tableau} \right ] =kT \left[ \begin{tableau}{c} x\\ y\\ 1\\ \end{tableau} \right ] = \left [ \begin{array}{ccc} ka_{11}& ka_{12} &ka_{13}\\ ka_{21}& ka_{22} &ka_{23}\\ ka_{31}& ka_{32} &ka_{ 33}\\ \end{tableau} \right ] \left[ \begin{tableau}{c} x\\ y\\ 1\\ \end{tableau} \right ] \end{équation}

        Les équations sont obtenues comme suit :

\begin{equation} \left\{ \begin{matix}{} x''=\frac{k(a_{11}x+a_{12}y+a_{13})}{k(a_{31} x+a_{32}y+a_{33})}=\frac{a_{11}x+a_{12}y+a_{13}}{a_{31}x+a_{32}y+a_{ 33}} \\ y''=\frac{k(a_{21}x+a_{22}y+a_{23})}{k(a_{31}x+a_{32}y+a_{33 })}=\frac{a_{21}x+a_{22}y+a_{23}}{a_{31}x+a_{32}y+a_{33}} \\ \end{matix} \ droite.  \end{équation}

        On constate que le T'calcul final est le même que le résultat calculé x'',y''à l'aide de la matrice de transformation .J

        Donc pour T= \left[ \begin{array}{ccc} a_{11}& a_{12} &a_{13}\\ a_{21}& a_{22} &a_{23}\\ a_{31}& a_{ 32} &a_{33}\\ \end{tableau} \right ], on peut toujours être équivalent à \frac{T}{a_{33}}= \left[ \begin{array}{ccc} \frac{a_{11}}{a_{33}}& \frac{a_{12}}{a_{33 }} &\frac{a_{13}}{a_{33}}\\ \frac{a_{21}}{a_{33}}& \frac{a_{22}}{a_{33}} &\ frac{a_{23}}{a_{33}}\\ \frac{a_{31}}{a_{33}}& \frac{a_{32}}{a_{33}} &1\\ \end{ tableau} \right ]la transformation opérée par .

        La matrice de transformation T peut être obtenue en renommant les variables ci-dessus :

T= \left[ \begin{array}{ccc} b_{11}& b_{12} &b_{13}\\ b_{21}& b_{22} &b_{23}\\ b_{31}& b_{ 32} &1\\ \end{tableau} \right ]

        A ce moment, il n'y a que 8 paramètres inconnus, et les équations de transformation deviennent également les suivantes :

\begin{equation} \left\{ \begin{matix}{} x''=\frac{x'}{z'}=\frac{b_{11}x+b_{12}y+b_{13} }{b_{31}x+b_{32}y+1} \\ y''=\frac{y'}{z'}=\frac{b_{21}x+b_{22}y+b_{ 23}}{b_{31}x+b_{32}y+1} \\ \end{tableau} \right.  \end{équation}

        La résolution de 8 inconnues nécessite 8 équations, et 1 ensemble de points de cartographie fournit 2 équations, donc 4 ensembles de points de cartographie doivent être trouvés, c'est pourquoi nous devons fournir 4 points avant et après la transformation pour représenter la transformation de perspective.

        Jusqu'à présent, nous pouvons également comprendre pourquoi la transformation de la perspective est un processus de bidimensionnel à tridimensionnel, et de tridimensionnel à bidimensionnel. Le point avant la transformation est \left[ \begin{tableau}{c} x\\ y\\ 1\\ \end{tableau} \right ], qui est un point dans l'espace tridimensionnel , mais nous reconnaissons que sa projection sur le plan bidimensionnel est . Les points dans l'espace tridimensionnel sont transformés par la matrice , puis les points dans l'espace tridimensionnel sont obtenus en divisant par la valeur , et enfin les points sont obtenus en projetant sur le plan bidimensionnel . L'ensemble du processus consiste à convertir le bidimensionnel en tridimensionnel, puis à retourner et à cartographier l' espace bidimensionnel précédent .Z=1(x,y)\left[ \begin{array}{c} x'\\ y'\\ z'\\ \end{array} \right ]z'\left[ \begin{array}{c} \frac{x'}{z'}\\ \frac{y'}{z'}\\ 1\\ \end{array} \right ]Z=1(x',y')

L'effet de         la transformation de perspective est équivalent au changement de l'image observé lorsque la perspective du spectateur change.

Pourquoi ce changement de perspective est-il possible         grâce à une matrice ? Je n'ai pas trouvé les principes mathématiques spécifiques sur Internet, et la plupart des articles se sont arrêtés à la matrice de transformationJ , et il n'y avait aucune explication approfondie des raisons spécifiques, ce qui m'a longtemps troublé.

        Après déduction, je pense que le processus de transformation de la perspective devrait être le suivant :

        Tout d'abord, le point d'observation est situé à l'origine , puis en regardant dans la direction positive(0,0,0) de l'axe z , le plan de projection est l'endroit où nous voyons l'objet à l'écran.(x,y,1)

        Après Javoir changé la matrice de transformation de perspective, l'original (x,y,1)devient (x',y',z')(on peut prouver que le point transformé est toujours sur le même plan dans l'espace ). À ce moment, les coordonnées ne sont pas seulement sur Z=1le plan, mais dans tout l'espace tridimensionnel, c'est-à-dire que la matrice Jconvertira Z=1la figure d'origine dans le plan en une figure sur un certain plan dans l'espace. C'est pourquoi la matrice de transformation modifie l'angle de vue de l'image.

        Ensuite, chaque point du graphique est connecté au point de vue (c'est-à-dire à l'origine)Z=1 et est projeté sur la surface de projection pour former un graphique (x'',y''). La performance numérique est que les trois valeurs de coordonnées sont divisées par z'. La raison en est en fait la mise à l'échelle géométrique :

\frac{0-1}{0-z'}=\frac{0-x''}{0-x'}=\frac{0-y''}{0-y'}

        Cela résout également ma confusion de longue date, c'est-à-dire que le changement de perspective de notre image causé par la transformation de perspective n'est pas le changement de perspective de notre observateur , mais le changement de la position spatiale de l'objet en raison de la perspective constante de l'observateur , ce qui entraîne ce que nous voyons Changements de perspective de l'image. Cela peut être compris comme suit : ce n'est pas que nous voyons l'angle de l'objet changer lorsque nous nous promenons, mais que la personne reste immobile et que d'autres personnes déplacent l'objet de sorte que nous voyons que l'angle de l'objet change.

2. Réalisation de la transformation de la perspective

        Dans l'application, deux fonctions d'OpenCV, getPerspectiveTransform() et warpPerspective(), doivent être utilisées .

1.getPerspectiveTransform()

Mat getPerspectiveTransform(InputArray src, InputArray dst, int solveMethod = DECOMP_LU)

① Description de la fonction

Calculez la matrice de transformation de la transformation de perspective         à partir de 4 paires de points de mappage et le type de données de matrice renvoyé est Mat . Il convient de noter ici que 1 paire de points de mappage fait référence à et , mais la matrice de transformation agit sur et . Tout de suite:J\left[ \begin{tableau}{c} x\\ y\\ 1\\ \end{tableau} \right ]\left[ \begin{array}{c} x''\\ y''\\ 1\\ \end{array} \right ]J\left[ \begin{tableau}{c} x\\ y\\ 1\\ \end{tableau} \right ]\left[ \begin{array}{c} x'\\ y'\\ z'\\ \end{array} \right ]

\left[ \begin{array}{c} x'\\ y'\\ z'\\ \end{array} \right ] = T \left[ \begin{array}{c} x\\ y\\ 1\\ \end{tableau} \right ]

②Description des paramètres

        Paramètre src : coordonnées des 4 sommets du quadrilatère de l'image source .

        Paramètre dst : L'image cible correspond aux coordonnées des 4 sommets du quadrilatère .

        Paramètre solveMethod : La méthode de calcul passée à cv::solve(#DecompTypes), la valeur par défaut est DECOMP_LU , n'a généralement pas besoin de saisir ce paramètre.

        Valeur de retour : Matrice de transformation de type Mat , utilisable directement dans la fonction warpPerspective()

2.warpPerspective()     

void warpPerspective(
	InputArray src,
	OutputArray dst,
	InputArray M,
	Size dsize,
	int flags=INTER_LINEAR,
	int borderMode = BORDER_CONSTANT, 
	const Scalar& borderValue = Scalar());

① Description de la fonction

        Applique une matrice de transformation Jà l'image d'origine pour transformer sa perspective en image de destination.

②Description des paramètres

        Paramètre src : image d'entrée.

        Paramètre dst : Image de sortie, besoin d'initialiser une matrice vide pour enregistrer le résultat, pas besoin de définir la taille de la matrice.

        Paramètre M : matrice de transformation 3x3 .

        Paramètre dsize : La taille de l'image de sortie.

        Indicateurs de paramètre : définissez la méthode d'interpolation. La valeur par défaut est INTER_LINEAR pour l'interpolation bilinéaire, INTER_NEAREST pour l'interpolation du plus proche voisin, WARP_INVERSE_MAP pour M comme une transformation inverse (dst->src).

        Paramètres borderMode : méthode d'extrapolation des pixels, la valeur par défaut est BORDER_CONSTANT, spécifiez un rembourrage constant. En parcourant la documentation officielle, j'ai trouvé une autre option est BORDER_REPLICATE .

        Paramètre borderValue : Le paramètre de couleur de la bordure lorsque la constante est remplie, la valeur par défaut est (0,0,0), ce qui signifie noir . C'est pourquoi l'image est entourée de noir après la transformation de la perspective. Notez ici que le type est Scalar (B, G, R) .

3. Utilisation des fonctions

        code afficher comme ci-dessous:

#include <iostream>
#include<opencv2/opencv.hpp>
using namespace std;
using namespace cv;

void main()
{
    Mat img = imread("test.png");  
    Point2f AffinePoints0[4] = { Point2f(0, 0), Point2f(img.cols, 0), Point2f(0, img.rows), Point2f(img.cols, img.rows) };//变化前的4个节点
    Point2f AffinePoints1[4] = { Point2f(100, 0), Point2f(img.cols - 100, 0),Point2f(0, img.rows), Point2f(img.cols, img.rows) };//变化后的4个节点

    Mat Trans = getPerspectiveTransform(AffinePoints0, AffinePoints1);//由4组映射点得到变换矩阵
    Mat dst_perspective;//存储目标透视图像
    warpPerspective(img, dst_perspective, Trans, Size(img.cols, img.rows));//执行透视变换

    imshow("原图像", img);
    imshow("透视变换后", dst_perspective);
    waitKey();
}

        Les résultats d'exécution sont les suivants :

        On peut voir que l'effet original est de regarder l'écran d'ordinateur de face, mais maintenant la transformation de perspective consiste à regarder l'écran d'ordinateur (ou l'écran d'ordinateur est incliné vers l'arrière).

4. Marqueurs de points de carte

        Afin de connaître plus clairement les points de mappage avant et après la transformation, nous pouvons les marquer sur le graphique. Ici, nous utilisons la fonction cercle d'OpenCV.

① Prototype de fonction

        cercle vide()

circle (
	InputOutputArray img, 
	Point center, 
	int radius, 
	const Scalar &color, 
	int thickness=1, 
	int lineType=LINE_8, 
	int shift=0)

② Fonction fonction

        Dessine un cercle creux ou plein avec le centre et le rayon donnés sur l'image.

③Paramètres de fonction

        Paramètre img : l'image dessinée par le cercle.

        Paramètre center : les coordonnées du centre du cercle, le type est Scalar(x, y) .

        Paramètre radius : Le rayon du cercle.

        Paramètre color : la couleur du cercle, la règle est (B,G,R) et le type est Scalar(B,G,R) .

        Paramètre épaisseur : Si un nombre positif indique l'épaisseur des lignes qui composent le cercle . S'il s'agit d'un nombre négatif, cela signifie si le cercle est rempli, comme REMPLI signifie dessiner un cercle plein.

        Paramètre line_type : le type de ligne, la valeur par défaut est LINE_8 .

        Décalage de paramètre : le nombre de décimales du point de coordonnée centrale et la valeur du rayon, la valeur par défaut est 0 .

④ Processus de codage

    for (int i = 0; i < 4; i++)//显示4组映射点的位置
    {
        //画一个圆心在映射点(转换前),半径为10,线条粗细为3,红色的圆
        circle(img, AffinePoints0[i], 10, Scalar(59, 23, 232), 3);
        //画一个圆心在映射点(转换后),半径为10,线条粗细为3,蓝色的圆
        circle(dst_perspective, AffinePoints1[i], 10, Scalar(139, 0, 0), 3);
    }

        Effet d'exécution :

        Vous pouvez voir que les 4 ensembles de points avant et après la conversion sont tous dessinés, car j'ai défini les points de mappage aux coins, de sorte qu'une partie seulement du cercle est visible. Le cercle entier peut être vu lorsqu'il est placé au milieu de l'image.

5. Mise en œuvre des fonctions de la bibliothèque

Généralement, la transformation de perspective peut être réalisée en appelant directement les fonctions ci-dessus.Ici, afin de mieux comprendre le processus de transformation, j'ai implémenté la fonction de transformation de perspective         dans les deux fonctions ci-dessus (la fonction de recherche de la matrice de transformation est un problème purement mathématique, qui ne sera pas répété ici) , qui peut différer de l'implémentation officielle de la fonction de bibliothèque.

        L'idée de réalisation de warpPerspective est :

        Compte tenu de la matrice de transformation et de l'image source , la matrice de transformation doit être utilisée pour transformer en perspective l'image source en image cible . Ce processus n'est pas le processus direct que nous avons compris ci-dessus , c'est-à-dire que la matrice de transformation T est multipliée par toutes les coordonnées de l'image d'origine \left[ \begin{tableau}{c} x\\ y\\ 1\\ \end{tableau} \right ]pour obtenir les coordonnées de l'image cible. Parce que cela entraînera le \left[ \begin{array}{c} x''\\ y''\\ 1\\ \end{array} \right ]non mappage des coordonnées individuelles sur l'image cible. Nous utilisons le processus inverse , \left[ \begin{array}{c} x''\\ y''\\ 1\\ \end{array} \right ]en revenant à l'image d'origine pour trouver la valeur de pixel cible. Le processus de dérivation du reverse mapping est le suivant :

        Un connu :

\begin{equation} \left[ \begin{array}{c} x'\\ y'\\ z'\\ \end{array} \right ] = T \left[ \begin{array}{c} x \\ y\\ 1\\ \end{tableau} \right ] \end{équation}

        Diviser avec z':

\begin{equation} \left[ \begin{array}{c} \frac{x'}{z'}\\ \frac{y'}{z'}\\ 1\\ \end{array} \right ] = T \left[ \begin{array}{c} \frac{x}{z'}\\ \frac{y}{z'}\\ \frac{1}{z'}\\ \end{ tableau} \right ] \end{équation}

        Multipliez par T^{-1}:

\begin{equation} T^{-1} \left[ \begin{array}{c} \frac{x'}{z'}\\ \frac{y'}{z'}\\ 1\\ \ end{array} \right ] = \left[ \begin{array}{c} \frac{x}{z'}\\ \frac{y}{z'}\\ \frac{1}{z'} \\ \end{tableau} \right ] \end{équation}

        Tout de suite:

\begin{equation} T^{-1} \left[ \begin{array}{c} x''\\ y''\\ 1\\ \end{array} \right ] = \left[ \begin{ tableau}{c} \frac{x}{z'}\\ \frac{y}{z'}\\ \frac{1}{z'}\\ \end{tableau} \right ] \end{équation }

        Supposons que l'inverse de la matrice de transformation T obtenue T^{-1}est :

T^{-1}= \left[ \begin{array}{ccc} c_{11}& c_{12} &c_{13}\\ c_{21}& c_{22} &c_{23}\\ c_{ 31}& c_{32} &c_{33}\\ \end{tableau} \right ]

        Après expansion, vous pouvez obtenir :

\begin{equation} \left\{ \begin{matrice}{} x=(c_{11}x''+c_{12}y''+c_{13})z' \\ y=(c_{21 }x''+c_{22}y''+c_{23})z'\\ z'=\frac{1}{c_{31}x''+c_{32}y''+c_{33 }}\\ \end{tableau} \right.  \end{équation}

        Cette formule montre qu'après avoir obtenu l'inverse de la matrice de transformation T T^{-1}, pour toute coordonnée sur l'image cible(x'',y'') , nous pouvons utiliser la formule ci-dessus pour obtenir la position correspondant à la coordonnée sur l'image d'origine(x,y) , puis obtenir la valeur du pixel à cette image. poste px(x,y). Bien sûr, les positions ne doivent pas nécessairement être des nombres entiers, une interpolation peut être nécessaire.

        Le processus d'implémentation du code est le suivant :

//自己实现的wrapPerspective函数
void _wrapPerspective(const Mat& src, const Mat& T, Mat& dst)//src为源图像,T为变换矩阵,dst为目标图像
{
    dst.create(src.size(), src.type());//创建一个和原图像一样大小的Mat
    Mat T_inverse;//变换矩阵的逆
    invert(T, T_inverse);//求矩阵T的逆,结果存到T_inverse
    //取出矩阵中的值
    double c11 = T_inverse.ptr<double>(0)[0];
    double c12 = T_inverse.ptr<double>(0)[1];
    double c13 = T_inverse.ptr<double>(0)[2];
    double c21 = T_inverse.ptr<double>(1)[0];
    double c22 = T_inverse.ptr<double>(1)[1];
    double c23 = T_inverse.ptr<double>(1)[2];
    double c31 = T_inverse.ptr<double>(2)[0];
    double c32 = T_inverse.ptr<double>(2)[1];
    double c33 = T_inverse.ptr<double>(2)[2];
    //遍历目标图像的每个位置,求取原图像对应位置的像素值
    
    for (int y = 0; y < dst.rows; y++)
    {
        for (int x = 0; x < dst.cols; x++)
        {
            double xp = c11 * x + c12 * y + c13;
            double yp = c21 * x + c22 * y + c23;
            double z = c31 * x + c32 * y + c33;//z'
            z = z ? 1.0 / z : 0;//z'不为0时求导数,否则设为0
            xp *= z;
            yp *= z;
            //将双精度坐标限制在整型能表示的最大最小值之间
            double fx = max((double)INT_MIN, min((double)INT_MAX, xp));
            double fy = max((double)INT_MIN, min((double)INT_MAX, yp));
            //转化为int,这里简单地使用了最近邻插值
            int X = saturate_cast<int>(fx);
            int Y = saturate_cast<int>(fy);
            //是否在原图像大小范围内
            if (X >= 0 && X < src.cols && Y >= 0 && Y < src.cols)
            {
                dst.at<Vec3b>(y, x)[0] = src.at<Vec3b>(Y, X)[0];
                dst.at<Vec3b>(y, x)[1] = src.at<Vec3b>(Y, X)[1];
                dst.at<Vec3b>(y, x)[2] = src.at<Vec3b>(Y, X)[2];
            }
            else//以黑色填充
            {
                dst.at<Vec3b>(y, x)[0] = 0;
                dst.at<Vec3b>(y, x)[1] = 0;
                dst.at<Vec3b>(y, x)[2] = 0;
            }
        }
    }
}

        résultat courant :

        Par rapport aux résultats officiels de mise en œuvre de la fonction, on peut constater qu'ils sont fondamentalement cohérents, ce qui indique que l'idée de mise en œuvre est correcte.

Troisièmement, l'application de la transformation de perspective

1. Programme interactif de transformation de perspective

①Exigences expérimentales :

        Concevez un programme interactif qui peut modifier les sommets du quadrilatère , et le résultat de la déformation de l'image peut être mis à jour en temps réel lorsque la position des sommets change .

② Idées d'expérience :

        Selon les connaissances mentionnées ci-dessus, il est très facile de réaliser la transformation de perspective des 4 paires de points cartographiques connues . Par conséquent, la difficulté du problème réside dans la conception d'un programme interactif, où l'événement de clic de souris d'OpenCV doit être utilisé.

void setMousecallback(const string& winname, MouseCallback onMouse, void* userdata=0)

Description du paramètre :

        winname : Le nom de la fenêtre.

        onMouse : fonction de réponse de la souris ou fonction de rappel. Pointeur de fonction à appeler chaque fois qu'un événement de souris se produit dans la fenêtre spécifiée. Le prototype de cette fonction est void on_Mouse(int event, int x, int y, int flags, void* param) .

        userdata : Le paramètre passé à la fonction de rappel, la valeur par défaut est 0 . Personnellement, je n'ai pas utilisé ce paramètre.

void MouseCallback(int event,int x,int y,int flags,void *useradata);

Description du paramètre :

        event : événement souris.

        x : La coordonnée x de l'événement de souris.

        y : La coordonnée y de l'événement souris.

        flags : Représente les événements de déplacement de la souris et les événements combinés clavier et souris.

        userdata : paramètre facultatif, non utilisé jusqu'à présent.

Il existe principalement les types         d'événements de souris suivants :

                EVENT_ MOUSEMOVE : déplacement de la souris

                EVENT_ LBUTTONDOWN : bouton gauche de la souris enfoncé

                EVENT_ RBUTTONDOWN : bouton droit de la souris enfoncé

                EVENT_ MBUTTONDOWN : bouton central de la souris enfoncé

                EVENT_ LBUTTONUP : Relâchez le bouton gauche de la souris

                EVENT_ RBUTTONUP : Relâchez le bouton droit de la souris

                EVENT_ MBUTTONUP : Relâchez le bouton du milieu

                EVENT_ LBUTTONDBLCLK : Double clic gauche

                EVENT_ RBUTTONDBLCLK : Double clic droit

                EVENT_ MBUTTONDBLCLK : Double-clic sur le bouton du milieu

Il existe principalement les types         de drapeaux suivants :

                EVENT_FLAG_ LBUTTON : clic gauche et glisser

                EVENT_FLAG_ RBUTTON : clic droit et glisser

                EVENT_FLAG_ MBUTTON : déplacement du bouton du milieu

                EVENT_FLAG_ CTRLKEY : Ctrl est enfoncé et maintenu

                EVENT_FLAG_ SHIFTKEY : la touche Maj est enfoncée et maintenue

                EVENT_FLAG_ALTKEY : la touche alt est enfoncée et maintenue

idée générale:

        Pour une meilleure apparence, nous avons besoin d'une toile légèrement plus grande que l'image d'origine pour y mettre l'image cible. Les 4 points de mappage initiaux se trouvent aux 4 coins de l'image d'origine . Sur ce canevas, lorsque la souris se déplace vers la plage circulaire des 4 points de cartographie , si le bouton gauche est enfoncé et non relâché à ce moment , le point de cartographie peut être déplacé . Lorsque le bouton gauche de la souris est relâché , la position de la souris est la position après le déplacement du point cartographique. Recalculez ensuite la matrice de transformation, implémentez la transformation de perspective sur l'image d'origine et affichez-la.

        Bien sûr, la mise à jour en temps réel peut également être comprise comme le calcul de la matrice de transformation et la réalisation de la transformation de perspective lorsque la position du point de mappage change lorsque le bouton gauche est déplacé. Il est clair de déplacer le point de mappage vers une certaine position cible, mais une transformation de perspective inutile sera effectuée pendant le mouvement.

③ Processus de réalisation

        L'accent est mis sur l'écriture d'événements de souris et d'autres processus sont similaires à la méthode de transformation de perspective précédente.

        Lorsque le bouton gauche est enfoncé, il est nécessaire de déterminer si le clic est dans la zone du point de cartographie. Si le clic est dans la zone effective, il est également nécessaire d'enregistrer quel point de cartographie a été cliqué.

        Lorsque le bouton gauche est enfoncé et déplacé, si la transformation de perspective doit être mise à jour en temps réel pendant le processus de déplacement, la position du point de mappage est enregistrée en temps réel et la transformation de perspective est effectuée. Dans ma méthode d'implémentation ici, je choisis d'effectuer une transformation de perspective après avoir relâché le bouton gauche, il n'est donc pas nécessaire d'enregistrer la position du point de mappage lorsque le bouton gauche est déplacé. Mais pour un meilleur effet interactif, j'enregistre toujours la position et dessine des lignes droites et des cercles pour les invites interactives.

        Lorsque le bouton gauche est relâché, effectuez une transformation de perspective.

Le code spécifique est le suivant :

void mouseHander(int event, int x, int y, int flags, void* p)
{
    if (event == EVENT_LBUTTONDOWN)//左键按下
    {
        for (int i = 0; i < 4; i++)
        {
            //判断是否选择了某个映射点
            if (abs(x - dstPoint[i].x) <= radius && abs(y - dstPoint[i].y) <= radius)
            {
                pickPoint = i;
                beforePlace = dstPoint[pickPoint];//记录原本的位置
                break;
            }
        }
    }
    else if (event == EVENT_MOUSEMOVE && pickPoint >= 0 )//左键按下后选取了某个点且拖拽
    {
        //更改映射后坐标
        dstPoint[pickPoint].x = x, dstPoint[pickPoint].y = y;
        //在临时图像上实时显示鼠标拖动时形成的图像
        //注意不能直接在dstImg上画,否则会画很多次
        Mat tmp = dstImg.clone();
        //原本的圆
        circle(tmp, beforePlace, radius, Scalar(228, 164, 140), -1);
        //绘制直线
        line(tmp, dstPoint[0], dstPoint[1], Scalar(246, 230, 171), 5, 8);
        line(tmp, dstPoint[1], dstPoint[2], Scalar(246, 230, 171), 5, 8);
        line(tmp, dstPoint[2], dstPoint[3], Scalar(246, 230, 171), 5, 8);
        line(tmp, dstPoint[3], dstPoint[0], Scalar(246, 230, 171), 5, 8);
        //重新绘制4个圆
        for (int i = 0; i < 4; i++)
        {
            if (i != pickPoint)
                circle(tmp, dstPoint[i], radius, Scalar(228, 164, 140), -1);
            else
                circle(tmp, dstPoint[i], radius, Scalar(96, 96, 240), -1);
        }
        imshow("透视变换后", tmp);
    }
    else if (event == EVENT_LBUTTONUP && pickPoint >= 0)//左键松开
    {
        //执行透视变换
        Mat Trans = getPerspectiveTransform(srcPoint, dstPoint);//由4组映射点得到变换矩阵
        warpPerspective(srcImg, dstImg, Trans, Size(dstImg.cols, dstImg.rows));//执行透视变换
        for (int i = 0; i < 4; i++)//显示4组映射点的位置
        {
            //画一个圆心在映射点(转换后),半径为10,线条粗细为3,黄色的圆
            circle(dstImg, dstPoint[i], radius, Scalar(0, 215, 255), 3);
        }
        imshow("透视变换后", dstImg);
        pickPoint = -1;//重置选取状态
    }
}

④Exécuter les résultats

        Testez ensuite différentes images :

        On peut voir qu'après la transformation de la perspective de l'affiche sur la route opposée, l'angle de vue que nous voyons passe de regarder vers le bas sur la route à regarder vers le bas sur la route.

        Aujourd'hui, j'ai pris une photo de la bibliothèque avec désinvolture, qui a été prise du côté gauche de la bibliothèque, et sa transformation en perspective :        

         On peut voir qu'après le réglage, l'angle de vision est plus proche de l'avant.

⑤ code source

#include <iostream>
#include<opencv2/opencv.hpp>
using namespace std;
using namespace cv;

Mat srcImg, dstImg;//原图像、目标图像
Point2f srcPoint[4], dstPoint[4];//原图像和目标图像的4个映射点
Point2f beforePlace;//记录移动映射点之前的位置
int radius;//映射点的判定半径
int pickPoint;//记录点击了哪个点

void mouseHander(int event, int x, int y, int flags, void* p)
{
    if (event == EVENT_LBUTTONDOWN)//左键按下
    {
        for (int i = 0; i < 4; i++)
        {
            //判断是否选择了某个映射点
            if (abs(x - dstPoint[i].x) <= radius && abs(y - dstPoint[i].y) <= radius)
            {
                pickPoint = i;
                beforePlace = dstPoint[pickPoint];//记录原本的位置
                break;
            }
        }
    }
    else if (event == EVENT_MOUSEMOVE && pickPoint >= 0 )//左键按下后选取了某个点且拖拽
    {
        //更改映射后坐标
        dstPoint[pickPoint].x = x, dstPoint[pickPoint].y = y;
        //在临时图像上实时显示鼠标拖动时形成的图像
        //注意不能直接在dstImg上画,否则会画很多次
        Mat tmp = dstImg.clone();
        //原本的圆
        circle(tmp, beforePlace, radius, Scalar(228, 164, 140), -1);
        //绘制直线
        line(tmp, dstPoint[0], dstPoint[1], Scalar(246, 230, 171), 5, 8);
        line(tmp, dstPoint[1], dstPoint[2], Scalar(246, 230, 171), 5, 8);
        line(tmp, dstPoint[2], dstPoint[3], Scalar(246, 230, 171), 5, 8);
        line(tmp, dstPoint[3], dstPoint[0], Scalar(246, 230, 171), 5, 8);
        //重新绘制4个圆
        for (int i = 0; i < 4; i++)
        {
            if (i != pickPoint)
                circle(tmp, dstPoint[i], radius, Scalar(228, 164, 140), -1);
            else
                circle(tmp, dstPoint[i], radius, Scalar(96, 96, 240), -1);
        }
        imshow("透视变换后", tmp);
    }
    else if (event == EVENT_LBUTTONUP && pickPoint >= 0)//左键松开
    {
        //执行透视变换
        Mat Trans = getPerspectiveTransform(srcPoint, dstPoint);//由4组映射点得到变换矩阵
        warpPerspective(srcImg, dstImg, Trans, Size(dstImg.cols, dstImg.rows));//执行透视变换
        for (int i = 0; i < 4; i++)//显示4组映射点的位置
        {
            //画一个圆心在映射点(转换后),半径为10,线条粗细为3,黄色的圆
            circle(dstImg, dstPoint[i], radius, Scalar(0, 215, 255), 3);
        }
        imshow("透视变换后", dstImg);
        pickPoint = -1;//重置选取状态
    }
}

void main()
{
    srcImg = imread("library.jpg");  
    radius = 10;//设置四个点的圆的半径
    pickPoint = -1;
    //映射前的4个点
    srcPoint[0] = Point2f(0, 0);
    srcPoint[1] = Point2f(srcImg.cols, 0);
    srcPoint[2] = Point2f(srcImg.cols, srcImg.rows);
    srcPoint[3] = Point2f(0, srcImg.rows);
    //创建一张略大于原图像的画布
    dstImg = Mat::zeros(Size(2 * radius + 100 + srcImg.cols, 2 * radius + 100 + srcImg.rows), srcImg.type());
    //初始映射后的4个点
    dstPoint[0] = Point2f(radius + 50, radius + 50);
    dstPoint[1] = Point2f(radius + 50 + srcImg.cols, radius + 50);
    dstPoint[2] = Point2f(radius + 50 + srcImg.cols, radius + 50 + srcImg.rows);
    dstPoint[3] = Point2f(radius + 50, radius + 50 + srcImg.rows);
    Mat Trans = getPerspectiveTransform(srcPoint, dstPoint);//由4组映射点得到变换矩阵
    Mat dst_perspective;//存储目标透视图像
    warpPerspective(srcImg, dstImg, Trans, Size(dstImg.cols, dstImg.rows));//执行透视变换

    for (int i = 0; i < 4; i++)//显示初始4组映射点的位置
    {
        //画一个圆心在映射点(转换后),半径为10,线条粗细为3,黄色的圆
        circle(dstImg, dstPoint[i], radius, Scalar(95, 180, 243), 3);
    }
    imshow("原图像", srcImg);
    imshow("透视变换后", dstImg);
    //鼠标事件
    setMouseCallback("透视变换后", mouseHander);
    waitKey();
}

2. Réalisation de billboard virtuel

        Selon le processus de transformation de perspective ci-dessus, nous pouvons penser à une application : transformer l'image A en tant que perspective de panneau d'affichage vers une position spécifique de l'image B.

        Le processus de mise en œuvre est également très simple. Nous définissons les points de mappage initiaux de l'image A sur les quatre coins, puis sélectionnons les 4 points de mappage après transformation de perspective sur l'image d'arrière-plan B, puis transformons la perspective de l'image A à la position spécifiée (définissez image B L'emplacement d'origine de l'écrasement). Il convient de noter ici que la sélection des points de cartographie doit spécifier un ordre fixe, sinon elle ne peut pas correspondre aux points de cartographie d'origine un par un. Mon ordre par défaut de sélection des points est de sélectionner dans le sens des aiguilles d'une montre à partir du coin supérieur gauche.

#include <iostream>
#include<opencv2/opencv.hpp>
using namespace std;
using namespace cv;

Mat srcImg, dstImg;//原图像、目标图像
Mat resultImg;//结果图像
vector<Point2f> srcPoints, dstPoints;//原图像和目标图像的映射点
int pickNums;//目前已选取的节点

void mouseHander(int event, int x, int y, int flags, void* p)
{
    if (event == EVENT_LBUTTONDOWN)//左键按下
    {
        Mat tmp = dstImg.clone();
        if (pickNums == 4)//选取的点超过4个后,下一次点击会实现透视变换
        {
            //执行透视变换
            Mat Trans = getPerspectiveTransform(srcPoints, dstPoints);//由4组映射点得到变换矩阵
            warpPerspective(srcImg, tmp, Trans, Size(tmp.cols, tmp.rows));//执行透视变换
            resultImg = dstImg.clone();
            for (int y = 0; y < dstImg.rows; y++)
            {
                for (int x = 0; x < dstImg.cols; x++)
                {
                    if ((int)tmp.at<Vec3b>(y, x)[0] == 0 && (int)tmp.at<Vec3b>(y, x)[1] == 0 && (int)tmp.at<Vec3b>(y, x)[2] == 0)//像素点全0
                        continue;
                    else//非全0
                    {
                        resultImg.at<Vec3b>(y, x)[0] = tmp.at<Vec3b>(y, x)[0];
                        resultImg.at<Vec3b>(y, x)[1] = tmp.at<Vec3b>(y, x)[1];
                        resultImg.at<Vec3b>(y, x)[2] = tmp.at<Vec3b>(y, x)[2];
                    }
                }
            }
            imshow("虚拟广告牌", resultImg);
            dstPoints.clear();
            pickNums = 0;
        }
        else//选取的节点还没4个
        {
            dstPoints.push_back(Point2f(x, y));
            pickNums++;
            for (int i = 0; i < dstPoints.size(); i++)
            {
                circle(tmp, dstPoints[i], 5, Scalar(0, 215, 255), 3);
            }
            imshow("虚拟广告牌", tmp);
        }
    }

}

int main()
{
    srcImg = imread("test.png");//透视变换的图像,也就是广告图

    //设置原图像的4个映射点
    srcPoints.push_back(Point2f(0, 0));
    srcPoints.push_back(Point2f(srcImg.cols, 0));
    srcPoints.push_back(Point2f(srcImg.cols, srcImg.rows));
    srcPoints.push_back(Point2f(0, srcImg.rows));

    dstImg = imread("library.jpg");//背景图

    imshow("虚拟广告牌", dstImg);
    //鼠标事件
    setMouseCallback("虚拟广告牌", mouseHander);
    waitKey(0);
    return 0;
}

        L'idée est relativement simple, c'est-à-dire sélectionner 4 points à chaque fois, puis mapper la transformation de perspective de l'image du panneau d'affichage à la position correspondante après la sélection.

        Étant donné que l'image transformée en perspective utilise du noir, tous les 0 pour remplir les bords, donc pour (0,0,0), utilisez l'image d'arrière-plan d'origine, et pour non-(0,0,0), utilisez l'image transformée en perspective. Cela a un problème que s'il y a des pixels dans le panneau d'affichage qui sont (0,0,0), il ne couvrira pas l'image d'arrière-plan, mais après test, il s'avère qu'il y a moins de pixels (0,0,0) dans l'image générale, ou nous pouvons également La valeur du pixel dans l'image d'origine (0,0,0) peut être ajoutée uniformément avec un petit décalage tel que (0,0,1), ce qui a peu d'effet sur l'ensemble.

        Réalisez l'effet:

         Nous pouvons également être utilisés pour transformer les écrans d'ordinateur des camarades de classe en photos que vous souhaitez :

        Afin de réaliser un panneau d'affichage dynamique, la modification que nous devons faire est de remplacer l'image du panneau d'affichage par un fichier vidéo, c'est-à-dire de lire chaque image du fichier vidéo pour une transformation de perspective.

        Voici le code modifié :

#include <iostream>
#include<opencv2/opencv.hpp>
using namespace std;
using namespace cv;

Mat dstImg, frame;//原图像、目标图像,视频帧
Mat resultImg;//结果图像
vector<Point2f> srcPoints, dstPoints;//原图像和目标图像的映射点
int pickNums;//目前已选取的节点

void mouseHander(int event, int x, int y, int flags, void* p)
{
	if (event == EVENT_LBUTTONDOWN)//左键按下
	{
		Mat tmp = dstImg.clone();
		if (pickNums == 4)//选取的点超过4个后,下一次点击会实现透视变换
		{
			//打开视频文件
			VideoCapture capture;
			capture.open("sdu_cut.mp4");
			if (!capture.isOpened())
			{
				cout << "无法打开视频文件!" << endl;
			}
			int num = 0;
			while (capture.read(frame))
			{
				num++;
				if (num == 1)//第一帧
				{
					//设置原图像的4个映射点
					srcPoints.push_back(Point2f(0, 0));
					srcPoints.push_back(Point2f(frame.cols, 0));
					srcPoints.push_back(Point2f(frame.cols, frame.rows));
					srcPoints.push_back(Point2f(0, frame.rows));
				}
				//执行透视变换
				Mat Trans = getPerspectiveTransform(srcPoints, dstPoints);//由4组映射点得到变换矩阵
				warpPerspective(frame, tmp, Trans, Size(tmp.cols, tmp.rows));//执行透视变换
				resultImg = dstImg.clone();
				for (int y = 0; y < dstImg.rows; y++)
				{
					for (int x = 0; x < dstImg.cols; x++)
					{
						if ((int)tmp.at<Vec3b>(y, x)[0] == 0 && (int)tmp.at<Vec3b>(y, x)[1] == 0 && (int)tmp.at<Vec3b>(y, x)[2] == 0)//像素点全0
							continue;
						else//非全0
						{
							resultImg.at<Vec3b>(y, x)[0] = tmp.at<Vec3b>(y, x)[0];
							resultImg.at<Vec3b>(y, x)[1] = tmp.at<Vec3b>(y, x)[1];
							resultImg.at<Vec3b>(y, x)[2] = tmp.at<Vec3b>(y, x)[2];
						}
					}
				}
				imshow("虚拟广告牌", resultImg);

				//中途退出
				char c = waitKey(1);
				if (c == 27)
				{
					break;
				}

			}
			dstPoints.clear();
			srcPoints.clear();
			pickNums = 0;
		}
		else//选取的节点还没4个
		{
			dstPoints.push_back(Point2f(x, y));
			pickNums++;
			for (int i = 0; i < dstPoints.size(); i++)
			{
				circle(tmp, dstPoints[i], 5, Scalar(0, 215, 255), 3);
			}
			imshow("虚拟广告牌", tmp);
		}
	}
}

int main()
{
	dstImg = imread("b.jpg");//背景图
	imshow("虚拟广告牌", dstImg);
	//鼠标事件
	setMouseCallback("虚拟广告牌", mouseHander);
	waitKey(0);
	return 0;
}

        L'idée est fondamentalement la même que celle des panneaux d'affichage statiques, principalement parce qu'elle est passée de la lecture d'images ordinaires à la lecture de fichiers vidéo image par image. Afin d'obtenir les points de mappage initiaux du fichier vidéo, il est nécessaire de lire la première image pour déterminer les positions des points de mappage des quatre coins.

        Une fois que le code ci-dessus a sélectionné 4 points, il doit attendre que le fichier vidéo soit lu avant de continuer à démarrer un nouveau cycle de sélection, sinon une erreur sera signalée.

        Voici quelques résultats expérimentaux. Étant donné que le fichier vidéo est trop volumineux pour être téléchargé, seule une partie du contenu est interceptée et convertie en affichage gif :

Je suppose que tu aimes

Origine blog.csdn.net/m0_51653200/article/details/127361624
conseillé
Classement