在计算机视觉和摄影测量领域,相机标定是获取相机内部参数的关键步骤。简单来说,就像给相机做一次"体检",通过特定的方法测量出它的"视力参数"。这些参数决定了相机如何将三维世界转换为二维图像,直接影响后续视觉算法的精度。
相机内参主要包括:
传统标定方法使用棋盘格,但存在角点检测不稳定的问题。Charuco(Chessboard + ArUco)标定板结合了棋盘格的规则性和ArUco标记的鲁棒性,即使在部分遮挡情况下也能准确定位角点。这种混合标定板由以下元素组成:
提示:选择Charuco而非纯棋盘格的主要优势在于,即使标定板部分被遮挡或光照不均,ArUco标记仍能提供可靠的定位参考,显著提高标定成功率。
Charuco标定板的设计需要考虑实际使用场景。我常用的参数配置如下:
python复制import cv2
import numpy as np
# Charuco板参数
squaresX = 7 # 横向方格数
squaresY = 5 # 纵向方格数
squareLength = 30 # 方格边长(mm)
markerLength = 15 # ArUco标记边长(mm)
dictionary = cv2.aruco.getPredefinedDictionary(cv2.aruco.DICT_6X6_250)
参数选择经验:
生成标定板图像并确保打印精度:
python复制# 生成Charuco板图像
board = cv2.aruco.CharucoBoard_create(
squaresX, squaresY, squareLength, markerLength, dictionary)
img = board.draw((2000, 2000)) # 输出图像分辨率
# 保存图像
cv2.imwrite("charuco_board.png", img)
打印注意事项:
实测发现:使用普通喷墨打印时,热胀冷缩可能导致尺寸变化达0.5%,建议打印后静置24小时再使用。
采集标定图像的质量直接影响最终结果。建议遵循以下流程:
python复制# 实时检测示例
cap = cv2.VideoCapture(0)
while True:
ret, frame = cap.read()
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 检测Charuco角点
corners, ids, _ = cv2.aruco.detectMarkers(gray, dictionary)
if len(corners) > 0:
ret, charuco_corners, charuco_ids = cv2.aruco.interpolateCornersCharuco(
corners, ids, gray, board)
# 可视化
cv2.aruco.drawDetectedMarkers(frame, corners, ids)
if ret > 0:
cv2.aruco.drawDetectedCornersCharuco(frame, charuco_corners, charuco_ids)
cv2.imshow('Live Detection', frame)
if cv2.waitKey(1) & 0xFF == ord('s'):
# 保存合格图像
cv2.imwrite(f'calib_{time.time()}.jpg', frame)
elif cv2.waitKey(1) & 0xFF == 27:
break
OpenCV提供了完整的Charuco标定接口:
python复制def calibrate_camera(all_corners, all_ids, board, image_size):
"""
all_corners: 所有图像检测到的角点列表
all_ids: 对应的角点ID列表
board: Charuco板对象
image_size: 图像尺寸 (w,h)
"""
cameraMatrixInit = np.array([
[1000., 0., image_size[0]/2.],
[0., 1000., image_size[1]/2.],
[0., 0., 1.]])
distCoeffsInit = np.zeros((5,1))
flags = (cv2.CALIB_USE_INTRINSIC_GUESS +
cv2.CALIB_RATIONAL_MODEL +
cv2.CALIB_FIX_ASPECT_RATIO)
(ret, camera_matrix, distortion_coeffs,
rvecs, tvecs) = cv2.aruco.calibrateCameraCharuco(
charucoCorners=all_corners,
charucoIds=all_ids,
board=board,
imageSize=image_size,
cameraMatrix=cameraMatrixInit,
distCoeffs=distCoeffsInit,
flags=flags)
return ret, camera_matrix, distortion_coeffs, rvecs, tvecs
关键参数说明:
CALIB_USE_INTRINSIC_GUESS:提供初始相机矩阵加速收敛CALIB_RATIONAL_MODEL:使用更复杂的畸变模型(包含k3)CALIB_FIX_ASPECT_RATIO:固定焦距比fx/fy,除非使用非方形像素传感器标定质量主要通过重投影误差评估:
python复制mean_error = 0
for i in range(len(all_corners)):
# 将角点投影到图像平面
img_points, _ = cv2.projectPoints(
objectPoints=board.chessboardCorners[all_ids[i]],
rvec=rvecs[i],
tvec=tvecs[i],
cameraMatrix=camera_matrix,
distCoeffs=distortion_coeffs)
# 计算误差
error = cv2.norm(all_corners[i], img_points, cv2.NORM_L2)
mean_error += error
mean_error /= len(all_corners)
print(f"重投影误差: {mean_error} 像素")
误差评估标准:
2.0像素:需重新标定
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 角点检测失败 | 光照不均/反光 | 调整光源位置,使用漫反射材料 |
| 重投影误差大 | 标定板不平整 | 更换刚性底板,确保平整 |
| 焦距异常 | 拍摄姿势单一 | 增加不同距离和角度的图像 |
| 畸变系数过大 | 镜头质量差 | 检查镜头是否有物理变形 |
| 标定结果不稳定 | 运动模糊 | 使用三脚架,提高快门速度 |
焦距初始化技巧:
python复制# 根据传感器尺寸和焦距估算初始值
sensor_width_mm = 4.8 # 例如iPhone的传感器宽度
focal_length_mm = 4.2 # 手机镜头的物理焦距
fx_init = (camera_matrix[0,0] / image_size[0]) * sensor_width_mm
畸变系数约束:
python复制flags = cv2.CALIB_FIX_K3 # 如果k3不稳定可以固定为0
标定板覆盖范围检查:
python复制# 计算标定板在图像中的覆盖面积
hull = cv2.convexHull(np.vstack(all_corners))
area_ratio = cv2.contourArea(hull) / (image_size[0]*image_size[1])
print(f"标定板覆盖面积比例: {area_ratio:.1%}")
建议保持在30%-70%之间
直线测试法:
距离测量验证:
python复制# 测量已知物理距离的两点在图像中的像素距离
pixel_length = np.linalg.norm(point1 - point2)
real_length_mm = 100 # 实际物理长度
calculated_focal = (pixel_length * real_length_mm) / (sensor_width_mm * image_size[0])
print(f"估算焦距: {calculated_focal}mm")
多位置一致性检查:
python复制# 去畸变示例
map1, map2 = cv2.initUndistortRectifyMap(
cameraMatrix=camera_matrix,
distCoeffs=distortion_coeffs,
R=None,
newCameraMatrix=camera_matrix,
size=(width, height),
m1type=cv2.CV_32FC1)
undistorted_img = cv2.remap(
src=distorted_img,
map1=map1,
map2=map2,
interpolation=cv2.INTER_LINEAR)
利用标定结果进行距离测量:
python复制def calculate_distance(point1, point2, camera_matrix, z_distance):
"""
计算图像中两点对应的实际距离
point1, point2: 图像坐标 (x,y)
z_distance: 目标平面到相机的垂直距离 (mm)
"""
# 将像素坐标转换到归一化平面
inv_cam_mat = np.linalg.inv(camera_matrix)
norm_pt1 = inv_cam_mat @ np.array([point1[0], point1[1], 1])
norm_pt2 = inv_cam_mat @ np.array([point2[0], point2[1], 1])
# 计算实际距离
world_pt1 = norm_pt1 * z_distance
world_pt2 = norm_pt2 * z_distance
return np.linalg.norm(world_pt1 - world_pt2)
当需要多个相机协同工作时,还需进行外参标定:
python复制# 使用Charuco板进行立体标定
ret, _, _, _, _, R, T, E, F = cv2.stereoCalibrate(
objectPoints=obj_points,
imagePoints1=img_points1,
imagePoints2=img_points2,
cameraMatrix1=cam_matrix1,
distCoeffs1=dist_coeffs1,
cameraMatrix2=cam_matrix2,
distCoeffs2=dist_coeffs2,
imageSize=image_size)
关键点:
经过多次项目实践,我发现标定过程中最容易忽视的是环境光照的影响。有次在窗户附近标定,下午的阳光导致图像一侧过曝,使得标定结果出现系统性偏差。后来我们建立了标准的光照环境,所有标定都在亮度可控的灯光下进行,结果一致性显著提高。另一个实用技巧是:在标定板四周添加额外的ArUco标记,这样即使主棋盘格部分超出画面,仍能进行标定。