Files
ros_contorl/models/README.md
T
arm_dev 2b77ec127d P4:权重/标签模块 —— 旁挂标签文件 + PT→ONNX→TRT 转换工具 + 归档归位
痛点:TensorRT 10.13 的 Python API 没有元数据读写,engine 一转就把类名丢了;
推理侧只能退化成 str(cid)("0"、"1"),界面上看不出是什么类,属于静默失效。
同时旧的 export_yolo_trt.sh 只能手工搬文件、手工改 site.yaml、标签无处安放。

标签子系统(model_labels.py)
- 旁挂标签文件 <模型名>.labels.yaml,放在权重旁边,文本、可手改、进 git
  (二进制权重不进 git,标签是配置所以进)
- 读取优先级:旁挂文件 > ONNX 元数据 > 模型元数据 > 占位符 class_N
  占位符是故意的:缺标签时要一眼看出来,而不是悄悄退回 cid
- 解析器容忍多种写法:dict / list / 逗号串 / 逐行文本 / Python repr 串

布局与转换(weights_layout.py + weights.py + weights.sh)
- models/<分组>/<模型名>.{pt,onnx,engine} + 同名 .labels.yaml
- convert:PT→ONNX→TensorRT,支持自定义名字、自动归位、固化标签
- staging/:非标准格式先进这里,转换后源文件归档到 staging/done/
- list / labels / doctor / show 四个子命令
- doctor --check-engines 能查出 engine 与 TensorRT 版本不匹配(反序列化返回空)

推理侧接入
- yolo_detector / onnx_workpiece / hole_detect_hough 统一走 read_labels()
- 画框标签不再硬编码 "gongjian",改用实际类名

顺手修掉的真实问题
- setup.py 撞上断掉的符号链接会让整包构建失败,报错还指向 build/ 里的幽灵文件
  (源文件被挪走后 build/ 里留下的断链被 os.walk 列出来)。加 _existing() 过滤。
- P3 遗漏:site.yaml / export_site_env.py / run_handeye.sh / handeye_teach.py /
  handeye_calibrate.py 里的 handeye 工作目录仍是 /tmp/handeye_calib*,
  与已迁到 handeye/ 的其余路径不一致。统一到 handeye/tmp/<臂号>。
- site.yaml 的 hole_weights / hole_detect.weights 原指向不存在的 best.pt,
  改为实际存在的 detect_best_1.onnx;active.pt 符号链接已废弃并清理。
- weights.sh labels 原先不接受 "分组/模型名" 写法,而 list 输出正是这个形式,
  照着敲会报"找不到模型"。补上,并让报错直接给出可复制的全名。

测试:225 项通过(新增 test_model_labels.py / test_weights_layout.py)。
真实 ONNX→engine 端到端转换已验过。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-23 12:39:18 +08:00

