双目相机标定与点云生成:从原理到实现
双目相机标定与点云生成:从原理到实现
本文详细介绍了从双目相机标定到点云生成的完整流程,包括相机标定、图片畸变矫正、极线矫正、视差图计算、深度图生成以及点云显示等关键步骤。通过本文,读者可以系统地学习双目视觉技术的核心内容,并通过提供的代码实现这些功能。
大致思路
双目视觉系统通过模拟人眼的视觉原理,利用两个摄像头同时拍摄同一场景,通过计算图像之间的差异(即视差)来获取深度信息,进而生成3D点云。整个过程主要包括以下几个步骤:
- 相机标定:获取每个相机的内参和外参,这是后续所有计算的基础。
- 图片畸变矫正:消除镜头畸变对图像质量的影响。
- 极线矫正:将立体匹配问题从二维空间降低到一维,提高计算效率。
- 视差图计算:通过立体匹配算法(如SGBM)计算左右图像之间的视差。
- 深度图生成:根据视差图计算每个像素点的深度信息。
- 点云生成:结合深度信息和颜色信息,生成3D点云。
点云数据展示,将图片像右旋转45°即和图片视角一致。
一、相机标定
相机标定是整个流程中最关键的一步,它获取了相机的内参和外参,这些参数用于后续的所有计算。
1. 相机参数介绍
相机参数主要包括内参和外参:
- 内参:包括焦距、像主点坐标、畸变参数等,共有11个参数变量。
- 外参:包括旋转矩阵和平移向量,用于描述两个摄像头之间的相对位置和姿态。
2. 单目相机标定
单目相机标定通常使用张正友标定法,需要准备一个标定板并拍摄多张照片。但要获得高精度的结果较为困难,因此本文选择使用已标定好的数据集。
3. 双目相机标定
双目相机标定除了求解每个摄像头的内参外,还需要求解两个摄像头之间的相对位置和姿态(外参)。例如,外参矩阵中的平移向量可以表示两个相机镜头之间的距离(基线长度)。
二、图片畸变矫正
使用相机的内参和畸变参数对图像进行矫正,消除镜头畸变的影响。OpenCV提供了cv.undistort
函数实现这一功能:
void undistort( InputArray src, //输入原图
OutputArray dst,//输出矫正后的图像
InputArray cameraMatrix,//内参矩阵
InputArray distCoeffs,//畸变系数
InputArray newCameraMatrix=noArray() );
三、极线矫正
极线矫正将立体匹配问题从二维空间降低到一维,显著提高计算效率。具体步骤如下:
1. 极线矫正
通过极线矫正,可以将匹配过程从整张图片(二维空间)降低到一维空间。具体实现代码如下:
# 读取图像
imgl = cv.imread('1_L.jpg')
imgr = cv.imread('1_R.jpg')
high, wide = imgl.shape[0:2]
# 读取相机参数
config = stereoCamera()
# 消除图像畸变
imgl_qb = cv.undistort(imgl, config.cam_matrix_l, config.distortion_l)
imgr_qb = cv.undistort(imgr, config.cam_matrix_r, config.distortion_r)
# 极线校正
map1x, map1y, map2x, map2y, Q = getRectifyTransform(high, wide, config)
imgl_jx, imgr_jx = rectifyImage(imgl_qb, imgr_qb, map1x, map1y, map2x, map2y)
# 绘制等间距平行线,检查效果
line = draw_line(imgl_jx, imgr_jx)
2. 投影矩阵Q
Q矩阵在生成3D点云时会用到,具体细节可参考相关文献。
3. 图片检查
极线矫正后的图片需要进行检查,确保左右图像在同一个平面内的点投影到同一条直线上。检查结果如下图所示:
极线矫正后的图片检查结果
四、SGBM局部匹配算法计算视差图
SGBM(Semi-Global Block Matching)是一种常用的立体匹配算法,用于计算视差图。
1. 设置立体匹配算法SGBM
SGBM算法的参数设置对结果影响较大,需要根据具体场景进行调整。关键参数包括窗口大小(blockSize
)、最大最小视差等。具体实现代码如下:
def opencv_SGBM(left_img, right_img, use_wls=False):
blockSize = 11
paramL = {"minDisparity": 0,
"numDisparities": 170,
"blockSize": blockSize,
"P1": 8 * 3 * blockSize * blockSize,
"P2": 32 * 3 * blockSize * blockSize,
"disp12MaxDiff": 1,
"uniquenessRatio": 10,
"speckleWindowSize": 50,
"speckleRange": 1,
"preFilterCap": 31,
"mode": cv.STEREO_SGBM_MODE_SGBM_3WAY
}
matcherL = cv.StereoSGBM_create(**paramL)
dispL = matcherL.compute(left_img, right_img)
if use_wls:
paramR = paramL
paramR['minDisparity'] = -paramL['numDisparities']
matcherR = cv.StereoSGBM_create(**paramR)
dispR = matcherR.compute(right_img, left_img)
filter = cv.ximgproc.createDisparityWLSFilter(matcher_left=matcherL)
filter.setLambda(80000)
filter.setSigmaColor(1.0)
dispL = filter.filter(dispL, left_img, None, dispR)
dispL = cv2.bilateralFilter(dispL.astype(np.float32), d=9, sigmaColor=75, sigmaSpace=75)
dispL[dispL < 0] = 1e-6
dispL = dispL.astype(np.int16)
dispL = dispL / 16.0
return dispL
2. WLS滤波平滑优化
WLS(Weighted Least Squares)滤波可以对视差图进行平滑处理,提高图像质量。具体实现代码已在上一步中给出。
五、通过视差图计算深度图
根据视差图计算深度图,深度计算公式为z = (f * b) / d
,其中f
是焦距,b
是基线长度,d
是视差值。需要注意单位的统一,例如f
和d
的单位是像素,b
的单位是米,计算出的深度单位也是米。
六、通过视差图和Q矩阵计算点云
通过视差图和Q矩阵可以计算出每个像素点的三维坐标,并结合颜色信息生成点云。具体实现代码略。
七、点云显示
点云数据量较大,可以在本地或在线展示。虽然可以在线展示点云,但通常没有颜色信息,直观性较差。
反思
在实际操作中,可能会遇到视差图生成后深度图显示异常的问题。这通常是因为视差图中存在大量接近0的数值,导致深度计算结果异常。解决方法是将这些异常值赋值为最小有效视差值,并通过直方图分析视差图的数值分布,确保数据的合理性。