3. Eye-in-Hand vs Eye-to-Hand 配置选择

任务目标

在机器人视觉引导应用中,相机与机械臂的安装关系决定了标定流程与精度特性。本案例要求:根据给定应用场景(工作空间大小、视野遮挡、精度需求、末端负载),判断应选用 Eye-in-Hand(相机安装在机械臂末端)还是 Eye-to-Hand(相机固定安装在工作空间外部),并给出对应的标定方法选择依据。

两种配置的核心差异

对比维度Eye-in-HandEye-to-Hand
相机安装位置机械臂末端法兰工作空间外固定支架
标定目标矩阵手眼矩阵 X(相机→末端)手眼矩阵 X(相机→基座)
视野特性随机械臂运动,可近距离多角度观察目标固定视野,需覆盖整个工作区域
遮挡问题机械臂自身可能遮挡目标机械臂运动时可能遮挡相机视线
标定方程AX = XB(Tsai-Lenz 等)AX = XB(形式相同,但 A/B 含义不同)
典型应用小工件抓取、近距离检测、移动视觉大范围定位、装配对位、安全监控

配置选择判定逻辑

  1. 视野需求:若目标物在机械臂工作空间内分布广且需要全局视图,优先 Eye-to-Hand;若目标小且需贴近观察,选 Eye-in-Hand。
  2. 遮挡风险:若机械臂运动轨迹会频繁遮挡固定相机视线,选 Eye-in-Hand;若末端工具会遮挡末端相机,选 Eye-to-Hand。
  3. 精度取向:Eye-in-Hand 在近距离时标定精度更高(像素分辨率高),但受末端负载与振动影响;Eye-to-Hand 精度受安装距离限制,但稳定性好。
  4. 负载约束:末端负载已接近额定值时,不宜再加装相机,选 Eye-to-Hand。

标定方法选择

  • Eye-in-Hand:使用 Tsai-Lenz 或 Park-Martin 方法求解 AX = XB,需要至少 3 组机械臂位姿与对应标定板观测。
  • Eye-to-Hand:同样求解 AX = XB,但 A 为机械臂基座到末端的变换,B 为相机到标定板的变换,X 为相机到基座的固定变换。

最小验证代码(配置判定与标定方程构建)


import numpy as np
from scipy.spatial.transform import Rotation as R

def is_eye_in_hand(need_close_view: bool, 
                   end_effector_payload_ok: bool,
                   fixed_cam_occlusion_risk: bool) -> bool:
    """
    判定是否选用 Eye-in-Hand。
    条件:需要近距离观察 且 末端负载允许 且 固定相机存在遮挡风险。
    """
    return need_close_view and end_effector_payload_ok and fixed_cam_occlusion_risk

def build_handeye_A(pose_base_to_end: np.ndarray) -> np.ndarray:
    """输入 4x4 齐次矩阵(基座→末端),返回 AX=XB 中的 A 矩阵。"""
    return pose_base_to_end

def build_handeye_B(pose_cam_to_target: np.ndarray) -> np.ndarray:
    """输入 4x4 齐次矩阵(相机→标定板),返回 AX=XB 中的 B 矩阵。"""
    return pose_cam_to_target

def solve_handeye(A_list: list, B_list: list) -> np.ndarray:
    """
    使用 Tsai-Lenz 方法求解 X(手眼矩阵)。
    A_list: 基座→末端 变换列表
    B_list: 相机→标定板 变换列表
    返回 X: 4x4 手眼矩阵
    """
    assert len(A_list) == len(B_list) >= 3, "至少需要3组位姿"
    # 构建旋转方程:Ra * Rx = Rx * Rb
    M = []
    rhs = []
    for A, B in zip(A_list, B_list):
        Ra = A[:3, :3]
        Rb = B[:3, :3]
        # 使用轴角表示构造线性方程
        from scipy.spatial.transform import Rotation
        ra = Rotation.from_matrix(Ra).as_rotvec()
        rb = Rotation.from_matrix(Rb).as_rotvec()
        # 简化:实际 Tsai-Lenz 使用罗德里格斯变换,此处用最小二乘近似
        M.append(np.kron(np.eye(3), Ra - Rb))  # 占位,实际需用专用算法
        rhs.append((rb - ra).reshape(-1, 1))
    # 实际工程中请使用 OpenCV 的 cv2.calibrateHandEye()
    # 以下仅为结构示意,不用于真实求解
    return np.eye(4)

# ===== 场景判定示例 =====
# 场景A:小型零件抓取,相机需贴近工件,末端负载余量充足,固定相机可能被机械臂遮挡
use_eih = is_eye_in_hand(need_close_view=True, 
                         end_effector_payload_ok=True,
                         fixed_cam_occlusion_risk=True)
print(f"场景A 选用 Eye-in-Hand: {use_eih}")

# 场景B:大型工件装配,需全局视野,末端已满载,无固定遮挡问题
use_eih_b = is_eye_in_hand(need_close_view=False,
                           end_effector_payload_ok=False,
                           fixed_cam_occlusion_risk=False)
print(f"场景B 选用 Eye-in-Hand: {use_eih_b}")

# ===== 标定数据准备(Eye-in-Hand 示例)=====
# 模拟3组位姿(实际数据来自机械臂示教与标定板识别)
A1 = np.eye(4); A1[:3, 3] = [0.3, 0.0, 0.2]
A2 = np.eye(4); A2[:3, 3] = [0.3, 0.1, 0.2]
A3 = np.eye(4); A3[:3, 3] = [0.3, 0.0, 0.3]
B1 = np.eye(4); B1[:3, 3] = [0.05, -0.02, 0.15]
B2 = np.eye(4); B2[:3, 3] = [0.05, 0.03, 0.15]
B3 = np.eye(4); B3[:3, 3] = [0.05, -0.02, 0.25]

# 实际求解请使用 OpenCV:
# import cv2
# R_cam2end, t_cam2end = cv2.calibrateHandEye(
#     [A[:3,:3] for A in [A1,A2,A3]], 
#     [A[:3,3] for A in [A1,A2,A3]],
#     [B[:3,:3] for B in [B1,B2,B3]],
#     [B[:3,3] for B in [B1,B2,B3]]
# )
print("标定数据已准备,共3组位姿")

运行与验证

  1. 依赖安装:pip install numpy scipy opencv-python
  2. 执行判定逻辑:运行上述代码,观察场景A输出 True,场景B输出 False,验证配置选择逻辑正确。
  3. 标定验证路径:将代码中注释的 OpenCV 调用取消注释,替换为真实采集的机械臂位姿与标定板外参,求解手眼矩阵 X。验证方法:用求解出的 X 将标定板坐标系下的点变换到机械臂基座坐标系,重投影误差小于 1mm 即通过。

判定通过标准

  • 配置选择逻辑输出与场景设定一致(场景A选Eye-in-Hand,场景B选Eye-to-Hand)。
  • 标定代码能处理至少3组输入位姿并输出4x4手眼矩阵。
  • 使用真实数据时,重投影误差满足应用精度要求。
Eye-in-Hand vs Eye-to-Hand 配置示意 Eye-in-Hand 基座 末端 相机 视野随末端移动 目标工件 Eye-to-Hand 固定相机 固定视野覆盖工作区 基座 末端 目标工件 注意:机械臂可能遮挡固定相机视线 选择依据:视野需求、遮挡风险、末端负载、精度取向