尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

彻底搞懂相机内外参标定(附标定完整思路)

彻底搞懂相机内外参标定(附标定完整思路) 01、首先需要了解什么是内外参内参描述的是相机自身的成像特性包括焦距、主点和镜头畸变目的是让我们能够校正图像畸变、准确地进行像素与空间射线/三维点之间的换算。通俗来讲它主要告诉我们三件事镜头能把多远的景物拉多近焦距、画面正中心那个像素点到底在哪儿主点以及镜头拍出来的直线会不会变弯畸变。有了这些数据我们才能把一张有点“变形”的照片掰正并且搞清楚某个像素对应的是现实空间里哪条光线、哪个方向相当于给了每个像素一张通往真实世界的“换算表”。外参描述相机相对于被测物体、标定板或机器人坐标系的位置和姿态目的是知道目标在相机坐标系或世界坐标系中的实际空间位置。通俗来讲它告诉我们这台相机相对于某个参照物——比如被测零件、标定板或者机器人的底座——到底离多远、歪多少、转了多少度。有了外参才能把相机自己眼里看到的坐标翻译成我们关心的那个世界里的真实位置也就是知道“这个点在相机前面半米偏左”而不是只知道“这个点在画面右下角”。简单说内参解决“相机怎么成像、图像哪里变形了”外参解决“物体相对相机在哪里、姿态是什么”。02、不标定内外参会怎么样如果不标定内外参普通拍照显示通常还能用但一旦涉及测量、定位或机器人引导就会不准不标定内参就无法修正镜头畸变和成像误差图像中心与边缘的像素比例不一致圆心、半径、距离等测量会产生系统误差不标定外参就不知道相机与物体之间的位置和姿态只能得到像素坐标无法准确换算成真实世界坐标。简单说不标定内参图像坐标不准不标定外参图像坐标无法对应到真实空间。03、内参标定及其原理1放置标定板采集30张图片左右平移、上下平移、小角度倾斜、旋转、远近变化质量比数量重要标定板需要覆盖整个视场图像边缘、四个角落。2获取完标定需要的图片后根据标定板类型行列数选择对应的棋盘检测器我这里以aruco.DICT_5X5_100字典款为例标定板尺寸CC300-20-15。标定代码如下import cv2 import numpy as np import glob import matplotlib.pyplot as plt import os def calibrate_camera_charuco(images_path_pattern): # 1. 标定板参数设置 SQUARES_X 14 # 列数 SQUARES_Y 9 # 行数 SQUARE_LENGTH 0.020 # 黑白方格物理边长 (单位: 米) MARKER_LENGTH 0.015 # ArUco 物理边长 (单位: 米) # 初始化字典与标定板 dictionary cv2.aruco.getPredefinedDictionary(cv2.aruco.DICT_5X5_100) board cv2.aruco.CharucoBoard((SQUARES_X, SQUARES_Y), SQUARE_LENGTH, MARKER_LENGTH, dictionary) # 2. 探测器参数设置与亚像素优化 detector_params cv2.aruco.DetectorParameters() # 开启 ArUco 码的亚像素级角点优化提升定位精度 detector_params.cornerRefinementMethod cv2.aruco.CORNER_REFINE_SUBPIX charuco_detector cv2.aruco.CharucoDetector(board, detectorParamsdetector_params) # 3. 读取图像并提取角点 image_files glob.glob(images_path_pattern) if not image_files: print(未找到任何图片请检查路径) return all_obj_points [] # 世界坐标系中的3D点 all_img_points [] # 图像平面中的2D点 valid_images [] # 成功提取特征的图片路径 image_size None print(f开始处理共找到 {len(image_files)} 张图片...) for img_path in image_files: img cv2.imread(img_path) if img is None: print(f警告图片读取失败: {img_path}) continue gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) if image_size is None: image_size gray.shape[::-1] # (width, height) # 检测 ChArUco 板 charuco_corners, charuco_ids, _, _ charuco_detector.detectBoard(gray) # 一张图上20个角点才能参与标定 if charuco_corners is not None and charuco_ids is not None and len(charuco_corners) 30: # 获取对应的 3D 世界坐标和 2D 图像坐标 obj_points, img_points board.matchImagePoints(charuco_corners, charuco_ids) all_obj_points.append(obj_points) all_img_points.append(img_points) valid_images.append(img_path) else: print(f警告图片 {os.path.basename(img_path)} 未提取到足够的角点已跳过。) if not all_obj_points: print(所有图片均未检测到有效的标定板。) return # 4. 第一次标定 (粗标定) print(f\n--- 开始第一次标定 (使用 {len(valid_images)} 张图片)...) calib_flags cv2.CALIB_FIX_K3 ret, mtx, dist, rvecs, tvecs cv2.calibrateCamera( all_obj_points, all_img_points, image_size, None, None ) # 计算每张图片的重投影误差 errors [] for i in range(len(all_obj_points)): imgpoints2, _ cv2.projectPoints(all_obj_points[i], rvecs[i], tvecs[i], mtx, dist) # 计算 L2 范数欧式距离并除以点数求平均像素误差 error cv2.norm(all_img_points[i], imgpoints2, cv2.NORM_L2) / len(imgpoints2) errors.append(error) mean_error np.mean(errors) std_error np.std(errors) print(f第一次标定平均重投影误差: {mean_error:.4f} 像素) print(f第一次标定重投影误差标准差误差的波动程度: {std_error:.4f} 像素) # 5. 误差分析与图集筛选 # 设定阈值剔除误差大于 (平均值 1倍标准差) 或 绝对误差大于 0.3 的坏图 threshold min(mean_error 1.0 * std_error, 0.2) good_obj_points [] good_img_points [] good_images [] print(f\n--- 正在剔除误差 {threshold:.4f} 像素的图片...) for i in range(len(errors)): if errors[i] threshold: good_obj_points.append(all_obj_points[i]) good_img_points.append(all_img_points[i]) good_images.append(valid_images[i]) else: print(f [剔除] 图片: {os.path.basename(valid_images[i])}, 误差: {errors[i]:.4f}) # 6. 第二次标定 (高精度最终标定) print(f\n--- 开始第二次标定 (使用剩余的最优的 {len(good_images)} 张图片)...) ret_final, mtx_final, dist_final, rvecs_final, tvecs_final cv2.calibrateCamera( good_obj_points, good_img_points, image_size, None, None,flagscalib_flags ) # 计算最终误差 final_errors [] for i in range(len(good_obj_points)): imgpoints2, _ cv2.projectPoints(good_obj_points[i], rvecs_final[i], tvecs_final[i], mtx_final, dist_final) error cv2.norm(good_img_points[i], imgpoints2, cv2.NORM_L2) / len(imgpoints2) final_errors.append(error) final_mean_error np.mean(final_errors) print(f\n 最终标定结果 ) print(f最终使用图片数量: {len(good_images)} 张) print(f最终平均重投影误差: {final_mean_error:.5f} 像素 (越小越好一般 0.1为极佳)) print(f\n内参矩阵 (Camera Matrix):\n{mtx_final}) print(f\n畸变系数 (Distortion Coeffs) [k1, k2, p1, p2, k3]:\n{dist_final}) # 7. 可视化重投影误差分布 plt.figure(figsize(10, 5)) plt.bar(range(len(errors)), errors, colorgray, labelRejected Images (Pass 1), alpha0.5) plt.axhline(ythreshold, colorr, linestyle--, labelfThreshold ({threshold:.3f})) # 标出留下的好图的最终误差 good_indices [valid_images.index(img) for img in good_images] plt.bar(good_indices, final_errors, colorblue, labelFinal Accepted Images (Pass 2)) plt.xlabel(Image Index) plt.ylabel(Reprojection Error (Pixels)) plt.title(Reprojection Error per Image (Before and After Filtering)) plt.legend() plt.grid(True, linestyle:, alpha0.6) plt.show() if __name__ __main__: # 照片路径 calibrate_camera_charuco(D:/001shixi/biaoding_images/*.bmp)内参标定代码主要涉及的原理就是基于张正友标定法通过建立 ChArUco 棋盘上已知的 3D 世界坐标点与检测到的 2D 图像坐标点之间的对应关系利用多视角几何约束求解相机内参矩阵和畸变系数。具体来说它利用平面标定板在不同姿态下的单应性矩阵通过优化算法最小化所有角点的重投影误差即 3D 点通过相机模型投影到图像平面后与实际检测到的 2D 点之间的像素距离迭代求解出焦距fx, fy、主点坐标cx, cy以及径向畸变参数和切向畸变参数。下图为得到的内参进行图片校准。04、外参标定及其原理
返回列表