在这一部分,
- 我们会了解到如何利用 calib3d 模块在图像中实现 3D 效果。
这将仅是很小一部分。在上一章节(相机校准),你已经找到了相机矩阵,畸变系数等等参数。给出一个图案图像,我们便可以利用上面的信息用于计算其姿势,或者物体在空间中位于何处,比如如何旋转,如何移动等等问题。对于一个平面物体,我们可以假定 Z = 0,这样,问题现在便转化为了如何放置摄像机才能查看到我们的图案图像。所以如果我们知道物体在空间中的位置,我们便可以绘制一些 2D 图像用以模拟 3D 效果。让我们看一下如何做到这件事情。
我们的问题是,我们想在我们棋盘的第一个角上绘制 3D 坐标系(x, y, z 坐标系),其中 X 轴是蓝色,Y 轴是绿色,Z 轴是红色。所以从效果上讲,Z 轴应该感觉像是与棋盘垂直的。
首先,让我们读取从上一章节(相机校准)存储的结果中读取出相机矩阵与畸变参数。
import numpy as np
import cv2 as cv
import glob
# 读取事先存好的数据
with np.load('B.npz') as X:
mtx, dist, _, _ = [X[i] for i in ('mtx','dist','rvecs','tvecs')]
现在让我们创建一个函数draw
,用以利用棋盘角点(利用cv.findChessboardCorners()函数)和坐标轴点绘制 3D 坐标系。
def draw(img, corners, imgpts):
corner = tuple(corners[0].ravel())
img = cv.line(img, corner, tuple(imgpts[0].ravel()), (255,0,0), 5)
img = cv.line(img, corner, tuple(imgpts[1].ravel()), (0,255,0), 5)
img = cv.line(img, corner, tuple(imgpts[2].ravel()), (0,0,255), 5)
return img
然后正如前面情况那样,我们创建了终止条件,物体点(棋盘上的 3D 角点)和坐标轴点。坐标轴点是 3D 空间中用于绘制轴的点。我们绘制长度为 3 的轴(单位是国际象棋的方形尺寸,我们会根据这个尺寸校准)。所以我们的 X 轴是从(0,0,0)到(3,0,0)绘制的,所以对于 Y 轴。对于 Z 轴,它是从(0,0,0)到(0,0,-3)绘制的。负数表示其接近摄像机。
criteria = (cv.TERM_CRITERIA_EPS + cv.TERM_CRITERIA_MAX_ITER, 30, 0.001)
objp = np.zeros((6*7,3), np.float32)
objp[:,:2] = np.mgrid[0:7,0:6].T.reshape(-1,2)
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()
函数连线。搞定!!!
for fname in glob.glob('left*.jpg'):
img = cv.imread(fname)
gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
ret, corners = cv.findChessboardCorners(gray, (7,6),None)
if ret == True:
corners2 = cv.cornerSubPix(gray,corners,(11,11),(-1,-1),criteria)
# 找到旋转和平移向量
ret,rvecs, tvecs = cv.solvePnP(objp, corners2, mtx, dist)
# 投射 3D 点到平面图像上
imgpts, jac = cv.projectPoints(axis, rvecs, tvecs, mtx, dist)
img = draw(img,corners2,imgpts)
cv.imshow('img',img)
k = cv.waitKey(0) & 0xFF
if k == ord('s'):
cv.imwrite(fname[:6]+'.png', img)
cv.destroyAllWindows()
请看下面的一些结果。注意,每个轴长 3 个方格:
pose 1 image如果你想要绘制一个立方体,你需要根据如下步骤改进draw()
函数和坐标轴点。
改进draw()
函数:
def draw(img, corners, imgpts):
imgpts = np.int32(imgpts).reshape(-1,2)
# 将底面绘制为绿色
img = cv.drawContours(img, [imgpts[:4]],-1,(0,255,0),-3)
# 将支柱绘制为蓝色
for i,j in zip(range(4),range(4,8)):
img = cv.line(img, tuple(imgpts[i]), tuple(imgpts[j]),(255),3)
# 将顶面绘制为红色
img = cv.drawContours(img, [imgpts[4:]],-1,(0,0,255),3)
return img
改进坐标轴点。它们是 3D 空间中立方体的 8 个角:
axis = np.float32([ [0,0,0], [0,3,0], [3,3,0], [3,0,0],
[0,0,-3],[0,3,-3],[3,3,-3],[3,0,-3] ])
然后看上去就像下面这样:
pose 2 image如果你对图形渲染,增强现实等感兴趣,你可以使用 OpenGL 来渲染更加复杂的图像。