404 lines
11 KiB
Markdown
404 lines
11 KiB
Markdown
# HandEyeCalib 手眼标定模块
|
||
|
||
## 概述
|
||
|
||
HandEyeCalib 是一个基于 Eigen 库实现的手眼标定模块,支持 Eye-To-Hand(眼在手外)和 Eye-In-Hand(眼在手上)两种标定模式。
|
||
|
||
## 依赖
|
||
|
||
- Eigen 3.3.9+(仅头文件)
|
||
- C++11 或更高版本
|
||
|
||
## 两种标定模式
|
||
|
||
### Eye-To-Hand(眼在手外)
|
||
|
||
相机固定安装在外部(如支架上),不随机器人运动。
|
||
|
||
```
|
||
┌─────────┐
|
||
│ 相机 │ ← 固定位置
|
||
└────┬────┘
|
||
│ 观测
|
||
▼
|
||
┌─────────┐ ┌─────────┐
|
||
│ 标定点 │ ←──→ │ 机器人 │
|
||
└─────────┘ └─────────┘
|
||
```
|
||
|
||
**求解目标**:相机坐标系到机器人基座坐标系的变换矩阵
|
||
|
||
**数学模型**:
|
||
```
|
||
P_robot = R * P_camera + T
|
||
```
|
||
|
||
### Eye-In-Hand(眼在手上)
|
||
|
||
相机安装在机器人末端,随机器人运动。
|
||
|
||
```
|
||
┌─────────┐
|
||
│ 机器人 │
|
||
└────┬────┘
|
||
│
|
||
┌────┴────┐
|
||
│ 末端 │
|
||
├─────────┤
|
||
│ 相机 │ ← 随末端运动
|
||
└────┬────┘
|
||
│ 观测
|
||
▼
|
||
┌─────────┐
|
||
│ 标定点 │ ← 固定位置
|
||
└─────────┘
|
||
```
|
||
|
||
**求解目标**:相机坐标系到末端坐标系的变换矩阵
|
||
|
||
**数学模型**(AX=XB 问题):
|
||
```
|
||
T_end * T_cam * P_camera = P_base
|
||
```
|
||
|
||
## 算法原理
|
||
|
||
### SVD 分解法(Eye-To-Hand)
|
||
|
||
1. 计算两组点的质心
|
||
2. 去中心化处理
|
||
3. 构建协方差矩阵 W = Σ(q1 * q2^T)
|
||
4. SVD 分解:W = U * S * V^T
|
||
5. 计算旋转矩阵:R = V * M * U^T
|
||
6. 计算平移向量:T = p2 - R * p1
|
||
|
||
### Tsai-Lenz 方法(Eye-In-Hand)
|
||
|
||
1. 构建相邻位姿间的相对变换 A = T_end_i^(-1) * T_end_j
|
||
2. 构建超定方程组
|
||
3. SVD 求解旋转矩阵
|
||
4. 最小二乘求解平移向量
|
||
|
||
## 欧拉角旋转顺序
|
||
|
||
支持6种常用旋转顺序:
|
||
|
||
| 枚举值 | 旋转顺序 | 说明 |
|
||
|--------|----------|------|
|
||
| `HECEulerOrder::XYZ` | X→Y→Z | Roll-Pitch-Yaw |
|
||
| `HECEulerOrder::XZY` | X→Z→Y | |
|
||
| `HECEulerOrder::YXZ` | Y→X→Z | |
|
||
| `HECEulerOrder::YZX` | Y→Z→X | |
|
||
| `HECEulerOrder::ZXY` | Z→X→Y | |
|
||
| `HECEulerOrder::ZYX` | Z→Y→X | Yaw-Pitch-Roll,机器人常用 |
|
||
|
||
## API 参考
|
||
|
||
### 创建/销毁实例
|
||
|
||
```cpp
|
||
// 创建实例
|
||
IHandEyeCalib* calib = CreateHandEyeCalibInstance();
|
||
|
||
// 使用完毕后销毁
|
||
DestroyHandEyeCalibInstance(calib);
|
||
```
|
||
|
||
### Eye-To-Hand 标定
|
||
|
||
```cpp
|
||
int CalculateRT(
|
||
const std::vector<HECPoint3D>& eyePoints, // 相机坐标系下的点
|
||
const std::vector<HECPoint3D>& robotPoints, // 机器人坐标系下的点
|
||
HECCalibResult& result); // 输出结果
|
||
```
|
||
|
||
### Eye-In-Hand 标定
|
||
|
||
```cpp
|
||
// 标定点位置未知
|
||
int CalculateEyeInHand(
|
||
const std::vector<HECEyeInHandData>& calibData, // 标定数据
|
||
HECCalibResult& result); // 输出结果
|
||
|
||
// 标定点位置已知
|
||
int CalculateEyeInHandWithTarget(
|
||
const std::vector<HECEyeInHandData>& calibData, // 标定数据
|
||
const HECPoint3D& targetInBase, // 标定点在基座下的坐标
|
||
HECCalibResult& result); // 输出结果
|
||
```
|
||
|
||
### 坐标变换
|
||
|
||
```cpp
|
||
// 完整变换: dst = R * src + T
|
||
void TransformPoint(
|
||
const HECRotationMatrix& R,
|
||
const HECTranslationVector& T,
|
||
const HECPoint3D& srcPoint,
|
||
HECPoint3D& dstPoint);
|
||
|
||
// 仅旋转: dst = R * src
|
||
void RotatePoint(
|
||
const HECRotationMatrix& R,
|
||
const HECPoint3D& srcPoint,
|
||
HECPoint3D& dstPoint);
|
||
```
|
||
|
||
### 欧拉角转换
|
||
|
||
```cpp
|
||
// 旋转矩阵 → 欧拉角
|
||
void RotationMatrixToEuler(
|
||
const HECRotationMatrix& R,
|
||
HECEulerOrder order,
|
||
HECEulerAngles& angles);
|
||
|
||
// 欧拉角 → 旋转矩阵
|
||
void EulerToRotationMatrix(
|
||
const HECEulerAngles& angles,
|
||
HECEulerOrder order,
|
||
HECRotationMatrix& R);
|
||
```
|
||
|
||
## 使用示例
|
||
|
||
### 示例1:Eye-To-Hand 标定
|
||
|
||
```cpp
|
||
#include "IHandEyeCalib.h"
|
||
|
||
void EyeToHandCalibration()
|
||
{
|
||
// 创建标定实例
|
||
IHandEyeCalib* calib = CreateHandEyeCalibInstance();
|
||
|
||
// 准备标定数据(至少3组对应点)
|
||
std::vector<HECPoint3D> cameraPoints; // 相机坐标系下的点
|
||
std::vector<HECPoint3D> robotPoints; // 机器人坐标系下的点
|
||
|
||
// 添加标定点(示例数据)
|
||
cameraPoints.push_back(HECPoint3D(100.0, 50.0, 200.0));
|
||
robotPoints.push_back(HECPoint3D(500.0, 300.0, 100.0));
|
||
|
||
cameraPoints.push_back(HECPoint3D(150.0, 80.0, 210.0));
|
||
robotPoints.push_back(HECPoint3D(550.0, 330.0, 110.0));
|
||
|
||
cameraPoints.push_back(HECPoint3D(120.0, 60.0, 195.0));
|
||
robotPoints.push_back(HECPoint3D(520.0, 310.0, 95.0));
|
||
|
||
// 更多点...(建议10组以上)
|
||
|
||
// 执行标定
|
||
HECCalibResult result;
|
||
int ret = calib->CalculateRT(cameraPoints, robotPoints, result);
|
||
|
||
if (ret == 0) {
|
||
// 标定成功,输出结果
|
||
printf("标定误差: %.3f mm\n", result.error);
|
||
|
||
// 使用标定结果进行坐标转换
|
||
HECPoint3D cameraPoint(130.0, 70.0, 205.0);
|
||
HECPoint3D robotPoint;
|
||
calib->TransformPoint(result.R, result.T, cameraPoint, robotPoint);
|
||
|
||
printf("相机点 (%.2f, %.2f, %.2f) -> 机器人点 (%.2f, %.2f, %.2f)\n",
|
||
cameraPoint.x, cameraPoint.y, cameraPoint.z,
|
||
robotPoint.x, robotPoint.y, robotPoint.z);
|
||
}
|
||
|
||
// 销毁实例
|
||
DestroyHandEyeCalibInstance(calib);
|
||
}
|
||
```
|
||
|
||
### 示例2:Eye-In-Hand 标定(标定点位置未知)
|
||
|
||
```cpp
|
||
#include "IHandEyeCalib.h"
|
||
|
||
void EyeInHandCalibration()
|
||
{
|
||
IHandEyeCalib* calib = CreateHandEyeCalibInstance();
|
||
|
||
// 准备标定数据
|
||
std::vector<HECEyeInHandData> calibData;
|
||
|
||
// 在不同位姿下采集数据
|
||
for (int i = 0; i < 10; i++) {
|
||
HECEyeInHandData data;
|
||
|
||
// 获取当前末端位姿(从机器人控制器读取)
|
||
// data.endPose = getRobotEndPose();
|
||
|
||
// 示例:构建末端位姿矩阵
|
||
HECRotationMatrix R_end;
|
||
HECTranslationVector T_end(500.0 + i * 10, 200.0, 300.0 - i * 5);
|
||
data.endPose = HECHomogeneousMatrix(R_end, T_end);
|
||
|
||
// 获取相机观测到的标定点坐标
|
||
// data.targetInCamera = detectTargetInCamera();
|
||
data.targetInCamera = HECPoint3D(100.0 - i * 2, 50.0 + i, 200.0);
|
||
|
||
calibData.push_back(data);
|
||
}
|
||
|
||
// 执行标定
|
||
HECCalibResult result;
|
||
int ret = calib->CalculateEyeInHand(calibData, result);
|
||
|
||
if (ret == 0) {
|
||
printf("Eye-In-Hand 标定成功\n");
|
||
printf("标定误差: %.3f mm\n", result.error);
|
||
|
||
// 输出相机到末端的变换矩阵
|
||
printf("旋转矩阵 R:\n");
|
||
for (int i = 0; i < 3; i++) {
|
||
printf(" [%.6f, %.6f, %.6f]\n",
|
||
result.R.at(i, 0), result.R.at(i, 1), result.R.at(i, 2));
|
||
}
|
||
printf("平移向量 T: [%.3f, %.3f, %.3f]\n",
|
||
result.T.at(0), result.T.at(1), result.T.at(2));
|
||
}
|
||
|
||
DestroyHandEyeCalibInstance(calib);
|
||
}
|
||
```
|
||
|
||
### 示例3:Eye-In-Hand 标定(标定点位置已知)
|
||
|
||
```cpp
|
||
#include "IHandEyeCalib.h"
|
||
|
||
void EyeInHandCalibrationWithTarget()
|
||
{
|
||
IHandEyeCalib* calib = CreateHandEyeCalibInstance();
|
||
|
||
std::vector<HECEyeInHandData> calibData;
|
||
|
||
// 采集多组数据(同示例2)
|
||
// ...
|
||
|
||
// 标定点在基座坐标系下的已知位置
|
||
HECPoint3D targetInBase(600.0, 400.0, 50.0);
|
||
|
||
// 执行标定
|
||
HECCalibResult result;
|
||
int ret = calib->CalculateEyeInHandWithTarget(calibData, targetInBase, result);
|
||
|
||
if (ret == 0) {
|
||
printf("标定成功,误差: %.3f mm\n", result.error);
|
||
}
|
||
|
||
DestroyHandEyeCalibInstance(calib);
|
||
}
|
||
```
|
||
|
||
### 示例4:欧拉角转换
|
||
|
||
```cpp
|
||
#include "IHandEyeCalib.h"
|
||
|
||
void EulerAngleConversion()
|
||
{
|
||
IHandEyeCalib* calib = CreateHandEyeCalibInstance();
|
||
|
||
// 从欧拉角创建旋转矩阵
|
||
HECEulerAngles angles;
|
||
angles.roll = 0.1; // 弧度
|
||
angles.pitch = 0.2;
|
||
angles.yaw = 0.3;
|
||
|
||
HECRotationMatrix R;
|
||
|
||
// 使用 ZYX 顺序(机器人常用)
|
||
calib->EulerToRotationMatrix(angles, HECEulerOrder::ZYX, R);
|
||
|
||
// 使用 XYZ 顺序
|
||
calib->EulerToRotationMatrix(angles, HECEulerOrder::XYZ, R);
|
||
|
||
// 从旋转矩阵提取欧拉角
|
||
HECEulerAngles extractedAngles;
|
||
calib->RotationMatrixToEuler(R, HECEulerOrder::ZYX, extractedAngles);
|
||
|
||
// 角度制转换
|
||
double roll_deg, pitch_deg, yaw_deg;
|
||
extractedAngles.toDegrees(roll_deg, pitch_deg, yaw_deg);
|
||
printf("欧拉角 (度): Roll=%.2f, Pitch=%.2f, Yaw=%.2f\n",
|
||
roll_deg, pitch_deg, yaw_deg);
|
||
|
||
DestroyHandEyeCalibInstance(calib);
|
||
}
|
||
```
|
||
|
||
### 示例5:完整的位姿转换
|
||
|
||
```cpp
|
||
#include "IHandEyeCalib.h"
|
||
|
||
void PoseTransformation()
|
||
{
|
||
IHandEyeCalib* calib = CreateHandEyeCalibInstance();
|
||
|
||
// 假设已完成标定
|
||
HECCalibResult calibResult;
|
||
// ... 标定过程 ...
|
||
|
||
// 相机检测到的目标位置
|
||
HECPoint3D eyePoint(100.0, 50.0, 200.0);
|
||
|
||
// 相机检测到的目标姿态(X/Y/Z轴方向向量)
|
||
std::vector<HECPoint3D> eyeDirVectors;
|
||
eyeDirVectors.push_back(HECPoint3D(1.0, 0.0, 0.0)); // X轴
|
||
eyeDirVectors.push_back(HECPoint3D(0.0, 1.0, 0.0)); // Y轴
|
||
eyeDirVectors.push_back(HECPoint3D(0.0, 0.0, 1.0)); // Z轴
|
||
|
||
// 转换到机器人坐标系
|
||
HECPoseResult poseResult;
|
||
calib->TransformPose(calibResult, eyePoint, eyeDirVectors, false, poseResult);
|
||
|
||
// 输出机器人位姿
|
||
printf("机器人位置: X=%.2f, Y=%.2f, Z=%.2f\n",
|
||
poseResult.position.x, poseResult.position.y, poseResult.position.z);
|
||
|
||
double roll_deg, pitch_deg, yaw_deg;
|
||
poseResult.angles.toDegrees(roll_deg, pitch_deg, yaw_deg);
|
||
printf("机器人姿态: Roll=%.2f°, Pitch=%.2f°, Yaw=%.2f°\n",
|
||
roll_deg, pitch_deg, yaw_deg);
|
||
|
||
DestroyHandEyeCalibInstance(calib);
|
||
}
|
||
```
|
||
|
||
## 标定建议
|
||
|
||
1. **采集点数**:建议采集 10-20 组标定点
|
||
2. **点分布**:标定点应均匀分布在工作空间内
|
||
3. **避免共面**:标定点不应全部位于同一平面
|
||
4. **精度要求**:机器人示教精度和相机检测精度直接影响标定结果
|
||
5. **验证**:标定完成后,使用新的测试点验证标定精度
|
||
|
||
## 错误码
|
||
|
||
| 错误码 | 说明 |
|
||
|--------|------|
|
||
| 0 (SUCCESS) | 成功 |
|
||
| APP_ERR_PARAM | 参数错误(点数不足或不匹配) |
|
||
|
||
## 文件结构
|
||
|
||
```
|
||
Module/HandEyeCalib/
|
||
├── Inc/
|
||
│ ├── HandEyeCalib_global.h # 导出宏定义
|
||
│ ├── HandEyeCalibTypes.h # 数据类型定义
|
||
│ └── IHandEyeCalib.h # 接口定义
|
||
├── _Inc/
|
||
│ └── HandEyeCalib.h # 实现类声明
|
||
├── Src/
|
||
│ └── HandEyeCalib.cpp # 实现
|
||
├── Doc/
|
||
│ └── HandEyeCalib.md # 本文档
|
||
└── HandEyeCalib.pro # Qt 工程文件
|
||
```
|