1. 相机标定基础
任务说明
本案例使用棋盘格标定板对单目相机完成内参标定。输入为不同位姿下拍摄的棋盘格图像(或直接使用下方生成的模拟图像),输出为相机内参矩阵(焦距 fx、fy,主点 cx、cy)、畸变系数(k1、k2、p1、p2、k3),并通过重投影误差评估标定质量。标定结果可直接用于后续手眼标定与视觉抓取案例。
标定原理与数据准备
采用OpenCV的cv2.calibrateCamera实现张正友标定法。核心流程为:提取棋盘格角点→亚像素细化→多视图对应点求解内参→非线性优化畸变系数。为便于独立复现,本案例提供两种图像来源:方式A使用真实相机拍摄的15张棋盘格图像(需自行采集,棋盘格规格为9×6内角点,方格边长25mm);方式B使用下方代码自动生成15张不同位姿的模拟棋盘格图像,无需真实相机即可完整跑通标定流程并验证算法正确性。
完整实现代码
以下代码同时支持方式A(真实图像)与方式B(模拟图像)。默认运行方式B,自动生成15张640×480的模拟棋盘格图像并完成标定。若使用真实相机,将use_simulated设为False并修改image_dir路径即可。
import cv2
import numpy as np
import os
# ========== 配置参数 ==========
use_simulated = True # True: 使用模拟图像; False: 使用真实拍摄图像
image_dir = "./calib_images" # 真实图像目录(use_simulated=False时有效)
chessboard_size = (9, 6) # 内角点数 (列, 行)
square_size = 25.0 # 方格边长(毫米)
num_images = 15 # 模拟图像数量
img_size = (640, 480) # 模拟图像尺寸
# ========== 方式B:生成模拟棋盘格图像 ==========
def generate_simulated_images():
"""生成不同位姿的模拟棋盘格图像,返回图像列表"""
images = []
# 模拟内参(用于生成图像,标定结果应接近此值)
true_K = np.array([[520.0, 0, 320.0],
[0, 520.0, 240.0],
[0, 0, 1.0]])
true_dist = np.array([-0.2, 0.05, 0.0, 0.0, 0.01])
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) * square_size
for i in range(num_images):
# 随机位姿(旋转向量 + 平移向量)
rvec = np.array([np.random.uniform(-0.3, 0.3),
np.random.uniform(-0.3, 0.3),
np.random.uniform(-0.2, 0.2)])
tvec = np.array([np.random.uniform(-50, 50),
np.random.uniform(-50, 50),
np.random.uniform(400, 600)])
# 投影到图像平面
imgpts, _ = cv2.projectPoints(objp, rvec, tvec, true_K, true_dist)
imgpts = imgpts.reshape(-1, 2)
# 绘制棋盘格图像
img = np.full((img_size[1], img_size[0]), 255, dtype=np.uint8)
# 计算每个角点对应的棋盘格方块并填充
for row in range(chessboard_size[1]):
for col in range(chessboard_size[0]):
idx = row * chessboard_size[0] + col
x, y = imgpts[idx]
# 以角点为基准绘制小方块(简化绘制,仅用于角点检测)
pts = np.array([[x-12, y-12], [x+12, y-12],
[x+12, y+12], [x-12, y+12]], dtype=np.int32)
if (row + col) % 2 == 0:
cv2.fillPoly(img, [pts], 0)
images.append(img)
return images
# ========== 主标定流程 ==========
def calibrate_camera(images):
"""输入棋盘格图像列表,返回内参、畸变系数和重投影误差"""
# 准备对象点(棋盘格角点的3D坐标)
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) * square_size
objpoints = [] # 3D点
imgpoints = [] # 2D点
valid_images = []
for img in images:
gray = img if len(img.shape) == 2 else cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 查找棋盘格角点
ret, corners = cv2.findChessboardCorners(gray, chessboard_size, None)
if ret:
# 亚像素细化
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
corners2 = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
objpoints.append(objp)
imgpoints.append(corners2)
valid_images.append(img)
if len(objpoints) < 3:
raise RuntimeError(f"有效图像不足({len(objpoints)}/3),请调整棋盘格尺寸或图像质量")
# 执行标定
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(
objpoints, imgpoints, gray.shape[::-1], None, None)
# 计算重投影误差
total_error = 0
for i in range(len(objpoints)):
imgpoints2, _ = cv2.projectPoints(objpoints[i], rvecs[i], tvecs[i], mtx, dist)
error = cv2.norm(imgpoints[i], imgpoints2, cv2.NORM_L2) / len(imgpoints2)
total_error += error
mean_error = total_error / len(objpoints)
return mtx, dist, mean_error, len(objpoints)
# ========== 执行标定 ==========
if use_simulated:
print("正在生成模拟棋盘格图像...")
images = generate_simulated_images()
print(f"已生成 {len(images)} 张模拟图像")
else:
# 方式A:读取真实图像
images = []
for fname in sorted(os.listdir(image_dir)):
if fname.endswith(('.jpg', '.png', '.bmp')):
img = cv2.imread(os.path.join(image_dir, fname))
if img is not None:
images.append(img)
print(f"从目录加载 {len(images)} 张图像")
# 执行标定
mtx, dist, rms_error, used_count = calibrate_camera(images)
# ========== 输出结果 ==========
print(f"\n有效标定图像数: {used_count}")
print(f"重投影误差: {rms_error:.4f} 像素")
print("\n内参矩阵 K:")
print(mtx)
print("\n畸变系数 [k1, k2, p1, p2, k3]:")
print(dist.ravel())
运行方式与依赖
需要安装Python 3.8+、OpenCV(pip install opencv-python)和NumPy。将代码保存为camera_calibration.py,直接运行python camera_calibration.py即可。默认使用模拟图像,无需额外数据文件。
验证方法与判定标准
运行代码后观察以下输出:
- 有效标定图像数:应≥3,模拟模式下通常为15;
- 重投影误差:模拟模式下应小于0.1像素(因图像无噪声且位姿理想);真实图像标定通常要求小于0.5像素,若超过1像素需检查角点提取质量或剔除异常图像;
- 内参合理性:模拟模式下fx、fy应接近520,主点接近(320,240);真实相机则与传感器分辨率及镜头焦距匹配。
判定标定通过的标准:重投影误差RMS小于0.5像素(真实图像)或小于0.1像素(模拟图像),且内参数值在合理物理范围内。若误差过大,可增加图像数量、改变标定板位姿多样性(倾斜、旋转、远近变化)后重新标定。