b903161113
眼在手上改用该 TCP 做 AX=XB,再折回相机→法兰;A5 内角点改为 11×8,09-04 / 10-09 旧结果留在 backup。一阶段 D435 迁臂改成一条 FollowJointTrajectory(余弦加减速,近处降速,XY 进门槛后再单独收垂直),跟踪锁高与悬停齐平,避免倾斜相机下先降一截把孔位重投影偏掉。--no-descent 只降到离面 15 mm;跳过三阶段时触觉 USB 打不开不再空等几分钟。 Co-authored-by: Cursor <cursoragent@cursor.com>
278 lines
12 KiB
Markdown
278 lines
12 KiB
Markdown
# 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)
|
||
├── 89/ **按臂号分目录**:两条臂同时标定不会互相覆盖
|
||
└── 91/ 不进 git
|
||
```
|
||
|
||
⚠️ `tmp/` 下是**按臂号分目录**的(`tmp/89`、`tmp/91`),不是所有臂共用一个。
|
||
真实生效的值来自 `site.yaml` 的 `arms.<臂>.handeye_workdir` 与顶层
|
||
`handeye_workdir`。两条臂若共用一个工作目录,先标定的那份采点会被后
|
||
标定的覆盖,而症状是"标定结果突然变得很差",不容易联想到是目录冲突。
|
||
|
||
**命名约定**:
|
||
- 眼在手上:`<臂号>_eye_in_hand.yaml`(`paths.handeye_file` 指向它)
|
||
- 眼在手外:`<臂号>_<相机>_eye_to_hand.yaml`(S03 按这个名字自己找,
|
||
不要改 `paths.handeye_file`)
|
||
- 臂号 = Flexiv 序列号后两位(`Rizon4-063989` → `89`)
|
||
- 示教点:`<臂号>_<模式>.yaml`(两种模式的关节角完全不同,分开存)
|
||
|
||
---
|
||
|
||
## 3. 标定板(最容易出错的地方)
|
||
|
||
两种装法用**两块不同的物理板**,尺寸按模式分开写,不能共用一组键:
|
||
|
||
| 模式 | 物理板 | 内角点(列 × 行) | 方格边长 | `site.yaml` 里的位置 |
|
||
|------|--------|-------------------|----------|----------------------|
|
||
| `eye_in_hand` | A5(腕部相机看台面) | 11 × 8 | 15 mm (`0.015`) | `handeye.boards.eye_in_hand` |
|
||
| `eye_to_hand` | A4(板夹在 `tool10120938` 上) | 11 × 8 | 20 mm (`0.020`) | `handeye.boards.eye_to_hand` |
|
||
|
||
权威只有 `config/site.yaml` 的 `handeye.boards.<模式>`。`run_handeye.sh` 按
|
||
`--mode` 取对应那一组,用 `-p` 传给节点。`handeye/calibrate.yaml` 和节点默认值
|
||
只是兜底(眼在手上那块板),**没配过的模式会被脚本当场拦下**,不会静默用兜底
|
||
尺寸去标(那会让结果系统性偏,还不报错)。
|
||
|
||
怎么看内角点:把棋盘当成一张网格,**数内部的黑白交叉点**,
|
||
是「方格数 − 1」。A5 板上画了 12×9 个格子 → 内角点 11×8;
|
||
A4 板上画了 12×9 个格子 → 内角点 11×8(方格 20 mm)。列/行顺序不用纠结,检测端两种顺序都会试。
|
||
|
||
> ⚠️ **历史上这里出过问题**:曾经三处分别写着 `9×6/0.025`、`11×16/0.011`、
|
||
> `8×11/0.015`,谁生效取决于覆盖顺序。后来又把两块板合成一组键,于是标眼在手外
|
||
> 时静默用了 A5 的 15 mm。症状都是"标定流程跑得很顺、不报错,但结果明显不对"。
|
||
|
||
---
|
||
|
||
## 4. 完整流程
|
||
|
||
三条命令,按顺序执行。**先决条件**:标定板摆在相机视野里,机械臂能自由拖动。
|
||
|
||
### 第一步:采点(拖动示教)
|
||
|
||
```bash
|
||
bash ~/ros2/scripts/run_handeye.sh teach --arm 89
|
||
```
|
||
|
||
会弹出实时预览窗口(**只显示画面,不做棋盘检测**——真正的角点稳定判定在
|
||
`calibrate` 那一步)。操作:
|
||
|
||
| 按键 | 作用 |
|
||
|------|------|
|
||
| `d` | 打开软拖拽(此时可以用手推机械臂) |
|
||
| `s` | 关闭软拖拽(锁住) |
|
||
| `Enter` / `空格` | **拍照记点**(记录当前关节角 + 图像) |
|
||
| `q` / `Esc` | 结束采点 |
|
||
|
||
要点:
|
||
- **点位要分散**:让标定板在画面里出现在不同位置、不同角度(远/近/左/右/倾斜)。
|
||
全挤在一个角落会让求解退化,结果不可信。
|
||
- **至少 5 个点**,推荐 **10~15 个**。数量在 `poses/*.yaml` 的 `num_poses` 里,
|
||
也可以直接多按几次 `Enter`。
|
||
- 采点数据写在 `handeye/tmp/<臂号>/`(如 `handeye/tmp/89/`),**确认无误后再 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`(算)。
|
||
|
||
### 一条命令跑完(手动模式 + 自动求解)
|
||
|
||
```bash
|
||
# 需要先在另一个终端跑 run_real.sh
|
||
bash ~/ros2/scripts/run_handeye.sh teach --ros --auto-solve --arm 89
|
||
```
|
||
|
||
采完点按 `q` 之后,脚本会自动 `merge`、重启相机、接着 `calibrate`,
|
||
不用再敲第二条命令。
|
||
|
||
> ⚠️ **`--auto-solve` 只在 `--ros`(示教器)模式能全程跑通。**
|
||
>
|
||
> 原因是一个硬件约束:标定必须通过 `run_real.sh` 的控制器驱动机械臂走点位,
|
||
> 而 `--rdk` 软拖拽要求 **RDK 独占**,两者无法同时存在。
|
||
> 所以 `--rdk --auto-solve` 会照常自动 merge,然后明确告诉你剩下那两条命令 ——
|
||
> **这是物理限制,不是脚本能绕过的**。
|
||
>
|
||
> 想要真正一条命令,就用示教器 Free Drive:先在示教器上打开 Free Drive,
|
||
> 再用 `--ros --auto-solve`。
|
||
|
||
|
||
### 切换 eye-in-hand / eye-to-hand
|
||
|
||
```bash
|
||
# 用腕部相机 + A5 板(默认,板放台面不动)
|
||
bash ~/ros2/scripts/run_handeye.sh --arm 89 teach
|
||
|
||
# 用固定相机 + A4 板(板必须装到法兰上,相机不动)
|
||
# 前置:另开终端 bash ~/ros2/scripts/run_camera_d435.sh --preview
|
||
bash ~/ros2/scripts/run_handeye.sh --arm 89 teach --mode eye_to_hand --points 12
|
||
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` 会同时切:标定板、相机、示教点文件、结果文件。所以:
|
||
|
||
- 两种模式**互不覆盖**,可以分别标定、共存
|
||
- 眼在手上结果:`handeye/results/<臂号>_eye_in_hand.yaml`(`T` = 相机 → 法兰)
|
||
- 眼在手外结果:`handeye/results/<臂号>_<相机>_eye_to_hand.yaml`
|
||
(`T` = 相机 → 这条臂的基座;双臂共用一台 D435 时必须按臂分开)
|
||
- **不要**把 `paths.handeye_file` 改成眼在手外那份 —— 那条路径是腕部相机
|
||
(S05 精拍定位)用的。手外那份由 `gauge_assembly.launch` 按名字自己找,
|
||
并以 **基座 ← 相机** 发布 TF(不是法兰 ← 相机)
|
||
|
||
传了非法模式会立刻报错并退出(不会悄悄退回默认值 —— 那会导致"以为标的是
|
||
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`)
|