立体匹配是计算机视觉中的一个重要领域,旨在匹配从不同角度拍摄的图像,以创建类似于人类视觉的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#mmCHESSBOARD_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_SIZEobjp是每个角点在物理坐标系中的三维坐标,即棋盘的位置。该变量将在后续的相机标定和立体匹配中使用。
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:定义误差范围、迭代次数以及其他停止迭代的标准,与上述标准相同。
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('ChessboardCorners-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('ChessboardCorners-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],无,无)ret_r,mtx_r,dist_r,rvecs_r,tvecs_r=cv2.calibrateCamera( obj_points,im g_points_right,gray_r .shape[:-1],无,无)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],无,无)ret_r,mtx_r,dist_r,rvecs_r,tvecs_r=cv2.calibrateCamera( obj_points,im g_points_right,gray_r .shape[:-1],None,None) 这里使用OpenCV的cv2.calibrateCamera()函数来校准左右相机。该函数的参数包括:
objectPoints:每张标定图片对应的物理坐标系中的角点坐标。
imagePoints:每个校准图像上检测到的像素坐标。
imageSize:校准图像的大小。
cameraMatrix:用于存储标定结果的内参矩阵。
distCoeffs:用于存储校准结果的失真系数。
rvecs:每个标定图片的外参矩阵中的旋转向量。
tvecs:每个标定图片的外参矩阵中的平移向量。
该函数的返回值包括:
ret:标定是否成功的标志。
cameraMatrix:用于存储标定结果的内参矩阵。
distCoeffs:用于存储校准结果的失真系数。
rvecs:每个标定图片的外参矩阵中的旋转向量。
tvecs:每个标定图片的外参矩阵中的平移向量。
然后校准双目相机:
标志=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_un Distor=cv2.un Distor(img_left,M1,D1)img_Right_unTort=cv2.unTortor(img_Right,M2,D2) 上面代码中,M1、M2、d1、d2是双目相机标定得到的参数。去畸变后的图像img_left_un Distor和img_right_unTort可以用于后续操作。
然后,进行极线校正以实现左右图像之间的几何一致性:
R1,R2,P1,P2,Q,roi1,roi2=cv2.stereoRectify(M1,d1,M2,d2,(宽度,高度),R,T,alpha=1)map1x,map1y=cv2.initUn DistortRectifyMap(M1, d1,R1,P1,(宽度,高度),cv2.CV_32FC1)map2x,map2y=cv2.initUn DistorifyMap(M2,d2,R2,P2,(宽度,高度),cv2.CV_32FC1)img_left_rectified=cv2.remap(img_left_un Distify, map1x,map1y,cv2.INTER_LINEAR)img_right_rectified=cv2.remap(img_right_un Distor,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,P 1=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_leftrect_ified,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()