姿势估计

目标

在这一部分,

  • 我们会了解到如何利用 calib3d 模块在图像中实现 3D 效果。

基础

这将仅是很小一部分。在上一章节(相机校准),你已经找到了相机矩阵,畸变系数等等参数。给出一个图案图像,我们便可以利用上面的信息用于计算其姿势,或者物体在空间中位于何处,比如如何旋转,如何移动等等问题。对于一个平面物体,我们可以假定 Z = 0,这样,问题现在便转化为了如何放置摄像机才能查看到我们的图案图像。所以如果我们知道物体在空间中的位置,我们便可以绘制一些 2D 图像用以模拟 3D 效果。让我们看一下如何做到这件事情。

我们的问题是,我们想在我们棋盘的第一个角上绘制 3D 坐标系(x, y, z 坐标系),其中 X 轴是蓝色,Y 轴是绿色,Z 轴是红色。所以从效果上讲,Z 轴应该感觉像是与棋盘垂直的。

首先,让我们读取从上一章节(相机校准)存储的结果中读取出相机矩阵与畸变参数。

  1. import numpy as np
  2. import cv2 as cv
  3. import glob
  4. # 读取事先存好的数据
  5. with np.load('B.npz') as X:
  6. mtx, dist, _, _ = [X[i] for i in ('mtx','dist','rvecs','tvecs')]

现在让我们创建一个函数draw,用以利用棋盘角点(利用cv.findChessboardCorners()函数)和坐标轴点绘制 3D 坐标系。

  1. def draw(img, corners, imgpts):
  2. corner = tuple(corners[0].ravel())
  3. img = cv.line(img, corner, tuple(imgpts[0].ravel()), (255,0,0), 5)
  4. img = cv.line(img, corner, tuple(imgpts[1].ravel()), (0,255,0), 5)
  5. img = cv.line(img, corner, tuple(imgpts[2].ravel()), (0,0,255), 5)
  6. return img

然后正如前面情况那样,我们创建了终止条件,物体点(棋盘上的 3D 角点)和坐标轴点。坐标轴点是 3D 空间中用于绘制轴的点。我们绘制长度为 3 的轴(单位是国际象棋的方形尺寸,我们会根据这个尺寸校准)。所以我们的 X 轴是从(0,0,0)到(3,0,0)绘制的,所以对于 Y 轴。对于 Z 轴,它是从(0,0,0)到(0,0,-3)绘制的。负数表示其接近摄像机。

  1. criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 30, 0.001)
  2. objp = np.zeros((6*7,3), np.float32)
  3. objp[:,:2] = np.mgrid[0:7,0:6].T.reshape(-1,2)
  4. axis = np.float32([[3,0,0], [0,3,0], [0,0,-3]]).reshape(-1,3)

现在,像往常一样,我们读取一张图片。搜索 7x6 网格。如果我们找到了,我们用子角像素优化一下它,然后计算旋转和平移,我们使用cv.solvePnPRansac()函数。一旦我们计算完那些旋转矩阵后,我们便用它们来将我们的坐标轴点投影到平面图像上。简而言之,我们寻找到平面图象上的点对应 3D 空间里的(3,0,0), (0,3,0), (0,0,3)。一旦我们找到后,我们便可以从第一个角到每个我们找到的坐标轴点之间利用draw()函数连线。搞定!!!

  1. for fname in glob.glob('left*.jpg'):
  2. img = cv.imread(fname)
  3. gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
  4. ret, corners = cv.findChessboardCorners(gray, (7,6),None)
  5. if ret == True:
  6. corners2 = cv.cornerSubPix(gray,corners,(11,11),(-1,-1),criteria)
  7. # 找到旋转和平移向量
  8. ret,rvecs, tvecs = cv.solvePnP(objp, corners2, mtx, dist)
  9. # 投射 3D 点到平面图像上
  10. imgpts, jac = cv.projectPoints(axis, rvecs, tvecs, mtx, dist)
  11. img = draw(img,corners2,imgpts)
  12. cv.imshow('img',img)
  13. k = cv.waitKey(0) & 0xFF
  14. if k == ord('s'):
  15. cv.imwrite(fname[:6]+'.png', img)
  16. cv.destroyAllWindows()

请看下面的一些结果。注意,每个轴长 3 个方格:

pose_1.jpg

pose 1 image

渲染立方体

如果你想要绘制一个立方体,你需要根据如下步骤改进draw()函数和坐标轴点。

改进draw()函数:

  1. def draw(img, corners, imgpts):
  2. imgpts = np.int32(imgpts).reshape(-1,2)
  3. # 将底面绘制为绿色
  4. img = cv.drawContours(img, [imgpts[:4]],-1,(0,255,0),-3)
  5. # 将支柱绘制为蓝色
  6. for i,j in zip(range(4),range(4,8)):
  7. img = cv.line(img, tuple(imgpts[i]), tuple(imgpts[j]),(255),3)
  8. # 将顶面绘制为红色
  9. img = cv.drawContours(img, [imgpts[4:]],-1,(0,0,255),3)
  10. return img

改进坐标轴点。它们是 3D 空间中立方体的 8 个角:

  1. axis = np.float32([ [0,0,0], [0,3,0], [3,3,0], [3,0,0],
  2. [0,0,-3],[0,3,-3],[3,3,-3],[3,0,-3] ])

然后看上去就像下面这样:

pose_2.jpg

pose 2 image

如果你对图形渲染,增强现实等感兴趣,你可以使用 OpenGL 来渲染更加复杂的图像。

其他资源

练习