从双目标定到立体匹配:Python实践指南

立体匹配是计算机视觉中的一个重要领域,旨在将从不同角度拍摄的图像匹配起来,以创建类似人类视觉的3d效果。实现立体匹配的过程需要涉及许多步骤,包括双目标定、立体校正、视差计算等。在这篇文章中,将介绍如何使用python实现立体匹配的基本步骤和技巧。
下面的代码实现了从相机标定到立体匹配的完整流程,下面将分别介绍各个函数的参数和输出。
标定 首先,该程序需要用到以下库:
numpycv2 (opencv)os 在程序开头,需要定义一些变量来存储标定图片的路径、棋盘格参数、角点坐标等等。具体介绍如下:
path_left = ./data/left/path_right = ./data/right/ path_left和path_right是左右相机标定图片文件夹的路径。
chessboard_size = (8, 11)chessboard_square_size = 15  # mm chessboard_size是棋盘格内部角点的行列数,chessboard_square_size是棋盘格内部每个小正方形的大小(单位为毫米)。
objp = np.zeros((chessboard_size[0] * chessboard_size[1], 3), np.float32)objp[:, :2] = np.mgrid[0:chessboard_size[0], 0:chessboard_size[1]].t.reshape(-1, 2) * chessboard_square_size objp是物理坐标系下每个角点的三维坐标,即棋盘格的位置。该变量在后续的相机标定以及立体匹配中都会被用到。
criteria = (cv2.term_criteria_eps + cv2.term_criteria_max_iter, 30, 0.001) criteria是角点检测的终止准则,一般都使用这个默认值。
img_list_left = sorted(os.listdir(path_left))img_list_right = sorted(os.listdir(path_right)) img_list_left和img_list_right分别是左、右图像的文件名列表,使用os.listdir()函数获取。
obj_points = []img_points_left = []img_points_right = [] obj_points、img_points_left和img_points_right分别是存储每个标定图片对应的物理坐标系下的角点坐标、左相机的像素坐标和右相机的像素坐标。这些变量同样在后续的相机标定和立体匹配中用到。
接下来,程序读取标定图片并检测角点。对于每幅图片,程序执行以下操作:
img_l = cv2.imread(path_left + img_list_left[i])img_r = cv2.imread(path_right + img_list_right[i])gray_l = cv2.cvtcolor(img_l, cv2.color_bgr2gray)gray_r = cv2.cvtcolor(img_r, cv2.color_bgr2gray) 首先读取左右图像,然后将它们转换为灰度图像。
ret_l, corners_l = cv2.findchessboardcorners(gray_l, chessboard_size, none)ret_r, corners_r = cv2.findchessboardcorners(gray_r, chessboard_size, none) 通过opencv的cv2.findchessboardcorners()函数检测左右图像上的棋盘格角点。这个函数的参数包括:
image:需要检测角点的灰度图像。patternsize:内部角点的行列数,即(chessboard_size[1]-1, chessboard_size[0]-1)。corners:用于存储检测到的角点坐标的数组。如果检测失败,则该参数为空(none)。flags:检测时使用的可选标志。这个函数的返回值包括:
ret:一个布尔值,用于指示检测是否成功。如果检测成功,则为true,否则为false。corners:用于存储检测到的角点坐标的数组。接下来是亚像素级别的角点检测。
cv2.cornersubpix(gray_l, corners_l, (11, 11), (-1, -1), criteria)cv2.cornersubpix(gray_r, corners_r, (11, 11), (-1, -1), criteria) 这里使用了opencv的cv2.cornersubpix()函数来进行亚像素级别的角点检测。这个函数的参数包括:
image:输入的灰度图像。
corners:用于存储检测到的角点坐标的数组。
winsize:每次迭代中搜索窗口的大小,即每个像素周围的搜索范围大小。通常为11x11。
zerozone:死区大小,表示怎样的对称性(如果有的话)不考虑。通常为(-1,-1)。
criteria:定义迭代停止的误差范围、迭代次数等标准,和以上的criteria一样。
img_points_left.append(corners_l)img_points_right.append(corners_r) 如果检测到了左右图像上的角点,则将这些角点的坐标存储到img_points_left和img_points_right中。
cv2.drawchessboardcorners(img_l, chessboard_size, corners_l, ret_l)cv2.imshow(chessboard corners - left, cv2.resize(img_l,(img_l.shape[1]//2,img_l.shape[0]//2)))cv2.waitkey(50)cv2.drawchessboardcorners(img_r, chessboard_size, corners_r, ret_r)cv2.imshow(chessboard corners - right, cv2.resize(img_r,(img_r.shape[1]//2,img_r.shape[0]//2)))cv2.waitkey(50) 在图片上标出检测到的角点,并在窗口中显示。这里使用了cv2.drawchessboardcorners()函数,该函数的参数包括:
img:需要标定角点的图像。patternsize:内部角点的行列数,即(chessboard_size[1]-1, chessboard_size[0]-1)。
corners:存储检测到的角点坐标的数组。patternfound:检测到角点的标记,即ret。
程序接下来对双目摄像机进行标定。
ret_l, mtx_l, dist_l, rvecs_l, tvecs_l = cv2.calibratecamera(obj_points, img_points_left, gray_l.shape[::-1],none,none)ret_r, mtx_r, dist_r, rvecs_r, tvecs_r = cv2.calibratecamera(obj_points, img_points_right, gray_r.shape[::-1],none,none)flags = 0flags |= cv2.calib_fix_intrinsiccriteria = (cv2.term_criteria_eps + cv2.term_criteria_max_iter, 30, 0.001)ret, m1, d1, m2, d2, r, t, e, f = cv2.stereocalibrate(    obj_points, img_points_left, img_points_right,    mtx_l, dist_l, mtx_r, dist_r,    gray_l.shape[::-1], criteria=criteria, flags=flags) 这段代码首先对左右相机进行单独标定:
ret_l, mtx_l, dist_l, rvecs_l, tvecs_l = cv2.calibratecamera(obj_points, img_points_left, gray_l.shape[::-1],none,none)ret_r, mtx_r, dist_r, rvecs_r, tvecs_r = cv2.calibratecamera(obj_points, img_points_right, gray_r.shape[::-1],none,none) 这里使用了opencv的cv2.calibratecamera()函数对左右相机进行标定。这个函数的参数包括:
objectpoints:每幅标定图片对应的物理坐标系下的角点坐标。
imagepoints:每幅标定图片上检测到的像素坐标。
imagesize:标定图片的尺寸。
cameramatrix:用于存储标定结果的内参数矩阵。
distcoeffs:用于存储标定结果的畸变系数。
rvecs:每幅标定图片的外参数矩阵中的旋转向量。
tvecs:每幅标定图片的外参数矩阵中的平移向量。
这个函数的返回值包括:
ret:一个标志位,表示标定是否成功。
cameramatrix:用于存储标定结果的内参数矩阵。
distcoeffs:用于存储标定结果的畸变系数。
rvecs:每幅标定图片的外参数矩阵中的旋转向量。
tvecs:每幅标定图片的外参数矩阵中的平移向量。
然后对双目摄像机进行标定:
flags = 0flags |= cv2.calib_fix_intrinsiccriteria = (cv2.term_criteria_eps + cv2.term_criteria_max_iter, 30, 0.001)ret, m1, d1, m2, d2, r, t, e, f = cv2.stereocalibrate(    obj_points, img_points_left, img_points_right,    mtx_l, dist_l, mtx_r, dist_r,    gray_l.shape[::-1], criteria=criteria, flags=flags) 这里使用了opencv的cv2.stereocalibrate()函数进行双目摄像机标定。这个函数的参数包括:
objectpoints:每幅标定图片对应的物理坐标系下的角点坐标。
imagepoints1:每幅标定图片的左相机上检测到的像素坐标。
imagepoints2:每幅标定图片的右相机上检测到的像素坐标。
cameramatrix1:左相机的内参数矩阵。
distcoeffs1:左相机的畸变系数。
cameramatrix2:右相机的内参数矩阵。
distcoeffs2:右相机的畸变系数。
imagesize:标定图片的尺寸。
criteria:定义迭代停止的误差范围、迭代次数等标准。
flags:标定的可选标志。
这个函数的返回值包括:
ret:一个标志,表示标定是否成功。
cameramatrix1:左相机的内参数矩阵。
distcoeffs1:左相机的畸变系数。
cameramatrix2:右相机的内参数矩阵。
distcoeffs2:右相机的畸变系数。
r:旋转矩阵。
t:平移向量。
e:本质矩阵。
f:基础矩阵。
立体匹配 通过图像标定得到的参数进行立体匹配的整个流程,如下:
首先,我们需要读取左右两张图像:
img_left = cv2.imread(./left.png)img_right = cv2.imread(./right.png) 其中,./left.png 和 ./right.png 是放置左右图像的路径。这两幅图像是未经校正和矫正的图像。
接下来,通过图像标定得到相机的参数,根据得到的参数,将图像进行去畸变:
img_left_undistort = cv2.undistort(img_left, m1, d1)img_right_undistort = cv2.undistort(img_right, m2, d2) 在上述代码中,m1、m2、d1、d2 是从双目相机标定中获得的参数。去畸变后的图像 img_left_undistort 和 img_right_undistort 可供之后的操作使用。
然后,进行极线校正,以实现左右图像在几何上的一致性:
r1, r2, p1, p2, q, roi1, roi2 = cv2.stereorectify(m1, d1, m2, d2, (width, height), r, t, alpha=1)map1x, map1y = cv2.initundistortrectifymap(m1, d1, r1, p1, (width, height), cv2.cv_32fc1)map2x, map2y = cv2.initundistortrectifymap(m2, d2, r2, p2, (width, height), cv2.cv_32fc1)img_left_rectified = cv2.remap(img_left_undistort, map1x, map1y, cv2.inter_linear)img_right_rectified = cv2.remap(img_right_undistort, map2x, map2y, cv2.inter_linear) 其中,r、t 是双目相机标定得到的旋转和平移矩阵, (width, height)是左右图像的尺寸。r1、r2 是左右图像的旋转矩阵,p1、p2 是左右图像的投影矩阵,q 是视差转换矩阵,roi1、roi2 是矫正后的图像中可以使用的区域。
然后,将左右图像拼接在一起以方便观察:
img_stereo = cv2.hconcat([img_left_rectified, img_right_rectified]) 接下来,需要计算视差图:
mindisparity = 0numdisparities = 256blocksize = 9p1 = 1200p2 = 4800disp12maxdiff = 10prefiltercap = 63uniquenessratio = 5specklewindowsize = 100specklerange = 32sgbm = cv2.stereosgbm_create(mindisparity=mindisparity, numdisparities=numdisparities, blocksize=blocksize,                             p1=p1, p2=p2, disp12maxdiff=disp12maxdiff, prefiltercap=prefiltercap,                             uniquenessratio=uniquenessratio, specklewindowsize=specklewindowsize,                             specklerange=specklerange, mode=cv2.stereo_sgbm_mode_sgbm_3way)disparity = sgbm.compute(img_left_rectified,img_right_rectified) 上面的代码块定义了使用的视差算法的参数,并使用了 sgbm(semi global block matching)算法计算了原始的视差图。注意,由于使用的是16位的 sgbm 输出,因此需要将它除以16。接下来,可以对视差图进行 wls 滤波,减少视差空洞:
# 定义 wls 滤波参数lambda_val = 4000sigma_val = 1.5# 运行 wls 滤波wls_filter = cv2.ximgproc.createdisparitywlsfiltergeneric(false)wls_filter.setlambda(lambda_val)wls_filter.setsigmacolor(sigma_val)filtered_disp = wls_filter.filter(disparity, img_left_rectified,  none, img_right_rectified)filtered_disp_nor = cv2.normalize(filtered_disp, filtered_disp, alpha=0, beta=255, norm_type=cv2.norm_minmax, dtype=cv2.cv_8u) 上述代码块中,wls 滤波为视差图降噪,并进行平滑处理。这里使用了 cv2.ximgproc.createdisparitywlsfiltergeneric 函数,创建一个生成 wls 滤波器的对象 wls_filter,然后设置了滤波参数 lambda_val 和 sigma_val。filtered_disp 是经过滤波后的视差图。filtered_disp_nor 是经过归一化处理后的、用于显示的视差图。
最后,可以在窗口中显示原始视差图、预处理后的 wls 滤波器的视差图:
cv2.imshow(disparity, cv2.resize(disparity_nor,(disparity_nor.shape[1]//2,disparity_nor.shape[0]//2)))cv2.imshow(filtered_disparity, cv2.resize(filtered_disp_nor,(filtered_disp_nor.shape[1]//2,filtered_disp_nor.shape[0]//2)))cv2.waitkey()cv2.destroyallwindows()

一文知道PLC是如何替代传统继电接触控制系统的
采用红外光谱吸收技术实现一氧化碳气体检测系统的设计
直流充电桩测试仪的工作原理是怎样的
导线覆冰形成的原因及危害、覆冰在线监测的必要性!
SITL代码分布结构
从双目标定到立体匹配:Python实践指南
2019年各种人工智能技术将加速落地 人工智能应用范围将不断扩大
长安汽车与华为签署合作备忘录,共建“Newcool”新合资公司
基于5G智能网关的智慧塔吊监测方案
电缆老化的原因_老化的电缆怎么处理
拥有1000多个传感器的恰纳卡莱大桥,值得我们学习什么?
射频技术——下一代WLAN的关键
苹果因侵犯两项与蓝牙技术有关的专利被起诉 此前三星也被起诉并被索赔1110万美元
阿玛尼EA Connected智能腕表评测:轻松智能更时尚!
1~7号电池怎么区分?
SMT贴片加工再流焊注意事项与紧急情况
C语言宏定义小技巧
机器人企业需要提前为AR时代的广告营销做准备
通用型PLC的硬件基本结构
日本将与美国合作预计2025财年启动本土2nm半导体制造基地