57 votes

Exécution de cv::warpPerspective pour un faux deskewing sur un ensemble de cv::Point

J'essaie de faire un transformation des perspectives d'un ensemble de points afin d'obtenir une désensorceler effet :

http://nuigroup.com/?ACT=28&fid=27&aid=1892_H6eNAaign4Mrnn30Au8d

J'utilise l'image ci-dessous pour les tests, et la fonction vert rectangle affichant la zone d'intérêt.

Je me demandais s'il est possible d'obtenir l'effet que j'espère en utilisant une simple combinaison de cv::getPerspectiveTransform et cv::warpPerspective . Je partage le code source que j'ai écrit jusqu'à présent, mais cela ne fonctionne pas. Voici l'image qui en résulte :

Il y a donc un vector<cv::Point> que définit la région d'intérêt mais les points sont ne sont pas stockés dans un ordre particulier à l'intérieur du vecteur, et c'est quelque chose que je ne peux pas changer dans la procédure de détection. Quoi qu'il en soit, plus tard les points de ce vecteur sont utilisés pour définir un RotatedRect qui, à son tour, est utilisé pour assembler cv::Point2f src_vertices[4]; l'une des variables requises par cv::getPerspectiveTransform() .

Ma compréhension de sommets et comment ils sont organisés pourrait être l'un des problèmes . Je pense également que l'utilisation d'un RotatedRect n'est pas la meilleure idée pour stocker les points d'origine du ROI, puisque l'option les coordonnées vont changer un peu pour s'adapter au rectangle pivoté, et ce n'est pas très cool .

#include <cv.h>
#include <highgui.h>
#include <iostream>

using namespace std;
using namespace cv;

int main(int argc, char* argv[])
{
    cv::Mat src = cv::imread(argv[1], 1);

    // After some magical procedure, these are points detect that represent 
    // the corners of the paper in the picture: 
    // [408, 69] [72, 2186] [1584, 2426] [1912, 291]
    vector<Point> not_a_rect_shape;
    not_a_rect_shape.push_back(Point(408, 69));
    not_a_rect_shape.push_back(Point(72, 2186));
    not_a_rect_shape.push_back(Point(1584, 2426));
    not_a_rect_shape.push_back(Point(1912, 291));

    // For debugging purposes, draw green lines connecting those points 
    // and save it on disk
    const Point* point = &not_a_rect_shape[0];
    int n = (int)not_a_rect_shape.size();
    Mat draw = src.clone();
    polylines(draw, &point, &n, 1, true, Scalar(0, 255, 0), 3, CV_AA);
    imwrite("draw.jpg", draw);

    // Assemble a rotated rectangle out of that info
    RotatedRect box = minAreaRect(cv::Mat(not_a_rect_shape));
    std::cout << "Rotated box set to (" << box.boundingRect().x << "," << box.boundingRect().y << ") " << box.size.width << "x" << box.size.height << std::endl;

    // Does the order of the points matter? I assume they do NOT.
    // But if it does, is there an easy way to identify and order 
    // them as topLeft, topRight, bottomRight, bottomLeft?
    cv::Point2f src_vertices[4];
    src_vertices[0] = not_a_rect_shape[0];
    src_vertices[1] = not_a_rect_shape[1];
    src_vertices[2] = not_a_rect_shape[2];
    src_vertices[3] = not_a_rect_shape[3];

    Point2f dst_vertices[4];
    dst_vertices[0] = Point(0, 0);
    dst_vertices[1] = Point(0, box.boundingRect().width-1);
    dst_vertices[2] = Point(0, box.boundingRect().height-1);
    dst_vertices[3] = Point(box.boundingRect().width-1, box.boundingRect().height-1);

    Mat warpMatrix = getPerspectiveTransform(src_vertices, dst_vertices);

    cv::Mat rotated;
    warpPerspective(src, rotated, warpMatrix, rotated.size(), INTER_LINEAR, BORDER_CONSTANT);

    imwrite("rotated.jpg", rotated);

    return 0;
}

Quelqu'un peut-il m'aider à résoudre ce problème ?

42voto

sammy Points 10591

