计算透视变换目标图像的纵横比

Dai*_*nis 8 android opencv image-processing computer-vision augmented-reality

我最近在OpenCV中将Perspective Transform实现到Android中的应用程序.几乎所有事情都没有问题,但有一方面需要做更多的工作.

问题是我不知道如何计算透视变换的目标图像的正确纵横比(它不必手动设置),因此它可以将图像的纵横比计算为真实的大小尽管相机的角度,事物/图像.请注意,起始坐标不形成梯形,它确实形成四边形.

如果我有一张从大约45度拍摄的书的照片,我希望目标图像宽高比与本书的宽高比几乎相同.拥有2D照片很难,但CamScanner应用程序完美无缺.我已经制作了非常简单的方法来计算目标图像的大小(没有期望它可以按我的意愿工作),但它使图像从45度角缩短约20%,当降低角度时图像高度降低显而易见,尽管角度如此,但CamScanner完美地做到了:

在此输入图像描述

在这里,CamScanner保持目标图像的纵横比(第二个)与书的纵横比相同,即使在~20度角也可以非常精确地保持.

同时,我的代码看起来像这样(在计算目标图像的大小时,我无意让它按照我在这个问题中的要求工作):

public static Mat PerspectiveTransform(Point[] cropCoordinates, float ratioW, float ratioH, Bitmap croppedImage)
{
    if (cropCoordinates.length != 4) return null;

    double width1, width2, height1, height2, avgw, avgh;
    Mat src = new Mat();
    List<Point> startCoords = new ArrayList<>();
    List<Point> resultCoords = new ArrayList<>();

    Utils.bitmapToMat(croppedImage, src);

    for (int i = 0; i < 4; i++)
    {
        if (cropCoordinates[i].y < 0 ) new Point(cropCoordinates[i].x, 0);
        startCoords.add(new Point(cropCoordinates[i].x * ratioW, cropCoordinates[i].y * ratioH));
    }

    width1 = Math.sqrt(Math.pow(startCoords.get(2).x - startCoords.get(3).x,2) + Math.pow(startCoords.get(2).y - startCoords.get(3).y,2));
    width2 = Math.sqrt(Math.pow(startCoords.get(1).x - startCoords.get(0).x,2) + Math.pow(startCoords.get(1).y - startCoords.get(0).y,2));
    height1 = Math.sqrt(Math.pow(startCoords.get(1).x - startCoords.get(2).x, 2) + Math.pow(startCoords.get(1).y - startCoords.get(2).y, 2));
    height2 = Math.sqrt(Math.pow(startCoords.get(0).x - startCoords.get(3).x, 2) + Math.pow(startCoords.get(0).y - startCoords.get(3).y, 2));
    avgw = (width1 + width2) / 2;
    avgh = (height1 + height2) / 2;

    resultCoords.add(new Point(0, 0));
    resultCoords.add(new Point(avgw-1, 0));
    resultCoords.add(new Point(avgw-1, avgh-1));
    resultCoords.add(new Point(0, avgh-1));

    Mat start = Converters.vector_Point2f_to_Mat(startCoords);
    Mat result = Converters.vector_Point2d_to_Mat(resultCoords);
    start.convertTo(start, CvType.CV_32FC2);
    result.convertTo(result,CvType.CV_32FC2);

    Mat mat = new Mat();
    Mat perspective = Imgproc.getPerspectiveTransform(start, result);
    Imgproc.warpPerspective(src, mat, perspective, new Size(avgw, avgh));

    return mat;
}
Run Code Online (Sandbox Code Playgroud)

从相对相同的角度来看,我的方法会产生这样的结果:

在此输入图像描述

我想知道的是怎么做?有趣的是,他们如何通过设置4个角的坐标来计算对象的长度.此外,如果可能,请提供一些代码/数学解释或类似/相同的文章.

先感谢您.

yhe*_*non 9

