代码记录 -学习标定双目相机
0、使用国内镜像安装OpenCV
- 打开PyCharm的Terminal:
- 在PyCharm中,找到并点击底部的“Terminal”标签页,打开命令行终端。
- 使用pip命令安装OpenCV:
- 在Terminal中,输入以下pip命令来安装OpenCV 4.5.1或更高版本,并使用国内镜像源加速下载:
bash复制代码
-
pip install opencv-python==4.5.4.60 -i https://pypi.tuna.tsinghua.edu.cn/simple或者,如果要安装包含额外功能的opencv-contrib-python包,可以使用:bash复制代码
-
pip install opencv-contrib-python==4.5.1 -i https://pypi.tuna.tsinghua.edu.cn/simple
-
如果要安装更高版本的OpenCV,只需将版本号“4.5.1”替换为所需的版本号即可。
- 在Terminal中,输入以下pip命令来安装OpenCV 4.5.1或更高版本,并使用国内镜像源加速下载:
- 验证安装:
- 安装完成后,可以在PyCharm中编写简单的代码来验证OpenCV是否安装成功。例如,导入cv2库并尝试读取和显示一张图片。
4、卸载指定版本的opencv:
查看版本或查看已安装的opencv库 pip show opencv-python
卸载:pip uninstall opencv-python==<指定版本号>
卸载:pip uninstall opencv-contrib-python==<指定版本号>
1、棋盘格拍照 20份以上
import cv2 import os cap = cv2.VideoCapture(1) # set the video frame width and height cap.set(3,1280) cap.set(4,480) if not os.path.exists("left"): os.makedirs("left") if not os.path.exists("right"): os.makedirs("right") i = 1 try: while True: ret, frame = cap.read() if not ret: print("Failed to capture video") break # split the frame into left and right left_frame = frame[:, :640, :] right_frame = frame[:, 640:, :] cv2.imshow("Left Camera", left_frame) cv2.imshow("Right Camera", right_frame) key = cv2.waitKey(1) & 0xFF if key == ord('q'): break elif key == ord('s'): left_filename = "left" + str(i) + ".png" right_filename = "right" + str(i) + ".png" cv2.imwrite("left/" + left_filename, left_frame) cv2.imwrite("right/" + right_filename, right_frame) print("Image saved! left image:", left_filename) print("Image saved! right image:", right_filename) i += 1 except KeyboardInterrupt: cv2.destroyAllWindows() cap.release()
使用tinkter对话框拍照标定照片
import cv2 import tkinter as tk from PIL import Image, ImageTk import os if not os.path.exists("left"): os.makedirs("left") if not os.path.exists("right"): os.makedirs("right") class App: def __init__(self, root): self.root = root self.root.title("OpenCV Image in Tkinter") # Label to display the image self.label = tk.Label(root) self.label.pack() # 创建一个保存按钮 self.save_button = tk.Button(root, text="保存图像", command=self.save_image) self.save_button.pack() # set the video frame width and height #1080P的摄像头,其像素通常为2073600,对应的分辨率是1920x1080。 # 这里,1920表示水平方向上的像素数量,而1080则表示垂直方向上的像素数量。 # 两者相乘,即1920x1080=2073600,得出了1080P摄像头的总像素数。 # self.frame_width = 1920*2 # self.frame_height = 1080 self.frame_width = 640*2 self.frame_height = 480 # Capture image using OpenCV self.cap = cv2.VideoCapture(1) # 0 for default camera # set the video frame width and height self.cap.set(3, self.frame_width) self.cap.set(4, self.frame_height) self.frame = None self.num_pics = 1 # Update the image every 1000 milliseconds (1 second) self.root.after(1000, self.update_image) def save_image(self): # split the frame into left and right left_frame = self.frame[:, :self.frame_width//2, :] right_frame = self.frame[:, self.frame_width//2:, :] # 按点击按钮顺序分左右相机保存照片,用来相机标定 i =self.num_pics left_filename = "left" + str(i) + ".png" right_filename = "right" + str(i) + ".png" cv2.imwrite("left/" + left_filename, left_frame) cv2.imwrite("right/" + right_filename, right_frame) print("Image saved! left image:", left_filename) print("Image saved! right image:", right_filename) i += 1 self.num_pics = i pass def update_image(self): ret, frame = self.cap.read() if not ret: print("Failed to grab frame") return self.frame = frame # Convert the frame from BGR to RGB frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) # Convert the frame to a PIL Image image = Image.fromarray(frame_rgb) # Convert the PIL Image to a Tkinter Image imgtk = ImageTk.PhotoImage(image=image) # Update the label with the new image self.label.imgtk = imgtk # Keep a reference to prevent garbage collection self.label.configure(image=imgtk) # Call this method again after 1000 milliseconds self.root.after(1000, self.update_image) def __del__(self): # Release the camera when the application is closed if self.cap.isOpened(): self.cap.release() if __name__ == "__main__": root = tk.Tk() app = App(root) root.mainloop()
2、OPENCV标定双目相机
# 文心一言 生成代码
import cv2 import numpy as np import glob # 棋盘格尺寸 (内部交叉点的数量) chessboard_size = (12-1, 9-1) # 使用的12行*9列方块组成的棋盘格,填错时程序报错:找不到角点 square_size = 15.0 # 棋盘格每个小格的边长,单位为毫米 # 用于存储三维点和二维点的列表 objpoints = [] # 三维点在世界坐标系中的位置 imgpoints_left = [] # 左相机图像中的二维点 imgpoints_right = [] # 右相机图像中的二维点 # 准备棋盘格角点的三维坐标 (0,0,0), (1,0,0), (2,0,0), ..., (8,5,0) 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) objp *= square_size # 加载左右相机的棋盘格图像 criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) left_images = glob.glob('left/*.png') # 替换为你的左相机图像路径 right_images = glob.glob('right/*.png') # 替换为你的右相机图像路径 print(left_images ) assert len(left_images) == len(right_images), "左右相机图像数量不一致" for left_img_path, right_img_path in zip(left_images, right_images): print(f"处理图像对: {left_img_path}, {right_img_path}") # 添加打印语句以显示正在处理的图像对 left_img = cv2.imread(left_img_path) right_img = cv2.imread(right_img_path) if left_img is None or right_img is None: print("警告: 无法加载一张或多张图像!") continue # 跳过无法加载的图像对 gray_left = cv2.cvtColor(left_img, cv2.COLOR_BGR2GRAY) gray_right = cv2.cvtColor(right_img, cv2.COLOR_BGR2GRAY) # 查找棋盘格角点 ret_left, corners_left = cv2.findChessboardCorners(gray_left, chessboard_size, None) ret_right, corners_right = cv2.findChessboardCorners(gray_right, chessboard_size, None) # 输出角点检测结果 print(f"左图像角点检测结果: {ret_left}") print(f"右图像角点检测结果: {ret_right}") if ret_left and ret_right: objpoints.append(objp) corners_left = cv2.cornerSubPix(gray_left, corners_left, (11, 11), (-1, -1), criteria) imgpoints_left.append(corners_left) corners_right = cv2.cornerSubPix(gray_right, corners_right, (11, 11), (-1, -1), criteria) imgpoints_right.append(corners_right) else: print("警告: 在一张或多张图像中未检测到棋盘格角点,跳过这对图像!") # 检查是否有有效的角点数据 if not objpoints or not imgpoints_left or not imgpoints_right: print("错误: 没有有效的角点数据用于相机标定!") else: # 标定相机(这部分代码与之前相同) # 标定相机 ret_left, mtx_left, dist_left, rvecs_left, tvecs_left = cv2.calibrateCamera(objpoints, imgpoints_left, gray_left.shape[::-1], None, None) ret_right, mtx_right, dist_right, rvecs_right, tvecs_right = cv2.calibrateCamera(objpoints, imgpoints_right, gray_right.shape[::-1], None, None) # 双目相机标定 flags = cv2.CALIB_FIX_INTRINSIC + cv2.CALIB_USE_INTRINSIC_GUESS criteria_stereo = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 1e-5) ret, camera_matrix1, dist_coeffs1, camera_matrix2, dist_coeffs2, T, E, R, F= cv2.stereoCalibrate(objpoints, imgpoints_left, imgpoints_right, mtx_left, dist_left, mtx_right, dist_right, gray_left.shape[::-1], criteria_stereo, flags=flags) # R 是旋转矩阵,T 是平移向量,E 是本质矩阵,F 是基础矩阵 # 注意:根据您使用的OpenCV版本和传递的参数,返回值可能有所不同 # 如果您不需要所有返回值,可以只接收您需要的部分 # 计算重投影误差 mean_error_left = 0 mean_error_right = 0 for i in range(len(objpoints)): imgpoints_left_proj, _ = cv2.projectPoints(objpoints[i], rvecs_left[i], tvecs_left[i], mtx_left, dist_left) imgpoints_right_proj, _ = cv2.projectPoints(objpoints[i], rvecs_right[i], tvecs_right[i], mtx_right, dist_right) error_left = cv2.norm(imgpoints_left[i], imgpoints_left_proj, cv2.NORM_L2) / len(imgpoints_left[i]) error_right = cv2.norm(imgpoints_right[i], imgpoints_right_proj, cv2.NORM_L2) / len(imgpoints_right[i]) mean_error_left += error_left mean_error_right += error_right print(f"左相机重投影误差: {mean_error_left / len(objpoints)}") print(f"右相机重投影误差: {mean_error_right / len(objpoints)}") # 输出标定结果 print("左相机内参矩阵:\n", mtx_left) print("左相机内参矩阵:\n", camera_matrix1) print("左相机畸变系数:\n", dist_left) print("右相机内参矩阵:\n", mtx_right) print("右相机内参矩阵:\n", camera_matrix2) print("右相机畸变系数:\n", dist_right) print("旋转矩阵:\n", R) print("平移向量:\n", T) print("本质矩阵:\n", E)#E(本质矩阵) print("基本矩阵:\n", F)#F(基础矩阵) # 已经完成了双目摄像机的标定,并且得到了以下结果: # R: 旋转矩阵 # T: 平移向量 # camera_matrix1, dist_coeffs1: 左相机的内参矩阵和畸变系数 # camera_matrix2, dist_coeffs2: 右相机的内参矩阵和畸变系数 # 保存数据 np.save('R.npy', R)#F(旋转矩阵) np.save('T.npy', T)#F(平移向量) np.save('E.npy', E)#F(基础矩阵) np.save('F.npy', F)#E(本质矩阵) np.save('camera_matrix1.npy', camera_matrix1) np.save('camera_matrix2.npy', camera_matrix2) np.save('dist_coeffs1.npy', dist_coeffs1) np.save('dist_coeffs2.npy', dist_coeffs2) #import numpy as np # 已经完成了双目摄像机的标定,并且将得到了的结果保存在npy文件中: # R: 旋转矩阵 # T: 平移向量 # camera_matrix1, dist_coeffs1: 左相机的内参矩阵和畸变系数 # camera_matrix2, dist_coeffs2: 右相机的内参矩阵和畸变系数 # 读取数据 _R = np.load('R.npy') _T = np.load('T.npy') _E = np.load('E.npy') _F = np.load('F.npy') _camera_matrix1= np.load('camera_matrix1.npy') _camera_matrix2 = np.load('camera_matrix2.npy') _dist_coeffs1 = np.load('dist_coeffs1.npy') _dist_coeffs2 = np.load('dist_coeffs2.npy') print("基本矩阵:\n", _F)#F(基础矩阵)
在计算机视觉和立体视觉中,R(旋转矩阵)、T(平移向量)、E(本质矩阵)和F(基础矩阵)具有特定的含义和作用。以下是它们的详细解释:
- 旋转矩阵 R:
- 旋转矩阵是一个3x3的正交矩阵,用于描述两个坐标系之间的旋转变换。
- 在立体视觉中,它通常表示两个相机坐标系之间的相对旋转。
- 通过旋转矩阵,可以将一个相机坐标系中的点转换到另一个相机坐标系中。
- 平移向量 T:
- 平移向量是一个三维向量,用于描述两个坐标系之间的平移变换。
- 在立体视觉中,它表示一个相机相对于另一个相机在三维空间中的位置偏移。
- 结合旋转矩阵,平移向量可以完整地描述两个相机之间的相对位置关系。
- 本质矩阵 E:
- 本质矩阵是一个3x3的矩阵,专门用于双目相机或多视图立体视觉的情况。
- 它描述了两个相机光心连线和三维空间中同名点构成的平面之间的关系。
- 本质矩阵由相对姿态(旋转矩阵R和平移向量T)派生而来,具体形式可以表示为E = [t]×R,其中[t]×是一个由平移向量t衍生出的反对称矩阵。
- 通过分解本质矩阵,可以得到相机的相对姿态参数(旋转矩阵R和平移向量T)。
- 基础矩阵 F:
- 基础矩阵是一个3x3的矩阵,用于描述在两个不同视点下观测到的同名点对之间的约束关系。
- 在理想情况下,一对共轭的图像点(即立体匹配成功找到的对应点对)必须满足p2TFp1 = 0的关系,其中p1和p2分别是左、右相机图像上的齐次坐标点。
- 基础矩阵由两幅图像的相对运动参数(旋转和平移)确定,但不包含相机的内参数(如焦距、主点等)。
- 通过至少四对匹配点可以估算出基础矩阵,并且可以通过相机内参矩阵将基础矩阵转换为本质矩阵。
这些矩阵和向量在计算机视觉和立体视觉中起着至关重要的作用,它们为我们提供了描述和计算两个相机之间相对运动和空间关系的工具。在实际应用中,这些矩阵和向量通常用于三维重建、目标检测、图像配准等任务。
3、MATLAB标定双目相机
参考:https://blog.csdn.net/weixin_74923758/article/details/144459840?spm=1001.2014.3001.5501
参考:https://blog.csdn.net/weixin_49305785/article/details/140690316
参考:https://blog.csdn.net/qq_58018816/article/details/136379594
径向畸变 选 三参数法
读出数据的m文件
% .m文件,注意相机相对位姿取逆
RD1 = stereoParams.CameraParameters1.RadialDistortion;
TD1 = stereoParams.CameraParameters1.TangentialDistortion;
D1 = [RD1(1), RD1(2), TD1(1), TD1(2), RD1(3)];
K1 = stereoParams.CameraParameters1.IntrinsicMatrix';
RD2 = stereoParams.CameraParameters2.RadialDistortion;
TD2 = stereoParams.CameraParameters2.TangentialDistortion;
D2 = [RD2(1), RD2(2), TD2(1), TD2(2), RD2(3)];
K2 = stereoParams.CameraParameters2.IntrinsicMatrix';
size = stereoParams.CameraParameters1.ImageSize;
rot = stereoParams.RotationOfCamera2;
trans = stereoParams.TranslationOfCamera2;
% K1, D1 是左相机的内参矩阵和畸变系数
% K2, D2 是右相机的内参矩阵和畸变系数
% rot 是旋转矩阵,trans 是平移向量(基线)
% 使用 disp 函数打印矩阵
disp('左相机的内参矩 K1:');
disp(K1);
% 使用 disp 函数打印矩阵
disp('右相机的内参矩 K2:');
disp(K2);
% 使用 disp 函数打印矩阵
disp('左相机的畸变系数 D1:');
disp(D1);
% 使用 disp 函数打印矩阵
disp('右相机的畸变系数 D2:');
disp(D2);
% 使用 disp 函数打印矩阵
disp('旋转矩阵 rot:');
disp(rot);
% 使用 disp 函数打印矩阵
disp('平移向量 trans:');
disp(trans);
m文件输出结果
左相机的内参矩 K1:
515.7906 0 306.2384
0 516.0670 230.6195
0 0 1.0000
右相机的内参矩 K2:
510.3229 0 336.3628
0 510.5720 240.0909
0 0 1.0000
左相机的畸变系数 D1:
-0.0541 0.1774 0 0 -0.1706
右相机的畸变系数 D2:
-0.0673 0.2375 0 0 -0.2685
旋转矩阵 rot:
0.9999 0.0006 0.0120
-0.0006 1.0000 0.0031
-0.0120 -0.0031 0.9999
平移向量 trans:
-57.2458 0.1363 0.3426
黏贴方式导入python
# 读取数据 # K1 = np.array([[fx1, 0, cx1], # [0, fy1, cy1], # [0, 0, 1]]) K1 = np.array([[515.790569407601, 0, 306.238372954221], [0, 516.067024877771, 230.619518439517], [0, 0, 1]]) # D1 = np.array([k1, k2, p1, p2, k3]) D1 = np.array([-0.0672521289171005, 0.237484161424944, 0, 0, -0.268450386761809]) # K2 = np.array([[fx2, 0, cx2], # [0, fy2, cy2], # [0, 0, 1]]) K2 = np.array([[510.322936372130, 0, 336.362809250390], [0, 510.571986602902, 240.090873551457], [0, 0, 1]]) # D2 = np.array([k1, k2, p1, p2, k3]) D2 = np.array([-0.0541106051905616, 0.177379443015095, 0, 0, -0.170624291666305]) # R = np.array([[r11, r12, r13], # [r21, r22, r23], # [r31, r32, r33]]) R = np.array([[0.999927669400735, 0.000570054364175426, 0.0120137839516681], [-0.000606770785500266, 0.999995156206836, 0.00305276793750484], [-0.0120119855156980, -0.00305983674209568, 0.999923171850259]]) # T = np.array([tx, ty, tz]) T = np.array([-57.2457931500438, 0.136290336273723, 0.342617012233637])
4、使用Matlab标定的数据 得到深度图
import cv2 import numpy as np # 假设你已经有了以下标定参数 # K1, D1 是左相机的内参矩阵和畸变系数 # K2, D2 是右相机的内参矩阵和畸变系数 # R 是旋转矩阵,T 是平移向量(基线) # 这些参数通常通过立体标定获得 # 通过粘贴的方式,使用matlab标定参数 # K1 = np.array([[fx1, 0, cx1], # [0, fy1, cy1], # [0, 0, 1]]) K1 = np.array([[515.790569407601, 0, 306.238372954221], [0, 516.067024877771, 230.619518439517], [0, 0, 1]]) # D1 = np.array([k1, k2, p1, p2, k3]) D1 = np.array([-0.0672521289171005, 0.237484161424944, 0, 0, -0.268450386761809]) # K2 = np.array([[fx2, 0, cx2], # [0, fy2, cy2], # [0, 0, 1]]) K2 = np.array([[510.322936372130, 0, 336.362809250390], [0, 510.571986602902, 240.090873551457], [0, 0, 1]]) # D2 = np.array([k1, k2, p1, p2, k3]) D2 = np.array([-0.0541106051905616, 0.177379443015095, 0, 0, -0.170624291666305]) # R = np.array([[r11, r12, r13], # [r21, r22, r23], # [r31, r32, r33]]) R = np.array([[0.999927669400735, 0.000570054364175426, 0.0120137839516681], [-0.000606770785500266, 0.999995156206836, 0.00305276793750484], [-0.0120119855156980, -0.00305983674209568, 0.999923171850259]]) # T = np.array([tx, ty, tz]) T = np.array([-57.2457931500438, 0.136290336273723, 0.342617012233637]) # 加载左右图像 imgL = cv2.imread('left1.png', cv2.IMREAD_GRAYSCALE) imgR = cv2.imread('right1.png', cv2.IMREAD_GRAYSCALE) cv2.imshow('imgR', imgR) # 使用标定参数创建立体校正映射 R1, R2, P1, P2, Q, _, _ = cv2.stereoRectify(K1, D1, K2, D2, (imgL.shape[1], imgL.shape[0]), R, T) print(R2) # 计算重投影矩阵和Q矩阵(用于从视差图计算3D点) left_map1, left_map2 = cv2.initUndistortRectifyMap(K1, D1, R1, P1, (imgL.shape[1], imgL.shape[0]), cv2.CV_32FC1) right_map1, right_map2 = cv2.initUndistortRectifyMap(K2, D2, R2, P2, (imgR.shape[1], imgR.shape[0]), cv2.CV_32FC1) # 对图像进行校正 imgL_rectified = cv2.remap(imgL, left_map1, left_map2, cv2.INTER_LINEAR) imgR_rectified = cv2.remap(imgR, right_map1, right_map2, cv2.INTER_LINEAR) cv2.imshow('imgR_rectified ', imgR_rectified) # 使用立体匹配算法计算视差图 # 这里使用BM(块匹配)算法作为示例 stereo = cv2.StereoBM_create(numDisparities=16, blockSize=15) disparity = stereo.compute(imgL_rectified, imgR_rectified).astype(np.float32) / 16.0 # 可视化视差图(可选) cv2.imshow('Disparity', cv2.normalize(disparity, None, alpha=0, beta=255, norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_8U)) cv2.waitKey(0) cv2.destroyAllWindows() # 注意:为了从视差图计算深度,你需要知道基线距离(T的模)和焦距(通常是K1或K2中的fx或fy) # 深度 Z = f * baseline / disparity # 在这里,我们假设使用fx作为焦距,并且基线是T的z分量(这取决于你的相机设置和标定结果) # 计算深度图(示例,可能需要根据实际情况调整) f = K1[0, 0] # 焦距,假设使用左相机的fx 像素尺寸 baseline = np.linalg.norm(T) # 基线距离,T的模 物理尺寸 depth = f * baseline / (disparity + 1e-3) # 避免除以零,添加一个小常数 物理尺寸 # 可视化深度图(可选,需要转换为可显示的格式) depth_vis = cv2.normalize(depth, None, alpha=0, beta=255, norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_8U) cv2.imshow('Depth', depth_vis) cv2.waitKey(0) cv2.destroyAllWindows()
5、基线距离 或 基线物理长度(区别于像素长度)
使用校正的图像做相机标定,得到结果如下:
棋盘格的正方形单元边长15mm
根据 平移向量,判断baseline长度可取值:57.2779 mm
左相机的内参矩 K1:
512.8914 0 321.1150
0 512.9279 235.2123
0 0 1.0000
右相机的内参矩 K2:
512.7059 0 320.8917
0 512.6788 235.3189
0 0 1.0000
左相机的畸变系数 D1:
0.0142 -0.0546 0 0 0.0834
右相机的畸变系数 D2:
-0.0124 0.0617 0 0 -0.1006
旋转矩阵 rot:
0.9997 0.0011 0.0235
-0.0013 1.0000 0.0064
-0.0235 -0.0064 0.9997
平移向量 trans:
-57.2779 0.0211 -0.2248
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)