Files
ros_contorl/handeye/README.md
T
arm_dev e4f513392d P3b:手眼标定支持 --mode 模式选择与 --points 目标点数
--mode eye_in_hand|eye_to_hand
- 切换读写的文件为 <臂号>_<模式>.yaml,两种模式的采点与结果互不覆盖、可共存
- 臂号从 Flexiv 序列号后两位取(Rizon4-063989 → 89),不做硬编码映射
- 非法模式立即报错退出。不静默退回默认值 —— 那会造成"以为标的是
  eye-to-hand、其实算的是 eye-in-hand",是这类工具最难排查的错误
- 求解侧本来就支持(calibrate_hand_eye(eye_to_hand=...) 对 gripper2base 求逆),
  这次把入口补上,让模式不再只存在于参数文件里

--points N(仅 teach)
- teach 新增 target_points 参数并实现计数:显示 [3/12] ... 还差 9 个点,
  采够时提示可以收工
- 刻意不强制关窗:实际采点常出现"某个角度还没覆盖",强制退出要重开

usage 重写:路径改为 handeye/,补上全局选项与组合示例;
usage() 的 sed 行号随之同步(原 2,18p 已不覆盖新头部)。

handeye/README.md 补第 5 节:模式切换与点数的完整用法,
并说明为什么非法模式要报错而不是兜底。

测试:224 passed
2026-09-23 11:48:11 +08:00

