一、双目立体匹配算法
在opencv中用的比较多的双目立体匹配算法有两种:BM和SGBM。SGBM是BM立体匹配算法的优化版,属于半全局匹配,相对于BM花的时间要更多,但效果优于BM。本文使用的是SGBM半全局匹配方式。 步骤: 1.打开相机,获取到左目和右目的图像; 2.矫正畸变; 3.图像灰度化; 4.立体匹配,输出结果。
代码步骤
导入所需的第三方库
import cv2
import numpy as np
import camera_config
矫正畸变
left_remap = cv2.remap(imgLeft, camera_config.left_map1, camera_config.left_map2, cv2.INTER_LINEAR)
right_remap = cv2.remap(imgRight, camera_config.right_map1, camera_config.right_map2, cv2.INTER_LINEAR)
灰度化
imgL_gray = cv2.cvtColor(left_remap, cv2.COLOR_BGR2GRAY)
imgR_gray = cv2.cvtColor(right_remap, cv2.COLOR_BGR2GRAY)
立体匹配
blockSize = 5
img_channels = 2
num_disp = 16 * 8
param = {
'preFilterCap': 63,
"minDisparity" : 0,
"numDisparities" : num_disp,
"blockSize" : blockSize,
"uniquenessRatio" : 10,
"speckleWindowSize" : 0,
"speckleRange" : 1,
"disp12MaxDiff" : 2,
"P1" : 8 * img_channels * blockSize** 2,
"P2" : 32 * img_channels * blockSize** 2,
}
left_matcher = cv2.StereoSGBM_create(**param)
left_disp = left_matcher.compute(imgL_gray, imgR_gray)
disp = cv2.normalize(dispL, dispL, alpha=0, beta=255, norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_8U)
效果
二、wls滤波
从上面的结果来看,深度图存在很多“黑色区域”,“黑色区域”是没有正确匹配的地方,也就是说这部分是没有深度信息的,这种情况就是“不够稠密”。 在opencv的扩展包opencv-contrib 里提供了一种WLS视差滤波的办法,可以使得重建更加稠密。 改进部分为立体匹配部分,在经过立体匹配后再做一步处理:
left_matcher = cv2.StereoSGBM_create(**param)
right_matcher = cv2.ximgproc.createRightMatcher(left_matcher)
left_disp = left_matcher.compute(imgL_gray, imgR_gray)
right_disp = right_matcher.compute(imgR_gray, imgL_gray)
wls_filter = cv2.ximgproc.createDisparityWLSFilter(left_matcher)
wls_filter.setLambda(8000.)
wls_filter.setSigmaColor(1.3)
wls_filter.setLRCthresh(24)
wls_filter.setDepthDiscontinuityRadius(3)
filtered_disp = wls_filter.filter(left_disp, imgL_gray, disparity_map_right=right_disp)
disp = cv2.normalize(filtered_disp, filtered_disp, alpha=0, beta=255, norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_8U)
可以看到滤波之后的深度图变得相当稠密了。 但这种办法有利有弊:好处就是重建变得稠密,弊端就是这种办法会抹除掉一些细节深度,使得一块地方由原来的不同深度变成同一高度。 举个例子,上图中的杯子在实际观察中他应该是圆柱形,但滤波之后整个杯身将变成同一高度,即原来的高度差被抹除了。因此该方法请根据具体情况决定是否使用。
三、open3d点云重建
open3d提供了RGBD重建的办法,具体实现如下: 首先要准备一个相机内参文件(这样做比较省事),命名为camera_intrinsic.json , 根据左目相机内参矩阵:
以列为顺序写入:
{
"width": 960,
"height": 960,
"intrinsic_matrix": [
fx,
0,
0,
0,
fy,
0,
cx,
cy,
1
]
}
保存,作为相机内参文件。再在主程序中继续接入以下代码:
intrinsic = o3d.io.read_pinhole_camera_intrinsic("camera_intrinsic.json")
color_image = o3d.geometry.Image(left_remap)
depth_image = o3d.geometry.Image(disp)
rgbd_image = o3d.geometry.RGBDImage.create_from_color_and_depth(color_image, depth_image, depth_trunc=4.0, convert_rgb_to_intensity=False)
temp = o3d.geometry.PointCloud.create_from_rgbd_image(rgbd_image, intrinsic)
pcd.points = temp.points
pcd.colors = temp.colors
o3d.visualization.draw_geometries_with_editing([pcd], window_name="3D", width=1280, height=720)
四、OpenCV的点云重建
除此之外也可以用opencv自带的办法重建,这个重建速度要比open3d快很多。 处理时需要剪掉一些不合理的点云数据。
def hw3ToN3(points):
height, width = points.shape[0:2]
points_1 = points[:, :, 0].reshape(height * width, 1)
points_2 = points[:, :, 1].reshape(height * width, 1)
points_3 = points[:, :, 2].reshape(height * width, 1)
points_ = np.hstack((points_1, points_2, points_3))
return points_
def DepthColor2Cloud(points_3d, colors):
rows, cols = points_3d.shape[0:2]
size = rows * cols
points_ = hw3ToN3(points_3d).astype(np.int16)
colors_ = hw3ToN3(colors).astype(np.int64)
blue = colors_[:, 0].reshape(size, 1)
green = colors_[:, 1].reshape(size, 1)
red = colors_[:, 2].reshape(size, 1)
rgb = blue + green + red
pointcloud = np.hstack((points_, red/255., green/255., blue/255.)).astype(np.float64)
X = pointcloud[:, 0]
Y = pointcloud[:, 1]
Z = pointcloud[:, 2]
remove_idx1 = np.where(Z <= 0)
remove_idx2 = np.where(Z > 1000)
remove_idx3 = np.where(X > 1000)
remove_idx4 = np.where(X < -1000)
remove_idx5 = np.where(Y > 1000)
remove_idx6 = np.where(Y < -1000)
remove_idx = np.hstack((remove_idx1[0], remove_idx2[0], remove_idx3[0], remove_idx4[0], remove_idx5[0], remove_idx6[0]))
pointcloud_1 = np.delete(pointcloud, remove_idx, 0)
return pointcloud_1
上面的函数可以单独用一个脚本文件保存再导入。 主脚本文件加入以下代码:
threeD = cv2.reprojectImageTo3D(disp, camera_config.Q)
pointcloud = DepthColor2Cloud(threeD, left_remap)
pcd = o3d.geometry.PointCloud()
pcd.points = o3d.utility.Vector3dVector(pointcloud[:,:3])
pcd.colors = o3d.utility.Vector3dVector(pointcloud[:,3:])
o3d.visualization.draw_geometries_with_editing([pcd], window_name="3D", width=1280, height=720)
|