相机内参与外参的概念
1. 相机内参(Intrinsic Parameters)
相机内参是描述相机自身光学和几何特性的参数,与拍摄场景无关,主要包括:
- 焦距(fx,fyf_x, f_yfx,fy):镜头焦距在图像平面上的投影(单位:像素),分别对应x轴和y轴方向(通常因像素非正方形可能不同)。
- 主点(cx,cyc_x, c_ycx,cy):光轴与图像平面的交点(单位:像素),理想情况下是图像中心。
- 畸变系数:镜头光学畸变的参数,包括:
- 径向畸变(k1,k2,k3k_1, k_2, k_3k1,k2,k3):因镜头形状导致的畸变(边缘像素偏移更明显)。
- 切向畸变(p1,p2p_1, p_2p1,p2):因镜头与图像平面不平行导致的畸变。
内参矩阵通常表示为:
K=[fx0cx0fycy001] K = \begin{bmatrix} f_x & 0 & c_x \\ 0 & f_y & c_y \\ 0 & 0 & 1 \end{bmatrix} K=
fx000fy0cxcy1
2. 相机外参(Extrinsic Parameters)
外参描述相机在世界坐标系中的位置和姿态,用于将世界坐标系中的3D点转换到相机坐标系,包括:
- 旋转矩阵(RRR):3×3矩阵,描述相机坐标系相对世界坐标系的旋转。
- 平移向量(ttt):3×1向量,描述相机坐标系原点相对世界坐标系原点的平移。
外参通过变换矩阵将世界坐标系点PwP_wPw转换为相机坐标系点PcP_cPc:
Pc=R⋅Pw+t P_c = R \cdot P_w + t Pc=R⋅Pw+t
相机标定方法
相机标定的核心是通过已知的3D世界点及其对应的2D图像点,求解内参和外参。最常用的是张正友标定法,步骤如下:
- 准备平面标定板(如棋盘格),已知格子的物理尺寸(如20mm×20mm)。
- 从不同角度、姿态拍摄标定板的多张图片(通常需要10-20张)。
- 检测每张图片中棋盘格的内角点(2D图像坐标)。
- 定义内角点在世界坐标系中的3D坐标(例如,令标定板平面为Z=0Z=0Z=0,左上角为原点)。
- 利用透视变换约束,通过非线性优化求解内参、外参和畸变系数。
Python实现相机标定
使用OpenCV库实现标定,主要步骤:读取图片→检测角点→亚像素优化→标定计算→评估误差。
代码实现
import cv2
import numpy as np
import glob
# ---------------------- 配置参数 ----------------------
# 棋盘格内角点数量(行×列,不含边框)
chessboard_size = (9, 6) # 例如:9列,6行内角点
# 棋盘格单个格子的物理尺寸(单位:mm)
square_size = 20.0
# 世界坐标系中的3D点(Z=0,仅X、Y有值)
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 # 缩放为实际物理尺寸
# 存储所有图片的3D世界点和2D图像点
obj_points = [] # 世界坐标系3D点
img_points = [] # 图像坐标系2D点
# ---------------------- 读取图片并检测角点 ----------------------
# 读取标定板图片(需提前准备,放在"calibration_images"文件夹)
images = glob.glob('calibration_images/*.jpg') # 支持jpg/png等格式
for fname in images:
img = cv2.imread(fname)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 转为灰度图
# 检测棋盘格内角点(返回角点是否找到,以及角点坐标)
ret, corners = cv2.findChessboardCorners(gray, chessboard_size, None)
# 如果找到角点,进行亚像素精确化
if ret:
obj_points.append(objp) # 添加3D世界点
# 亚像素角点优化(提高精度)
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
corners_subpix = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
img_points.append(corners_subpix) # 添加优化后的2D图像点
# 可视化角点检测结果
img = cv2.drawChessboardCorners(img, chessboard_size, corners_subpix, ret)
cv2.imshow('Chessboard Corners', img)
cv2.waitKey(500) # 每张图显示0.5秒
cv2.destroyAllWindows()
# ---------------------- 相机标定 ----------------------
# 调用OpenCV标定函数
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(
obj_points, img_points, gray.shape[::-1], None, None
)
# ---------------------- 输出标定结果 ----------------------
print("标定是否成功:", "成功" if ret else "失败")
print("\n内参矩阵 K:\n", mtx)
print("\n畸变系数(k1, k2, p1, p2, k3):\n", dist)
print("\n外参(旋转向量和平移向量):")
for i in range(len(rvecs)):
print(f"第{i+1}张图:")
print(f"旋转向量 rvec:\n{rvecs[i]}")
print(f"平移向量 tvec:\n{tvecs[i]}\n")
# ---------------------- 评估标定精度(重投影误差) ----------------------
# 重投影误差:标定后的3D点投影到图像上的位置与实际检测的2D点的偏差
mean_error = 0
for i in range(len(obj_points)):
img_points2, _ = cv2.projectPoints(
obj_points[i], rvecs[i], tvecs[i], mtx, dist
)
error = cv2.norm(img_points[i], img_points2, cv2.NORM_L2) / len(img_points2)
mean_error += error
print(f"平均重投影误差:{mean_error / len(obj_points):.6f} 像素")
print("(误差越小,标定精度越高,通常应小于0.5像素)")
# ---------------------- 保存标定结果 ----------------------
np.savez('camera_calibration.npz', mtx=mtx, dist=dist, rvecs=rvecs, tvecs=tvecs)
print("\n标定结果已保存至 camera_calibration.npz")
测试用例说明
准备数据:
- 打印一张棋盘格标定板(如9×6内角点),测量单个格子的物理尺寸(如20mm)。
- 用相机从不同角度、距离拍摄10-20张标定板图片(确保所有内角点都可见),保存到
calibration_images
文件夹。
运行代码:
- 安装依赖:
pip install opencv-python numpy
。 - 运行脚本,程序会自动检测角点并显示,最终输出内参、外参和重投影误差。
- 安装依赖:
结果评估:
- 若平均重投影误差小于0.5像素,说明标定效果较好;若误差过大,需检查图片质量(如模糊、角点不全)或增加图片数量。
代码关键步骤解释
- 角点检测:
cv2.findChessboardCorners
检测棋盘格内角点,返回粗略坐标。 - 亚像素优化:
cv2.cornerSubPix
通过迭代提高角点坐标精度(至亚像素级别)。 - 标定核心:
cv2.calibrateCamera
利用3D-2D点对应关系,求解内参(mtx
)、畸变系数(dist
)、外参(旋转向量rvecs
、平移向量tvecs
)。 - 精度评估:重投影误差反映标定参数的准确性,误差越小说明模型越可靠。
通过上述方法,可快速实现相机内外参的标定,为后续的立体视觉(如深度估计)、图像校正等任务奠定基础。