1. 从“拍照片”到“建模型”为什么我们需要知道相机在看哪里大家好我是老陈在计算机视觉和智能硬件这行摸爬滚打了十几年。今天想和大家聊聊一个听起来有点“硬核”但实际上一旦搞懂就能让你在三维重建、机器人导航SLAM这些酷炫项目上少走很多弯路的主题相机内参和外参。很多刚入门的朋友可能会觉得这不就是一堆数学公式和矩阵变换吗理论书上看看就得了。但在我实际做项目的经历里恰恰是这些基础参数的标定精度直接决定了你后续三维点云的清晰度、机器人定位的准确性甚至是整个视觉系统的成败。你可以把它想象成你要用一把尺子去测量世界如果连你这把尺子本身的刻度内参和你拿尺子的位置角度外参都不准那量出来的结果肯定是一团糟。简单来说内参描述的是相机“自己长什么样”比如它的“眼睛”镜头焦距是多少它的“视网膜”成像芯片中心点在哪里有没有“近视散光”畸变。这些参数由相机硬件本身决定一旦出厂对于同一个镜头和传感器组合基本就固定了。而外参描述的是相机“在哪儿、怎么看”也就是相机在真实世界中的位置和朝向。当你拿着手机移动或者机器人上的相机随着机器人运动时外参就在时刻变化。所以从“拍一张照片”到“从照片重建三维世界”核心的一步就是通过相机标定精确地拿到这把“尺子”的说明书内参并在它每次移动时都能知道它新的位置和姿势外参。这个过程就是我们今天要深入实战解析的内容。无论你是想用手机做个简单的三维扫描APP还是为自动驾驶小车装上“眼睛”理解并掌握内参外参的标定与应用都是无法绕开的必修课。2. 拆解相机内参、外参与四个坐标系的“接力赛”要理解标定我们得先回到最基础的成像过程。这个过程就像一场四个坐标系之间的“接力赛”而内参和外参就是传递接力棒的规则。2.1 四个坐标系世界、相机、图像与像素想象一下你用相机拍摄桌面上的一盘国际象棋。世界坐标系棋盘所在的桌面就是我们的世界。我们可以规定棋盘一角为原点建立三维的X, Y, Z轴。每个棋子的位置比如“王”在a1格在这个坐标系下都有一个三维坐标 (X, Y, Z)。这是最直观的、描述物体绝对位置的坐标系。相机坐标系以相机的镜头中心光心为原点。Z轴沿着镜头光轴指向拍摄方向X轴和Y轴分别平行于成像平面的两边。当你移动相机时这个坐标系也跟着动。外参本质上就是描述世界坐标系到相机坐标系的变换关系。图像物理坐标系光线通过镜头会在相机内部的传感器平面上形成一个倒立的像。这个平面就是成像平面。以光轴与这个平面的交点主点为中心建立二维的x, y轴单位是毫米。这里记录的是像的物理位置。图像像素坐标系最终传感器上的这个模拟像会被离散化成一个一个的像素点存储为我们的JPG或PNG图片。这个坐标系的原点通常在图片的左上角u轴向右v轴向下坐标单位是“第几行第几列”的像素。这场接力赛的流程是一个棋盘格角点的世界坐标 (X, Y, Z)经过外参旋转R平移t变换成了它在相机坐标系下的坐标 (Xc, Yc, Zc)。接着通过针孔模型相似三角形原理这个三维点被投影到图像物理坐标系下的一个二维点 (x, y)。最后通过内参矩阵K这个以毫米为单位的物理位置被转换到了图像像素坐标系下的具体像素坐标 (u, v)。用一句大白话总结外参负责告诉相机“世界在哪里”内参负责告诉相机“如何把看到的世界画到照片上”。2.2 内参矩阵K相机的“身份证”内参矩阵K是一个3x3的矩阵它封装了相机最核心的几何属性K [ fx, s, cx; 0, fy, cy; 0, 0, 1 ]fx, fy分别是x轴和y轴方向的焦距单位是像素。为什么有两个因为传感器上单个像素的物理尺寸在x和y方向可能不是严格的正方形像素不是完美的正方形所以需要用焦距除以不同的像素尺寸得到以像素为单位的fx和fy。如果像素是正方形的那么fx ≈ fy。cx, cy主点坐标即光轴与成像平面交点图像物理坐标系原点在像素坐标系下的位置。理想情况下它在图像正中心但实际由于组装误差会略有偏移。s扭曲因子skew描述的是图像坐标轴的不垂直度。对于绝大多数现代相机这个值非常接近于0可以忽略。内参为什么重要我举个实际项目中的例子。我们曾用一个广角镜头做SLAM初期忽略了它的径向畸变这也是内参的一部分通常用k1, k2, k3等参数描述。结果在重建地图时远离图像中心的特征点投影位置误差极大导致机器人轨迹估计严重漂移就像一个人戴着度数不对的眼镜走路肯定会撞墙。后来我们做了严谨的标定准确求出了包括畸变系数在内的全套内参并用它去校正每一帧输入的图像去畸变SLAM系统立刻稳定了。2.3 外参 [R|t]相机的“瞬时快照”外参由旋转矩阵R3x3和平移向量t3x1组成合起来可以写成一个3x4的矩阵 [R | t]。它描述的是如何把世界坐标系下的一个点变换到相机坐标系下。公式是[Xc; Yc; Zc] R * [X; Y; Z] t或者用齐次坐标更简洁地表示。外参的动态性是理解其用途的关键。在三维重建中我们通常用固定相机拍摄物体不同角度的照片。这时世界坐标系可以固定在被测物体上比如以标定板一角为原点而每一张照片都对应一组不同的外参因为这组外参描述了拍摄该照片时相机相对于物体的位置和姿态。在视觉SLAM中情况更动态。我们通常将第一帧相机的位置设为世界坐标系原点。那么对于后续每一帧新的图像SLAM算法核心任务之一就是估计当前帧相对于第一帧即世界坐标系的外参也就是相机的位姿。这个估计的精度直接决定了构建的地图是否准确、机器人定位是否可靠。3. 实战第一步手把手完成相机标定理论说了这么多不上手都是空谈。下面我就以最常用的张正友标定法为例带大家走一遍完整的标定流程。我会使用OpenCV库因为它工具链完整非常适合学习和原型开发。3.1 准备工作制作标定板与采集图像标定需要一个已知尺寸的、高对比度的图案。最常用的就是棋盘格。你可以自己用打印机打印一张。棋盘格尺寸比如每个方格边长25毫米。这个物理尺寸必须精确因为它是连接像素世界和真实世界的尺度桥梁。采集图像用你要标定的相机从不同角度、不同距离拍摄大约15-20张标定板的照片。这里有几个我踩过坑后总结的要点覆盖整个画面让标定板出现在图像的各个区域中心、四个角、边缘。姿态多样化不仅平拍还要有一些倾斜、旋转的角度。这能帮助算法更好地约束所有参数特别是畸变系数。保持清晰对焦要准避免运动模糊。模糊的图像会导致角点检测不准。均匀光照避免反光和强烈的阴影确保棋盘格黑白分明。3.2 核心代码解析使用OpenCV进行标定假设你已经把拍摄好的图片放在一个文件夹里下面是Python代码的核心步骤。import numpy as np import cv2 import glob # 1. 定义棋盘格尺寸内角点数量比如9x6表示每行10个方格每列7个方格内角点就是行列交点 pattern_size (9, 6) # 请根据你打印的棋盘格实际内角点数修改 # 2. 准备物体点世界坐标系中的点Z坐标全为0 objp np.zeros((pattern_size[0] * pattern_size[1], 3), np.float32) objp[:, :2] np.mgrid[0:pattern_size[0], 0:pattern_size[1]].T.reshape(-1, 2) # 假设每个方格实际边长为0.025米25毫米 square_size 0.025 objp * square_size # 3. 用于存储所有图像的对象点和图像点的列表 objpoints [] # 真实世界3D点 imgpoints [] # 图像中的2D点 # 4. 读取所有标定图片 images glob.glob(./calibration_images/*.jpg) for fname in images: img cv2.imread(fname) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 查找棋盘格角点 ret, corners cv2.findChessboardCorners(gray, pattern_size, None) # 如果找到添加对象点和图像点细化角点位置后 if ret: objpoints.append(objp) # 亚像素级角点检测提高精度 corners_refined cv2.cornerSubPix(gray, corners, (11,11), (-1,-1), criteria(cv2.TERM_CRITERIA_EPS cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)) imgpoints.append(corners_refined) # 可视化可选 cv2.drawChessboardCorners(img, pattern_size, corners_refined, ret) cv2.imshow(Found Corners, img) cv2.waitKey(500) cv2.destroyAllWindows() # 5. 进行相机标定 ret, camera_matrix, dist_coeffs, rvecs, tvecs cv2.calibrateCamera( objpoints, imgpoints, gray.shape[::-1], None, None) print(标定是否成功:, ret) print(\n相机内参矩阵 K:) print(camera_matrix) print(\n畸变系数 [k1, k2, p1, p2, k3, ...]:) print(dist_coeffs.ravel()) # rvecs, tvecs 就是每张图片对应的外参旋转向量和平移向量代码解读与注意事项cv2.findChessboardCorners自动检测棋盘格的内角点。这是标定自动化的关键。cv2.cornerSubPix将角点位置优化到亚像素精度这是提升标定精度的关键一步。cv2.calibrateCamera核心函数。它通过最小化重投影误差计算出的像素点与实际检测到的像素点之间的距离一次性求解出我们想要的所有参数camera_matrix就是我们的内参矩阵K。dist_coeffs畸变系数通常包含径向畸变(k1, k2, k3)和切向畸变(p1, p2)。这也是内参的一部分。rvecs,tvecs这就是每张标定图片对应的外参。注意OpenCV返回的是旋转向量罗德里格斯向量可以通过cv2.Rodrigues()函数转换为旋转矩阵R。标定结果评估标定完成后一定要计算重投影误差。这个误差值反映了标定的整体质量。通常误差小于0.5个像素可以认为是很好的结果。OpenCV的calibrateCamera函数返回值ret就是平均重投影误差。你可以自己写个循环用求得的内外参将世界坐标点投影回图像与检测到的角点计算误差直观感受一下。4. 从参数到应用内参外参如何驱动三维重建拿到了精确的K和畸变系数我们就能在后续任务中大展拳脚了。三维重建的核心思路是三角测量从两个或多个不同的视角即不同的外参观察同一个三维点通过它在这些图像上的投影位置像素坐标反向求解出它的三维坐标。4.1 单目三维重建从运动恢复结构SFM如果你只有一台移动的相机比如手持手机环绕物体拍摄这就是典型的SFM问题。特征提取与匹配对连续两帧图像I1, I2使用SIFT、ORB等算法提取特征点并匹配得到一组对应点对(u1_i, v1_i) - (u2_i, v2_i)。估计基础矩阵与本质矩阵利用这些匹配点可以计算两帧图像之间的基础矩阵F或本质矩阵E。本质矩阵E包含了这两帧相机之间的相对旋转和平移信息即从I1到I2的外参变化[R_rel | t_rel]但缺少尺度信息。从本质矩阵恢复相对外参通过SVD分解本质矩阵E可以得到四个可能的[R_rel | t_rel]解需要通过将三维点重建到两个相机前方深度为正的约束来选出唯一正确的解。三角化假设第一帧相机的外参为[I | 0]即世界坐标系与第一帧相机坐标系重合第二帧的外参就是上一步求出的[R_rel | t_rel]。现在对于每一对匹配点我们知道了它们对应的相机矩阵P1 K[I|0], P2 K[R_rel|t_rel]和像素坐标就可以通过线性或非线性方法三角化出对应的三维点坐标。增量式重建将新重建的三维点和相机位姿作为初始值加入更多图片进行大规模的光束法平差优化。这是一个同时优化所有三维点坐标和所有相机外参以及内参如果允许内参也微调的过程目的是最小化整体的重投影误差。BA是SFM和SLAM系统的核心优化步骤能显著提升重建精度。# 一个简化的三角化示例使用线性方法 def triangulate_point(pt1, pt2, P1, P2): pt1, pt2: 两幅图像上的匹配点坐标 (u, v) P1, P2: 两个相机的投影矩阵 P K [R|t] 返回三角化后的三维点齐次坐标前三个是XYZ A np.zeros((4, 4)) A[0] pt1[0] * P1[2] - P1[0] A[1] pt1[1] * P1[2] - P1[1] A[2] pt2[0] * P2[2] - P2[0] A[3] pt2[1] * P2[2] - P2[1] # 对A进行SVD分解最小奇异值对应的右奇异向量即为解 U, S, Vt np.linalg.svd(A) X Vt[-1] return X / X[3] # 转换为非齐次坐标4.2 立体视觉与深度图如果你有两只经过标定的相机双目系统且已知它们之间的相对外参通过立体标定获得那么三维重建就变得更直接、更实时。立体校正利用双目的内参和相对外参对左右两张图像进行重投影使得两幅图像的行完全对齐。这个过程叫立体校正它会生成两个新的“理想”的图像平面其光轴平行且极线是水平的。OpenCV中的cv2.stereoRectify函数可以完成这个工作并给出左右相机的校正映射表。立体匹配在矫正后的图像上寻找左图每一个像素在右图上对应行的匹配像素。由于行已对齐匹配搜索只需要在一维水平方向上进行大大降低了计算量和歧义性。这会生成一张视差图每个像素的值表示该点在左右图中的水平位置差。计算深度根据三角测距原理深度 Z (焦距 * 基线距离) / 视差。其中基线距离就是两个相机光心之间的距离由立体标定的外参中的平移向量t的模长给出。这样你就能实时地得到一幅深度图进而生成稠密的三维点云。# 立体视觉深度计算核心公式示意 focal_length camera_matrix[0,0] # 假设fxfy baseline np.linalg.norm(T) # T是右相机相对于左相机的平移向量来自立体标定 disparity 120 # 某个像素点的视差值像素单位 if disparity 0: depth (focal_length * baseline) / disparity # 深度值与baseline单位一致如毫米在实际的机器人或AR/VR应用中这个深度信息是感知环境、避障、实现虚实融合的基石。我做过一个基于双目相机的室内扫地机器人原型其避障模块的核心就是靠实时计算的深度图来识别前方的障碍物高度和距离。5. 进阶话题与避坑指南掌握了基础流程我们再来聊聊那些容易出问题的地方和进阶技巧。5.1 标定质量的影响因素与提升技巧标定板质量打印的棋盘格要平整黑白对比度高。亚光材质比光面更好避免反光。有条件可以使用更精确的陶瓷或玻璃标定板。图像数量与姿态我强烈建议至少15张以上并且姿态要尽可能覆盖相机视野的各个角落和深度。只在一个距离上平拍是标定的大忌这样无法有效约束焦距和畸变参数。角点检测精度务必使用cv2.cornerSubPix。在采集图像时确保标定板清晰、对焦准确。镜头畸变模型选择OpenCV默认使用Brown-Conrady畸变模型。对于鱼眼镜头或超广角镜头需要使用cv2.fisheye命名空间下的专门函数进行标定它使用不同的畸变模型。评估与验证标定后一定要用未参与标定的新图像进行验证。计算这些新图像上棋盘格角点的重投影误差看看是否依然很小。这是检验标定结果泛化能力的关键。5.2 外参的持续估计视觉里程计与SLAM在动态的SLAM系统中相机外参即相机位姿是持续估计的而不是一次性标定。这里的关键是特征点跟踪和PnP算法。特征跟踪在连续帧间跟踪特征点得到2D-2D的对应关系。PnP当已知场景中一些3D点来自之前重建的地图和它们在当前图像中的2D投影点时就可以使用PnP算法直接求解当前相机相对于这些3D点的外参。OpenCV中的cv2.solvePnP函数就是干这个的。这是SLAM中实现定位的核心。尺度问题单目SLAM最大的挑战是尺度不确定性。通过三角化恢复的三维结构和相机运动其尺度是未知的。通常需要引入一些先验信息如已知高度的物体、IMU数据来恢复真实尺度。而双目或RGB-D相机由于有已知的基线天生就具有尺度信息。5.3 内外参的联合优化与自标定在大型SFM项目中我们有时会对内参也进行微调尤其是焦距这被称为自标定或BA中的内参优化。光束法平差BA的强大之处在于它可以将所有参数所有相机的外参、所有三维点坐标、以及可能的所有相机的内参放在一个巨大的非线性最小二乘问题中一起优化。这能极大地提升整个模型的全局一致性。常用的工具是COLMAP、Bundler等。最后我想分享一个深刻的体会相机标定和三维重建是一个“差之毫厘谬以千里”的领域。初期可能觉得参数差不多就行但当你做的系统对精度要求越来越高时你会发现内参标定误差的零点几个像素或者外参估计的微小偏差经过多次投影和三角化的累积最终会导致重建模型严重变形或定位彻底失败。因此花时间做好严谨、精确的标定是所有后续高级视觉应用最坚实、最值得投入的第一步。希望这篇从理论到实战的解析能帮你把这第一步走稳、走扎实。