赞
踩
目录
双目立体视觉深度相机实现双目测距功能,主要分为4个步骤:相机标定+双目校正+双目匹配+计算深度信息:
(1)相机标定:需要对双目相机进行标定,得到两个相机的内外参数、单应矩阵。
(2) 双目校正:根据标定结果对原始图像进行校正,校正后的两张图像位于同一平面且互相平行。
(3)双目匹配:对校正后的两张图像进行像素点匹配。
(4)计算深度图:根据匹配结果计算每个像素的深度,从而获得深度图。
本篇将着重介绍双目测距的深度图计算原理和实现,关于相机标定+双目校正+双目匹配等内容,鄙人已经整理了一个完整的项目内容,详情请查看博客《双目三维重建系统(双目标定+立体校正+双目测距+点云显示)Python》
原理很简单,利用了相似三角形计算距离,所以双目测距的主要任务在于前期摄像头的定标、双目图像点的特征匹配上。
实现双目测距最关键的步骤是获得视差图,视差图计算请查看博客《双目三维重建系统(双目标定+立体校正+双目测距+点云显示)Python》
得到了视差图之后,就可以计算像素深度了,在opencv中使用StereoRectify()函数可以得到一个重投影矩阵Q,它是一个4*4的视差图到深度图的映射矩阵(disparity-to-depth mapping matrix ),使用Q矩阵和cv2.reprojectImageTo3D即可实现将像素坐标转换为三维坐标,该函数会返回一个3通道的矩阵,分别存储X、Y、Z坐标(左摄像机坐标系下)。
- def reprojectImageTo3D(disparity, Q, _3dImage=None, handleMissingValues=None, ddepth=None):
- """
- :param disparity: 输入视差图
- :param Q: 输入4*4的视差图到深度图的映射矩阵,即重投影矩阵 通过stereoRectify得到
- (disparity-to-depth mapping matrix)
- :param _3dImage: 映射后存储三维坐标的图像
- contains 3D coordinates of the point (x,y) computed from the disparity map
- :param handleMissingValues: 计算得到的非正常值是否给值,如果为true则给值10000
- :param ddepth: 输出类型 -1 即默认为CV_32FC3 还可以是 CV_16S, CV_32S, CV_32F
- :return:
- """
运算如下:
重投影矩阵Q中和为左相机主点在图像中的坐标,f为焦距,为两台相机投影中心间的平移(负值),即基线baseline,相当于平移向量T[0], 是右相机主点在图像中的坐标。
其中Z即是深度距离depth:
其中 f 为焦距长度(像素焦距),b为基线长度,d为视差,与为两个相机主点的列坐标。
这里有个地方需要注意,如果获得视差图像是CV_16S类型的,这样的视差图的每个像素值由一个16bit表示,其中低位的4位存储的是视差值得小数部分,所以真实视差值应该是该值除以16。在进行映射后应该乘以16,以获得毫米级真实位置。
- def get_depth(self, disparity, Q, scale=1.0, method=False):
- """
- reprojectImageTo3D(disparity, Q),输入的Q,单位必须是毫米(mm)
- :param disparity: 视差图
- :param Q: 重投影矩阵Q=[[1, 0, 0, -cx]
- [0, 1, 0, -cy]
- [0, 0, 0, f]
- [1, 0, -1/Tx, (cx-cx`)/Tx]]
- 其中f为焦距,Tx相当于平移向量T的第一个参数
- :param scale: 单位变换尺度,默认scale=1.0,单位为毫米
- :return depth:ndarray(np.uint16),depth返回深度图, 即距离
- """
- # 将图片扩展至3d空间中,其z方向的值则为当前的距离
- if method:
- points_3d = cv2.reprojectImageTo3D(disparity, Q) # 单位是毫米(mm)
- x, y, depth = cv2.split(points_3d)
- else:
- # baseline = abs(camera_config["T"][0])
- baseline = 1 / Q[3, 2] # 基线也可以由T[0]计算
- fx = abs(Q[2, 3])
- depth = (fx * baseline) / disparity
- depth = depth * scale
- # depth = np.asarray(depth, dtype=np.uint16)
- depth = np.asarray(depth, dtype=np.float32)
- return depth
demo.py
后,鼠标点击图像任意区域,终端会打印对应距离- (x,y)=(203,273),depth=633.881653mm
- (x,y)=(197,329),depth=640.386047mm
- (x,y)=(222,292),depth=631.549072mm
- (x,y)=(237,270),depth=630.389221mm
- (x,y)=(208,246),depth=652.560669mm
双目测距的精度 说明:
根据上式可以看出,某点像素的深度精度取决于该点处估计的视差d的精度。假设视差d的误差恒定,当测量距离越远,得到的深度精度则越差,因此使用双目相机不适宜测量太远的目标。
如果想要对与较远的目标能够得到较为可靠的深度,一方面需要提高相机的基线距离,但是基线距离越大,左右视图的重叠区域就会变小,内容差异变大,从而提高立体匹配的难度,另一方面可以选择更大焦距的相机,然而焦距越大,相机的视域则越小,导致离相机较近的物体的距离难以估计。
理论上,深度方向的测量误差与测量距离的平方成正比,而X/Y方向的误差与距离成正比;而距离很近时,由于存在死角,会导致难以匹配的问题;想象一下,如果你眼前放置一块物体,那你左眼只能看到物体左侧表面,右眼同理只能看到物体右侧表面,这时由于配准失败,导致视差计算失败;这个问题在基线越长,问题就越严重
恢复三维坐标后,就可以使用python-pcl和Open3D库显示点云图
PCL Python版比较难安装,如果安装不了,那可以采用Open3D勉强凑合使用吧
如下图所示,你可以用鼠标旋转坐标轴,放大点云
2D-RGB | Open3D点云显示 | PCL点云显示 |
| | |
- def show_3dcloud_for_open3d(self, frameL, frameR, points_3d):
- """
- 使用open3d显示点云
- :param frameL:
- :param frameR:
- :param points_3d:
- :return:
- """
- if self.use_open3d:
- x, y, depth = cv2.split(points_3d) # depth = points_3d[:, :, 2]
- self.open3d_viewer.show(color_image=frameL, depth_image=depth)
-
- def show_3dcloud_for_pcl(self, frameL, frameR, points_3d):
- """
- 使用PCL显示点云
- :param frameL:
- :param frameR:
- :param points_3d:
- :return:
- """
- if self.use_pcl:
- self.pcl_viewer.add_3dpoints(points_3d, frameL)
- self.pcl_viewer.show()
【完整的项目代码】双目三维重建系统(双目标定+立体校正+双目测距+点云显示)Python源码
目前已经实现了OpenCV C++版本的双目测距,与Python版本效果几乎一致,
详细请查看鄙人另一篇博客《双目摄像头实现双目测距(C++)》OpenCV C++双目三维重建:双目摄像头实现双目测距
如果你需要Android版本的双目测距, 请查看鄙人另一篇博客《Android OpenCV实现双目三维重建:双目摄像头实现双目测距》
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。