3D手眼标定全流程详解及准备工作指南
手眼标定(Hand-Eye Calibration)是连接机器人与视觉系统的核心环节,通过确定相机与机器人基座/工具的空间变换关系,实现视觉数据到机器人坐标的精准转换。
一、硬件准备工作
设备类型 | 要求说明 | 示例规格 |
---|---|---|
工业机器人 | 提供精确的姿态数据接口(TCP位姿) | ABB IRB 120, KUKA KR6 |
3D相机 | 支持点云/深度图输出 | Ensenso N35, Zivid |
标定板 | 高对比度、高精度、标定图案已知尺寸 | 15×10圆点阵列 (±5μm) |
刚性安装支架 | 消除相机/标定板振动 | 碳纤维夹具 |
通信模块 | 相机-机器人数据同步采集接口 | EtherCAT/PROFINET |
二、环境搭建要求
-
照明条件
- 均匀漫射光源(避免反光/阴影)
- 光照强度 ≥ 1000 lux(推荐条形LED)
-
空间布局
三、四大坐标系定义与关联
坐标系 | 描述 | 作用 | 观测方式 |
---|---|---|---|
W | 世界坐标系(基座) | 机器人运动的绝对参考 | 固定在机器人基座 |
T | 工具坐标系(末端) | 描述末端执行器的位姿 | 通过机器人正运动学获取 |
C | 相机坐标系 | 描述物体在相机视野中的3D位置 | 相机内参+标定板检测 |
B | 标定板坐标系 | 提供已知的物理参考点 | 印刷的棋盘格/圆点图案 |
四、手眼关系的数学本质
1. Eye-in-Hand(眼在手上)
2. Eye-to-Hand(眼在手外)
五、所得矩阵的具体应用方法
场景1:物体抓取(Eye-in-Hand)
场景2:视觉引导定位(Eye-to-Hand)
六、标定流程详述
步骤1:相机内参标定(需提前完成)
* 拍摄多角度标定板图像
for Index := 1 to 15 by 1
grab_image (Image, AcqHandle)
find_calib_object (Image, CalibDataID, Index, 0, [], [])
endfor
* 计算内参矩阵
calibrate_cameras (CalibDataID, Error)
get_calib_data (CalibDataID, 'camera', 0, 'params', CamParam)
步骤2:手眼数据采集
参数 | 数值要求 | 采集示例(Halcon代码) |
---|---|---|
位姿数量 | ≥15组(推荐20-30组) | T_base_tool_i := [0.1,0.2,0.5,0,0,1.57] |
姿态分布 | 覆盖80%工作空间 | rotate_tool_z(deg(30)) |
倾角范围 | X/Y轴 ±45° | set_tool_pose(rx:deg(40)) |
同步精度 | <10ms 延时 | sync_robot_vision() |
步骤3:坐标系定义(关键!)
World Frame (W) : 机器人基座标系
Tool Frame (T) : 机器人末端坐标系
Camera Frame (C) : 相机光学中心
Calib Frame (B) : 标定板坐标系
手眼关系求解目标:
Eye-in-Hand: T_tool_cam (W→T→C)
Eye-to-Hand: T_base_cam (W→C)
步骤4:执行标定计算
* 创建标定模型
create_hand_eye_calib_model ('eye_in_hand', CalibHandle)
* 添加数据点
for i := 1 to NumPoses by 1
* 获取机器人T_base_tool位姿
get_robot_pose(robotHandle, PoseBaseTool)
* 获取标定板在相机系位姿
find_calib_object_pose(Image, CamParam, CalibObjDescr, PoseCamCalib)
* 添加到位姿池
set_hand_eye_calib_data(CalibHandle, 'tool', i, 'tool_pose', PoseBaseTool)
set_hand_eye_calib_data(CalibHandle, 'calib_obj', i, 'obj_pose', PoseCamCalib)
endfor
* 执行标定
hand_eye_calibration(CalibHandle, 'nonlinear', 'error', Errors)
get_hand_eye_calib_result(CalibHandle, 'camera_pose', PoseToolCam)
步骤5:结果验证
* 坐标转换验证
T_tool_workpiece := [0.3, -0.1, 0.5, 0, 0, 0]
pose_compose(PoseToolCam, T_tool_workpiece, T_cam_workpiece)
predict_shape_model_3d(T_cam_workpiece, Model3D, ScenePointCloud)
* 投影误差计算
dev_display(ScenePointCloud)
calculate_3d_distance(ProjectedPoints, MeasuredPoints, AvgError)
if (AvgError > 0.5)
* 误差>0.5mm需重新标定
endif
我将按照您要求的四种情况分别详细介绍手眼标定的原理、流程步骤、流程图、矩阵应用及代码实现。
下面我将为您详细解析基于3D模型和标定板的3D手眼标定原理、流程步骤、流程图及矩阵应用,并提供相应的OpenCV和Halcon代码实现。
七、基于3D模型的手眼标定
1. 眼在手上(Eye-in-Hand)基于3D模型
原理:
相机安装在机器人末端,与末端执行器一起移动。固定位置的3D已知几何模型作为标定目标。通过比较不同机器人位姿下相机观测到的模型位置,建立相机坐标系与机器人坐标系之间的转换关系。
详细流程步骤:
-
准备阶段:
- 固定3D模型在工作空间
- 测量/准备3D模型的几何数据和特征点
- 标定相机内参(焦距、畸变等)
-
数据采集:
- 将机器人移动到15-20个不同位姿
- 每个位姿下:
a. 记录机器人末端位姿(齐次变换矩阵)
b. 采集特征点图像
c. 保存机器人位姿数据
-
特征提取与匹配:
- 对每张图像提取特征点(角点、边缘等)
- 将图像特征点匹配到3D模型对应点
-
求解相机外参:
- 对每个位姿使用PnP算法计算模型坐标到相机坐标的变换
- 公式:s * [u v 1]ᵀ = K * [R | t] * [X Y Z 1]ᵀ
-
建立手眼方程:
- 对每个位姿i:T_base_cam_i = T_base_gripper_i * T_gripper_cam
- 对连续位姿变化:A * X = X * B
A = T_cam_i_cam_j = T_camj_inv * T_cami
B = T_gripper_i_gripper_j = T_gripperj_inv * T_gripperi
X = T_gripper_cam(待求手眼矩阵)
-
求解手眼矩阵:
- 收集多个位姿变化对
- 使用线性方法(如Tsai, Park)或非线性优化求解
-
验证与优化:
- 重投影误差分析
- 标定精度验证
- 优化(束调整)提高精度
流程图:
矩阵应用:
设:
T_base_gripper
:基座→末端的变换T_model_cam
:模型→相机的变换T_gripper_cam
:末端→相机的变换(待求)
完整变换链:
T_base_model = T_base_gripper * T_gripper_cam * T_model_cam⁻¹
实际应用中主要用于:
T_base_target = T_base_gripper * T_gripper_cam * T_cam_target
OpenCV代码(详细注释版):
void eyeInHand3DCalibration(
const vector<Mat>& gripper_poses, // 机器人末端姿态,4x4齐次矩阵
const vector<vector<Point3f>>& model_points, // 每个位姿对应的3D模型点
const vector<vector<Point2f>>& image_points, // 对应的2D特征点
const Mat& camera_matrix,
const Mat& dist_coeffs,
Mat& T_gripper_cam, // 输出的手眼矩阵
vector<double>& reproj_errors) // 重投影误差
{
vector<Mat> camera_poses;
reproj_errors.clear();
// Step 1: 计算每个位姿下相机相对模型的位姿
for(size_t i = 0; i < image_points.size(); i++) {
Mat rvec, tvec;
// 使用迭代法提高精度
solvePnP(model_points[i], image_points[i],
camera_matrix, dist_coeffs,
rvec, tvec, false, SOLVEPNP_ITERATIVE);
// 计算重投影误差
vector<Point2f> reprojected;
projectPoints(model_points[i], rvec, tvec,
camera_matrix, dist_coeffs,
reprojected);
double error = norm(image_points[i], reprojected, NORM_L2) / reprojected.size();
reproj_errors.push_back(error);
// 转换为4x4变换矩阵
Mat R;
Rodrigues(rvec, R);
Mat pose = Mat::eye(4, 4, CV_64F);
R.copyTo(pose(Rect(0, 0, 3, 3)));
tvec.copyTo(pose(Rect(3, 0, 1, 3)));
camera_poses.push_back(pose);
}
// Step 2: 构建相对运动对
vector<Mat> A_rel, B_rel;
for(size_t i = 1; i < gripper_poses.size(); i++) {
// 计算相机相对运动: A = P_i⁻¹ * P_{i-1}
Mat A_i = camera_poses[i].inv() * camera_poses[i-1];
// 计算机械臂相对运动: B = G_i⁻¹ * G_{i-1}
Mat B_i = gripper_poses[i].inv() * gripper_poses[i-1];
A_rel.push_back(A_i);
B_rel.push_back(B_i);
}
// Step 3: 使用Tsai方法求解手眼矩阵
calibrateHandEye(A_rel, B_rel, T_gripper_cam, CALIB_HAND_EYE_TSAI);
// Step 4: 验证手眼矩阵
double avg_error = 0;
for(size_t i = 0; i < gripper_poses.size(); i++) {
// 通过手眼矩阵计算相机位置
Mat T_base_cam_calc = gripper_poses[i] * T_gripper_cam;
// 直接通过PnP得到的相机位置
Mat T_base_cam_meas = camera_poses[i];
// 计算位置误差
double pos_error = norm(T_base_cam_calc(Rect(3,0,1,3)) -
T_base_cam_meas(Rect(3,0,1,3)));
// 计算方向误差
Mat R_calc, R_meas;
T_base_cam_calc(Rect(0,0,3,3)).copyTo(R_calc);
T_base_cam_meas(Rect(0,0,3,3)).copyTo(R_meas);
double rot_error = norm(R_calc - R_meas);
avg_error += (pos_error + rot_error) / 2.0;
}
avg_error /= gripper_poses.size();
cout << "Average calibration error: " << avg_error << endl;
}
Halcon代码:
* 创建手眼标定模型
create_calib_data ('hand_eye', 1, 1, CalibDataID)
* 设置相机参数(内参已标定)
set_calib_data_cam_param (CalibDataID, 0, CamParam, 'area_scan_polynomial')
* 准备3D模型数据(假设已定义模型点)
NumPoints := 12
gen_object_model_3d_from_points ([X,Y,Z], ObjectModel3D)
* 循环添加位姿数据
for Index := 0 to NumPoses-1 by 1
* 获取机器人末端位姿(从机器人控制器)
get_robot_pose (RobotPose, Index)
* 获取当前图像和3D模型点对应的2D点
dev_display (Image)
find_object_model_3d (ObjectModel3D, Image, CamParam, ObjInCamPose,
MatchStartValue, MatchStopValue,
MinSize, PointIndex, PointCoord)
* 添加到位姿列表中
set_calib_data (CalibDataID, 'hand_eye', 'add_observation', [Index,0], [RobotPose, ObjInCamPose])
endfor
* 执行手眼标定
calibrate_hand_eye (CalibDataID, Errors)
* 获取标定结果(相机在末端坐标系中的位姿)
get_calib_data (CalibDataID, 'hand_eye', 'camera_in_hand_pose', CameraInHandPose)
2. 眼在手外(Eye-to-Hand)基于3D模型
原理:
相机固定在工作环境中,观测安装在机器人末端上的3D模型。通过不同机器人位姿下模型在相机视野中的位置变化,求解相机相对于机器人基座的位姿。
详细流程步骤:
-
准备阶段:
- 在机器人末端安装3D标定模型
- 固定相机位置
- 标定相机内参
-
数据采集:
- 移动机器人到多个位姿
- 每个位姿下:
a. 记录机器人末端位姿
b. 采集末端模型图像
-
特征匹配:
- 检测图像特征
- 匹配到3D模型对应点
-
求解模型位姿:
- 使用PnP求解模型在相机坐标系中的位置
- 公式:s * p = K * [R|t] * P
-
建立手眼方程:
T_cam_base * T_base_gripper = T_cam_gripper = T_cam_model * T_model_gripper
T_cam_base * ΔT_base_gripper = ΔT_cam_model * T_cam_base
转换为:
A * X = X * B
其中:
A = T_cam_model_j⁻¹ * T_cam_model_i
B = T_base_gripper_j⁻¹ * T_base_gripper_i
X = T_cam_base
-
求解变换矩阵:
- 收集多个运动对
- 使用Shah或Daniilidis方法求解
-
验证与优化:
- 检查变换一致性
- 重投影误差计算
- 束调整优化位姿
流程图:
矩阵应用:
设:
T_base_gripper
:基座→末端的变换T_cam_model
:相机→模型的变换T_cam_base
:相机→基座的变换(待求)
变换关系:
T_base_model = T_cam_base⁻¹ * T_cam_model
实际应用:
T_base_cam = (T_cam_base)⁻¹
OpenCV代码:
void eyeToHand3DCalibration(
const vector<Mat>& gripper_poses,
const vector<vector<Point3f>>& model_points,
const vector<vector<Point2f>>& image_points,
const Mat& camera_matrix,
const Mat& dist_coeffs,
Mat& T_cam_base)
{
vector<Mat> model_in_cam_poses;
// 计算模型在相机坐标系中的位姿
for(size_t i = 0; i < image_points.size(); i++) {
Mat rvec, tvec;
solvePnP(model_points[i], image_points[i],
camera_matrix, dist_coeffs, rvec, tvec);
Mat R;
Rodrigues(rvec, R);
Mat pose = Mat::eye(4, 4, CV_64F);
R.copyTo(pose(Rect(0, 0, 3, 3)));
tvec.copyTo(pose(Rect(3, 0, 1, 3)));
model_in_cam_poses.push_back(pose);
}
vector<Mat> A, B;
// 考虑所有可能的运动对组合(需考虑运动幅度)
vector<pair<int, int>> pairs = generateOptimalPairs(gripper_poses);
for(auto& p : pairs) {
int i = p.first, j = p.second;
// 计算相机坐标系中模型的相对运动
Mat A_ij = model_in_cam_poses[j] * model_in_cam_poses[i].inv();
// 计算机器人基座坐标系中的相对运动
Mat B_ij = gripper_poses[j] * gripper_poses[i].inv();
A.push_back(A_ij);
B.push_back(B_ij);
}
// 使用Daniilidis方法求解眼在手外标定
calibrateHandEye(A, B, T_cam_base, CALIB_HAND_EYE_DANIILIDIS);
// 验证标定结果
double avg_error = 0;
for(size_t i = 0; i < gripper_poses.size(); i++) {
// 直接通过PnP得到的模型位姿
Mat T_cam_model = model_in_cam_poses[i];
// 通过标定得到的关系:T_cam_model = T_cam_base * T_base_gripper * T_gripper_model
// 其中T_gripper_model是常数(模型在末端的位置)
Mat T_cam_model_calc = T_cam_base * gripper_poses[i] * T_model_on_gripper;
// 比较旋转矩阵
Mat R_calc, R_meas;
T_cam_model_calc(Rect(0,0,3,3)).copyTo(R_calc);
T_cam_model(Rect(0,0,3,3)).copyTo(R_meas);
double error = rotationDifference(R_calc, R_meas);
avg_error += error;
}
avg_error /= gripper_poses.size();
cout << "Average alignment error: " << avg_error << " degrees" << endl;
}
Halcon代码:
* 眼在手外标定模型创建
create_calib_data ('hand_eye', 1, 1, CalibDataID)
* 设置相机参数
set_calib_data_cam_param (CalibDataID, 0, CamParam, 'area_scan_telecentric_division')
* 准备模型点数据
ModelPoints := [[x1,y1,z1], [x2,y2,z2], ... ]
* 添加观测数据
for Index := 0 to NumPoses-1 by 1
* 从机器人获取末端位姿
T_base_gripper := get_robot_pose(Index)
* 采集图像并匹配模型
grab_image (Image, AcqHandle)
find_surface_model (SurfaceModelID, Image, CamParam,
ObjInCamPose, 0.7, ...
'num_matches', 1, ...
'pose_refinement', 'true')
* 添加观测:机器人位姿和模型在相机中的位姿
set_calib_data (CalibDataID, 'hand_eye', 'add_observation',
[Index,0], [T_base_gripper, ObjInCamPose])
endfor
* 设置标定参数(眼在手外模式)
set_calib_data (CalibDataID, 'hand_eye', 'calibration_type', 'eye_to_hand')
* 执行标定
calibrate_hand_eye (CalibDataID, Errors)
* 获取相机在基座坐标系中的位姿
get_calib_data (CalibDataID, 'hand_eye', 'camera_in_base_pose', T_cam_base)
3D模型方法的优缺点:
优点:
- 无需物理标定板(适合复杂环境)
- 可以使用工件作为标定目标
- 算法鲁棒性较高(尤其在遮挡情况下)
缺点:
- 需要精确的3D模型数据
- 对环境光线变化敏感
- 计算复杂度较高
- 特征匹配置信度影响精度
八、基于标定板的手眼标定
1. 眼在手上(Eye-in-Hand)基于标定板
原理:
相机安装在机器人末端,观测固定位置的标定板。通过多个位姿下的标定板图像和对应机器人位姿,建立相机坐标系与机器人坐标系的关系。
详细流程步骤:
-
准备标定板:
- 固定标定板在视野内(如棋盘格、Charuco板)
- 精确测量标定板物理尺寸
-
数据采集:
- 移动机器人到多个姿势(5-30个)
- 每个姿势下:
a. 记录机器人末端位姿
b. 采集标定板图像
c. 确保标定板在视场范围内
-
角点检测:
- 自动检测标定板角点
- 计算亚像素级精确位置
- 关联对应3D物理点坐标
-
标定板位姿估计:
- 使用PnP算法计算每个位姿下标定板相对相机的位置
- 公式:s * [u v 1]ᵀ = K * [R|t] * [X Y 0]ᵀ(标定板在Z=0平面)
-
建立运动链:
T_base_board = T_base_gripper * T_gripper_cam * T_cam_board
通过位姿变换:
ΔT_cam_board = T_gripper_cam⁻¹ * ΔT_base_gripper * T_gripper_cam
手眼方程:A * X = X * B
-
求解手眼矩阵:
- 收集多个运动对
- 使用Horaud或Andreff方法求解
-
验证与优化:
- 重投影误差计算
- 标定板位姿一致性检查
- 全局优化手眼矩阵
流程图:
矩阵应用:
设:
T_base_board
:基座→标定板的变换(常量)T_base_gripper
:基座→末端的变换T_board_cam
:标定板→相机的变换(每个位姿变化)
关系:
T_board_cam = T_board_base * T_base_gripper * T_gripper_cam
因T_board_base
为常量,移项得:
T_board_cam_j⁻¹ * T_board_cam_i = T_gripper_cam * (T_base_gripper_j⁻¹ * T_base_gripper_i) * T_gripper_cam⁻¹
OpenCV代码(棋盘格标定板):
void eyeInHandCheckerboardCalib(
const vector<Mat>& gripperPoses,
const vector<Mat>& images,
const Size& boardSize, // 标定板角点数
float squareSize, // 标定板格子尺寸(单位:mm)
Mat& cameraMatrix,
Mat& distCoeffs,
Mat& T_gripper_cam)
{
vector<vector<Point2f>> cornersList;
vector<Point3f> objectPoints;
// 生成3D物理坐标(标定板Z=0)
for(int i=0; i<boardSize.height; i++) {
for(int j=0; j<boardSize.width; j++) {
objectPoints.push_back(Point3f(j*squareSize, i*squareSize, 0));
}
}
// 检测所有图像的角点
for(size_t i=0; i<images.size(); i++) {
vector<Point2f> corners;
bool found = findChessboardCorners(images[i], boardSize, corners);
if(found) {
// 亚像素精确化
Mat gray;
cvtColor(images[i], gray, COLOR_BGR2GRAY);
cornerSubPix(gray, corners, Size(11,11), Size(-1,-1),
TermCriteria(TermCriteria::EPS+TermCriteria::COUNT, 30, 0.1));
cornersList.push_back(corners);
}
}
// 计算每个位姿的相机外部参数
vector<Mat> T_board_cams;
for(size_t i=0; i<cornersList.size(); i++) {
Mat rvec, tvec;
solvePnP(objectPoints, cornersList[i],
cameraMatrix, distCoeffs,
rvec, tvec);
Mat R;
Rodrigues(rvec, R);
Mat T = Mat::eye(4,4,CV_64F);
R.copyTo(T(Rect(0,0,3,3)));
tvec.copyTo(T(Rect(3,0,1,3)));
T_board_cams.push_back(T);
}
// 构建相对运动对
vector<Mat> A, B;
for(size_t i=1; i<gripperPoses.size(); i++) {
// 相机相对运动
Mat A_i = T_board_cams[i] * T_board_cams[i-1].inv();
// 机器人相对运动
Mat B_i = gripperPoses[i] * gripperPoses[i-1].inv();
A.push_back(A_i);
B.push_back(B_i);
}
// 手眼标定(使用Horaud方法)
calibrateHandEye(A, B, T_gripper_cam, CALIB_HAND_EYE_HORAUD);
}
Halcon代码(Halcon棋盘格标定):
* 创建标定数据模型
create_calib_data ('hand_eye', 1, 1, CalibDataID)
* 设置相机参数(或进行内参标定)
start_cam_calibration (CalibDataID, 0.03, 8, 8, 0.03, 'xyz_order', 'z_vector')
...
* 设置棋盘格标定板参数
set_calib_data_calib_object (CalibDataID, 0, 'calplate_9x9_30mm.dat')
* 采集多个位姿数据
for Index := 1 to NumPoses by 1
* 移动机器人到新位姿
move_robot_to_pose(Poses[Index])
* 采集图像
grab_image (Image, AcquisitionHandle)
* 查找标定板
find_calib_object (Image, CalibDataID, 0, 0, Index, [], [])
* 记录当前机器人位姿(基座坐标系)
get_tcp_pose (RobotPose)
set_calib_data (CalibDataID, 'hand_eye', 'add_observation', [Index-1,0], RobotPose)
endfor
* 执行手眼标定(眼在手上)
calibrate_hand_eye (CalibDataID, Errors)
* 获取结果
get_calib_data (CalibDataID, 'hand_eye', 'camera_in_hand_pose', HCamInHand)
2. 眼在手外(Eye-to-Hand)基于标定板
原理:
相机固定在工作环境中,观测安装在机器人末端上的标定板。通过不同位姿下标定板在图像中的位置变化,求解相机相对于机器人基座的变换关系。
详细流程步骤:
-
安装标定板:
- 将标定板固定在机器人末端
- 确保标定板与末端刚性连接
-
相机固定:
- 将相机固定在立体视觉支架
- 覆盖机器人工作空间
-
数据采集:
- 移动机器人到多个位姿
- 每个位姿下:
a. 记录机器人末端位姿
b. 采集标定板图像
c. 确保标定板可见
-
角点检测与关联:
- 检测图像中的标定板角点
- 精确计算亚像素坐标
- 关联物理3D坐标(考虑标定板安装位置)
-
位姿估计:
- PnP求解标定板在相机坐标系中的位姿
- 理解为:T_cam_board = T_cam_base * T_base_gripper * T_gripper_board
-
建立方程:
ΔT_cam_board = T_cam_base * ΔT_base_gripper * T_cam_base⁻¹
转换为:A = X * B * X⁻¹
其中:
A = 标定板的相对运动(相机坐标系)
B = 机器人的相对运动(基座坐标系)
X = T_cam_base(待求) -
求解与优化:
- 收集多个运动对
- 使用Shah或Nakov方法求解
- 非线性优化提高精度
流程图:
矩阵应用:
设:
T_gripper_board
:末端→标定板的变换(常量)T_base_gripper
:基座→末端的变换T_cam_board
:相机→标定板的变换(每个位姿)
关系:
T_cam_board = T_cam_base * T_base_gripper * T_gripper_board
移项:T_gripper_board = T_base_gripper⁻¹ * T_cam_base⁻¹ * T_cam_board
OpenCV代码(Charuco标定板):
void eyeToHandArucoCalibration(
const vector<Mat>& gripperPoses,
const Ptr<aruco::CharucoBoard>& board,
const vector<Mat>& images,
Mat& cameraMatrix,
Mat& distCoeffs,
Mat& T_cam_base)
{
Ptr<aruco::Dictionary> dictionary = board->dictionary;
vector<Mat> boardInCamPoses;
vector<double> timestamps;
vector<vector<Point2f>> allCorners;
vector<vector<int>> allIds;
// 检测所有图像的标定板
for(size_t i=0; i<images.size(); i++) {
vector<int> ids;
vector<vector<Point2f>> corners, rejected;
// 检测Aruco标记
aruco::detectMarkers(images[i], dictionary, corners, ids);
if(ids.size() > 0) {
// 精确定位角点
vector<Point2f> charucoCorners;
vector<int> charucoIds;
aruco::interpolateCornersCharuco(corners, ids, images[i],
board, charucoCorners, charucoIds);
// 使用角点估计位姿
Mat rvec, tvec;
if(charucoIds.size() > 4) {
solvePnP(board->chessboardCorners, charucoCorners,
cameraMatrix, distCoeffs, rvec, tvec);
// 转换为4x4齐次矩阵
Mat R;
Rodrigues(rvec, R);
Mat pose = Mat::eye(4,4,CV_64F);
R.copyTo(pose(Rect(0,0,3,3)));
tvec.copyTo(pose(Rect(3,0,1,3)));
boardInCamPoses.push_back(pose);
// 记录时间戳用于同步
timestamps.push_back(getCurrentTime());
}
}
}
// 构建运动对
vector<Mat> A, B;
for(size_t i=1; i<gripperPoses.size(); i++) {
// 忽略时间差过大的数据对
if(fabs(timestamps[i] - timestamps[i-1]) > 0.1) continue;
// 标定板在相机坐标系中的相对运动
Mat A_i = boardInCamPoses[i] * boardInCamPoses[i-1].inv();
// 机器人在基座坐标系的相对运动
Mat B_i = gripperPoses[i] * gripperPoses[i-1].inv();
// 使用带权重的运动对
double weight = motionQuality(B_i); // 基于运动幅度和质量
if(weight > 0.3) {
A.push_back(A_i);
B.push_back(B_i);
}
}
// 标定求解
calibrateHandEye(A, B, T_cam_base, CALIB_HAND_EYE_SHAH);
}
Halcon代码(Halcon标定板):
* 创建手眼标定模型
create_calib_data ('hand_eye', 1, 1, CalibDataID)
* 设置相机参数
set_calib_data_cam_param (CalibDataID, 0, CamParam)
* 设置标定板参数(安装于机器人末端)
set_calib_data_calib_object (CalibDataID, 0, 'caltab_15x10.cpd')
* 采集多个位姿数据
for Index := 1 to NumPoses by 1
* 移动机器人
move_robot(Poses[Index])
* 采集图像
grab_image (Image, AcqHandle)
* 查找标定板
find_calib_object (Image, CalibDataID, 0, 0, Index, [], [])
* 获取并记录机器人位姿(基座坐标系)
get_robot_tcp (T_base_gripper)
set_calib_data (CalibDataID, 'hand_eye',
'add_observation', [Index-1,0],
T_base_gripper)
endfor
* 配置为眼在手外模式
set_calib_data (CalibDataID, 'hand_eye',
'general', 'camera_in_base_pose[0]',
[0,0,0,0,0,0])
* 执行标定
calibrate_hand_eye (CalibDataID, Errors)
* 获取标定结果
get_calib_data (CalibDataID, 'hand_eye',
'camera_in_base_pose', HCamInBase)
标定板方法的优缺点:
优点:
- 算法成熟可靠
- 精度高(亚像素级角点检测)
- 鲁棒性好(不受光照影响)
- 开源库支持完善
缺点:
- 需要物理标定板
- 标定板需在视野内(有操作空间限制)
- 单一标定板分辨率和精度固定
九、3D模型与标定板方法的比较
特性 | 基于3D模型 | 基于标定板 |
---|---|---|
硬件要求 | 3D模型可定制 | 物理标定板 |
环境适应 | 强(可适应复杂光照) | 弱(需要平面可见) |
精度 | ■■■□□ (0.5-2°) | ■■■■■ (0.1-0.5°) |
计算复杂度 | 高(需要特征匹配) | 低(角点检测直接) |
自动化程度 | 中(需要模型匹配) | 高(全自动检测) |
适用场景 | 现场工件标定 | 实验室高精度标定 |
特征稳定性 | 动态(依赖于特征) | 静态(固定角点) |
十、手眼标定关键问题解决
1. 精度提升技巧
- 运动轨迹优化:
- 采用满秩运动(6自由度运动)
- 旋转和平移均在所需精度级别的10倍以上
理想运动轨迹:
Position 1: Rx 0°, Ry 0°, Rz 0°, Tx 0, Ty 0, Tz 0
Position 2: Rx 30°, Ry 0°, Rz 0°, Tx 50mm, Ty 0, Tz 0
Position 3: Rx 0°, Ry 30°, Rz 0°, Tx 0, Ty 50mm, Tz 0
Position 4: Rx 0°, Ry 0°, Rz 30°, Tx 0, Ty 0, Tz 50mm
Position 5: Rx -30°, Ry 0°, Rz 0°, Tx -50mm, Ty 0, Tz 0
...
- 数据筛选策略:
// 筛选有效数据对 vector<Mat> A_good, B_good; for(size_t i=0; i<A.size(); i++) { double rotation_angle = rotationNorm(B[i]); double translation_dist = translationNorm(B[i]); // 筛选标准 if(rotation_angle > 10.0 && // 最小旋转角度 translation_dist > 30.0 && // 最小移动距离 conditionNumber(A[i]) < 50) // 最小条件数 { A_good.push_back(A[i]); B_good.push_back(B[i]); } }
2. 鲁棒性增强
-
多算法融合投票:
Mat T1, T2, T3; calibrateHandEye(A, B, T1, CALIB_HAND_EYE_TSAI); calibrateHandEye(A, B, T2, CALIB_HAND_EYE_HORAUD); calibrateHandEye(A, B, T3, CALIB_HAND_EYE_PARK); // 计算一致性 double diff12 = matDiff(T1, T2); double diff13 = matDiff(T1, T3); double diff23 = matDiff(T2, T3); // 选择最一致的解 if(diff12 < diff13 && diff12 < diff23) { T_final = (T1 + T2) / 2.0; } else if(/* 其他条件 */) { // ... }
-
抗噪声优化:
# Halcon中的鲁棒性标定 set_calib_data (CalibDataID, 'general', 'sigma_rot', 0.005) set_calib_data (CalibDataID, 'general', 'sigma_trans', 0.5) calibrate_hand_eye (CalibDataID, 'robust', 0.95, 'residual_limit', 0.1)
3. 实际应用建议
-
标定环境:
- 避免强光反射
- 减少环境运动干扰
- 固定所有部件对应振
-
运动规划原则:
角度覆盖:±30°绕每个轴 平移范围:工作空间的50-80% 运动路径:避免奇异位形,保持速度稳定
-
验证方法:
相对误差 = ||实测值 - 转换值||/实测值 绝对误差 = ||T_base_gripper * X - T_base_cam|| 重投影误差 = Σ||原图像点 - 投影点||²
-
维护周期:
- 正常使用:3-6个月
- 撞击后立即检测
- 机械结构改变后重新标定
应用场景选择矩阵:
性能指标对比:
方法 | 平均误差(mm) | 角度误差(°) | 计算时间(ms) | 适用性 |
---|---|---|---|---|
EIH-3D | 1.2-2.5 | 0.5-1.0 | 200-500 | ★★★★☆ |
EIH-标定板 | 0.3-0.8 | 0.1-0.3 | 50-150 | ★★★★★ |
ETH-3D | 1.5-3.0 | 0.7-1.5 | 300-800 | ★★★☆☆ |
ETH-标定板 | 0.4-1.0 | 0.2-0.4 | 100-200 | ★★★★☆ |
发展趋势:
- 多模态融合标定:结合深度相机与RGB相机
- 在线自适应标定:机器人在工作过程中实时优化
- 无标定物方法:利用环境特征进行动态标定
- 深度学习方法:端到端的手眼标定网络
十一、关键处理对比
处理步骤 | OpenCVSharp | Halcon |
---|---|---|
标定板检测 | FindChessboardCorners() + SolvePnP | find_calib_object() |
手眼标定算法 | CalibrateHandEye() | calibrate_hand_eye() |
坐标转换 | 手动矩阵乘法 | pose_compose() |
机器人接口 | 需自定义通信协议 | 内置KUKA/ABB等机器人接口支持 |
实时性优化 | 多线程采集 | 硬件加速图像采集 |
十二、误差分析与优化策略
误差类型 | 产生原因 | 解决方案 |
---|---|---|
平移跳跃 | TCP标定不准 | 重新进行机器人工具标定 |
角度偏差 | 旋转轴未对齐 | 检查坐标系定义一致性 |
周期性抖动 | 机械谐振 | 降低运动速度/增加阻尼 |
区域性发散 | 位姿分布集中 | 增加边缘点姿态 |
黄金法则:当平均3D投影误差 > 预期精度×2时,必须重新标定(e.g.目标精度0.1mm → 误差>0.2mm时重做)
十三、典型应用场景设置
场景 | 推荐配置 | 精度控制要点 |
---|---|---|
精密装配 | Eye-in-Hand | 末端抖动补偿 <50μm |
大型工件检测 | Eye-to-Hand | 相机视野覆盖规划 |
动态抓取 | 双相机融合 | 采样频率 >30Hz |
曲面跟踪 | Robot-mounted | 工具坐标系实时更新 |
通过严谨执行上述步骤,可实现<0.1mm的定位精度(六轴机器人负载<5kg场景)。建议每3个月或生产批次变更后执行全流程再标定以保证系统稳定性。
以下是针对手眼标定中坐标系关系和矩阵应用的系统性解析,包含数学原理、应用方法和场景案例:
十四、关键技术的工程细节
1. 时间同步问题
- 问题:机器人位姿 ( T_{base}^{tool} ) 和相机采集存在延迟
- 方案:
- 使用硬件触发信号同步
- 添加机器人运动状态补偿(预测末端实际位置)
2. 误差传递分析
误差来源:
- 相机内参标定误差(约±0.2像素)
- 机器人重复定位误差(±0.1mm级)
- 标定板平面度误差(需<0.05mm/m)
误差累积公式:
( \epsilon_{total} = \sqrt{(\epsilon_{cam}^2 + \epsilon_{robot}^2 + \epsilon_{calib}^2)} )
3. 动态补偿技术
当机械臂负载变化导致变形时:
# 根据负载重量动态调整标定矩阵
def adjust_calibration(load_weight):
k = load_weight * 0.001 # 假设每kg产生0.001mm的形变系数
T_adjusted = T_tool_cam.copy()
T_adjusted[2,3] += k # Z方向补偿
return T_adjusted
十五、不同场景的配置建议
应用场景 | 推荐配置 | 原因 |
---|---|---|
精密装配(<0.1mm) | Eye-in-Hand | 避免机器人绝对定位误差影响 |
大范围搬运 | Eye-to-Hand | 相机固定可覆盖更大工作空间 |
多相机协同 | 混合配置 | 部分相机随动,部分固定 |
动态目标跟踪 | Eye-in-Hand | 相机随动保持目标在视野中心 |
十六、验证与调试流程
-
重投影验证
- 机械臂运动到多个已知位置
- 比较标定板的理论投影坐标与实际检测坐标
# 计算重投影误差 error = np.linalg.norm(actual_pixel - projected_pixel)
-
实物基准测试
- 在工作台上放置物理基准点
- 用机器人重复触碰基准点,统计实际偏差
-
热漂移测试
- 连续运行1小时后重新测量关键点
- 检查标定矩阵的稳定性
十七、调试建议
-
OpenCVSharp常见问题:
- 标定板角点检测失败 → 调整
FindChessboardCorners
的winSize
参数 - 解不稳定 → 确保机器人运动包含充分旋转(建议>30°)
- 标定板角点检测失败 → 调整
-
Halcon优化方向:
- 使用
set_calib_data_observ_points
添加额外观测点 - 启用
'check_hand_eye_motion'
参数验证运动充分性
- 使用
-
跨平台兼容处理:
// 坐标系方向修正(ROS↔工业机器人) Mat correction = new Mat(4,4,MatType.CV_64F, new double[] { -1,0,0,0, 0,0,1,0, 0,1,0,0, 0,0,0,1 }); T_tool_cam = T_tool_cam * correction;
通过上述代码可实现毫米级精度的视觉引导,实际应用时建议:
- Eye-in-Hand配置:误差控制在±0.3mm内
- Eye-to-Hand配置:误差控制在±1.0mm内(受相机距离影响)
通过以上方法,可确保手眼标定结果在实际应用中的精确性和可靠性。对于Eye-to-Hand系统,只需将 ( T_{tool}^{cam} ) 替换为 ( T_{base}^{cam} ),其他计算逻辑完全一致。