249 lines
9.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# handeye/ —— 手眼标定
这个文件夹放**手眼标定所需的全部配置、采点与结果**。以前这些东西散在
`src/arm_application/config/` 里,和别的配置混在一起,改板子尺寸要翻三个文件 ——
现在集中在这里,改一个地方就够了。
---
## 1. 先搞懂"手眼标定"在干什么
机械臂要"看着东西去操作",就必须知道**相机看到的位置**和**机械臂自己认为的位置**
之间的对应关系。这个对应关系就是一个 4×4 的变换矩阵,标定过程就是把它解出来。
两种装法,决定了解出来的矩阵含义不同:
| 模式 | 相机装在哪 | 解出的 X 是什么 | 本项目的用法 |
|------|-----------|----------------|-------------|
| `eye_in_hand` | **相机装在机械臂末端(法兰/手腕)上** | 相机 → 法兰 | **D405 腕部相机(当前使用)** |
| `eye_to_hand` | **相机固定在旁边不动** | 相机 → 机械臂基座 | D435 顶置相机(预留) |
**怎么选**:看相机是不是跟着机械臂动。
- 跟着动 → `eye_in_hand`
- 不动 → `eye_to_hand`
选错模式的后果:标定能算完、不报错,但结果**完全不对**(点云/抓取会系统性偏掉)。
所以模式在文件名里就标出来了(见第 4 节),避免拿错文件。
---
## 2. 文件夹结构
```
handeye/
├── README.md ← 你正在看的这份
├── calibrate.yaml ← 标定节点参数(板尺寸、运动、稳定性)
├── waypoints.yaml ← 旧的"巡航记点"配置(legacy,一般用不到)
├── poses/ ← 【输入】示教采点(每个点一组关节角)
│ ├── 89_eye_in_hand.yaml
│ └── 91_eye_in_hand.yaml
├── results/ ← 【输出】标定结果(4×4 变换矩阵)
│ ├── 89_eye_in_hand.yaml
│ └── 91_eye_in_hand.yaml
└── tmp/ ← 临时产物(采点图、taught_poses.txt、captures.json)
不进 git
```
**命名约定**:`<臂号>_<模式>.yaml`
- 臂号 = Flexiv 序列号后两位(`Rizon4-063989` → `89`,`Rizon4-063991` → `91`)
- 模式 = `eye_in_hand` | `eye_to_hand`
运行时谁读结果:`config/site.yaml` 里 `robots[89].paths.handeye_file` 指向
`handeye/results/89_eye_in_hand.yaml`,再由各 launch 文件发布成静态 TF。
**换模式 = 把 site.yaml 里那个路径改成 `_eye_to_hand` 那份**,不需要改代码。
---
## 3. 标定板(最容易出错的地方)
当前使用的板:
```
内角点 8 列 × 11 行 (注意是"内角点",不是方格数!)
方格边长 15 mm (0.015 m)
```
**这个尺寸必须三处一致**,否则标定结果会系统性偏错:
| 位置 | 内容 |
|------|------|
| `config/site.yaml` → `handeye.board_cols/rows/square_size_m` | 8 / 11 / 0.015 |
| `handeye/calibrate.yaml` | `board_cols/rows/square_size_m` |
| `src/.../handeye_calibrate.py` 的参数默认值 | 代码兜底 |
> ⚠️ **历史上这里出过问题**:曾经三处分别写着 `9×6/0.025`、`11×16/0.011`、
> `8×11/0.015`,谁生效取决于覆盖顺序。症状是"标定流程跑得很顺、不报错,
> 但结果明显不对",非常难查。现已统一为 `8×11/0.015`。
> **换板子时,请把上面三处一起改。**
怎么看内角点:把棋盘当成一张网格,**数内部的黑白交叉点**,
是「方格数 − 1」。比如 A5 板上画了 9×12 个格子 → 内角点是 8×11。
---
## 4. 完整流程
三条命令,按顺序执行。**先决条件**:标定板摆在相机视野里,机械臂能自由拖动。
### 第一步:采点(拖动示教)
```bash
bash ~/ros2/scripts/run_handeye.sh teach --arm 89
```
会弹出实时预览窗口,里面能看到标定板有没有被识别到。操作:
| 按键 | 作用 |
|------|------|
| `d` | 打开软拖拽(此时可以用手推机械臂) |
| `s` | 关闭软拖拽(锁住) |
| `Enter` / `空格` | **拍照记点**(记录当前关节角 + 图像) |
| `q` / `Esc` | 结束采点 |
要点:
- **点位要分散**:让标定板在画面里出现在不同位置、不同角度(远/近/左/右/倾斜)。
全挤在一个角落会让求解退化,结果不可信。
- **至少 5 个点**,推荐 **10~15 个**。数量在 `poses/*.yaml` 的 `num_poses` 里,
也可以直接多按几次 `Enter`。
- 采点数据写在 `handeye/tmp/`,**确认无误后再 merge**(下一步)。
### 第二步:合并点位
```bash
bash ~/ros2/scripts/run_handeye.sh merge --arm 89
```
把临时采到的关节角写入 `handeye/poses/89_eye_in_hand.yaml`,
并自动同步 `num_poses`。这一步不做数学计算,只是搬运+格式化。
### 第三步:自动标定
```bash
bash ~/ros2/scripts/run_handeye.sh calibrate --arm 89
```
机械臂会**自动**走一遍刚才记下的所有点位,每个点:
1. 移动到目标位姿
2. 校验确实到位了(防止控制器假报成功)
3. **等机械臂真的停下来**(看关节速度,不是干等固定秒数)
4. 等画面里的标定板**连续多帧稳定**(角点抖动小于阈值)
5. 拍照、记录法兰位姿 + 板位姿
6. 屏幕上打一条进度条
全部走完后一次性解算出结果,写入
`handeye/results/89_eye_in_hand.yaml`。
```
[ 4/12] [████████░░░░░░░░░░░░░░░░] 33.3% moving...
```
某个点看不到板时会**跳过该点**继续(`skip_unstable_poses: true`),
不会整轮中止 —— 但如果跳掉的点很多,说明采点时板的位置不好,建议重采。
### 查看当前状态
```bash
bash ~/ros2/scripts/run_handeye.sh show --arm 89
```
打印臂号、配置文件路径、板尺寸、已记录点数,以及当前的标定结果是否存在。
---
## 5. 两种模式
- **自动模式** = 上面的 `calibrate`。机械臂自己走点位,人不用管,
适合"点位已经采好、只想重算一遍"。
- **手动模式** = 上面的 `teach`。人拖着机械臂摆姿势、手动决定什么时候记这个点。
适合第一次标定,或者想补几个点位。
两者共用同一份 `poses/*.yaml`,顺序是 `teach`(采)→ `merge`(存)→ `calibrate`(算)。
### 切换 eye-in-hand / eye-to-hand
```bash
# 用腕部相机(默认)
bash ~/ros2/scripts/run_handeye.sh --arm 89 teach
# 用固定相机
bash ~/ros2/scripts/run_handeye.sh --arm 89 teach --mode eye_to_hand
bash ~/ros2/scripts/run_handeye.sh --arm 89 merge --mode eye_to_hand
bash ~/ros2/scripts/run_handeye.sh --arm 89 calibrate --mode eye_to_hand
```
`--mode` 会把读写的文件切到 `<臂号>_<模式>.yaml`,所以:
- 两种模式**互不覆盖**,可以分别标定、共存
- 跑完想切换使用,改 `config/site.yaml` 里 `robots[89].paths.handeye_file`
指向对应那份即可,代码不用动
传了非法模式会立刻报错并退出(不会悄悄退回默认值 —— 那会导致"以为标的是
eye-to-hand、其实算的是 eye-in-hand"这种最难查的错)。
### 设定采集点数
```bash
bash ~/ros2/scripts/run_handeye.sh --arm 89 teach --points 12
```
采点过程中会显示 `[3/12] ... 还差 9 个点`,采够时提示可以收工。
**不会强制关窗** —— 常常采到一半发现某个角度还没覆盖,强制退出反而要重开;
想多采就继续按 `Enter`,想结束按 `q`。
不加 `--points` 则不限数量(默认),建议 8~12 个点,至少 5 个。
---
## 6. 结果怎么被用起来
`handeye/results/89_eye_in_hand.yaml` 的内容长这样:
```yaml
translation_m: [-0.06085181, -0.00864320, 0.12364093]
T_4x4:
- [0.01784499, -0.86878381, 0.49486994, -0.06085181]
- [0.99977570, 0.02115147, 0.00108124, -0.00864320]
- [-0.01140659, 0.49473964, 0.86896638, 0.12364093]
- [0.00000000, 0.00000000, 0.00000000, 1.00000000]
```
它被以下地方读取,最终以**静态 TF** 的形式发布出去:
| 谁读 | 在哪 |
|------|------|
| `pipeline.launch.py` | 发布 `{法兰}` → `{相机}_color_optical_frame` 静态 TF |
| `visual_servo.launch.py` | 同上,并把路径传给 `servo_node` |
| `stage2_locate.launch.py` | 同上;文件缺失会直接报错 |
| `stage3_search.launch.py` | 同上 |
| `servo_node.py` | 用 `T_4x4` 算相机倾角、做 hover/lookat |
也就是说:**标定结果不对,上面这些全会跟着错**。改完结果建议重启相关节点。
---
## 7. 出了问题先看这里
| 现象 | 多半是 |
|------|--------|
| 标定跑完但抓取/定位系统性偏 | 板尺寸三处不一致;或模式选错(eye-in-hand / eye-to-hand 弄反) |
| 一直"棋盘不稳定"、点位被跳过 | 标定板没完整入镜 / 反光过曝 / 太远太小;调低 `stable_pixel_std` 或改善光照 |
| `ERROR: 机械臂控制器未激活` | 先另开终端跑 `bash ~/ros2/scripts/run_real.sh --arm 89` |
| `hand-eye result missing` | `site.yaml` 里的 `paths.handeye_file` 指到了不存在的文件 |
| 拖动时臂不动 | 软拖拽没开(按 `d`);或 RDK 与 `run_real.sh` 抢占,见 `docs/INTERFACES.md` |
| 采的点少于 5 个 | `merge` 会拒绝(求解至少需要 5 组,且姿态要有区分度) |
---
## 8. 相关文件
- 拖动引擎:`scripts/rdk_drag_bridge.py`
- 拖拽的 ROS 服务:`/soft_drag`(在 `arm_driver_flexiv` 里)
- 数学与视觉:`src/arm_application/arm_application/handeye_lib.py`
- 采点节点:`src/arm_application/arm_application/handeye_teach.py`
- 求解节点:`src/arm_application/arm_application/handeye_calibrate.py`
- 单元测试:`src/arm_application/test/test_handeye_lib.py`
(用合成数据验证求解正确性,无需硬件:
`python3 -m pytest src/arm_application/test/test_handeye_lib.py`)