在计算机视觉和机器人领域,相机标定是最基础也是最重要的前置工作之一。传统标定方法通常依赖棋盘格或圆点标定板,需要人工手持标定板在不同位置拍摄多张照片。这种方法虽然成熟,但在仿真环境中就显得不够高效。
Mujoco作为一款强大的物理仿真引擎,其内置的视觉传感器模块可以模拟真实相机的成像过程。我们完全可以在虚拟环境中构建一个标准化场景,通过程序化控制自动完成相机内参标定。棋格盘标定法因其简单可靠的特点,成为仿真环境中的理想选择。
这种方法的核心优势在于:
首先需要确保Mujoco环境正确安装。建议使用最新版的Mujoco 2.3.0+,其对视觉传感器的支持更加完善。在XML模型文件中,我们需要定义以下几个关键组件:
xml复制<visual>
<global offwidth="640" offheight="480"/>
</visual>
<sensor>
<camera name="main_cam" pos="0 0 1.5" xyaxes="1 0 0 0 1 0"/>
</sensor>
这里设置了640x480分辨率的渲染窗口,并定义了一个位于(0,0,1.5)位置的正视相机。xyaxes参数确定了相机的朝向。
棋格盘的建模需要考虑以下几个参数:
xml复制<asset>
<material name="white" rgba="1 1 1 1"/>
<material name="black" rgba="0 0 0 1"/>
</asset>
<body name="checkerboard" pos="0 0 0">
<geom type="plane" size="0.4 0.3 0.01" pos="0 0 0"
material="checker" rgba="1 1 1 1"/>
</body>
在仿真环境中,我们可以获得比真实场景更理想的角点。使用OpenCV的findChessboardCorners函数时,建议设置以下参数:
python复制criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
flags = cv2.CALIB_CB_ADAPTIVE_THRESH + cv2.CALIB_CB_NORMALIZE_IMAGE
由于仿真图像没有噪声,可以将adaptiveThreshWinSizeStep设置为较小的值(如5)来提高检测速度。
在仿真环境中,我们可以精确控制标定板的位姿变化。建议采用以下位姿序列:
python复制for pitch in np.linspace(-30, 30, 5):
for yaw in np.linspace(-30, 30, 5):
# 设置标定板位姿
data.qpos[board_joint_ids] = [0, 0, 1, pitch, yaw, 0]
mujoco.mj_step(model, data)
# 捕获图像
img = data.render()
corners = detect_corners(img)
if corners is not None:
img_points.append(corners)
obj_points.append(objp)
使用OpenCV的calibrateCamera函数时,建议启用以下选项:
python复制ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(
objectPoints=obj_points,
imagePoints=img_points,
imageSize=(640,480),
flags=cv2.CALIB_FIX_K3 + cv2.CALIB_FIX_TANGENT_DIST
)
在仿真环境中,我们可以关闭一些不必要的光学畸变参数,因为虚拟相机通常不存在切向畸变和高阶径向畸变。
python复制import mujoco
import cv2
import numpy as np
def main():
# 初始化Mujoco
model = mujoco.MjModel.from_xml_path("scene.xml")
data = mujoco.MjData(model)
# 标定参数设置
board_size = (8,6) # 内部角点数量
square_size = 0.05 # 方格物理尺寸(m)
# 准备标定数据
objp = prepare_object_points(board_size, square_size)
img_points, obj_points = collect_calibration_data(model, data, objp)
# 执行标定
calibrate_camera(img_points, obj_points)
def prepare_object_points(board_size, square_size):
objp = np.zeros((board_size[0]*board_size[1],3), np.float32)
objp[:,:2] = np.mgrid[0:board_size[0],0:board_size[1]].T.reshape(-1,2)
objp *= square_size
return objp
python复制def collect_calibration_data(model, data, objp, num_poses=20):
img_points = [] # 2D图像点
obj_points = [] # 3D物体点
board_joint_ids = [model.joint(name).id for name in ["board_x", "board_y", "board_z"]]
for _ in range(num_poses):
# 随机生成标定板位姿
pos = np.random.uniform(-0.5, 0.5, size=3)
rot = np.random.uniform(-30, 30, size=3) * np.pi/180
data.qpos[board_joint_ids] = np.concatenate([pos, rot])
mujoco.mj_step(model, data)
# 渲染并检测角点
img = data.render()
gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
ret, corners = cv2.findChessboardCorners(gray, board_size, None)
if ret:
corners = cv2.cornerSubPix(gray, corners, (5,5), (-1,-1), criteria)
img_points.append(corners)
obj_points.append(objp)
return img_points, obj_points
标定完成后,我们需要评估标定质量:
python复制mean_error = 0
for i in range(len(obj_points)):
imgpoints2, _ = cv2.projectPoints(obj_points[i], rvecs[i], tvecs[i], mtx, dist)
error = cv2.norm(img_points[i], imgpoints2, cv2.NORM_L2)/len(imgpoints2)
mean_error += error
print(f"Total reprojection error: {mean_error/len(obj_points):.3f} pixels")
在理想仿真环境中,这个误差应该小于0.1像素。如果误差较大,可能是以下原因导致:
建议绘制标定板的检测结果和重投影点:
python复制for i in range(3): # 可视化前3个样本
img = data.render()
img = cv2.drawChessboardCorners(img, board_size, img_points[i], True)
cv2.imshow(f'Frame {i}', img)
cv2.waitKey(500)
虽然仿真环境没有真实的光照变化,但我们可以模拟不同曝光条件下的标定:
python复制# 在数据采集循环中添加
for exposure in [0.5, 1.0, 2.0]:
model.vis.global_.offwidth = int(640 * exposure)
model.vis.global_.offheight = int(480 * exposure)
# 重新渲染并采集数据
为验证相机模型在不同分辨率下的稳定性,可以进行多尺度标定:
python复制resolutions = [(320,240), (640,480), (1280,960)]
for w, h in resolutions:
model.vis.global_.offwidth = w
model.vis.global_.offheight = h
# 执行完整标定流程
为模拟真实环境,可以人为添加噪声:
python复制img = data.render()
noise = np.random.normal(0, 5, img.shape).astype(np.uint8)
noisy_img = cv2.add(img, noise)
标定板尺寸选择:在仿真中可以使用比真实环境更大的标定板,建议物理尺寸在0.5m×0.5m左右,这样可以获得更好的远距离标定效果。
位姿分布优化:确保标定板位姿在6D空间(3平移+3旋转)中均匀分布,避免所有位姿都集中在一个小区域内。
并行化采集:由于仿真环境可以精确控制,可以设计并行的数据采集策略,大幅提高标定效率。
参数验证:在标定完成后,应该使用独立的测试集验证参数准确性,避免过拟合。
模型导出:将标定结果保存为标准的相机模型格式(如ROS相机信息格式),方便与其他系统集成:
python复制import yaml
def save_camera_info(filename, mtx, dist, size):
data = {
'image_width': size[0],
'image_height': size[1],
'camera_matrix': {
'rows': 3,
'cols': 3,
'data': mtx.flatten().tolist()
},
'distortion_coefficients': {
'rows': 1,
'cols': len(dist[0]),
'data': dist[0].flatten().tolist()
}
}
with open(filename, 'w') as f:
yaml.dump(data, f)
在实际项目中,我发现仿真标定的最大优势是可以快速验证不同标定算法的稳定性。通过脚本化控制,我们可以轻松生成数千组不同条件下的标定数据,这在真实环境中几乎是不可能完成的任务。建议在项目初期使用这种方法确定最优的标定参数组合,再迁移到真实系统中使用。