6

好的,我不得不承认我是 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);

更新:也许我的结果看起来不太好,因为我使用的矩阵是错误的。由于我无法观察到里面发生了什么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];

(虽然输出矩阵getPerspectiveTransform()是 3x3,但尝试直接访问它的值T.at<float>(row,col)会导致分段错误。)

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

4

3 回答 3

7

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

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)

示例结果:

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

于 2020-01-14T21:02:15.000 回答
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);
于 2013-08-08T13:31:42.530 回答
0

只晚了五年!...我将一次一个地回答您的问题:

“我的问题是用户应该如何事先知道该尺寸是多少”

你实际上只是错过了一步。我还建议使用perspectiveTransform只是为了方便自己计算最小和最大 X 和 Y。

因此,一旦您计算出最小 X 和 Y,请认识到它们可能是负数。如果它们是负数,则意味着您的图像将被裁剪。为了解决这个问题,您创建一个翻译矩阵,然后更正您的原始单应性:

Mat translate = Mat::eye(3, 3, CV_64F);
translate.at<CV_64F>(2, 0) = -minX;
translate.at<CV_64F>(2, 1) = -minY;
Mat corrected_H = translate * H;

那么目标大小的计算就是:

Size(maxX - minX, maxY - minY)

尽管还请注意,您需要将minXmaxXminY和转换maxY为整数。

“因为我无法观察到 getPerspectiveTransform() 内部发生了什么,所以我不知道这个矩阵是如何计算出来的”

https://github.com/opencv/opencv

这是 OpenCV 的源代码。你绝对可以观察到里面发生了什么getPerspectiveTransform

还有这个:https ://docs.opencv.org/2.4/modules/imgproc/doc/geometric_transformations.html

getPerspectiveTransform没有关于他们正在做什么的很好的文档,但是该功能findHomography确实如此。我很确定getPerspectiveTransform这只是一个简单的情况,当您恰好具有解决 8 个参数(4 对点,即角)所需的最小点数时。

于 2019-04-17T01:32:23.720 回答