相机标定(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):

  1. 准备标定板:打印一张高精度的黑白棋盘格或 ChArUco 标定板(通常贴在平整的刚体表面,如玻璃或碳纤维板上)。
  2. 采集图像:变换不同的角度、距离、高度,拍摄 15∼25 张标定板的照片。
    • 避坑指南:标定板要尽量占满整个画面,特别是图像的四周边缘,因为边缘的畸变最大,只有覆盖到边缘才能准确计算畸变参数。
  1. 提取角点(Corner Extraction):在计算机中识别棋盘格黑白相间的交点。
  2. 优化求解:利用数学优化算法(如 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. 提高标定精度的实战技巧

关键要素

错误做法

正确做法

画面覆盖率

标定板只在屏幕中心晃悠

必须把标定板移动到图像的四个角落和边缘

角度丰富度

标定板永远平行于相机(正对)

标定板需要产生倾斜角度

(俯仰角度、翻转角度)

光照与对焦

画面有反光、阴影、或者运动模糊

使用高均匀度光源,运动时拿稳停顿

再拍,严禁动态模糊

硬件刚性

打印纸贴在软纸板上,产生肉眼难察的弯曲

必须贴在高平整度

的亚克力板、玻璃或专业定制的铝合金板上



 

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