Donc, le premier problème est l'ordre des coins. Ils doivent être dans le même ordre dans les deux vecteurs. Donc, si dans le premier vecteur votre ordre est :(haut-gauche, bas-gauche, bas-droit, haut-droit), ils DOIVENT être dans le même ordre dans l'autre vecteur.

Deuxièmement, pour que l'image résultante ne contienne que l'objet d'intérêt, vous devez définir sa largeur et sa hauteur pour qu'elles soient identiques à celles du rectangle résultant. Ne vous inquiétez pas, les images src et dst dans warpPerspective peuvent avoir des tailles différentes.

Troisièmement, un problème de performance. Bien que votre méthode soit absolument précise, car vous n'effectuez que des transformations affines (rotation, redimensionnement, redressement), mathématiquement, vous pouvez utiliser le corespondant affine de vos fonctions. Ils sont beaucoup plus plus rapide .

  • getAffineTransform()

  • warpAffine().

Note importante : la transformation getAffine ne nécessite et n'attend QUE 3 points, et la matrice de résultat est 2 par 3, au lieu de 3 par 3.

Comment faire en sorte que l'image de résultat ait une taille différente de celle de l'entrée :

cv::warpPerspective(src, dst, dst.size(), ... );

utiliser

cv::Mat rotated;
cv::Size size(box.boundingRect().width, box.boundingRect().height);
cv::warpPerspective(src, dst, size, ... );

Vous êtes donc là, et votre mission de programmation est terminée.

void main()
{
    cv::Mat src = cv::imread("r8fmh.jpg", 1);

    // After some magical procedure, these are points detect that represent 
    // the corners of the paper in the picture: 
    // [408, 69] [72, 2186] [1584, 2426] [1912, 291]

    vector<Point> not_a_rect_shape;
    not_a_rect_shape.push_back(Point(408, 69));
    not_a_rect_shape.push_back(Point(72, 2186));
    not_a_rect_shape.push_back(Point(1584, 2426));
    not_a_rect_shape.push_back(Point(1912, 291));

    // For debugging purposes, draw green lines connecting those points 
    // and save it on disk
    const Point* point = &not_a_rect_shape[0];
    int n = (int)not_a_rect_shape.size();
    Mat draw = src.clone();
    polylines(draw, &point, &n, 1, true, Scalar(0, 255, 0), 3, CV_AA);
    imwrite("draw.jpg", draw);

    // Assemble a rotated rectangle out of that info
    RotatedRect box = minAreaRect(cv::Mat(not_a_rect_shape));
    std::cout << "Rotated box set to (" << box.boundingRect().x << "," << box.boundingRect().y << ") " << box.size.width << "x" << box.size.height << std::endl;

    Point2f pts[4];

    box.points(pts);

    // Does the order of the points matter? I assume they do NOT.
    // But if it does, is there an easy way to identify and order 
    // them as topLeft, topRight, bottomRight, bottomLeft?

    cv::Point2f src_vertices[3];
    src_vertices[0] = pts[0];
    src_vertices[1] = pts[1];
    src_vertices[2] = pts[3];
    //src_vertices[3] = not_a_rect_shape[3];

    Point2f dst_vertices[3];
    dst_vertices[0] = Point(0, 0);
    dst_vertices[1] = Point(box.boundingRect().width-1, 0); 
    dst_vertices[2] = Point(0, box.boundingRect().height-1);

   /* Mat warpMatrix = getPerspectiveTransform(src_vertices, dst_vertices);

    cv::Mat rotated;
    cv::Size size(box.boundingRect().width, box.boundingRect().height);
    warpPerspective(src, rotated, warpMatrix, size, INTER_LINEAR, BORDER_CONSTANT);*/
    Mat warpAffineMatrix = getAffineTransform(src_vertices, dst_vertices);

    cv::Mat rotated;
    cv::Size size(box.boundingRect().width, box.boundingRect().height);
    warpAffine(src, rotated, warpAffineMatrix, size, INTER_LINEAR, BORDER_CONSTANT);

    imwrite("rotated.jpg", rotated);
}

18voto

karlphillip Points 46502

Le problème était l'ordre dans lequel les points étaient déclarés à l'intérieur du vecteur, et il y avait aussi un autre problème lié à ceci sur la définition de dst_vertices .

