双目三维重建系统(双目标定+立体校正+双目测距+点云显示)Python
目录
双目三维重建系统(双目标定+立体校正+双目测距+点云显示)Python
1.项目结构
2. Environment
3.双目相机标定和校准
(0) 双目摄像头
(1) 采集标定板的左右视图
(2) 单目相机标定和校准
(3) 双目相机标定和校准
4.视差图和深度图
(1) 立体校正
(2) 立体匹配与视差图计算
(3) Demo
5.双目测距
6.3D点云显示
7.项目代码
8.参考资料
本篇博客将实现Python版本的双目三维重建系统,项目代码实现包含:`双目标定`,`立体校正(含消除畸变)`,`立体匹配`,`视差计算`和`深度距离计算/3D坐标计算` 的知识点。限于篇幅,本博客不会过多赘述算法原理,而是手把手教你,如何搭建一套属于自己的双目三维重建的系统。项目代码包含:
- 支持双USB连接线的双目摄像头
- 支持单USB连接线的双目摄像头(左右摄像头被拼接在同一个视频中显示)
- 支持单目相机标定:mono_camera_calibration.py ,无需Matlab标定
- 支持双目相机标定:stereo_camera_calibration.py,无需Matlab标定
- 支持使用WLS滤波器对视差图进行滤波
- 支持双目测距,误差在1cm内(鼠标点击图像即可获得其深度距离)
- 支持Open3D和PCL点云显示
诚然,网上有很多双测距的代码,但项目都不是十分完整,而且恢复视差图效果也一般,难以达到商业实际应用,究其原因,主要有下面2个:
- 双目摄像头质量问题,
- 双目标定存在问题,导致校准误差较大
- 没有使用WLS滤波器对视差图进行滤波,该方法可以极大提高视差图的效果
【完整的项目代码】双目三维重建系统(双目标定+立体校正+双目测距+点云显示)Python源码
先放一张动图,这是最终重建的效果:
三维重建中,除了双目相机,还有TOF和结构光3D 相机
- 飞行时间(Time of flight,TOF),代表公司微软Kinect2,PMD,SoftKinect, 联想 Phab,在手机中一般用于3D建模、AR应用,AR测距(华为TOF镜头)
- 双目视觉(Stereo Camera),代表公司 Leap Motion, ZED, 大疆;
- 结构光(Structured-light),代表公司有奥比中光,苹果iPhone X(Prime Sense),微软 Kinect1,英特尔RealSense, Mantis Vision 等,在手机(iPhone,华为)中3D结构光主要用于人脸解锁、支付、美颜等场景。
关于3D相机技术(飞行时间+双目+结构光)的区别,可以参考我的一篇博客《3D相机技术调研(飞行时间TOF+双目+结构光)》
1.项目结构
2. Environment
- 依赖包,可参考requirements.txt
- python-pcl (安装python-pcl需要一丢丢耐心,实在不行,就用open3d吧)
- open3d-python=0.7.0.0
- opencv-python
- opencv-contrib-python
3.双目相机标定和校准
(0) 双目摄像头
下面这款双目摄像头(RGB+RGB)是在某宝购买的(几百元,链接就不发了,免得说我打广告),作为本项目的双目相机,其基线是固定的6cm,是单USB连接线的双目摄像头(左右摄像头被拼接在同一个视频中显示),基本满足我们测试需求。一般基线越长,可测量的距离越远,网友也可以根据自己需要购买。
一点注意事项:
- 双目相机三维重建也可以使用RGB+IR(红外)的摄像头,甚至IR+IR的也是可以,本人亲测,RGB+IR的相机,其效果也是杠杠的。
- 基线不太建议太小,作为测试,一般baseline在3~9cm就可以满足需求,有些无人车的双目基线更是恐怖到1~2米长
- 从双目三维重建原理中可知,左右摄像头的成像平面尽可能在一个平面内,成像平面不在同一个平面的,尽管可以立体矫正,其效果也差很多。
- 一分钱,一分货,相机的质量好坏,直接决定了你的成像效果
(1) 采集标定板的左右视图
- 采集数据前,请调节相机焦距,尽可能保证视图中标定板清洗可见
- 采集棋盘格图像时,标定板一般占视图1/2到1/3左右
- 一般采集15~30张左右
参数说明:
- 参数width指的是棋盘格宽方向黑白格子相交点个数
- 参数height指的是棋盘格长方向黑白格子相交点个数
- 参数left_video是左路相机ID,一般就是相机连接主板的USB接口号
- 参数right_video是右路相机ID,一般就是相机连接主板的USB接口号
- PS:如果你的双目相机是单USB连接线的双目摄像头(左右摄像头被拼接在同一个视频中显示),则设置left_video=相机ID,而right_video=-1,
- 参数
detect
建议设置True
,这样可实时检测棋盘格,方面调整角度- 按键盘
s
或者c
保存左右视图图片
left_image |
right_image |
下面是采集双目摄像头标定板左右视图的Python代码:get_stereo_images.py,除了OpenCV,没啥依赖,直接干就完事。
双目标定的目标是获得左右两个相机的内参、外参和畸变系数,其中内参包括左右相机的fx,fy,cx,cy,外参包括左相机相对于右相机的旋转矩阵和平移向量,畸变系数包括径向畸变系数(k1, k2,k3)和切向畸变系数(p1,p2)。
双目标定工具最常用的莫过于是MATLAB的工具箱: Stereo Camera Calibrator App,网上有太多教程,我就不赘述了。
我采用的是Deepin系统,懒得去安装Matlab了,所以就参考各路神仙,使用OpenCV实现了单目和双目的标定程序。
(2) 单目相机标定和校准
一点注意事项:
- 标定代码会显示每一张图像的棋盘格的角点检测效果,如果发现有检测不到,或者角点检测出错,则需要自己手动删除这些图像,避免引入太大的误差
- 若误差超过0.1,建议重新调整摄像头并标定,不然效果会差很多
执行后,在$save_dir
目录下会生成left_cam.yml
和right_cam.yml
左右相机参数文件
其中K是相机内参矩阵,D是畸变系数矩阵
(3) 双目相机标定和校准
一点注意事项:
- 若误差超过0.1,建议重新调整摄像头并标定
执行后,在$save_dir
目录下会生成stereo_cam.yml
相机参数文件,这个文件,包含了左右相机的相机内参矩阵(K1,K2),畸变系数矩阵(D1,D2),左右摄像机之间的旋转矩阵R和平移矩阵T,以及本质矩阵E和基本矩阵F等.
有了双目相机内外参数信息(stereo_cam.yml
),下面就可以进行立体矫正,计算视差了
4.视差图和深度图
(1) 立体校正
立体校正的目的是将拍摄于同一场景的左右两个视图进行数学上的投影变换,使得两个成像平面平行于基线,且同一个点在左右两幅图中位于同一行,简称共面行对准。只有达到共面行对准以后才可以应用三角原理计算距离。
(2) 立体匹配与视差图计算
立体匹配的目的是为左图中的每一个像素点在右图中找到其对应点(世界中相同的物理点),这样就可以计算出视差:
(xi和xj分别表示两个对应点在图像中的列坐标)。
大部分立体匹配算法的计算过程可以分成以下几个阶段:匹配代价计算、代价聚合、视差优化、视差细化。立体匹配是立体视觉中一个很难的部分,主要困难在于:
1.图像中可能存在重复纹理和弱纹理,这些区域很难匹配正确;
2.由于左右相机的拍摄位置不同,图像中几乎必然存在遮挡区域,在遮挡区域,左图中有一些像素点在右图中并没有对应的点,反之亦然;
3.左右相机所接收的光照情况不同;
4.过度曝光区域难以匹配;
5.倾斜表面、弯曲表面、非朗伯体表面;
6.较高的图像噪声等。
常用的立体匹配方法基本上可以分为两类:局部方法,例如BM、SGM、ELAS、Patch Match等,非局部的,即全局方法,例如Dynamic Programming、Graph Cut、Belief Propagation等,局部方法计算量小,匹配质量相对较低,全局方法省略了代价聚合而采用了优化能量函数的方法,匹配质量较高,但是计算量也比较大。
目前OpenCV中已经实现的方法有BM、binaryBM、SGBM、binarySGBM、BM(cuda)、Bellief Propogation(cuda)、Constant Space Bellief Propogation(cuda)这几种方法。比较好用的是SGBM算法,它的核心是基于SGM算法,但和SGM算法又有一些不同,比如匹配代价部分用的是BT代价(原图+梯度图)而不是HMI代价等等。有关SGM算法的原理解释,可以参考另一篇博客 : 《双目立体匹配算法:SGM 》javascript:void(0)
(3) Demo
- 运行Demo进行立体矫正,计算视差图并恢复深度图,
- Demo的参数说明如下
参数 |
类型 |
说明 |
calibration_file |
str |
双目相机的配置文件,如"configs/lenacv-camera/stereo_cam.yml" |
left_video |
str |
左路相机ID或者视频文件 |
right_video |
str |
右路相机ID或者视频文件 |
left_file |
str |
左路测试图像文件 |
right_file |
str |
右路测试图像文件 |
filter |
bool |
是否对视差图进行滤波 |
- 视差图滤波:
在立体匹配生成视差图之后,还可以对视差图进行滤波后处理,例如Guided Filter、Fast Global Smooth Filter(一种快速WLS滤波方法)、Bilatera Filter、TDSR、RBS等。 视差图滤波能够将稀疏视差转变为稠密视差,并在一定程度上降低视差图噪声,改善视差图的视觉效果,但是比较依赖初始视差图的质量。
左视图 |
右视图 |
视差图(未滤波) |
深度图(未滤波) |
视差图(滤波后) |
深度图(滤波后) |
可以看到,使用WLS滤波后,视差图的整体效果都有明显改善
- 最终效果图
5.双目测距
得到了视差图之后,就可以计算像素深度了,在opencv中使用StereoRectify()函数可以得到一个重投影矩阵Q,它是一个4*4的视差图到深度图的映射矩阵(disparity-to-depth mapping matrix ),使用Q矩阵和cv2.reprojectImageTo3D即可实现将像素坐标转换为三维坐标,该函数会返回一个3通道的矩阵,分别存储X、Y、Z坐标(左摄像机坐标系下)。
运算如下:
重投影矩阵Q中
和
为左相机主点在图像中的坐标,f为焦距,
为两台相机投影中心间的平移(负值),即基线baseline,相当于平移向量T[0],
是右相机主点在图像中的坐标。
其中Z即是深度距离depth:
其中 f 为焦距长度(像素焦距),b为基线长度,d为视差,
与
为两个相机主点的列坐标。
这里有个地方需要注意,如果获得视差图像是CV_16S类型的,这样的视差图的每个像素值由一个16bit表示,其中低位的4位存储的是视差值得小数部分,所以真实视差值应该是该值除以16。在进行映射后应该乘以16,以获得毫米级真实位置。
- 运行
demo.py
后,鼠标点击图像任意区域,终端会打印对应距离 - 鼠标点击手部区域会打印距离摄像头的距离约633mm,即0.63米,还是比较准的
双目测距的精度 说明:
根据上式可以看出,某点像素的深度精度取决于该点处估计的视差d的精度。假设视差d的误差恒定,当测量距离越远,得到的深度精度则越差,因此使用双目相机不适宜测量太远的目标。
如果想要对与较远的目标能够得到较为可靠的深度,一方面需要提高相机的基线距离,但是基线距离越大,左右视图的重叠区域就会变小,内容差异变大,从而提高立体匹配的难度,另一方面可以选择更大焦距的相机,然而焦距越大,相机的视域则越小,导致离相机较近的物体的距离难以估计。
理论上,深度方向的测量误差与测量距离的平方成正比,而X/Y方向的误差与距离成正比;而距离很近时,由于存在死角,会导致难以匹配的问题;想象一下,如果你眼前放置一块物体,那你左眼只能看到物体左侧表面,右眼同理只能看到物体右侧表面,这时由于配准失败,导致视差计算失败;这个问题在基线越长,问题就越严重
6.3D点云显示
恢复三维坐标后,就可以使用python-pcl和Open3D库显示点云图
PCL Python版比较难安装,如果安装不了,那可以采用Open3D勉强凑合使用吧
如下图所示,你可以用鼠标旋转坐标轴,放大点云
2D-RGB |
Open3D点云显示 |
PCL点云显示 |
7.项目代码
完整的项目代码】双目三维重建系统(双目标定+立体校正+双目测距+点云显示)Python源码
示例代码如下, 项目配套了双目摄像头的校准参数文件,以及左右摄像的视频文件,可以作为Demo直接测试和使用
8.参考资料
- <双目测距理论及其python实现>
- 【完整的项目代码】双目三维重建系统(双目标定+立体校正+双目测距+点云显示)Python源码