这已经出现过几次了,但我从来没有看到完整的答案,所以这里.这里显示的实现是基于本文得出的完整方程:http://research.microsoft.com/en-us/um/people/zhang/papers/tr03-39.pdf

基本上,它表明假设针孔相机模型,可以计算投影矩形的纵横比(但不是规模,不出所料).基本上,人们可以解决焦距,然后获得纵横比.这是使用OpenCV在python中的示例实现.请注意,您需要按正确的顺序检测4个角落,否则它将无法工作(请注意顺序,它是曲折的).报告的错误率在3-5%的范围内.

import math
import cv2
import scipy.spatial.distance
import numpy as np

img = cv2.imread('img.png')
(rows,cols,_) = img.shape

#image center
u0 = (cols)/2.0
v0 = (rows)/2.0

#detected corners on the original image
p = []
p.append((67,74))
p.append((270,64))
p.append((10,344))
p.append((343,331))

#widths and heights of the projected image
w1 = scipy.spatial.distance.euclidean(p[0],p[1])
w2 = scipy.spatial.distance.euclidean(p[2],p[3])

h1 = scipy.spatial.distance.euclidean(p[0],p[2])
h2 = scipy.spatial.distance.euclidean(p[1],p[3])

w = max(w1,w2)
h = max(h1,h2)

#visible aspect ratio
ar_vis = float(w)/float(h)

#make numpy arrays and append 1 for linear algebra
m1 = np.array((p[0][0],p[0][1],1)).astype('float32')
m2 = np.array((p[1][0],p[1][1],1)).astype('float32')
m3 = np.array((p[2][0],p[2][1],1)).astype('float32')
m4 = np.array((p[3][0],p[3][1],1)).astype('float32')

#calculate the focal disrance
k2 = np.dot(np.cross(m1,m4),m3) / np.dot(np.cross(m2,m4),m3)
k3 = np.dot(np.cross(m1,m4),m2) / np.dot(np.cross(m3,m4),m2)

n2 = k2 * m2 - m1
n3 = k3 * m3 - m1

n21 = n2[0]
n22 = n2[1]
n23 = n2[2]

n31 = n3[0]
n32 = n3[1]
n33 = n3[2]

f = math.sqrt(np.abs( (1.0/(n23*n33)) * ((n21*n31 - (n21*n33 + n23*n31)*u0 + n23*n33*u0*u0) + (n22*n32 - (n22*n33+n23*n32)*v0 + n23*n33*v0*v0))))

A = np.array([[f,0,u0],[0,f,v0],[0,0,1]]).astype('float32')

At = np.transpose(A)
Ati = np.linalg.inv(At)
Ai = np.linalg.inv(A)

#calculate the real aspect ratio
ar_real = math.sqrt(np.dot(np.dot(np.dot(n2,Ati),Ai),n2)/np.dot(np.dot(np.dot(n3,Ati),Ai),n3))

if ar_real < ar_vis:
    W = int(w)
    H = int(W / ar_real)
else:
    H = int(h)
    W = int(ar_real * H)

pts1 = np.array(p).astype('float32')
pts2 = np.float32([[0,0],[W,0],[0,H],[W,H]])

#project the image with the new w/h
M = cv2.getPerspectiveTransform(pts1,pts2)

dst = cv2.warpPerspective(img,M,(W,H))

cv2.imshow('img',img)
cv2.imshow('dst',dst)
cv2.imwrite('orig.png',img)
cv2.imwrite('proj.png',dst)

cv2.waitKey(0)
Run Code Online (Sandbox Code Playgroud)

原版的:

在此输入图像描述

投影(分辨率非常低,因为我从截图中裁剪了图像,但宽高比似乎正确):

在此输入图像描述

  • 知道为什么我得到 fSquare 的负数,因此 f = nAn?我试图在 java 中实现这一点...... 编辑:这只是有时发生......我的 double 是否有可能溢出? (2认同)