1. 相机内参标定
任务目标
使用棋盘格标定板对单目相机进行内参标定,输出相机内参矩阵(焦距、主点)与畸变系数,并通过重投影误差评估标定质量。
环境依赖
- Python 3.8+
- OpenCV(含contrib模块):
pip install opencv-contrib-python - NumPy:
pip install numpy
实现方案
采集10~20张不同位姿的棋盘格图像(推荐使用cv2.findChessboardCorners自动检测角点),通过cv2.calibrateCamera计算内参。核心流程:读取图像→角点检测与亚像素细化→传入标定函数→输出内参与畸变系数→计算重投影误差。
关键完整代码
import cv2
import numpy as np
import glob
# ========== 配置 ==========
CHESSBOARD_SIZE = (9, 6) # 内角点数量(列, 行)
SQUARE_SIZE = 25.0 # 棋盘格边长(单位:mm)
IMAGE_DIR = "calib_images/" # 存放标定图像的目录(支持jpg/png)
# ========== 准备对象点 ==========
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
obj_points = [] # 世界坐标系中的3D点
img_points = [] # 图像中的2D点
gray = None
# ========== 读取图像并检测角点 ==========
images = glob.glob(IMAGE_DIR + "*.jpg") + glob.glob(IMAGE_DIR + "*.png")
if len(images) < 5:
raise ValueError(f"至少需要5张标定图像,当前仅找到{len(images)}张")
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)
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
corners_sub = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
img_points.append(corners_sub)
# 可视化检测结果(可选)
cv2.drawChessboardCorners(img, CHESSBOARD_SIZE, corners_sub, ret)
cv2.imshow("Corners", img)
cv2.waitKey(100)
cv2.destroyAllWindows()
# ========== 相机标定 ==========
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(
obj_points, img_points, gray.shape[::-1], None, None
)
# ========== 重投影误差评估 ==========
total_error = 0.0
for i in range(len(obj_points)):
imgpoints_proj, _ = cv2.projectPoints(obj_points[i], rvecs[i], tvecs[i], mtx, dist)
error = cv2.norm(img_points[i], imgpoints_proj, cv2.NORM_L2) / len(imgpoints_proj)
total_error += error
mean_error = total_error / len(obj_points)
# ========== 输出结果 ==========
print("相机内参矩阵 K:")
print(mtx)
print("\n畸变系数 (k1, k2, p1, p2, k3):")
print(dist.ravel())
print(f"\n平均重投影误差: {mean_error:.4f} px")
# 保存标定结果
np.savez("camera_calib.npz", mtx=mtx, dist=dist, mean_error=mean_error)
print("\n标定结果已保存至 camera_calib.npz")
运行方式
- 创建目录
calib_images/,放入10~20张棋盘格照片(不同角度、距离、姿态),确保角点完整可见且光照均匀。 - 将上述代码保存为
calibrate.py,在终端执行:python calibrate.py
验证方法与通过标准
- 角点检测:运行过程中每张图像应弹出窗口并正确绘制检测到的角点(红色圆点按顺序连接),若某张图像检测失败会跳过,但有效图像数需≥5。
- 内参合理性:焦距
fx、fy应接近图像宽度量级(例如1920×1080图像,fx约1000~2000);主点cx、cy应接近图像中心坐标。 - 重投影误差:平均重投影误差小于0.5像素即标定合格;若大于1像素,说明角点检测精度不足或图像数量/质量不够,需补充图像或调整光照。
- 结果保存:当前目录生成
camera_calib.npz文件,可用np.load('camera_calib.npz')验证内含mtx、dist、mean_error三个数组。
常见问题
- 检测不到角点:确认棋盘格为奇数×偶数内角点(如9×6),避免使用反光或模糊图像,适当调整
CHESSBOARD_SIZE与实际棋盘匹配。 - 误差偏大:增加图像数量至15张以上,覆盖画面边缘区域,并确保棋盘格在每张图像中占据约1/4~1/2画面面积。
- 标定结果不稳定:固定相机光圈与焦距,标定过程中不得变焦或调整对焦。