L'ordre des points importe à getPerspectiveTransform() et doivent être spécifiés dans l'ordre suivant :

1st-------2nd
 |         |
 |         |
 |         |
3rd-------4th

Par conséquent, les points d'origine ont dû être réorganisés en conséquence :

vector<Point> not_a_rect_shape;
not_a_rect_shape.push_back(Point(408, 69));
not_a_rect_shape.push_back(Point(1912, 291));
not_a_rect_shape.push_back(Point(72, 2186));
not_a_rect_shape.push_back(Point(1584, 2426));

et la destination :

Point2f dst_vertices[4];
dst_vertices[0] = Point(0, 0);
dst_vertices[1] = Point(box.boundingRect().width-1, 0); // Bug was: had mistakenly switched these 2 parameters
dst_vertices[2] = Point(0, box.boundingRect().height-1);
dst_vertices[3] = Point(box.boundingRect().width-1, box.boundingRect().height-1);

Après cela, il faut faire quelques recadrages. parce que l'image résultante n'est pas seulement la zone située à l'intérieur du rectangle vert comme je le pensais :

Je ne sais pas si c'est un bug d'OpenCV ou si je rate quelque chose, mais le problème principal a été résolu.

5voto

Tim Points 773

Lorsque vous travaillez avec un quadrangle, OpenCV n'est pas vraiment votre ami. RotatedRect vous donnera des résultats incorrects. Vous aurez également besoin d'une projection en perspective au lieu d'une projection affine comme d'autres l'ont mentionné ici

En gros, ce qu'il faut faire, c'est :

  • Bouclez sur tous les segments du polygone et connectez ceux qui sont presque égaux.
  • Triez-les de manière à obtenir les 4 segments de ligne les plus grands.
  • Intersecter ces lignes et vous avez les 4 points d'angle les plus probables.
  • Transformer la matrice sur la perspective recueillie à partir des points d'angle et du rapport d'aspect de l'objet connu.

J'ai implémenté une classe Quadrangle qui s'occupe de la conversion des contours en quadrilatères et les transformera également sur la bonne perspective.

Voir une mise en œuvre fonctionnelle ici : Java OpenCV désalignement d'un contour

4voto

VaporwareWolf Points 1452

MISE À JOUR : RÉSOLU

J'ai presque réussi à le faire fonctionner. Si près d'être utilisable. Le redressement est correct, mais il semble y avoir un problème d'échelle ou de translation. J'ai mis le point d'ancrage à zéro et j'ai également essayé de changer le mode d'échelle (aspectFill, scale to fit, etc...).

Configurez les points de désalignement (le rouge les rend difficiles à voir) : enter image description here

Appliquez la transformation calculée : enter image description here

Maintenant, il se déséquilibre. Cela semble assez bien, sauf que l'image n'est pas centrée sur l'écran. En ajoutant un geste de panoramique à la vue de l'image, je peux la faire glisser et vérifier qu'elle s'aligne : enter image description here

Ce n'est pas aussi simple que de traduire par -0,5, -0,5 parce que l'image originale devient un polygone qui s'étend très très loin (potentiellement), de sorte que son rectangle de délimitation est beaucoup plus grand que le cadre de l'écran.

Est-ce que quelqu'un voit ce que je peux faire pour que ça se termine ? J'aimerais qu'elle soit engagée et partagée ici. C'est un sujet populaire, mais je n'ai pas trouvé de solution aussi simple que le copier/coller.

Le code source complet est ici :

clone git https://github.com/zakkhoyt/Quadrilateral.git

git checkout demo

Cependant, je vais coller les parties pertinentes ici. Cette première méthode est la mienne et c'est là que j'obtiens les points de désalignement.

