使用Python在OpenCV中进行透视校正

29

我试图对倾斜的矩形(信用卡)进行透视校正,该矩形在四个方向上都倾斜。我已经找到了其四个角落和相应的倾斜角度,但我无法找到坐标的确切位置,需要将其投影到那里。 我正在使用cv2.getPerspectiveTransform进行转换。

我有实际卡片(未倾斜)的长宽比,我想要这样的坐标,以便保持原始的长宽比。我尝试过使用边界矩形,但这会增加卡片的大小。

任何帮助将不胜感激。测试图片

边界矩形


1
你可以在这里查看答案 https://dev59.com/ZmEh5IYBdhLWcg3wWySr#22550609 ,可能会有帮助。 - Haris
我在问题中已经指明了,我已经尝试将其投影到边界矩形顶点,但我没有得到实际的纵横比,宽度增加了,我会发布图片。 - Anuradha
1
取边界矩形的顶点会高估卡片的宽度。 - Anuradha
是的,那是真的。 - Haris
9
我是否错了,那个条形码不是编码了被徒劳地遮盖住的任何数字吗? - Christopher Galpin
使用OpenCV来检测条形码,然后应用单应性变换将其矫正,怎么样? - Omar Morales Rivera
3个回答

18

这是你需要遵循的步骤...

为了方便起见,我已将您的图像调整为较小的尺寸,

在此输入图像描述

  • 计算源图像的四边形顶点,在此我手动找到了它们,您也可以选择边缘检测、霍夫线等方法。
  Q1=manual calculation;
  Q2=manual calculation;
  Q3=manual calculation;
  Q4=manual calculation;
  • 通过保持宽高比,在目标图像中计算四边形顶点,此时可以从源图像的四边形顶点中获取卡片的宽度,并通过乘以宽高比来计算高度。
   // compute the size of the card by keeping aspect ratio.
    double ratio=1.6;
    double cardH=sqrt((Q3.x-Q2.x)*(Q3.x-Q2.x)+(Q3.y-Q2.y)*(Q3.y-Q2.y)); //Or you can give your own height
    double cardW=ratio*cardH;
    Rect R(Q1.x,Q1.y,cardW,cardH);
  • 现在你已经有了源和目标四边形的顶点,那么就可以应用 warpPerspective 函数。

enter image description here

您可以参考以下 C++ 代码:

   //Compute quad point for edge
    Point Q1=Point2f(90,11);
    Point Q2=Point2f(596,135);
    Point Q3=Point2f(632,452);
    Point Q4=Point2f(90,513);

    // compute the size of the card by keeping aspect ratio.
    double ratio=1.6;
    double cardH=sqrt((Q3.x-Q2.x)*(Q3.x-Q2.x)+(Q3.y-Q2.y)*(Q3.y-Q2.y));//Or you can give your own height
    double cardW=ratio*cardH;
    Rect R(Q1.x,Q1.y,cardW,cardH);

    Point R1=Point2f(R.x,R.y);
    Point R2=Point2f(R.x+R.width,R.y);
    Point R3=Point2f(Point2f(R.x+R.width,R.y+R.height));
    Point R4=Point2f(Point2f(R.x,R.y+R.height));

    std::vector<Point2f> quad_pts;
    std::vector<Point2f> squre_pts;

    quad_pts.push_back(Q1);
    quad_pts.push_back(Q2);
    quad_pts.push_back(Q3);
    quad_pts.push_back(Q4);

    squre_pts.push_back(R1);
    squre_pts.push_back(R2);
    squre_pts.push_back(R3);
    squre_pts.push_back(R4);


    Mat transmtx = getPerspectiveTransform(quad_pts,squre_pts);
    int offsetSize=150;
    Mat transformed = Mat::zeros(R.height+offsetSize, R.width+offsetSize, CV_8UC3);
    warpPerspective(src, transformed, transmtx, transformed.size());

    //rectangle(src, R, Scalar(0,255,0),1,8,0);

    line(src,Q1,Q2, Scalar(0,0,255),1,CV_AA,0);
    line(src,Q2,Q3, Scalar(0,0,255),1,CV_AA,0);
    line(src,Q3,Q4, Scalar(0,0,255),1,CV_AA,0);
    line(src,Q4,Q1, Scalar(0,0,255),1,CV_AA,0);

    imshow("quadrilateral", transformed);
    imshow("src",src);
    waitKey();

