銘萬做的網(wǎng)站國內(nèi)設(shè)計(jì)公司前十名
雙目攝像頭標(biāo)定及矯正
- 棋盤格標(biāo)定板
- 標(biāo)定
- 矯正
棋盤格標(biāo)定板
本文使用棋盤格標(biāo)定板,可以到這篇博客中下載:https://blog.csdn.net/qq_39330520/article/details/107864568
標(biāo)定
要進(jìn)行標(biāo)定首先需要雙目拍的棋盤格圖片,20張左右,由于本文的雙目攝像頭嵌入在開發(fā)板底板中,并且使用的是ros進(jìn)行開發(fā),所以對(duì)于大部分人拍照這里是沒有參考價(jià)值的,對(duì)于也是使用ros開發(fā)的小伙伴,需要寫一個(gè)節(jié)點(diǎn)發(fā)布雙目攝像頭的圖像數(shù)據(jù),然后再寫一個(gè)節(jié)點(diǎn)訂閱雙目攝像頭數(shù)據(jù)進(jìn)行拍照保存。本文重點(diǎn)也不在拍照,對(duì)于其他小伙伴可以直接搜索一些適用的拍照方法,只要能獲得到圖片即可。
左攝像頭圖片如下:
右攝像頭圖片如下:
由于攝像頭底層代碼有問題,所以圖像很暗,但不影響標(biāo)定。
標(biāo)定代碼如下:
import cv2
import os
import numpy as np
import itertools
import yaml# 定義文件夾路徑
left_folder = "C:/new_pycharm_project/yolov10-main/shuangmu_left_pic"
right_folder = "C:/new_pycharm_project/yolov10-main/shuangmu_right_pic"# 獲取圖像文件列表并排序
left_images = sorted(os.listdir(left_folder))
right_images = sorted(os.listdir(right_folder))# 確保左右相機(jī)圖像數(shù)量一致
assert len(left_images) == len(right_images), "左右相機(jī)圖像數(shù)量不一致"# 加載兩個(gè)攝像頭圖片文件夾并將里面的彩圖轉(zhuǎn)換為灰度圖
def load_images(folder, images):img_list = []for img_name in images:img_path = os.path.join(folder, img_name)frame = cv2.imread(img_path)if frame is not None:gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)img_list.append((frame, gray))else:print(f"無法讀取圖像: {img_path}")return img_list# 檢測(cè)棋盤格角點(diǎn)
def get_corners(imgs, pattern_size):corners = []for frame, gray in imgs:ret, c = cv2.findChessboardCorners(gray, pattern_size) #ret 表示是否成功找到棋盤格角點(diǎn),c 是一個(gè)數(shù)組,包含了檢測(cè)到的角點(diǎn)的坐標(biāo)if not ret:print("未能檢測(cè)到棋盤格角點(diǎn)")continuec = cv2.cornerSubPix(gray, c, (5, 5), (-1, -1),(cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)) #cv2.cornerSubPix 函數(shù)用于提高棋盤格角點(diǎn)的精確度,對(duì)初始檢測(cè)到的角點(diǎn)坐標(biāo) c 進(jìn)行優(yōu)化corners.append(c) #將優(yōu)化后的角點(diǎn)坐標(biāo) c 添加到 corners 列表中# 繪制角點(diǎn)并顯示vis = frame.copy()cv2.drawChessboardCorners(vis, pattern_size, c, ret)new_size = (1280, 800)resized_img = cv2.resize(vis, new_size)cv2.imshow('Corners', resized_img)cv2.waitKey(150)return corners# 相機(jī)標(biāo)定
def calibrate_camera(object_points, corners, imgsize):cm_input = np.eye(3, dtype=np.float32)ret = cv2.calibrateCamera(object_points, corners, imgsize, cm_input, None)return retdef save_calibration_to_yaml(file_path, cameraMatrix_l, distCoeffs_l, cameraMatrix_r, distCoeffs_r, R, T, E, F):data = {'camera_matrix_left': {'rows': 3,'cols': 3,'dt': 'd','data': cameraMatrix_l.flatten().tolist()},'dist_coeff_left': {'rows': 1,'cols': 5,'dt': 'd','data': distCoeffs_l.flatten().tolist()},'camera_matrix_right': {'rows': 3,'cols': 3,'dt': 'd','data': cameraMatrix_r.flatten().tolist()},'dist_coeff_right': {'rows': 1,'cols': 5,'dt': 'd','data': distCoeffs_r.flatten().tolist()},'R': {'rows': 3,'cols': 3,'dt': 'd','data': R.flatten().tolist()},'T': {'rows': 3,'cols': 1,'dt': 'd','data': T.flatten().tolist()},'E': {'rows': 3,'cols': 3,'dt': 'd','data': E.flatten().tolist()},'F': {'rows': 3,'cols': 3,'dt': 'd','data': F.flatten().tolist()}}with open(file_path, 'w') as file:yaml.dump(data, file, default_flow_style=False)print(f"Calibration parameters saved to {file_path}")img_left = load_images(left_folder, left_images) #img_left是個(gè)列表,存放左攝像頭所有的灰度圖片。
img_right = load_images(right_folder, right_images)
pattern_size = (8, 5)
corners_left = get_corners(img_left, pattern_size) #corners_left的長度表示檢測(cè)到棋盤格角點(diǎn)的圖像數(shù)量。corners_left[i] 和 corners_right[i] 中存儲(chǔ)了第 i 張圖像檢測(cè)到的棋盤格角點(diǎn)的二維坐標(biāo)。
corners_right = get_corners(img_right, pattern_size)
cv2.destroyAllWindows()# 斷言,確保所有圖像都檢測(cè)到角點(diǎn)
assert len(corners_left) == len(img_left), "有圖像未檢測(cè)到左相機(jī)的角點(diǎn)"
assert len(corners_right) == len(img_right), "有圖像未檢測(cè)到右相機(jī)的角點(diǎn)"# 準(zhǔn)備標(biāo)定所需數(shù)據(jù)
points = np.zeros((8 * 5, 3), dtype=np.float32) #創(chuàng)建40 行 3 列的零矩陣,用于存儲(chǔ)棋盤格的三維坐標(biāo)點(diǎn)。棋盤格的大小是 8 行 5 列,40 個(gè)角點(diǎn)。數(shù)據(jù)類型為 np.float32,這是一張圖的,因?yàn)橐粋€(gè)角點(diǎn)對(duì)應(yīng)一個(gè)三維坐標(biāo)
points[:, :2] = np.mgrid[0:8, 0:5].T.reshape(-1, 2) * 21 #給這些點(diǎn)賦予實(shí)際的物理坐標(biāo),* 21 是因?yàn)槊總€(gè)棋盤格的大小為 21mmobject_points = [points] * len(corners_left) #包含了所有圖像中棋盤格的三維物理坐標(biāo)點(diǎn) points。這里假設(shè)所有圖像中棋盤格的物理坐標(biāo)是相同的,因此用 points 復(fù)制 len(corners_left) 次。
imgsize = img_left[0][1].shape[::-1] #img_left[0] 是左相機(jī)圖像列表中的第一張圖像。img_left[0][1] 是該圖像的灰度圖像。shape[::-1] 取灰度圖像的寬度和高度,并反轉(zhuǎn)順序,以符合 calibrateCamera 函數(shù)的要求。print('開始左相機(jī)標(biāo)定')
ret_l = calibrate_camera(object_points, corners_left, imgsize) #object_points表示標(biāo)定板上檢測(cè)到的棋盤格角點(diǎn)的三維坐標(biāo);corners_left[i]表示棋盤格角點(diǎn)在圖像中的二維坐標(biāo);imgsize表示圖像大小
retval_l, cameraMatrix_l, distCoeffs_l, rvecs_l, tvecs_l = ret_l[:5] #返回值里就包含了標(biāo)定的參數(shù)print('開始右相機(jī)標(biāo)定')
ret_r = calibrate_camera(object_points, corners_right, imgsize)
retval_r, cameraMatrix_r, distCoeffs_r, rvecs_r, tvecs_r = ret_r[:5]# 立體標(biāo)定,得到左右相機(jī)的外參:旋轉(zhuǎn)矩陣、平移矩陣、本質(zhì)矩陣、基本矩陣
print('開始立體標(biāo)定')
criteria_stereo = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 1e-5)
ret_stereo = cv2.stereoCalibrate(object_points, corners_left, corners_right,cameraMatrix_l, distCoeffs_l,cameraMatrix_r, distCoeffs_r,imgsize, criteria=criteria_stereo,flags=cv2.CALIB_FIX_INTRINSIC)
ret, _, _, _, _, R, T, E, F = ret_stereo# 輸出結(jié)果
print("左相機(jī)內(nèi)參:\n", cameraMatrix_l)
print("左相機(jī)畸變系數(shù):\n", distCoeffs_l)
print("右相機(jī)內(nèi)參:\n", cameraMatrix_r)
print("右相機(jī)畸變系數(shù):\n", distCoeffs_r)
print("旋轉(zhuǎn)矩陣 R:\n", R)
print("平移向量 T:\n", T)
print("本質(zhì)矩陣 E:\n", E)
print("基本矩陣 F:\n", F)
print("標(biāo)定完成")# 保存標(biāo)定結(jié)果
save_calibration_to_yaml('calibration_parameters.yaml', cameraMatrix_l, distCoeffs_l, cameraMatrix_r, distCoeffs_r, R, T, E, F)# 計(jì)算重投影誤差
def compute_reprojection_errors(objpoints, imgpoints, rvecs, tvecs, mtx, dist):total_error = 0total_points = 0for i in range(len(objpoints)):imgpoints2, _ = cv2.projectPoints(objpoints[i], rvecs[i], tvecs[i], mtx, dist)error = cv2.norm(imgpoints[i], imgpoints2, cv2.NORM_L2) / len(imgpoints2)total_error += errortotal_points += len(imgpoints2)mean_error = total_error / total_pointsreturn mean_error# 計(jì)算并打印左相機(jī)和右相機(jī)的重投影誤差
print("左相機(jī)重投影誤差: ", compute_reprojection_errors(object_points, corners_left, rvecs_l, tvecs_l, cameraMatrix_l, distCoeffs_l))
print("右相機(jī)重投影誤差: ", compute_reprojection_errors(object_points, corners_right, rvecs_r, tvecs_r, cameraMatrix_r, distCoeffs_r))# 立體矯正和顯示
def stereo_rectify_and_display(img_l, img_r, cameraMatrix_l, distCoeffs_l, cameraMatrix_r, distCoeffs_r, R, T):img_size = img_l.shape[:2][::-1]# 立體校正R1, R2, P1, P2, Q, _, _ = cv2.stereoRectify(cameraMatrix_l, distCoeffs_l, cameraMatrix_r, distCoeffs_r, img_size, R, T)map1x, map1y = cv2.initUndistortRectifyMap(cameraMatrix_l, distCoeffs_l, R1, P1, img_size, cv2.CV_32FC1)map2x, map2y = cv2.initUndistortRectifyMap(cameraMatrix_r, distCoeffs_r, R2, P2, img_size, cv2.CV_32FC1)# 圖像矯正rectified_img_l = cv2.remap(img_l, map1x, map1y, cv2.INTER_LINEAR)rectified_img_r = cv2.remap(img_r, map2x, map2y, cv2.INTER_LINEAR)# 顯示矯正后的圖像combined_img = np.hstack((rectified_img_l, rectified_img_r))cv2.imshow('Rectified Images', combined_img)cv2.imwrite("stereo_jiaozheng.png",combined_img)cv2.waitKey(0)cv2.destroyAllWindows()# 加載并矯正示例圖像
example_idx = 0
img_l = img_left[example_idx][0]
img_r = img_right[example_idx][0]
stereo_rectify_and_display(img_l, img_r, cameraMatrix_l, distCoeffs_l, cameraMatrix_r, distCoeffs_r, R, T)
標(biāo)定完成后會(huì)顯示一張矯正后的圖像。代碼重要的地方都給出了注釋,主要流程就是分別對(duì)左右相機(jī)進(jìn)行標(biāo)定,然后對(duì)兩個(gè)相機(jī)進(jìn)行聯(lián)合標(biāo)定(立體標(biāo)定),最后得到的參數(shù)會(huì)保存到y(tǒng)aml文件中:
---
camera_matrix_left:rows: 3cols: 3dt: ddata:- 531.7200210313852- 0- 642.0170539101581- 0- 533.6471323984354- 420.4033045027399- 0- 0- 1
dist_coeff_left:rows: 1cols: 5dt: ddata:- -0.1670007968198256- 0.04560028196221921- 0.0011938487550718078- -0.000866537907860316- -0.00805042100882671
camera_matrix_right:rows: 3cols: 3dt: ddata:- 525.9058345430292- 0- 628.7761214904813- 0- 528.2078922687268- 381.8575789135264- 0- 0- 1
dist_coeff_right:rows: 1cols: 5dt: ddata:- -0.15320688387351564- 0.03439886104586617- -0.0003732170677440928- -0.0024909528446780153- -0.005138400994014348
R:rows: 3cols: 3dt: ddata:- 0.9999847004116569- -0.00041406631566505544- 0.005516112008926496- 0.0003183979929468572- 0.9998497209492369- 0.017333036100216304- -0.005522460079247196- -0.017331014592906722- 0.9998345554979852
T:rows: 3cols: 1dt: ddata:- -55.849260376265015- 2.1715925432988743- 0.46949841441903933
E:rows: 3cols: 3dt: ddata:- -0.012142020481601675- -0.5070637607007459- 2.1630954322858496- 0.1610659204031652- -0.9681187500627653- 55.84261022903612- -2.189341611238282- -55.83996821910631- -0.9800159939787676
F:rows: 3cols: 3dt: ddata:- -2.4239149875305048e-8- -0.0000010085973649868748- 0.0027356495714066175- 3.2013501988129346e-7- -0.0000019172863951399893- 0.05961765359743852- -0.002405523166325036- -0.057046539240958545- 1
分別是左相機(jī)的內(nèi)參矩陣、畸變系數(shù),右相機(jī)的內(nèi)參矩陣和畸變系數(shù),兩個(gè)相機(jī)之間的旋轉(zhuǎn)矩陣、平移矩陣、本質(zhì)矩陣、基本矩陣。
矯正
import cv2
import yaml
import numpy as np# 定義函數(shù)讀取標(biāo)定數(shù)據(jù)
def read_calibration_data(calibration_file):with open(calibration_file, 'r') as f:calib_data = yaml.safe_load(f)cameraMatrix_l = np.array(calib_data['camera_matrix_left']['data']).reshape(3, 3)distCoeffs_l = np.array(calib_data['dist_coeff_left']['data'])cameraMatrix_r = np.array(calib_data['camera_matrix_right']['data']).reshape(3, 3)distCoeffs_r = np.array(calib_data['dist_coeff_right']['data'])R = np.array(calib_data['R']['data']).reshape(3, 3)T = np.array(calib_data['T']['data']).reshape(3, 1)return cameraMatrix_l, distCoeffs_l, cameraMatrix_r, distCoeffs_r, R, T# 定義函數(shù)對(duì)圖像進(jìn)行矯正
def rectify_images(left_image_path, right_image_path, calibration_file):# 讀取標(biāo)定數(shù)據(jù)cameraMatrix_l, distCoeffs_l, cameraMatrix_r, distCoeffs_r, R, T = read_calibration_data(calibration_file)# 讀取左右圖像img_left = cv2.imread(left_image_path)img_right = cv2.imread(right_image_path)# 獲取圖像尺寸(假設(shè)左右圖像尺寸相同)img_size = img_left.shape[:2][::-1]# 立體校正R1, R2, P1, P2, Q, roi1, roi2 = cv2.stereoRectify(cameraMatrix_l, distCoeffs_l,cameraMatrix_r, distCoeffs_r,img_size, R, T)# 計(jì)算映射參數(shù)map1_l, map2_l = cv2.initUndistortRectifyMap(cameraMatrix_l, distCoeffs_l, R1, P1, img_size, cv2.CV_32FC1)map1_r, map2_r = cv2.initUndistortRectifyMap(cameraMatrix_r, distCoeffs_r, R2, P2, img_size, cv2.CV_32FC1)# 應(yīng)用映射并顯示結(jié)果rectified_img_l = cv2.remap(img_left, map1_l, map2_l, cv2.INTER_LINEAR)rectified_img_r = cv2.remap(img_right, map1_r, map2_r, cv2.INTER_LINEAR)# 合并圖像顯示combined_img = np.hstack((rectified_img_l, rectified_img_r))cv2.imshow('Rectified Images', combined_img)cv2.waitKey(0)cv2.destroyAllWindows()# 設(shè)置路徑和文件名
left_image_path = "C:/new_pycharm_project/yolov10-main/shuangmu_left_pic/left_image0.png"
right_image_path = "C:/new_pycharm_project/yolov10-main/shuangmu_right_pic/right_image0.png"
calibration_file = "C:/new_pycharm_project/yolov10-main/calibration_parameters.yaml"# 調(diào)用函數(shù)進(jìn)行圖像矯正
rectify_images(left_image_path, right_image_path, calibration_file)
結(jié)果對(duì)比:
第一張是矯正前的左右相機(jī)圖像,第二張是矯正后的??梢钥吹饺コ嘶?#xff0c;并且兩圖像基本出于同一水平線。