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

j'essaie de faire une transformation de perspective d'un ensemble de points afin de réaliser un deskewing effet:

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

j'utilise l'image ci-dessous pour les tests, et le rectangle Vert affiche la zone d'intérêt.

je me demandais s'il était possible d'obtenir l'effet que j'espère obtenir en utilisant une simple combinaison de cv::getPerspectiveTransform et cv::warpPerspective . Je partage le code source que j'ai écrit jusqu'à présent, mais ça ne marche pas. C'est l'image résultante:

donc il y a un vector<cv::Point> que définit la région d'intérêt , mais les points sont non stocké dans aucune 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 dans le 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 exigées par cv::getPerspectiveTransform() .

ma compréhension de sommets et comment ils sont organisés pourrait être l'un des problèmes . Je pense aussi que l'utilisation d'un RotatedRect n'est pas la meilleure idée pour stocker les points originaux du ROI, puisque les coordonnées changera un petit peu pour s'adapter dans le rectangle tourné, et qui 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?

53
demandé sur karlphillip 2011-10-20 19:37:13

6 réponses

donc, le premier problème est l'ordre de coin. 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-Droite, Haut-Droite) , 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 paramétrer sa largeur et sa hauteur de manière à ce qu'elles soient identiques à la largeur et à la hauteur du rectangle résultant. Ne vous inquiétez pas, les images src et dst dans warpPerspective peuvent être différentes tailles.

troisièmement, un problème de performance. Alors que votre méthode est absolument précise, parce que vous ne faites que les transformations affine (rotation, redimensionner, deskew), mathématiquement, vous pouvez utiliser le corespondent affine de vos fonctions. Ils sont beaucoup plus rapide .

  • getAffineTransform ()

  • warpAffine().

Note importante: getaffine transform nécessite et attend seulement 3 points, et la matrice de résultat est de 2 par 3, au lieu de 3 par 3.

comment faire l'image de résultat ont une taille différente que 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, ... );

donc vous voilà, 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);
}
39
répondu Sam 2012-04-30 13:19:58

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

L'ordre des points de la matière à getPerspectiveTransform() et doivent être spécifiés dans l'ordre suivant:

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

par conséquent, les points d'origine devaient être réordonnés à ceci:

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 le 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 un peu de culture parce que l'image résultante n'est pas seulement la zone à l'intérieur du rectangle vert comme je le pensais:

Je ne sais pas s'il s'agit d'un bug D'OpenCV ou si je manque quelque chose, mais le problème principal a été résolu.

17
répondu karlphillip 2011-10-24 15:41:23

en travaillant 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 mentionnés ici..

d'Abord ce qui doit être fait est la suivante:

  • boucle à travers tous les segments de polygones et connecter ceux qui sont presque équel.
  • Classez - les de sorte que vous avez les 4 plus grands segments de ligne.
  • croisent ces lignes et vous avez les 4 points d'angle les plus probables.
  • transformer la matrice sur la perspective recueillie des points d'angle et le rapport d'aspect de l'objet connu.

j'ai mis en œuvre une classe Quadrangle qui prend soin de la conversion contour to quadrangle et qui la transformera également au-dessus de la bonne perspective.

voir la mise en œuvre ici: Java OpenCV correction d'un contour

5
répondu Tim 2017-05-23 12:17:41

MISE À JOUR: RÉSOLU

j'ai presque réussi. Si près d'être utilisable. Il désapprouve correctement mais je semble avoir une échelle ou traduire problème. J'ai mis le point d'ancrage à zéro et expérimente aussi de changer le mode d'échelle (aspectFill, ajuster à la page, etc...).

configurer les points de deskew (rouge les rend difficiles à voir): enter image description here

Appliquer la transformation calculée: enter image description here

maintenant il désapprouve. Cela semble assez bon, sauf que ses pas centré sur l'écran. En ajoutant un geste de pan à la vue d'image je peux le faire glisser et vérifier qu'il s'aligne: enter image description here

ce n'est pas aussi simple que traduire par -0.5, -0.5 parce que l'image originale devient un polygone qui s'étire très très loin (potentiellement), donc il est bordant rect est beaucoup plus grand que le cadre de l'écran.

est-ce que quelqu'un voir ce que je peux faire pour obtenir cette enveloppé? J'aimerais avoir commis et de le partager ici. C'est un sujet populaire, mais je n'ai pas trouvé de solution aussi simple que copier/coller.

le code source complet est ici:

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

git checkout démo

Cependant, je vais coller les parties pertinentes ici. Cette première méthode est à moi et est où Je reçois le redressement des points.

- (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: je l'ai fait fonctionner correctement. Les coordonnées devaient provenir du Centre, pas de l'upperleft. J'ai appliqué xOffset et yOffset et viola. Code de démonstration à l'endroit mentionné ci-dessus ("demo" branch)

4
répondu VaporwareWolf 2014-07-24 20:46:49

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

vous pouvez voir comment je l'ai fait dans cette question: transformer une image rectangulaire en un quadrilatère en utilisant un CATransform3D

3
répondu MonsieurDart 2017-05-23 12:34:11

très inspiré de la réponse de @VaporwareWolf, implémentée en C# utilisant Xamarin MonoTouch pour iOS. La principale différence est que j'utilise GetPerspectiveTransform au lieu de FindHomography et TopLeft plutôt que ScaleToFit pour le mode contenu:

    void SetupWarpedImage(UIImage sourceImage, Quad sourceQuad, RectangleF destRectangle)
    {
        var imageContainerView = new UIView(destRectangle)
        {
            ClipsToBounds = true,
            ContentMode = UIViewContentMode.TopLeft
        };

        InsertSubview(imageContainerView, 0);

        var imageView = new UIImageView(imageContainerView.Bounds)
        {
            ContentMode = UIViewContentMode.TopLeft,
            Image = sourceImage
        };

        var offset = new PointF(-imageView.Bounds.Width / 2, -imageView.Bounds.Height / 2);
        var dest = imageView.Bounds;
        dest.Offset(offset);
        var destQuad = dest.ToQuad();

        var transformMatrix = Quad.GeneratePerspectiveTransformMatrixFromQuad(sourceQuad, destQuad);
        CATransform3D transform = transformMatrix.ToCATransform3D();

        imageView.Layer.AnchorPoint = new PointF(0f, 0f);
        imageView.Layer.Transform = transform;

        imageContainerView.Add(imageView);
    }
1
répondu Bjørn Egil 2014-12-07 08:36:30