1. 相机标定与手眼标定基础

任务目标

本案例完成两项标定:相机内参标定(获取焦距、主点及畸变系数)与眼在手外(Eye-to-Hand)手眼标定(获取相机坐标系到机器人基坐标系的变换矩阵)。标定结果将用于后续视觉引导抓取中,将像素坐标转换为机器人基坐标系下的三维位置。

环境与依赖

  • Python 3.8+,安装OpenCV(pip install opencv-python opencv-contrib-python)与NumPy
  • 棋盘格标定板(如10×7内角点,方格边长30mm)
  • 机器人需支持通过API读取当前末端位姿(本例以通用接口示意,实际使用时替换为对应机器人SDK调用)

实现方案

内参标定采用张正友法,采集15~20张不同位姿的棋盘格图像,提取角点后计算内参矩阵与畸变系数。手眼标定采用眼在手外构型,固定标定板于机器人末端,控制机器人移动多个位姿,在每个位姿下同时记录:①相机拍摄标定板得到的外参(标定板→相机);②机器人读取的末端→基座变换。利用AX=XB方程求解相机→基座的固定变换。

关键完整代码

import cv2
import numpy as np
import glob

# ========== 1. 相机内参标定 ==========
def calibrate_camera(img_paths, pattern_size=(7, 10), square_size=30.0):
    """img_paths: 棋盘格图像路径列表; pattern_size: 内角点数(宽,高)"""
    criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
    objp = np.zeros((pattern_size[0]*pattern_size[1], 3), np.float32)
    objp[:, :2] = np.mgrid[0:pattern_size[0], 0:pattern_size[1]].T.reshape(-1, 2) * square_size

    obj_points, img_points = [], []
    for fname in img_paths:
        img = cv2.imread(fname)
        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        ret, corners = cv2.findChessboardCorners(gray, pattern_size, None)
        if ret:
            obj_points.append(objp)
            corners2 = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
            img_points.append(corners2)
    ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(obj_points, img_points, gray.shape[::-1], None, None)
    return mtx, dist  # 内参矩阵与畸变系数

# ========== 2. 眼在手外手眼标定 ==========
def hand_eye_calibration(robot_poses, cam_poses):
    """
    robot_poses: 机器人末端→基座的变换列表, 每个为4x4矩阵 (从机器人API读取)
    cam_poses:   标定板→相机的变换列表, 每个为4x4矩阵 (由cv2.solvePnP获得)
    返回: 相机→基座的4x4变换矩阵
    """
    R_gripper2base, t_gripper2base = [], []
    R_target2cam, t_target2cam = [], []
    for T_base_gripper, T_cam_target in zip(robot_poses, cam_poses):
        # 机器人返回的是 末端→基座, 即 gripper→base
        R_gripper2base.append(T_base_gripper[:3, :3])
        t_gripper2base.append(T_base_gripper[:3, 3])
        # 相机外参给出 标定板→相机, 即 target→cam
        R_target2cam.append(T_cam_target[:3, :3])
        t_target2cam.append(T_cam_target[:3, 3])
    R_cam2base, t_cam2base = cv2.calibrateHandEye(
        R_gripper2base, t_gripper2base, R_target2cam, t_target2cam, method=cv2.CALIB_HAND_EYE_TSAI)
    T_cam2base = np.eye(4)
    T_cam2base[:3, :3] = R_cam2base
    T_cam2base[:3, 3] = t_cam2base.flatten()
    return T_cam2base

# ========== 3. 采集标定板位姿(示例: 单帧处理) ==========
def get_cam_pose_from_image(img, mtx, dist, pattern_size=(7, 10), square_size=30.0):
    """从单张图像计算标定板→相机的变换矩阵"""
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    ret, corners = cv2.findChessboardCorners(gray, pattern_size, None)
    if not ret:
        return None
    objp = np.zeros((pattern_size[0]*pattern_size[1], 3), np.float32)
    objp[:, :2] = np.mgrid[0:pattern_size[0], 0:pattern_size[1]].T.reshape(-1, 2) * square_size
    ret, rvec, tvec = cv2.solvePnP(objp, corners, mtx, dist)
    T_cam_target = np.eye(4)
    R, _ = cv2.Rodrigues(rvec)
    T_cam_target[:3, :3] = R
    T_cam_target[:3, 3] = tvec.flatten()
    return T_cam_target

# ========== 主流程 ==========
if __name__ == "__main__":
    # --- 内参标定 ---
    mtx, dist = calibrate_camera(glob.glob("calib_images/*.jpg"))
    print("内参矩阵:\n", mtx)
    print("畸变系数:", dist)

    # --- 手眼标定数据采集(需在实际机器人上执行) ---
    robot_poses = []   # 每个元素: 4x4矩阵, 末端→基座
    cam_poses = []     # 每个元素: 4x4矩阵, 标定板→相机

    # 示例: 假设已采集N个位姿(实际运行时循环采集)
    # for i in range(15):
    #     img = cv2.imread(f"handeye_images/{i}.jpg")
    #     T_cam_target = get_cam_pose_from_image(img, mtx, dist)
    #     T_base_gripper = robot.get_pose()  # 替换为实际机器人API
    #     if T_cam_target is not None:
    #         robot_poses.append(T_base_gripper)
    #         cam_poses.append(T_cam_target)

    # --- 计算手眼变换 ---
    # T_cam2base = hand_eye_calibration(robot_poses, cam_poses)
    # print("相机→基座变换矩阵:\n", T_cam2base)
    # np.save("cam2base.npy", T_cam2base)  # 保存结果供后续使用

运行与验证

  1. 内参标定:准备15~20张不同角度拍摄的棋盘格图像,放入calib_images/目录,运行代码后观察重投影误差(可在calibrateCamera返回值中获取),通常RMS应小于0.5像素。
  2. 手眼标定数据采集:将标定板固定在机器人末端,控制机器人运动15个以上不同位姿(保证姿态差异明显),每个位姿下同时拍摄图像并记录机器人末端位姿。将代码中注释部分取消注释并接入实际机器人SDK。
  3. 验证方法:计算得到T_cam2base后,选取一个未参与标定的测试位姿,将标定板角点的像素坐标通过内参反投影到相机坐标系,再用T_cam2base变换到机器人基坐标系,与机器人实际读取的该角点基坐标位置比较,误差小于5mm即标定合格。

常见问题

  • 内参标定图像数量不足或角度单一会导致畸变估计不准,确保覆盖画面边缘区域。
  • 手眼标定中机器人位姿变化范围过小会降低求解精度,尽量让末端做多方向旋转运动。
  • 若使用不同棋盘格尺寸,需同步修改pattern_size与square_size参数。