OpenCV:使用solvePnP确定单应性矩阵

5
在过去几周中,我尝试学习校正图像,并在这里的人们的帮助下成功地理解了它。大约一周前,我设置了一个测试示例,我想要矫正它(从上方查看图像)。使用函数 T = cv2.getPerspectiveTransform(UV_cp, XYZ_gcp) 这个Homography已经可以实现了 (原始: http://sitedezign.net/original.jpg 和 矫正后: http://sitedezign.net/rectified.jpg)。
当我尝试使用真实世界的照片时,它失败了,因为真实世界坐标不完全位于平面上(但仅围绕空间中测量的X、Y和Z坐标的大约10个控制点)。因此我决定使用solvePnP,并希望能够创建一个我可以使用的Homography。
我在测试示例上尝试过这种方法,但是没有得到我预期的结果:图像没有被校正,而且我使用solvePnP计算出的Homography与使用getPerspectiveTransform计算出的Homography不相等。
我的代码:
# Set UV (image) and XYZ (real life)
UV_cp = np.array([[1300.0, 2544.0], # left down
                  [1607.0, 1000.0], # left up
                  [3681.0, 2516.0], # right down
                  [3320.0, 983.0]], np.float32) # right up

# Z is on 0 plane, so Z=0.0
XYZ_gcp = np.array([[0.0, 400.0, 0.0],
                    [0.0, 0.0, 0.0],
                    [300.0, 400.0, 0.0],
                    [300.0, 0.0, 0.0]], np.float32)

rvec, tvec = cv2.solvePnP(XYZ_gcp, UV_cp, K, D)
rotM_cam = cv2.Rodrigues(rvec)[0]

# calculate camera position (= translation), in mm from 0,0,0 point
cameraPosition = -np.matrix(rotM_cam).T * np.matrix(tvec)

# 3x3 Identity matrix
I = np.identity(3)

# [I|-C]
I1_extended = np.hstack((I,-cameraPosition))

# P = K*R*I
P_cam = K.dot(rotM_cam).dot(I1_extended)

# create P2 = image from above: R = 0,0,0, translation = x, y, z = 0,0,-1000 (mm)
R_rec = matr.getR(0.0,0.0,0.0)
newZ = -1000.0
new_cameraPosition = np.array([[0.0],[0.0],[newZ]])
I2_extended = np.hstack((I,new_cameraPosition))
P_rec = K.dot(R_rec).dot(I2_extended)

# correct Homography T from getPerspectiveTransform:
T = np.array([[4.70332834e-01, 9.35182514e-02, -4.24671558e+02],
              [9.62104844e-03, 9.69462117e-01, -4.92461571e+02],
              [3.54859924e-06, 6.80081146e-04, 1.00000000e+00]])

# Homography Matrix = H = P_rect * pinv(P) => P2 * pinv(P1)
H = P_rec.dot(np.linalg.pinv(P_cam))

结果是一张扭曲的图像,远远不如上面显示的那个(矫正后的图像)。此外,应该正确的单应性T(来自getPerspectiveTransform)与使用solvePnP的结果计算得出的单应性H并不接近。

H from solvePnP:
[[  1.01865631e+00   2.68683332e-01  -2.04519580e+03]
 [ -3.24304366e-02   6.82672680e-01  -1.15688010e+03]
 [  2.03399902e-05   1.24191993e-04  -5.41378561e-01]]

H from getPerspectiveTransform:
[[  4.70332834e-01   9.35182514e-02  -4.24671558e+02]
 [  9.62104844e-03   9.69462117e-01  -4.92461571e+02]
 [  3.54859924e-06   6.80081146e-04   1.00000000e+00]]

有人知道出了什么问题吗?

附注:用于确定K矩阵和畸变系数的代码(值是根据我的相机Pentax K-5在33mm焦距下根据Adobe Camera Raw获取的):

# Focal length, sensor size (mm and px)
f = 33.0 # mm
pix_width = 4928.0 # sensor size has 4928px in width
pix_height = 3624.0 # sensor size has 4928px in width
sensor_width = 23.7 # mm
sensor_height = 15.7 # mm

# set center pixel
u0 = int(pix_width / 2.0)
v0 = int(pix_height / 2.0)

# determine values of camera-matrix
mu = pix_width / sensor_width # px/mm
alpha_u = f * mu # px

mv = pix_height / sensor_height # px/mm
alpha_v = f * mv # px

# Distortion coefs 
D = np.array([[0.0, 0.0, 0.0, 0.0]])

# Camera matrix
K = np.array([[alpha_u, 0.0, u0],
              [0.0, alpha_v, v0],
              [0.0, 0.0, 1.0]])

你能发布一下与 solvePnP 调用相对应的代码示例以及如何从中获取 H 吗? - BConic
我编辑了我的帖子,使其更易于阅读。创建单应性的代码在第一个代码块中。简单来说,我做了以下几件事: 1)在图像中设置UV点 2)在真实世界中设置XYZ点 3)使用K(相机)矩阵和D(畸变系数)进行solvePnP 4)使用结果获取旋转矩阵和平移向量(几乎完全正确(使用真实世界测量值检查了值)) 5)创建另一个平面矩阵(P2),旋转为:0,0,0,平移为:0,0,-1000(所以在表面上方1000毫米)使用P_rect * pinv(P_cam)创建单应性。 - Yorian
@Yorian,我尝试了你上面发布的代码,但是出现了一个错误,提示rvec,tvec = cv2.solvePnP(XYZ_gcp,UV_cp,K,D)ValueError:要解包的值太多。我该怎么办?另外,你能告诉我如何在现实世界中找到XYZ点吗? - Clive
好的,我解决了我的问题。使用retval、rvec、tvec = cv2.solvePnP(XYZ_gcp, UV_cp, K, D)。你上面发布的代码中缺少了retval。solvePnP返回3个参数而不是2个。 - Clive
1个回答

1
你的K矩阵似乎合适,但这可能无法在实际图像中实现较高的准确性。我认为,你应该使用calibrateCamera函数(文档链接教程)校准相机,而不是提前给出合理的值(特别是光学中心像素和镜头畸变系数)。但我不认为你提到的问题是由此引起的。 我认为你的问题源于P_rec的定义。 首先,请注意,如果你使用newZ = -1000.0,你实际上将相机向后移动了1000米(而不是毫米)。
其次,你必须非常小心地考虑哪些三维点并在何处将它们投影到图像中:
  1. 由于您在solvePnP函数中使用了XYZ_gcp,这意味着您将这些坐标用作3D点。

  2. 由于您在getPerspectiveTransform函数中使用了XYZ_gcp,这意味着您也将其用作2D坐标。请注意,严格来说,您不能这样做,因为getPerspectiveTransform需要两个4x2数组(而不是4x2和4x3),但我假设您删除了始终为0的第三个坐标。

因此,您的P_rec应该被定义为[x; y; 1] = P_rec * [x; y; 0; 1]。因此,P_rec应该定义如下:

P_rec = [ [1 0 0 0] [0 1 0 0] [0 0 0 1] ].

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