274 lines
10 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.
# 算法权重模块(models/)
这个目录放**算法模型权重**和它们的**类别标签**。机器人软件里"算法"和"机械/电气"
变化节奏完全不同:机械改一次要几个月,模型换一版可能一周几次。所以这一块单独
成模块,有自己的一套约定和工具,改模型不需要动任何 C++/节点代码。
> **要改这个模块的人,先读完本文。** 下面是规则,不是建议。
---
## 1. 目录长什么样
```
models/
├── README.md ← 本文(模块规则)
├── staging/ ← 非标准格式先丢这里,等转换
│ ├── .gitkeep
│ └── done/ ← 转换完成后源文件自动归档到这里
├── yolo/ ← 一个"分组"(就是个子目录,名字随意)
│ ├── detect_best_0.onnx
│ ├── detect_best_0.engine
│ └── detect_best_0.labels.yaml
└── hole_insert/
├── detect_best_1.onnx
└── detect_best_1.labels.yaml
```
**分组**只是一个子目录名,用来分门别类,没有程序语义。默认用 `yolo`。
---
## 2. 命名规则(重要)
同一个模型的**所有格式 + 标签**必须用**同一个名字主干**、放在**同一个目录**:
| 文件 | 作用 | 进 git? |
|---|---|---|
| `<名字>.pt` | PyTorch 原始权重 | 否 |
| `<名字>.onnx` | 中间格式,CPU 推理后端也用 | 否 |
| `<名字>.engine` | TensorRT 引擎,GPU 推理用 | 否 |
| `<名字>.labels.yaml` | **类别标签** | **是** |
因为推理代码是按"同目录同主干"去找配套文件的(`resolve_weights` 找 `.engine`,
标签找 `.labels.yaml`)。所以:
- ✅ `detect_best_0.onnx` + `detect_best_0.engine` + `detect_best_0.labels.yaml`
- ❌ `detect_best_0.onnx` + `detect_best_0_fp16.engine`(名字对不上,找不到)
- ❌ `detect_best_0.onnx` + `labels_detect_best_0.yaml`(标签名对不上)
**一份标签服务同一个模型的所有格式。** 换 `.pt`→`.engine` 后端时类名不会变。
---
## 3. 标签(class names)
### 为什么要有单独的标签文件
模型输出的是**类别索引**(0/1/2…),要翻译成人看得懂的名字。这件事原先没有统一
做法,三种后端各来一套,而且**TensorRT 那一环会把标签弄丢**。
丢的原因是硬的:TensorRT 引擎要用 `trt.OnnxParser` 重新构建,而
**当前安装的 TensorRT(10.13)Python API 根本没有 metadata 读写接口** ——
`INetworkDefinition` / `IBuilderConfig` / `ICudaEngine` 上都没有任何 metadata 方法。
实测二进制里搜一下就能看出来:
```
detect_best_0.engine 搜不到 "workpiece" → 标签已丢
```
丢了的后果很隐蔽:读不到名字时代码会退化成把索引当名字用(类名变成 `"0"`),
而预览界面又不画类名 —— **没有任何地方看得出标签丢了**。
所以标签放在**旁边的文本文件**里。它不是"退而求其次",而是唯一可行且有好处:
1. **可修改** —— 改标签不用重新转换模型,存盘即生效;
2. **跨格式统一** —— `.pt`/`.onnx`/`.engine` 共用一份,换后端名字不变。
### 写法
`<名字>.labels.yaml`:
```yaml
model: detect_best_0 # 可选,写对了能帮你发现"标签文件拷错了模型"
names:
- workpiece # 第 1 行 = 类别 0
- tongzhigui # 第 2 行 = 类别 1
```
第 1 行对应类别 0,第 2 行对应类别 1,依次类推。也认这些写法(从别处拷配置常见):
```yaml
names: {0: workpiece, 1: tongzhigui} # 字典形式(Ultralytics data.yaml 风格)
```
```yaml
names: "workpiece, tongzhigui" # 逗号串
```
```yaml
workpiece # 纯文本,一行一个(classes.txt 风格,注释行以 # 开头)
```
### 读取优先级
```
<名字>.labels.yaml > ONNX 内嵌 metadata > 模型内嵌 metadata > 占位符 class_0
```
旁挂文件**必须**赢,因为它是人能改的那一份。否则"我明明改了标签为什么不生效"
会变成一个很难查的问题。
全都读不到时会用占位名 `class_0`、`class_1`…,并且**打一条 warning**。
刻意不用裸数字 `"0"` —— 那个和"真的有个叫 0 的类"分不出来。
### 怎么改 / 怎么看
```bash
bash scripts/weights.sh labels detect_best_0 # 看
bash scripts/weights.sh labels detect_best_0 --set "工件,通止规" # 直接改
bash scripts/weights.sh labels detect_best_0 --set-file classes.txt # 从文本文件导入
bash scripts/weights.sh labels detect_best_0 --from-model # 把模型内嵌标签抄出来变成可编辑
bash scripts/weights.sh labels detect_best_0 --json # 给程序读
bash scripts/weights.sh labels detect_best_0 --clear # 删掉,回到模型内嵌
```
改完**重跑一下节点**就生效,不用重新转换模型、不用重建 engine。
---
## 4. 加一个新模型(完整流程)
### 步骤 1:把原始文件丢进 staging
不管它原来叫什么、什么格式,先扔进 `models/staging/`:
```bash
cp ~/下载/best_v3_final\(1\).pt ~/ros2/models/staging/
```
### 步骤 2:一条命令转换 + 改名 + 归位
```bash
bash ~/ros2/scripts/weights.sh convert staging/best_v3_final\(1\).pt --name workpiece --group yolo
```
这条命令会:
1. `--name workpiece` → 产出统一叫 `workpiece.onnx` / `workpiece.engine`(**自动改名**)
2. 依次跑 `PT → ONNX → engine`(TensorRT 只能由 ONNX 构建,所以中间产物必出)
3. 把产出放到 `models/yolo/`(**自动归位**)
4. **顺带把标签落成 `workpiece.labels.yaml`**(从源模型内嵌 metadata 抄)
5. 把 staging 里的源文件挪到 `models/staging/done/`(staging 永远是一份"待办清单")
常用选项:
| 选项 | 用途 |
|---|---|
| `--to onnx` | 只要 ONNX,不建 engine(不需要 GPU) |
| `--to engine` | 只要 engine(仍会先产出 onnx 做中间产物) |
| `--imgsz 640` | 输入尺寸(默认取 `config/site.yaml` 的 `yolo.imgsz`) |
| `--labels "工件,通止规"` | 顺手写标签 |
| `--group <分组>` | 输出到别的子目录 |
| `--force` | 覆盖已存在的产出(不加则拒绝覆盖,防误删) |
| `--dry-run` | 只看计划,不动文件 |
| `--no-fp16` | engine 不用 FP16 |
| `--keep-staging` | 保留 staging 里的源文件 |
### 步骤 3:让配置指向它
改 `config/site.yaml`:
```yaml
paths:
yolo_weights: "/home/USER/ros2/models/yolo/workpiece.onnx" # 写 onnx;engine 会自动找同名的
yolo:
backend: "tensorrt" # tensorrt / onnx / auto
imgsz: 640 # 必须等于该模型的输入尺寸
```
> 写 `.onnx` 路径即可。`backend: tensorrt` 时程序会自动找同名的 `.engine`;
> 写 `.pt` 也一样能找到 `.engine`。所以换后端不用改配置。
### 步骤 4:确认
```bash
bash ~/ros2/scripts/weights.sh list # 模型在不在、标签读不读得出来
bash ~/ros2/scripts/weights.sh doctor # 体检
```
---
## 5. 体检与排错
```bash
bash ~/ros2/scripts/weights.sh doctor --check-engines
```
会检查:
- 每个模型的标签来源;缺标签的会指出期望的文件名
- **engine 能不能被当前 TensorRT 加载**(见下)
- `config/site.yaml` 里指向不存在的文件的**悬空引用**
- `backend: tensorrt` 但同名 `.engine` 不存在(启动会直接失败)
- staging 里有没有待转换的文件
### 常见问题
**「engine 明明在,启动却说加载失败」**
**引擎和 TensorRT 版本是绑死的。** 换了 TensorRT(重装环境、升级 CUDA)
之后旧引擎会直接反序列化失败,报 `incompatible serialization version` 之类。
表现是"文件在、大小也对,就是不能用"。用 `doctor --check-engines` 能查出来,
重导一次即可:
```bash
bash scripts/weights.sh convert models/yolo/<名字>.onnx --name <名字> --to engine --force
```
> 实测本仓库里的 `detect_hc90.engine` 就是这样失效的(用旧版 TensorRT 构建,
> 当前 10.13 无法加载)。另外两个 engine 正常。
**「类名显示成 `class_0`」**
标签没配上。`weights.sh labels <名字>` 会告诉你缺什么,然后
`--from-model` 或 `--set` 补一份。
**「同一个模型,换个后端类名就变了」**
不该发生。如果出现了,说明少了 `.labels.yaml`,各后端的回退来源不一致。
补上旁挂文件即可。
**「`convert` 说目标已存在」**
默认不覆盖,防止把别人的模型冲掉。确认要覆盖加 `--force`。
---
## 6. 规则(改动本模块时必须遵守)
1. **二进制不进 git。** 靠 `.gitignore` 的通配规则挡住。新加分组不用改 `.gitignore`。
2. **`.labels.yaml` 要进 git。** 它是配置,体积小,是团队间的唯一事实来源。
3. **不要把类名写死在代码里。** 一律走 `arm_application.model_labels`。
已清理过的反例:`onnx_workpiece.py` 里曾硬编码 `{0: "workpiece"}`,
`hole_detect_hough.py` 里曾硬编码框上画的字 `"gongjian"`(而真类名是 `workpiece`)。
4. **不要在"就地"改权重名字来换模型。** 用 `weights.sh convert --name`,
让工具保证"名字主干一致 + 同目录"这条不变量。
5. **不要手工搬 engine 到别的目录或改名。** 改名会切断和 onnx/标签的关联。
6. **新增推理后端时,标签必须走 `model_labels.read_labels()`**,
否则又会造出一个"某个后端读不到标签"的坑。
7. **`models/staging/` 不是永久存储。** 它只放"待转换"的东西。
---
## 7. 代码在哪
| 位置 | 作用 |
|---|---|
| `arm_application/model_labels.py` | 标签读写、来源优先级、占位符(纯逻辑,无 ROS) |
| `arm_application/weights_layout.py` | 目录布局、模型发现、转换链规划(纯逻辑,无 ROS) |
| `scripts/weights.py` | 命令行实现(转换真正干活的地方) |
| `scripts/weights.sh` | 薄封装:准备环境 → 转发给 `weights.py` |
| `scripts/export_yolo_trt.sh` | **旧命令**,现在转发到 `weights.sh`,保持兼容 |
| `arm_application/yolo_detector.py` | 运行时加载模型 + `resolve_class_labels()` |
| `arm_application/onnx_workpiece.py` | ONNX Runtime 后端,读标签同样走 `model_labels` |
测试:`src/arm_application/test/test_model_labels.py`(标签)、
`test_weights_layout.py`(布局/发现/CLI)。
---
## 8. 相关文档
- `docs/SITE_CONFIG.md` —— `paths.yolo_weights` / `yolo.backend` 等配置项
- `docs/WORKSPACE.md` —— 工作区总览
- `docs/TRACKING.md` —— 换模型要同时改哪几处