请查看此答案 http://answers.opencv.org/question/23910/boundary-detection-what-function-will-be-the-best-for-this/#23916 可能会有所帮助。 - Haris
最后一个问题,如果可以的话。您能否展示一下如何从这张图片中获得一个好的鸟瞰图:http://imgur.com/a/v1mD0 ,我尝试了各种方法来获得一个合适的结果,但这是我能够得到的最好的结果:http://imgur.com/a/MtseW 对我来说不可能得到比这更好的鸟瞰图。 - LandonZeKepitelOfGreytBritn
1
@LandonZeKepitelOfGreytBritn 有点离题,但这是我能做到的最好的 https://imgur.com/a/jXFtD - jkd
请参考类似的实现和演示,链接为https://www.youtube.com/watch?v=ZxFn5-5YLhA。 - CodeDezk
在OpenCV 4中,CV_AA已经更改为cv::LINE_AA。 - Eros Guil

10

我正在使用Python编写@Haris提供的答案。

import cv2
import math
import numpy as np
import matplotlib.pyplot as plt

img = cv2.imread('test.jpg')
rows,cols,ch = img.shape

pts1 = np.float32([[360,50],[2122,470],[2264, 1616],[328,1820]])

ratio=1.6
cardH=math.sqrt((pts1[2][0]-pts1[1][0])*(pts1[2][0]-pts1[1][0])+(pts1[2][1]-pts1[1][1])*(pts1[2][1]-pts1[1][1]))
cardW=ratio*cardH;
pts2 = np.float32([[pts1[0][0],pts1[0][1]], [pts1[0][0]+cardW, pts1[0][1]], [pts1[0][0]+cardW, pts1[0][1]+cardH], [pts1[0][0], pts1[0][1]+cardH]])

M = cv2.getPerspectiveTransform(pts1,pts2)

offsetSize=500
transformed = np.zeros((int(cardW+offsetSize), int(cardH+offsetSize)), dtype=np.uint8);
dst = cv2.warpPerspective(img, M, transformed.shape)

plt.subplot(121),plt.imshow(img),plt.title('Input')
plt.subplot(122),plt.imshow(dst),plt.title('Output')
plt.show()

enter image description here


为什么比率是1.6? - Curious G.
2
@CuriousG.卡片尺寸的比率。 - Vedanshu

8

enter image description here 我有一个更好的解决方案,非常简单:

  • The red rectangle on original image and the corners points of the rectangle are source points

  • We use cv2.getPerspectiveTransform(src, dst) that takes source points and destination points as arguments and returns the transformation matrix which transforms any image to destination image as show in the diagram

  • We use this transformation matrix in cv2.warpPerspective()
    - As you can see results are better. You get a very nice bird view of the image

    import cv2
    import matplotlib.pyplot as plt
    import numpy as np
    
    
    def unwarp(img, src, dst, testing):
        h, w = img.shape[:2]
        # use cv2.getPerspectiveTransform() to get M, the transform matrix, and Minv, the inverse
        M = cv2.getPerspectiveTransform(src, dst)
        # use cv2.warpPerspective() to warp your image to a top-down view
        warped = cv2.warpPerspective(img, M, (w, h), flags=cv2.INTER_LINEAR)
    
        if testing:
            f, (ax1, ax2) = plt.subplots(1, 2, figsize=(20, 10))
            f.subplots_adjust(hspace=.2, wspace=.05)
            ax1.imshow(img)
            x = [src[0][0], src[2][0], src[3][0], src[1][0], src[0][0]]
            y = [src[0][1], src[2][1], src[3][1], src[1][1], src[0][1]]
            ax1.plot(x, y, color='red', alpha=0.4, linewidth=3, solid_capstyle='round', zorder=2)
            ax1.set_ylim([h, 0])
            ax1.set_xlim([0, w])
            ax1.set_title('Original Image', fontsize=30)
            ax2.imshow(cv2.flip(warped, 1))
            ax2.set_title('Unwarped Image', fontsize=30)
            plt.show()
        else:
            return warped, M
    im = cv2.imread("so.JPG")
    w, h = im.shape[0], im.shape[1]
    # We will first manually select the source points 
    # we will select the destination point which will map the source points in
    # original image to destination points in unwarped image
    src = np.float32([(20,     1),
                      (540,  130),
                      (20,    520),
                      (570,  450)])
    
    dst = np.float32([(600, 0),
                      (0, 0),
                      (600, 531),
                      (0, 531)])
    
    unwarp(im, src, dst, True)
    
    cv2.imshow("so", im)
    cv2.waitKey(0)[![enter image description here][1]][1]
    cv2.destroyAllWindows()
    
    

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