什么是相机标定
相机标定(Camera Calibration)是计算机视觉、自动驾驶、机器人(SLAM)以及 AR/VR 领域中不可或缺的“基本功”。
简单来说,相机的镜头就像是一面哈哈镜(多多少少都有畸变),而标定的目的就是帮相机找回“视力”,看清真实的三维世界。
下面为你带来一份通俗易懂、包含数学原理与代码实战的相机标定全景指南。
一、 核心原理:我们在标定什么?
相机成像的过程,实际上是把**三维世界(World)中的点,映射到相机的二维像素平面(Pixel)上。这个过程可以用四个坐标系的转换来描述:
世界坐标系→相机坐标系→图像坐标系→像素坐标系
标定的核心任务,就是求解两个矩阵(内参和外参)以及一组畸变参数:
1. 相机内参(Intrinsic Parameters)—— 解决“相机本身”的问题
内参是由相机自身的机械和光学结构决定的(如焦距、感光元件大小)。一旦相机组装好并且不失焦,内参就是固定不变的。 内参矩阵通常写为K:
- fx,fy:横向和纵向的焦距(以像素为单位)。
- cx,cy:主点坐标,即相机光轴在图像传感器上的中心点(通常接近图像的正中央)。
2. 相机外参(Extrinsic Parameters)—— 解决“相机摆放”的问题
外参决定了相机在三维世界中的位置和朝向。
- 旋转矩阵R:相机朝哪儿看(俯仰、偏航、翻滚)。
- 平移向量T:相机在什么位置。
- 注意:如果相机在移动(比如装在车上),外参是会随着运动实时变化的。
3. 畸变参数(Distortion Coefficients)—— 解决“镜头变型”的问题
普通的透镜(尤其是广角或鱼眼镜头)往往会导致直线变曲线。主要分为两种:
- 径向畸变(Radial Distortion):由于透镜形状导致,越靠近图像边缘变型越厉害。表现为桶形畸变(鼓起来)或枕形畸变(凹进去)。
- 切向畸变(Tangential Distortion):由于镜头组装时,透镜与感光芯片(CMOS/CCD)没有完全平行导致。
二、 主流标定方法:张正友标定法
目前工业界和学术界最常用的方案是**“张正友标定法”(Zhang’s Method)。在它问世之前,标定需要搭建昂贵的三维标定物;而张氏标定法只需要一张打印出来的二维黑白棋盘格**,在不同角度拍十几张照片即可。
标定的标准流水线(Workflow):
- 准备标定板:打印一张高精度的黑白棋盘格或 ChArUco 标定板(通常贴在平整的刚体表面,如玻璃或碳纤维板上)。
- 采集图像:变换不同的角度、距离、高度,拍摄 15∼25 张标定板的照片。
- 避坑指南:标定板要尽量占满整个画面,特别是图像的四周边缘,因为边缘的畸变最大,只有覆盖到边缘才能准确计算畸变参数。
- 提取角点(Corner Extraction):在计算机中识别棋盘格黑白相间的交点。
- 优化求解:利用数学优化算法(如 LM 算法),让重投影误差最小,从而解出内参、外参和畸变系数。
三、 代码实战:使用 OpenCV 自动标定
以下是使用 Python 和 OpenCV 实现相机标定的标准脚本。你只需要把拍摄好的照片放到一个文件夹里即可运行。
import cv2 import numpy as np import glob # 1. 设定标定板的参数 # 注意:这里的行列数是黑白格子交点的个数,不是格子的个数! # 例如:一个 9x7 格子的棋盘,内角点是 8x6 CHECKERBOARD = (8, 6) square_size = 25.0 # 单个黑白格子的实际物理边长(单位:毫米 mm) # 定义三维世界坐标系下的棋盘格点坐标 objp = np.zeros((CHECKERBOARD[0] * CHECKERBOARD[1], 3), np.float32) objp[:, :2] = np.mgrid[0:CHECKERBOARD[0], 0:CHECKERBOARD[1]].T.reshape(-1, 2) objp = objp * square_size # 存储所有照片的三维世界点和二维像素点 objpoints = [] # 3d points in real world space imgpoints = [] # 2d points in image plane. # 读取文件夹中所有的标定照片 images = glob.glob('calibration_images/*.jpg') for fname in images: img = cv2.imread(fname) gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 2. 寻找棋盘格角点 ret, corners = cv2.findChessboardCorners(gray, CHECKERBOARD, None) if ret == True: objpoints.append(objp) # 亚像素级角点精准定位(提升标定精度) criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) corners2 = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria) imgpoints.append(corners2) # 可视化角点(可选) cv2.drawChessboardCorners(img, CHECKERBOARD, corners2, ret) cv2.imshow('Chessboard Detection', img) cv2.waitKey(100) cv2.destroyAllWindows() # 3. 核心步骤:相机标定 # 返回值:ret(重投影误差), mtx(内参), dist(畸变系数), rvecs(旋转外参), tvecs(平移外参) ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None) print(f"--- 标定完成 ---") print(f"重投影误差 (Re-projection Error): {ret:.4f} 像素") print(f"相机内参矩阵 (Intrinsic Matrix):\n{mtx}") print(f"畸变系数 (Distortion Coefficients):\n{dist}") # 4. 利用标定结果矫正一张畸变照片 test_img = cv2.imread('calibration_images/test_pic.jpg') h, w = test_img.shape[:2] # 优化内参矩阵 newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (w, h), 1, (w, h)) # 矫正去畸变 dst = cv2.undistort(test_img, mtx, dist, None, newcameramtx) # 裁剪并保存 x, y, w_box, h_box = roi dst = dst[y:y+h_box, x:x+w_box] cv2.imwrite('calibrated_result.jpg', dst) print("去畸变照片已保存为 'calibrated_result.jpg'")四、 工业级标定的避坑与评估
拿到标定数据后,你怎么知道标定得准不准?
1. 核心指标:重投影误差(Re-projection Error)
它是指把标定计算出的三维世界点,重新投影回像素平面,与实际拍摄到的角点之间的像素距离。
- 优秀:误差 << 0.5 像素。
- 合格:误差 << 1.0 像素。
- 如果误差 >> 1.5 像素,说明部分照片拍糊了、标定板不平整,或者角点识别错误,建议剔除坏照片重新计算。
2. 提高标定精度的实战技巧
关键要素 | 错误做法 | 正确做法 |
画面覆盖率 | 标定板只在屏幕中心晃悠 | 必须把标定板移动到图像的四个角落和边缘 |
角度丰富度 | 标定板永远平行于相机(正对) | 标定板需要产生倾斜角度 (俯仰角度、翻转角度) |
光照与对焦 | 画面有反光、阴影、或者运动模糊 | 使用高均匀度光源,运动时拿稳停顿 再拍,严禁动态模糊 |
硬件刚性 | 打印纸贴在软纸板上,产生肉眼难察的弯曲 | 必须贴在高平整度 的亚克力板、玻璃或专业定制的铝合金板上 |
