3D手眼标定全流程详解

3D手眼标定全流程详解及准备工作指南

手眼标定(Hand-Eye Calibration)是连接机器人与视觉系统的核心环节,通过确定相机与机器人基座/工具的空间变换关系,实现视觉数据到机器人坐标的精准转换。


一、硬件准备工作

设备类型要求说明示例规格
工业机器人提供精确的姿态数据接口(TCP位姿)ABB IRB 120, KUKA KR6
3D相机支持点云/深度图输出Ensenso N35, Zivid
标定板高对比度、高精度、标定图案已知尺寸15×10圆点阵列 (±5μm)
刚性安装支架消除相机/标定板振动碳纤维夹具
通信模块相机-机器人数据同步采集接口EtherCAT/PROFINET

二、环境搭建要求

  1. 照明条件

    • 均匀漫射光源(避免反光/阴影)
    • 光照强度 ≥ 1000 lux(推荐条形LED)
  2. 空间布局

    Eye-to-Hand配置
    固定
    固定在
    支架
    3D相机
    机器人末端
    标定板

在这里插入图片描述

Eye-in-Hand配置
固定
固定
3D相机
机器人末端
工作台
标定板

在这里插入图片描述


三、四大坐标系定义与关联

坐标系描述作用观测方式
W世界坐标系(基座)机器人运动的绝对参考固定在机器人基座
T工具坐标系(末端)描述末端执行器的位姿通过机器人正运动学获取
C相机坐标系描述物体在相机视野中的3D位置相机内参+标定板检测
B标定板坐标系提供已知的物理参考点印刷的棋盘格/圆点图案

四、手眼关系的数学本质

1. Eye-in-Hand(眼在手上)

在这里插入图片描述

2. Eye-to-Hand(眼在手外)

在这里插入图片描述


五、所得矩阵的具体应用方法

场景1:物体抓取(Eye-in-Hand)

在这里插入图片描述

场景2:视觉引导定位(Eye-to-Hand)

![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/9db775842be54f5fa1dc83e70a114b20.png


六、标定流程详述

在这里插入图片描述

步骤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已知几何模型作为标定目标。通过比较不同机器人位姿下相机观测到的模型位置,建立相机坐标系与机器人坐标系之间的转换关系。

详细流程步骤:
  1. 准备阶段

    • 固定3D模型在工作空间
    • 测量/准备3D模型的几何数据和特征点
    • 标定相机内参(焦距、畸变等)
  2. 数据采集

    • 将机器人移动到15-20个不同位姿
    • 每个位姿下:
      a. 记录机器人末端位姿(齐次变换矩阵)
      b. 采集特征点图像
      c. 保存机器人位姿数据
  3. 特征提取与匹配

    • 对每张图像提取特征点(角点、边缘等)
    • 将图像特征点匹配到3D模型对应点
  4. 求解相机外参

    • 对每个位姿使用PnP算法计算模型坐标到相机坐标的变换
    • 公式:s * [u v 1]ᵀ = K * [R | t] * [X Y Z 1]ᵀ
  5. 建立手眼方程

    • 对每个位姿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(待求手眼矩阵)
  6. 求解手眼矩阵

    • 收集多个位姿变化对
    • 使用线性方法(如Tsai, Park)或非线性优化求解
  7. 验证与优化

    • 重投影误差分析
    • 标定精度验证
    • 优化(束调整)提高精度
流程图:
开始
固定3D模型
移动机器人到不同位姿
记录末端位姿 T_base_gripper_i
采集模型图像
提取图像特征点
匹配3D模型点
PnP求解 T_model_cam_i
构建相对运动对
求解 AX = XB
得到 T_gripper_cam
验证精度
精度合格?
结束
矩阵应用:

设:

  • 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模型。通过不同机器人位姿下模型在相机视野中的位置变化,求解相机相对于机器人基座的位姿。

详细流程步骤:
  1. 准备阶段

    • 在机器人末端安装3D标定模型
    • 固定相机位置
    • 标定相机内参
  2. 数据采集

    • 移动机器人到多个位姿
    • 每个位姿下:
      a. 记录机器人末端位姿
      b. 采集末端模型图像
  3. 特征匹配

    • 检测图像特征
    • 匹配到3D模型对应点
  4. 求解模型位姿

    • 使用PnP求解模型在相机坐标系中的位置
    • 公式:s * p = K * [R|t] * P
  5. 建立手眼方程
    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

  6. 求解变换矩阵

    • 收集多个运动对
    • 使用Shah或Daniilidis方法求解
  7. 验证与优化

    • 检查变换一致性
    • 重投影误差计算
    • 束调整优化位姿
流程图:
开始
末端安装3D模型
固定相机位置
移动机器人到不同位姿
记录末端位姿
采集模型图像
提取特征点
匹配3D模型点
PnP求解 T_cam_model
构建相对运动对
求解 AX = XB
得到 T_cam_base
验证精度
精度合格?
结束
矩阵应用:

设:

  • 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)基于标定板
原理:

相机安装在机器人末端,观测固定位置的标定板。通过多个位姿下的标定板图像和对应机器人位姿,建立相机坐标系与机器人坐标系的关系。

