自动透视校正 OpenCV

31

我正在尝试在我的iOS程序中实现自动透视校正,在使用教程中的测试图像时,一切都符合预期。但是当我拍照时,我得到了一个奇怪的结果。

我使用的是这个教程中的代码。

当我给它一个看起来像这样的图像时:

enter image description here

我得到了这个结果:

enter image description here

这是dst给我的内容,可能会有所帮助。

enter image description here

我使用以下代码调用包含此代码的方法。

quadSegmentation(Img, bw, dst, quad);

有人能告诉我为什么我会得到比教程中多得多的绿线吗?我该如何修复它并正确裁剪图像以仅包含卡片?


1
你的结果看起来像垃圾一样。你需要展示更多的代码。具体而言,你正在以什么格式发送 Img,并且你是如何解释输出行的? - StilesCrisis
确实非常有帮助!!但是需要注意的是,如果名片相对于水平轴保持一定角度,则可能无法正常工作。在这种情况下,边界矩形也将与水平轴保持相同的角度,因此角点也会不同。 - user158420
2个回答

43

透视变换需要:

源点->源图像四边形顶点的坐标。

目标点->目标图像中相应四边形顶点的坐标。

这里我们将通过轮廓过程计算这些点。

计算源图像中四边形顶点的坐标

  • 通过模糊、二值化、寻找轮廓、寻找最大轮廓等步骤,您可以将卡片作为轮廓来获取。
  • 在找到最大轮廓后,只需计算近似多边形曲线,这里您应该得到4个表示您卡片角落的点。您可以调整参数epsilon以获得4个坐标。

enter image description here

计算目标图像中相应四边形顶点的坐标

  • 这可通过计算最大轮廓的边界矩形来轻松找到。

enter image description here

在下面的图像中,红色矩形表示源点,绿色矩形表示目标点。

enter image description here

调整坐标顺序并应用透视变换

查看最终结果

enter image description here

代码

 Mat src=imread("card.jpg");
 Mat thr;
 cvtColor(src,thr,CV_BGR2GRAY);
 threshold( thr, thr, 70, 255,CV_THRESH_BINARY );

 vector< vector <Point> > contours; // Vector for storing contour
 vector< Vec4i > hierarchy;
 int largest_contour_index=0;
 int largest_area=0;

 Mat dst(src.rows,src.cols,CV_8UC1,Scalar::all(0)); //create destination image
 findContours( thr.clone(), contours, hierarchy,CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE ); // Find the contours in the image
 for( int i = 0; i< contours.size(); i++ ){
    double a=contourArea( contours[i],false);  //  Find the area of contour
    if(a>largest_area){
    largest_area=a;
    largest_contour_index=i;                //Store the index of largest contour
    }
 }

 drawContours( dst,contours, largest_contour_index, Scalar(255,255,255),CV_FILLED, 8, hierarchy );
 vector<vector<Point> > contours_poly(1);
 approxPolyDP( Mat(contours[largest_contour_index]), contours_poly[0],5, true );
 Rect boundRect=boundingRect(contours[largest_contour_index]);
 if(contours_poly[0].size()==4){
    std::vector<Point2f> quad_pts;
    std::vector<Point2f> squre_pts;
    quad_pts.push_back(Point2f(contours_poly[0][0].x,contours_poly[0][0].y));
    quad_pts.push_back(Point2f(contours_poly[0][1].x,contours_poly[0][1].y));
    quad_pts.push_back(Point2f(contours_poly[0][3].x,contours_poly[0][3].y));
    quad_pts.push_back(Point2f(contours_poly[0][2].x,contours_poly[0][2].y));
    squre_pts.push_back(Point2f(boundRect.x,boundRect.y));
    squre_pts.push_back(Point2f(boundRect.x,boundRect.y+boundRect.height));
    squre_pts.push_back(Point2f(boundRect.x+boundRect.width,boundRect.y));
    squre_pts.push_back(Point2f(boundRect.x+boundRect.width,boundRect.y+boundRect.height));

    Mat transmtx = getPerspectiveTransform(quad_pts,squre_pts);
    Mat transformed = Mat::zeros(src.rows, src.cols, CV_8UC3);
    warpPerspective(src, transformed, transmtx, src.size());
    Point P1=contours_poly[0][0];
    Point P2=contours_poly[0][1];
    Point P3=contours_poly[0][2];
    Point P4=contours_poly[0][3];


    line(src,P1,P2, Scalar(0,0,255),1,CV_AA,0);
    line(src,P2,P3, Scalar(0,0,255),1,CV_AA,0);
    line(src,P3,P4, Scalar(0,0,255),1,CV_AA,0);
    line(src,P4,P1, Scalar(0,0,255),1,CV_AA,0);
    rectangle(src,boundRect,Scalar(0,255,0),1,8,0);
    rectangle(transformed,boundRect,Scalar(0,255,0),1,8,0);

    imshow("quadrilateral", transformed);
    imshow("thr",thr);
    imshow("dst",dst);
    imshow("src",src);
    imwrite("result1.jpg",dst);
    imwrite("result2.jpg",src);
    imwrite("result3.jpg",transformed);
    waitKey();
   }
   else
    cout<<"Make sure that your are getting 4 corner using approxPolyDP..."<<endl;

非常感谢!我明天会研究一下这个。 - Clip
我几乎理解所有的代码,但你能解释一下 if 语句后面的 squre_pts 吗? - Clip
1
这只是按照我们考虑源点的顺序获取边界矩形的四个角点。 - Haris
有没有自适应阈值可以使用,以便在每次图像不同的情况下获得类似的设置? - Clip
2
一个答案中包含许多酷炫而简单的想法,例如:“...可以通过计算最大轮廓的边界矩形来轻松找到这个问题的解决方法...”!非常有趣和实用的指导。非常感谢! - Valentin H
显示剩余3条评论

7
这通常发生在您依赖他人的代码来解决您特定问题而不是采用该代码时。查看处理阶段以及其图像与您的图像之间的差异(顺便说一句,从他们的图像开始并确保代码有效是个好主意):
  1. 获取边缘地图-可能有效,因为您的边缘很好
  2. 使用霍夫变换检测线条-失败,因为您的卡片上不仅有轮廓上的线条,还有内部的线条,因此会出现许多误报线条
  3. 通过找到线条交点来获取角落-由于上述原因而失败
  4. 检查近似多边形曲线是否有4个顶点-失败
  5. 确定左上角,左下角,右上角和右下角-失败
  6. 应用透视变换-完全失败
要解决您的问题,您必须确保仅提取周边的线条。如果您始终具有深色背景,则可以利用此事实丢弃其他对比度/极性的线条。或者,您可以提取所有线条,然后选择最靠近图像边界的线条(如果您的背景没有线条)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接