3. Eye-in-Hand vs Eye-to-Hand 配置选择
任务目标
在机器人视觉引导应用中,相机与机械臂的安装关系决定了标定流程与精度特性。本案例要求:根据给定应用场景(工作空间大小、视野遮挡、精度需求、末端负载),判断应选用 Eye-in-Hand(相机安装在机械臂末端)还是 Eye-to-Hand(相机固定安装在工作空间外部),并给出对应的标定方法选择依据。
两种配置的核心差异
| 对比维度 | Eye-in-Hand | Eye-to-Hand |
|---|---|---|
| 相机安装位置 | 机械臂末端法兰 | 工作空间外固定支架 |
| 标定目标矩阵 | 手眼矩阵 X(相机→末端) | 手眼矩阵 X(相机→基座) |
| 视野特性 | 随机械臂运动,可近距离多角度观察目标 | 固定视野,需覆盖整个工作区域 |
| 遮挡问题 | 机械臂自身可能遮挡目标 | 机械臂运动时可能遮挡相机视线 |
| 标定方程 | AX = XB(Tsai-Lenz 等) | AX = XB(形式相同,但 A/B 含义不同) |
| 典型应用 | 小工件抓取、近距离检测、移动视觉 | 大范围定位、装配对位、安全监控 |
配置选择判定逻辑
- 视野需求:若目标物在机械臂工作空间内分布广且需要全局视图,优先 Eye-to-Hand;若目标小且需贴近观察,选 Eye-in-Hand。
- 遮挡风险:若机械臂运动轨迹会频繁遮挡固定相机视线,选 Eye-in-Hand;若末端工具会遮挡末端相机,选 Eye-to-Hand。
- 精度取向:Eye-in-Hand 在近距离时标定精度更高(像素分辨率高),但受末端负载与振动影响;Eye-to-Hand 精度受安装距离限制,但稳定性好。
- 负载约束:末端负载已接近额定值时,不宜再加装相机,选 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组位姿")
运行与验证
- 依赖安装:
pip install numpy scipy opencv-python - 执行判定逻辑:运行上述代码,观察场景A输出
True,场景B输出False,验证配置选择逻辑正确。 - 标定验证路径:将代码中注释的 OpenCV 调用取消注释,替换为真实采集的机械臂位姿与标定板外参,求解手眼矩阵
X。验证方法:用求解出的X将标定板坐标系下的点变换到机械臂基座坐标系,重投影误差小于 1mm 即通过。
判定通过标准
- 配置选择逻辑输出与场景设定一致(场景A选Eye-in-Hand,场景B选Eye-to-Hand)。
- 标定代码能处理至少3组输入位姿并输出4x4手眼矩阵。
- 使用真实数据时,重投影误差满足应用精度要求。