OpenCV 4.12.0
开源计算机视觉
加载中...
搜索中...
无匹配项
姿态估计

目标

在本节中,

  • 我们将学习利用 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().astype("int32"))
imgpts = imgpts.astype("int32")
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'):
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)
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 > &params=std::vector< int >())
将图像保存到指定文件。
CV_EXPORTS_W Mat imread(const String &filename, int flags=IMREAD_COLOR_BGR)
从文件加载图像。
void cvtColor(InputArray src, OutputArray dst, int code, int dstCn=0, AlgorithmHint hint=cv::ALGO_HINT_DEFAULT)
将图像从一个颜色空间转换为另一个颜色空间。
void cornerSubPix(InputArray image, InputOutputArray corners, Size winSize, Size zeroZone, TermCriteria criteria)
精炼角点位置。

请参阅下面的一些结果。请注意,每个轴的长度为 3 个方格。

image

渲染一个立方体

如果要绘制一个立方体,请修改 generateImage() 函数和轴点,如下所示。

修改后的 generateImage() 函数

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
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] ])

看看下面的结果

image

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