OpenCV warpPerspective - 如何知道目标图像大小?

blo*_*mir 5 opencv perspective

好吧,我不得不承认我是 OpenCV 的新手,我的 MATLAB/lin. 代数知识可能会引入偏见。但是我想做的事情真的很简单,而我仍然没有设法找到答案。

在透视变换下尝试校正图像(或图像的一部分)时,您基本上执行两个步骤(假设您有 4 个定义扭曲对象的点):

  1. 找到一些完美矩形和扭曲形状之间的转换(在 OpenCV 中,通过findHomography()getPerspectiveTransform()- 为什么这两个在同一点上的操作不同是另一回事,也令人沮丧);这给了我们一个矩阵 T。
  2. 将 T 的倒数应用于最初扭曲的形状以将其转换为矩形(在 OpenCV 中,这是通过 完成的warpPerspective())。

现在,最后一个函数 ( warpPerspective()) 要求用户指定目标图像的大小。

我的问题是用户应该如何事先知道该尺寸是多少。这样做的低级方法是简单地将变换 T 应用于找到对象的图像的角点,从而保证您不会超出新变换形状的边界。但是,即使您从 T 中取出矩阵并手动将其应用于这些点,结果看起来也很奇怪。

有没有办法在 OpenCV 中做到这一点?谢谢!

PS下面是一些代码:

float leftX, lowerY, rightX, higherY;   

float minX = std::numeric_limits<float>::max(), maxX = std::numeric_limits<float>::min(), minY = std::numeric_limits<float>::max(), maxY = std::numeric_limits<float>::min();

Mat value, pt;
for(int i=0; i<4; i++)
{
    switch(i)
    {
        case 0:
            pt = (Mat_<float>(3, 1) << 1.00,1.00,1.00);                         
            break;
        case 1:
            pt = (Mat_<float>(3, 1) << srcIm.cols,1.00,1.00);
            break;
        case 2:
            pt = (Mat_<float>(3, 1) << 1.00,srcIm.rows,1.00);
            break;
        case 3:
            pt = (Mat_<float>(3, 1) << srcIm.cols,srcIm.rows,1.00);
            break;
        default:
            cerr << "Wrong switch." << endl;
            break;
    }               
    value = invH*pt;    
    value /= value.at<float>(2);        
    minX = min(minX,value.at<float>(0));
    maxX = max(maxX,value.at<float>(0));
    minY = min(minY,value.at<float>(1));
    maxY = max(maxY,value.at<float>(1));
}
leftX = std::min<float>(1.00,-minX);
lowerY = std::min<float>(1.00,-minY);
rightX = max(srcIm.cols-minX,maxX-minX);
higherY = max(srcIm.rows-minY,maxY-minY);

warpPerspective(srcIm, dstIm, H, Size(rightX-leftX,higherY-lowerY), cv::INTER_CUBIC);
Run Code Online (Sandbox Code Playgroud)

更新:也许我的结果看起来不太好,因为我使用的矩阵是错误的。由于我无法观察里面发生了什么getPerspectiveTransform(),我无法知道这个矩阵是如何计算的,但它有一些非常小的和非常大的值,这让我觉得它们是垃圾。这是我从 T 获取数据的方式:

for(int row=0;row<3;row++)
    for(int col=0;col<3;col++)
        T.at<float>(row,col) = ((float*)(H.data + (size_t)H.step*row))[col];
Run Code Online (Sandbox Code Playgroud)

(尽管来自 的输出矩阵getPerspectiveTransform()是 3x3,但尝试通过直接访问其值T.at<float>(row,col)会导致分段错误。)

这是正确的方法吗?也许这就是原始问题出现的原因,因为我没有得到正确的矩阵......

小智 6

如果您在调用 warpPerspective 之前就知道图像的大小,那么您可以获取其四个角的坐标,并使用perspectiveTransform 对它们进行变换,以查看它们在变换后的效果。据推测,它们将不再形成一个漂亮的矩形,因此您可能需要计算最小值和最大值以获得边界框。然后,这个边界框的大小就是你想要的目标大小。(此外,如果任何角低于零,请不要忘记根据需要平移框。)这是一个 Python 示例,它使用 warpPerspective 将变换后的图像置于其自身之上。

