name: camera-calibration description: OpenCV4 相机标定技能 - 单目/双目标定、畸变校正、立体匹配、深度估计、3D 重建 user-invocable: true argument-hint: 相机标定 OR 畸变校正 OR 立体匹配 OR 深度估计 OR 3D重建 OR 内参 OR 外参
OpenCV4 Camera Calibration Skill
相机标定与立体视觉完整指南
何时使用
当需要以下帮助时使用此技能:
- 单目相机标定(内参)
- 双目相机标定(内外参)
- 畸变校正
- 立体校正(极线校正)
- 立体匹配和深度估计
- 3D 重建
快速参考
标定板生成
import cv2
import numpy as np
import glob
# 生成棋盘格标定板
def generate_chessboard(size=(9, 6), square_size=1.0, save_path='chessboard.png'):
objp = np.zeros((size[0]*size[1], 3), np.float32)
objp[:, :2] = np.mgrid[0:size[0], 0:size[1]].T.reshape(-1, 2) * square_size
fig, ax = plt.subplots(figsize=(8, 6))
ax.set_xlim(0, size[0])
ax.set_ylim(0, size[1])
ax.set_aspect('equal')
ax.set_facecolor('white')
for i in range(size[0] + 1):
ax.axvline(i, color='black', linewidth=1)
for i in range(size[1] + 1):
ax.axhline(i, color='black', linewidth=1)
plt.axis('off')
plt.savefig(save_path, dpi=150, bbox_inches='tight',
facecolor='white', edgecolor='none')
plt.close()
# 或使用 OpenCV 打印标定板
# 国际象棋:9x6 角点,80mm 格子
# 圆形标定板:4x11 圆点阵列
单目相机标定
import cv2
import numpy as np
import glob
# 标定板参数
objp = np.zeros((6*9, 3), np.float32)
objp[:, :2] = np.mgrid[0:9, 0:6].T.reshape(-1, 2)
objp *= 25 # 格子大小 mm
# 存储所有图像的点
objpoints = [] # 3D 点
imgpoints = [] # 2D 点
images = glob.glob('calibration/*.jpg')
img_size = None
for fname in images:
img = cv2.imread(fname)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img_size = gray.shape[::-1]
# 查找棋盘格角点
ret, corners = cv2.findChessboardCorners(gray, (9, 6), None)
if ret:
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, (9, 6), corners2, ret)
cv2.imshow('Corners', img)
cv2.waitKey(500)
cv2.destroyAllWindows()
# 标定
ret, camera_matrix, dist_coeffs, rvecs, tvecs = cv2.calibrateCamera(
objpoints, imgpoints, img_size, None, None)
print(f"Camera Matrix:\n{camera_matrix}")
print(f"Distortion Coefficients:\n{dist_coeffs}")
# 保存标定结果
np.savez('calibration.npz',
camera_matrix=camera_matrix,
dist_coeffs=dist_coeffs,
rvecs=rvecs,
tvecs=tvecs)
畸变校正
# 加载标定结果
data = np.load('calibration.npz')
camera_matrix = data['camera_matrix']
dist_coeffs = data['dist_coeffs']
# 读取图像
img = cv2.imread('test.jpg')
h, w = img.shape[:2]
# 方法1:矫正映射
new_camera_matrix, roi = cv2.getOptimalNewCameraMatrix(
camera_matrix, dist_coeffs, (w, h), 1, (w, h))
mapx, mapy = cv2.initUndistortRectifyMap(
camera_matrix, dist_coeffs, None, new_camera_matrix, (w, h), 5)
undistorted = cv2.remap(img, mapx, mapy, cv2.INTER_LINEAR)
# 方法2:一步校正
undistorted = cv2.undistort(img, camera_matrix, dist_coeffs, None, new_camera_matrix)
# 裁剪 ROI 区域
x, y, w, h = roi
undistorted = undistorted[y:y+h, x:x+w]
双目相机标定
import cv2
import numpy as np
import glob
# 左右相机标定板点
objp = np.zeros((6*9, 3), np.float32)
objp[:, :2] = np.mgrid[0:9, 0:6].T.reshape(-1, 2) * 25
objpoints_l, imgpoints_l = [], []
objpoints_r, imgpoints_r = [], []
# 读取左右图像
images_l = sorted(glob.glob('stereo/left/*.jpg'))
images_r = sorted(glob.glob('stereo/right/*.jpg'))
for fname_l, fname_r in zip(images_l, images_r):
img_l = cv2.imread(fname_l)
img_r = cv2.imread(fname_r)
gray_l = cv2.cvtColor(img_l, cv2.COLOR_BGR2GRAY)
gray_r = cv2.cvtColor(img_r, cv2.COLOR_BGR2GRAY)
ret_l, corners_l = cv2.findChessboardCorners(gray_l, (9, 6), None)
ret_r, corners_r = cv2.findChessboardCorners(gray_r, (9, 6), None)
if ret_l and ret_r:
objpoints_l.append(objp)
objpoints_r.append(objp)
# 亚像素优化
corners_l = cv2.cornerSubPix(gray_l, corners_l, (11,11), (-1,-1),
(cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001))
corners_r = cv2.cornerSubPix(gray_r, corners_r, (11,11), (-1,-1),
(cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001))
imgpoints_l.append(corners_l)
imgpoints_r.append(corners_r)
# 分别标定左右相机
ret_l, mtx_l, dist_l, _, _ = cv2.calibrateCamera(objpoints_l, imgpoints_l, gray_l.shape[::-1], None, None)
ret_r, mtx_r, dist_r, _, _ = cv2.calibrateCamera(objpoints_r, imgpoints_r, gray_r.shape[::-1], None, None)
# 双目标定
ret, mtx_l, dist_l, mtx_r, dist_r, R, T, E, F = cv2.stereoCalibrate(
objpoints_l, imgpoints_l, imgpoints_r,
mtx_l, dist_l, mtx_r, dist_r,
gray_l.shape[::-1],
flags=cv2.CALIB_FIX_INTRINSIC)
print(f"Rotation R:\n{R}")
print(f"Translation T:\n{T}")
立体校正和匹配
# 立体校正
R1, R2, P1, P2, Q, _, _ = cv2.stereoRectify(
mtx_l, dist_l, mtx_r, dist_r,
gray_l.shape[::-1], R, T)
# 计算映射表
map1_l, map2_l = cv2.initUndistortRectifyMap(mtx_l, dist_l, R1, P1, gray_l.shape[::-1], cv2.CV_32FC2)
map1_r, map2_r = cv2.initUndistortRectifyMap(mtx_r, dist_r, R2, P2, gray_r.shape[::-1], cv2.CV_32FC2)
# 校正图像
rect_l = cv2.remap(img_l, map1_l, map2_l, cv2.INTER_LINEAR)
rect_r = cv2.remap(img_r, map1_r, map2_r, cv2.INTER_LINEAR)
# SGBM 立体匹配
stereo = cv2.StereoSGBM_create(minDisparity=0, numDisparities=64, blockSize=9)
disparity = stereo.compute(rect_l, rect_r)
# 转换为深度图
depth = (mtx_l[0,0] * T[0,0]) / disparity
ROS2 集成
# ROS2 相机标定
from rclpy.node import Node
from sensor_msgs.msg import Image
from cv_bridge import CvBridge
import cv2
import numpy as np
class CameraCalibrator(Node):
def __init__(self):
super().__init__('camera_calibrator')
self.bridge = CvBridge()
self.objpoints = []
self.imgpoints = []
self.objp = np.zeros((6*9, 3), np.float32)
self.objp[:, :2] = np.mgrid[0:9, 0:6].T.reshape(-1, 2)
self.subscription = self.create_subscription(
Image, '/camera/image_raw', self.calibration_callback, 10)
def calibration_callback(self, msg):
img = self.bridge.imgmsg_to_cv2(msg, 'bgr8')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, corners = cv2.findChessboardCorners(gray, (9, 6), None)
if ret:
corners2 = cv2.cornerSubPix(gray, corners, (11,11), (-1,-1),
(cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001))
self.objpoints.append(self.objp)
self.imgpoints.append(corners2)
cv2.drawChessboardCorners(img, (9, 6), corners2, ret)
cv2.imshow('Calibration', img)
cv2.waitKey(1)
def calibrate(self, img_size):
ret, mtx, dist, _, _ = cv2.calibrateCamera(
self.objpoints, self.imgpoints, img_size, None, None)
return mtx, dist
最佳实践
-
标定板准备:
- 打印在高精度纸上,或使用显示器显示
- 确保标定板平整无褶皱
- 格子尺寸精确(常用 25mm 或 30mm)
-
标定图像采集:
- 至少 15-20 张图像
- 覆盖整个视野(中心和边缘)
- 不同角度和距离(俯仰、偏航、滚动)
- 光照均匀,避免反光
-
标定质量评估:
- 重投影误差 < 0.5 像素
- 检查各图像的角点检测精度
- 剔除误差大的图像重新标定
-
双目标定:
- 确保左右相机同步采集
- 两相机在同一平面上(简化标定)
- 基线距离影响深度精度
相关技能
- opencv-image-processing - 图像处理基础
- opencv-depth-estimation - 深度估计