目标
在本节中,
- 我们将会学习利用 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
void line(InputOutputArray img, Point pt1, Point pt2, const Scalar &color, int thickness=1, int lineType=LINE_8, int shift=0)
绘制连接两点的线段。
随后与前一个案例一样,我们创建终止条件、对象点(棋盘格各个角的 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) 的图像平面上查找点。找到这些点后,我们将使用 generateImage() 函数从第一个角向每个点绘制线段。完成 !!!
for fname in glob.glob('left*.jpg')
if ret == True
ret,rvecs, tvecs =
cv.solvePnP(objp, corners2, mtx, dist)
img = draw(img,corners2,imgpts)
if k == ord('s')
void projectPoints(InputArray objectPoints, InputArray rvec, InputArray tvec, InputArray cameraMatrix, InputArray distCoeffs, OutputArray imagePoints, OutputArray jacobian=noArray(), double aspectRatio=0)
投影图像平面上网格。
bool solvePnP(InputArray objectPoints, InputArray imagePoints, InputArray cameraMatrix, InputArray distCoeffs, OutputArray rvec, OutputArray tvec, bool useExtrinsicGuess=false, int flags=SOLVEPNP_ITERATIVE)
根据 3D 2D 点对应关系查找对象姿势。
bool findChessboardCorners(InputArray image, Size patternSize, OutputArray corners, int flags=CALIB_CB_ADAPTIVE_THRESH+CALIB_CB_NORMALIZE_IMAGE)
在棋盘内部的角点找到位置。
void imshow(const String &winname, InputArray mat)
在指定的窗口中显示图片。
int waitKey(int delay=0)
等待按下键。
void destroyAllWindows()
销毁所有 HighGUI 窗口。
CV_EXPORTS_W bool imwrite(const String &filename, InputArray img, const std::vector< int > ¶ms=std::vector< int >())
将图像保存到指定文件中。
CV_EXPORTS_W Mat imread(const String &filename, int flags=IMREAD_COLOR)
从文件中加载图像。
void cvtColor(InputArray src, OutputArray dst, int code, int dstCn=0)
将图像从一种颜色空间转换为另一种颜色空间。
void cornerSubPix(InputArray image, InputOutputArray corners, Size winSize, Size zeroZone, TermCriteria criteria)
优化角点位置。
请看下面的结果。注意,每个轴的长度为 3 个方格。
图像
渲染一个立方体
如果你想绘制一个立方体,请修改 generateImage() 函数和轴点,如下所示。
修改后的 generateImage() 函数
def draw(img, corners, imgpts)
imgpts = np.int32(imgpts).reshape(-1,2)
for i,j in zip(range(4),range(4,8))
img =
cv.line(img, tuple(imgpts[i]), tuple(imgpts[j]),(255),3)
return img
void drawContours(InputOutputArray image, InputArrayOfArrays contours, int contourIdx, const Scalar &color, int thickness=1, int lineType=LINE_8, InputArray hierarchy=noArray(), int maxLevel=INT_MAX, Point offset=Point())
绘制轮廓轮廓或填充轮廓。
修改后的轴点。它们是立方体在 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] ])
查看下面的结果
图像
如果你对图形、增强现实等感兴趣,可以使用 OpenGL 来渲染更复杂的图形。
其他资源
练习题