- (IBAction)buttonAction:(id)sender {

    Quadrilateral quadFrom;
    float scale = 1.0;
    quadFrom.topLeft.x = self.topLeftView.center.x / scale;
    quadFrom.topLeft.y = self.topLeftView.center.y / scale;
    quadFrom.topRight.x = self.topRightView.center.x / scale;
    quadFrom.topRight.y = self.topRightView.center.y / scale;
    quadFrom.bottomLeft.x = self.bottomLeftView.center.x / scale;
    quadFrom.bottomLeft.y = self.bottomLeftView.center.y / scale;
    quadFrom.bottomRight.x = self.bottomRightView.center.x / scale;
    quadFrom.bottomRight.y = self.bottomRightView.center.y / scale;

    Quadrilateral quadTo;
    quadTo.topLeft.x = self.view.bounds.origin.x;
    quadTo.topLeft.y = self.view.bounds.origin.y;
    quadTo.topRight.x = self.view.bounds.origin.x + self.view.bounds.size.width;
    quadTo.topRight.y = self.view.bounds.origin.y;
    quadTo.bottomLeft.x = self.view.bounds.origin.x;
    quadTo.bottomLeft.y = self.view.bounds.origin.y + self.view.bounds.size.height;
    quadTo.bottomRight.x = self.view.bounds.origin.x + self.view.bounds.size.width;
    quadTo.bottomRight.y = self.view.bounds.origin.y + self.view.bounds.size.height;

    CATransform3D t = [self transformQuadrilateral:quadFrom toQuadrilateral:quadTo];
//    t = CATransform3DScale(t, 0.5, 0.5, 1.0);
    self.imageView.layer.anchorPoint = CGPointZero;
    [UIView animateWithDuration:1.0 animations:^{
        self.imageView.layer.transform = t;
    }];

}

#pragma mark OpenCV stuff...
-(CATransform3D)transformQuadrilateral:(Quadrilateral)origin toQuadrilateral:(Quadrilateral)destination {

    CvPoint2D32f *cvsrc = [self openCVMatrixWithQuadrilateral:origin];
    CvMat *src_mat = cvCreateMat( 4, 2, CV_32FC1 );
    cvSetData(src_mat, cvsrc, sizeof(CvPoint2D32f));

    CvPoint2D32f *cvdst = [self openCVMatrixWithQuadrilateral:destination];
    CvMat *dst_mat = cvCreateMat( 4, 2, CV_32FC1 );
    cvSetData(dst_mat, cvdst, sizeof(CvPoint2D32f));

    CvMat *H = cvCreateMat(3,3,CV_32FC1);
    cvFindHomography(src_mat, dst_mat, H);
    cvReleaseMat(&src_mat);
    cvReleaseMat(&dst_mat);

    CATransform3D transform = [self transform3DWithCMatrix:H->data.fl];
    cvReleaseMat(&H);

    return transform;
}

- (CvPoint2D32f*)openCVMatrixWithQuadrilateral:(Quadrilateral)origin {

    CvPoint2D32f *cvsrc = (CvPoint2D32f *)malloc(4*sizeof(CvPoint2D32f));
    cvsrc[0].x = origin.topLeft.x;
    cvsrc[0].y = origin.topLeft.y;
    cvsrc[1].x = origin.topRight.x;
    cvsrc[1].y = origin.topRight.y;
    cvsrc[2].x = origin.bottomRight.x;
    cvsrc[2].y = origin.bottomRight.y;
    cvsrc[3].x = origin.bottomLeft.x;
    cvsrc[3].y = origin.bottomLeft.y;

    return cvsrc;
}

-(CATransform3D)transform3DWithCMatrix:(float *)matrix {
    CATransform3D transform = CATransform3DIdentity;

    transform.m11 = matrix[0];
    transform.m21 = matrix[1];
    transform.m41 = matrix[2];

    transform.m12 = matrix[3];
    transform.m22 = matrix[4];
    transform.m42 = matrix[5];

    transform.m14 = matrix[6];
    transform.m24 = matrix[7];
    transform.m44 = matrix[8];

    return transform; 
}

Mise à jour : J'ai réussi à le faire fonctionner correctement. Les coordonnées devaient être l'origine au centre, pas en haut à gauche. J'ai appliqué xOffset et yOffset et viola. Code de démonstration à l'endroit mentionné ci-dessus (branche "demo")

3voto

MonsieurDart Points 3133

J'ai eu le même genre de problème et je l'ai résolu en utilisant la fonction d'extraction d'homographie d'OpenCV.

Vous pouvez voir comment je me suis débrouillé dans cette question : Transformation d'une image rectangulaire en un quadrilatère à l'aide d'un CATransform3D

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X