详细流程步骤:
  1. 准备标定板

    • 固定标定板在视野内(如棋盘格、Charuco板)
    • 精确测量标定板物理尺寸
  2. 数据采集

    • 移动机器人到多个姿势(5-30个)
    • 每个姿势下:
      a. 记录机器人末端位姿
      b. 采集标定板图像
      c. 确保标定板在视场范围内
  3. 角点检测

    • 自动检测标定板角点
    • 计算亚像素级精确位置
    • 关联对应3D物理点坐标
  4. 标定板位姿估计

    • 使用PnP算法计算每个位姿下标定板相对相机的位置
    • 公式:s * [u v 1]ᵀ = K * [R|t] * [X Y 0]ᵀ(标定板在Z=0平面)
  5. 建立运动链
    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

  6. 求解手眼矩阵

    • 收集多个运动对
    • 使用Horaud或Andreff方法求解
  7. 验证与优化

    • 重投影误差计算
    • 标定板位姿一致性检查
    • 全局优化手眼矩阵
流程图:
开始
固定标定板
移动机器人
记录末端位姿
采集标定板图像
检测角点
关联物理点
PnP求解 T_board_cam
构建相对运动
求解手眼方程
得到 T_gripper_cam
验证精度
OK?
结束
矩阵应用:

设:

  • 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)基于标定板
原理:

相机固定在工作环境中,观测安装在机器人末端上的标定板。通过不同位姿下标定板在图像中的位置变化,求解相机相对于机器人基座的变换关系。

详细流程步骤:
  1. 安装标定板

    • 将标定板固定在机器人末端
    • 确保标定板与末端刚性连接
  2. 相机固定

    • 将相机固定在立体视觉支架
    • 覆盖机器人工作空间
  3. 数据采集

    • 移动机器人到多个位姿
    • 每个位姿下:
      a. 记录机器人末端位姿
      b. 采集标定板图像
      c. 确保标定板可见
  4. 角点检测与关联

    • 检测图像中的标定板角点
    • 精确计算亚像素坐标
    • 关联物理3D坐标(考虑标定板安装位置)
  5. 位姿估计

    • PnP求解标定板在相机坐标系中的位姿
    • 理解为:T_cam_board = T_cam_base * T_base_gripper * T_gripper_board
  6. 建立方程
    ΔT_cam_board = T_cam_base * ΔT_base_gripper * T_cam_base⁻¹
    转换为:A = X * B * X⁻¹
    其中:
    A = 标定板的相对运动(相机坐标系)
    B = 机器人的相对运动(基座坐标系)
    X = T_cam_base(待求)

  7. 求解与优化

    • 收集多个运动对
    • 使用Shah或Nakov方法求解
    • 非线性优化提高精度
流程图:
开始
末端安装标定板
固定相机位置
移动机器人
记录末端位姿
采集标定板图像
检测角点
关联物理坐标
PnP求解 T_cam_board
构建相对运动
求解 XB = AX
得到 T_cam_base
验证精度
OK?
结束
矩阵应用:

设:

  • 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. 实际应用建议
  1. 标定环境

    • 避免强光反射
    • 减少环境运动干扰
    • 固定所有部件对应振
  2. 运动规划原则

    角度覆盖:±30°绕每个轴
    平移范围:工作空间的50-80%
    运动路径:避免奇异位形,保持速度稳定
    
  3. 验证方法

    相对误差 = ||实测值 - 转换值||/实测值
    绝对误差 = ||T_base_gripper * X - T_base_cam||
    重投影误差 = Σ||原图像点 - 投影点||²
    
  4. 维护周期

    • 正常使用:3-6个月
    • 撞击后立即检测
    • 机械结构改变后重新标定
应用场景选择矩阵:
标定板无法放置
有标准平面
移动相机
固定相机
移动相机
固定相机
应用需求
环境限制
3D模型方法
标定板方法
眼在手上/眼在手外
眼在手上/眼在手外
眼在手上+3D模型
眼在手外+3D模型
眼在手上+标定板
眼在手外+标定板
性能指标对比:
方法平均误差(mm)角度误差(°)计算时间(ms)适用性
EIH-3D1.2-2.50.5-1.0200-500★★★★☆
EIH-标定板0.3-0.80.1-0.350-150★★★★★
ETH-3D1.5-3.00.7-1.5300-800★★★☆☆
ETH-标定板0.4-1.00.2-0.4100-200★★★★☆
发展趋势:
  1. 多模态融合标定:结合深度相机与RGB相机
  2. 在线自适应标定:机器人在工作过程中实时优化
  3. 无标定物方法:利用环境特征进行动态标定
  4. 深度学习方法:端到端的手眼标定网络

十一、关键处理对比

处理步骤OpenCVSharpHalcon
标定板检测FindChessboardCorners() + SolvePnPfind_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相机随动保持目标在视野中心

十六、验证与调试流程

  1. 重投影验证

    • 机械臂运动到多个已知位置
    • 比较标定板的理论投影坐标与实际检测坐标
    # 计算重投影误差
    error = np.linalg.norm(actual_pixel - projected_pixel)
    
  2. 实物基准测试

    • 在工作台上放置物理基准点
    • 用机器人重复触碰基准点,统计实际偏差
  3. 热漂移测试

    • 连续运行1小时后重新测量关键点
    • 检查标定矩阵的稳定性

十七、调试建议

  1. OpenCVSharp常见问题

    • 标定板角点检测失败 → 调整FindChessboardCornerswinSize参数
    • 解不稳定 → 确保机器人运动包含充分旋转(建议>30°)
  2. Halcon优化方向

    • 使用set_calib_data_observ_points添加额外观测点
    • 启用'check_hand_eye_motion'参数验证运动充分性
  3. 跨平台兼容处理

    // 坐标系方向修正(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} ),其他计算逻辑完全一致。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

X-Vision

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值