from typing import Tuple
    import cv2
    import numpy as np
    import math

    # Input: a source image and perspective transform
    # Output: a warped image and 2 translation terms
    def perspective_warp(image: np.ndarray, transform: np.ndarray) -> Tuple[np.ndarray, int, int]:
        h, w = image.shape[:2]
        corners_bef = np.float32([[0, 0], [w, 0], [w, h], [0, h]]).reshape(-1, 1, 2)
        corners_aft = cv2.perspectiveTransform(corners_bef, transform)
        xmin = math.floor(corners_aft[:, 0, 0].min())
        ymin = math.floor(corners_aft[:, 0, 1].min())
        xmax = math.ceil(corners_aft[:, 0, 0].max())
        ymax = math.ceil(corners_aft[:, 0, 1].max())
        x_adj = math.floor(xmin - corners_aft[0, 0, 0])
        y_adj = math.floor(ymin - corners_aft[0, 0, 1])
        translate = np.eye(3)
        translate[0, 2] = -xmin
        translate[1, 2] = -ymin
        corrected_transform = np.matmul(translate, transform)
        return cv2.warpPerspective(image, corrected_transform, (math.ceil(xmax - xmin), math.ceil(ymax - ymin))), x_adj, y_adj

    # Just like perspective_warp, but it also returns an alpha mask that can be used for blitting
    def perspective_warp_with_mask(image: np.ndarray, transform: np.ndarray) -> Tuple[np.ndarray, np.ndarray, int, int]:
        mask_in = np.empty(image.shape, dtype = np.uint8)
        mask_in.fill(255)
        output, x_adj, y_adj = perspective_warp(image, transform)
        mask, _, _ = perspective_warp(mask_in, transform)
        return output, mask, x_adj, y_adj

    # alpha_blits src onto dest according to the alpha values in mask at location (x, y),
    # ignoring any parts that do not overlap
    def alpha_blit(dest: np.ndarray, src: np.ndarray, mask: np.ndarray, x: int, y: int) -> None:
        dl = max(x, 0)
        dt = max(y, 0)
        sl = max(-x, 0)
        st = max(-y, 0)
        sr = max(sl, min(src.shape[1], dest.shape[1] - x))
        sb = max(st, min(src.shape[0], dest.shape[0] - y))
        dr = dl + sr - sl
        db = dt + sb - st
        m = mask[st:sb, sl:sr]
        dest[dt:db, dl:dr] = (dest[dt:db, dl:dr].astype(np.float) * (255 - m) + src[st:sb, sl:sr].astype(np.float) * m) / 255

    # blits a perspective-warped src image onto dest
    def perspective_blit(dest: np.ndarray, src: np.ndarray, transform: np.ndarray) -> None:
        blitme, mask, x_adj, y_adj = perspective_warp_with_mask(src, transform)
        cv2.imwrite("blitme.png", blitme)
        alpha_blit(dest, blitme, mask, int(transform[0, 2] + x_adj), int(transform[1, 2] + y_adj))


    # Read an input image
    image: np.array = cv2.imread('input.jpg')

    # Make a perspective transform
    h, w = image.shape[:2]
    corners_in = np.float32([[[0, 0]], [[w, 0]], [[w, h]], [[0, h]]])
    corners_out = np.float32([[[100, 100]], [[300, -100]], [[500, 300]], [[-50, 500]]])
    transform = cv2.getPerspectiveTransform(corners_in, corners_out)

    # Blit the warped image on top of the original
    perspective_blit(image, image, transform)
    cv2.imwrite('output.jpg', image)

Run Code Online (Sandbox Code Playgroud)

结果示例:

左:输入图像。 右:输出图像


Dim*_*lyn 2

如果结果看起来很奇怪,可能是因为您的点在 getPerspectiveTransform 中设置不正确。您的点向量必须按正确的顺序排列(左上、右上、右下、左下)。

但要回答您最初的问题,不存在“最佳输出大小”之类的东西。你必须根据你想做什么来决定。尝试并尝试找到适合您的尺寸。

编辑 :

如果问题出在变换矩阵上,那么如何创建它?openCV 中的一个好方法是:

vector<Point2f> corners;
corners.push_back(topleft);
corners.push_back(topright);
corners.push_back(bottomright);
corners.push_back(bottomleft);


// Corners of the destination image
// output is the output image, should be defined before this operation
vector<cv::Point2f> output_corner;
output_corner.push_back(cv::Point2f(0, 0));
output_corner.push_back(cv::Point2f(output.cols, 0));
output_corner.push_back(cv::Point2f(output.cols, output.rows));
output_corner.push_back(cv::Point2f(0, output.rows));

// Get transformation matrix
Mat H = getPerspectiveTransform(corners, output_corner);
Run Code Online (Sandbox Code Playgroud)

  • 我不会说“尝试并尝试找到适合你的尺寸”很有建设性。 (2认同)