Files
yolov26_3d/tools/convert_merge_tracking_bundle/USAGE.md

324 lines
8.8 KiB
Markdown
Raw Normal View History

2026-06-24 09:35:46 +08:00
# convert_merge_tracking_bundle
## 目录说明
这个目录整理了 `convert_merge_tracking.py` 及其运行依赖,并额外包含了 `make_jl.py`,方便在独立目录中完成以下两步:
1.`merge_tracking.json` 转成 `ObjectPerceptionObjectList` 的 protobuf 三件套。
2. 再把三件套转成便于查看的 JSON 文本输出。
目录中的关键文件:
- `convert_merge_tracking.py`:把 `merge_tracking.json` 转成 protobuf 输出。
- `make_jl.py`:把 `ObjectPerceptionObjectList.data.json` 对应的数据转成 JSON 文本。
- `proto_reader/reader.py`:按 `data.json + index.json + bin` 的组合方式读取二进制消息。
- `pyproto/*.py`protobuf 生成代码,提供 `ObjectList``Object` 等消息定义。
- `requirements.txt`:这个独立目录所需的最小第三方 Python 依赖。
## 环境准备
建议使用 Python 3。
安装依赖:
```bash
pip install -r requirements.txt
```
`requirements.txt` 中只有两个最小外部依赖:
- `protobuf>=3.6.1,<4`:用于加载当前目录中的 `*_pb2.py` 生成代码。
- `absl-py>=0.7``proto_reader/reader.py` 中使用了 `absl.logging`
之所以把 `protobuf` 限制在 `<4`,是因为当前打包的 `pyproto/*.py` 是旧风格生成代码,和 4.x 及以上版本可能不兼容。
## 使用方法
### 1) 把 merge_tracking.json 转成 protobuf 三件套
```bash
python3 convert_merge_tracking.py /path/to/merge_tracking.json -o ./out
```
如果需要强制指定相机 ID
```bash
python3 convert_merge_tracking.py /path/to/merge_tracking.json -o ./out --cam-id 4
```
执行后会在输出目录生成:
- `ObjectPerceptionObjectList.bin`
- `ObjectPerceptionObjectList.index.json`
- `ObjectPerceptionObjectList.data.json`
这三个文件的关系如下:
- `*.bin`:连续写入的 protobuf 二进制消息。
- `*.index.json`:记录每一帧消息在 `*.bin` 里的 `offset``size`
- `*.data.json`:入口配置文件,告诉读取程序去哪里找 `bin``index`
### 2) 把 protobuf 三件套转成 JSON 文本
```bash
python3 make_jl.py ./out/ObjectPerceptionObjectList.data.json -o ./out/ObjectPerceptionObjectList.jl
```
如果不加 `-o`,结果会直接输出到终端:
```bash
python3 make_jl.py ./out/ObjectPerceptionObjectList.data.json
```
说明:
- 虽然脚本命名和注释中把输出叫做 `jl``JsonLines`,但当前实现会给每条记录加缩进,所以每条记录可能占多行。
- 因此它更准确地说是“逐条追加的 JSON 文本文件”,而不是严格意义上的单行 JSONL。
## 脚本功能说明
### convert_merge_tracking.py
功能:
- 读取 `merge_tracking.json`
- 把每一帧的 `detections` 转成 `ObjectPerceptionObjectList` protobuf。
- 按帧顺序写入二进制文件,并生成配套索引文件和入口描述文件。
适用场景:
- 需要把检测结果接入已有的 `ObjectPerceptionObjectList` 数据链路。
- 需要生成能被 `make_jl.py` 继续消费的中间数据。
### make_jl.py
功能:
- 读取 `ObjectPerceptionObjectList.data.json`
- 根据 `index.json``bin` 逐条取出 protobuf 消息。
- 将每条 `ObjectList` 转成 JSON 文本并输出到文件或标准输出。
适用场景:
- 想快速查看 protobuf 内容。
- 想把二进制结果转成更易读、更方便后处理的 JSON 文本。
### proto_reader/reader.py
功能:
- 解析 `data.json` 中记录的文件位置。
- 读取 `index.json`
-`offset + size``bin` 中精确提取每一条 protobuf 消息的原始字节。
它是 `make_jl.py` 的底层读取器。
## 每个脚本的实现逻辑
### convert_merge_tracking.py 的实现逻辑
#### 1. 加载 protobuf 定义
脚本启动时会把当前目录下的 `pyproto` 加入 `sys.path`,然后导入:
- `object_pb2`
- `geometry_pb2`
- `camera_pb2`
其中真正核心的是 `object_pb2`,它定义了 `Object``ObjectList`
#### 2. 定义映射表
脚本内置了三类映射:
- `CLASS_ID_MAP`:把输入里的 `class_id` 映射到 `ObjectType` 枚举。
- `ANCHOR_MAP`:把 anchor 字符串映射到 `AnchorPtInfo` 枚举。
- `FACE_CLS_MAP`:把 `face_cls` 映射到 `VehiclePose` 枚举。
这些映射决定了输入字段如何落到 protobuf 枚举值上。
#### 3. 从输入帧中提取 frame_id 和 cam_id
`parse_image_name()` 会优先从 `image_name` 中解析:
- 帧号 `frame_id`
- 相机号 `cam_id`
同时脚本也支持:
- 从 detection 的 `frame_id``frameId` 中覆盖帧号。
-`--cam-id` 强制覆盖相机号。
#### 4. 把单个 detection 的公共字段填入 protobuf
`populate_object_fields()` 负责把一个 detection 的常用字段写入目标 `Object`,包括:
- 类别和枚举映射
- `track_id`
- `frame_id`
- `timestamp`
- `lane_assignment`
- 图像框信息 `bbox`
- 世界坐标下的 `pos / size / yaw`
- `face_cls` 对应的姿态
- 可选的 `model_3d` 信息
这个函数是整个转换逻辑的公共字段装配器。
#### 5. 构建 Mono3D 量测组件
`build_mono_measure_component()` 会从 `object_3d_ego` 中取出:
- `x, y, z`
- `l, h, w`
- `yaw`
再构造一个 `measure_type = kMeasureMono3D``Object`,作为量测组件挂到上层对象里。
#### 6. 构建最终 Object 的层次结构
`build_object()` 会生成两层核心对象,并形成嵌套关系:
- `source_obj`:表示单目来源对象,包含图像框、模型 3D、Mono3D 量测等信息。
- `obj`:最终输出对象,把 `source_obj` 作为 `key_components` 挂进去。
如果 `object_3d_ego` 存在,还会额外挂入一个更底层的 Mono3D 量测 `Object`
因此最终会形成一种层次化组织:
- 最终目标对象
- 来源单目对象
- Mono3D 量测对象
#### 7. 按帧生成 ObjectList
`build_object_list()` 对单帧做处理:
- 创建一个 `ObjectList`
- 设置帧号和相机号
- 遍历该帧所有 `detections`
- 调用 `build_object()` 把每个 detection 追加到 `ObjectList.list`
如果第一条 detection 里带了 `version`,也会写到 `ObjectList.version`
#### 8. 序列化并生成三件套文件
`convert()` 会遍历所有帧:
- 把每帧 `ObjectList` 序列化成字节串
- 依次写入 `ObjectPerceptionObjectList.bin`
- 记录每条消息的 `frame_id / offset / size`
- 最后生成 `ObjectPerceptionObjectList.index.json`
- 再生成 `ObjectPerceptionObjectList.data.json`
这样输出结果既能高效存储,也能被后续工具顺序读取。
### make_jl.py 的实现逻辑
#### 1. 解析命令行参数
脚本接收:
- 位置参数 `data_json`
- 可选参数 `-o / --output`
其中 `data_json` 就是 `ObjectPerceptionObjectList.data.json`
#### 2. 通过 Reader 定位真实数据文件
`make_jsonl()` 内部先创建:
```python
reader = pb_reader.Reader(data_json_path)
```
`Reader` 会:
- 读取 `data.json`
- 找到其中声明的 `index``data`
- 拼出真实的 `index.json``bin` 路径
#### 3. 逐条读取 protobuf 消息
`reader.each()` 会遍历 `index.json` 中的每个索引项:
- 从索引中拿到 `offset`
- 从索引中拿到 `size`
-`bin` 中 seek 到对应位置
- 读取这一条消息的原始字节
#### 4. 把字节反序列化成 ObjectList
读取到的每个 chunk 会被解析成:
```python
object_pb2.ObjectList.FromString(chunk)
```
这样就把原始二进制恢复为 protobuf 对象。
#### 5. 转成 JSON 并输出
随后脚本调用:
```python
json_format.MessageToDict(objs, including_default_value_fields=True)
```
把 protobuf 对象转成 Python 字典,并保留默认值字段。
最后再用 `json.dumps(..., indent=2)` 输出到:
- 指定文件 `-o`
- 或标准输出
### proto_reader/reader.py 的实现逻辑
#### 1. 读取 data.json
`Reader.__init__()` 会先打开 `data.json`,读取:
- `index[0]`
- `data[0]`
并把它们解析成相对于 `data.json` 所在目录的真实路径。
#### 2. 加载 index.json
接着脚本把 `index.json` 读入内存,保存为 `self.object_index`
索引中的每一项通常是:
```json
[frame_id, offset, size]
```
后续真正读取数据时主要使用 `offset``size`
#### 3. 迭代返回原始消息字节
`each()` 打开二进制文件后,会对每条索引执行:
- `seek(offset)`
- `read(size)`
- `yield bts`
因此上层调用方可以把它当作一个“按条产出 protobuf 消息”的生成器。
## 推荐使用顺序
```bash
python3 convert_merge_tracking.py /path/to/merge_tracking.json -o ./out
python3 make_jl.py ./out/ObjectPerceptionObjectList.data.json -o ./out/ObjectPerceptionObjectList.jl
```
如果你只想验证转换是否成功,优先检查:
- `./out/ObjectPerceptionObjectList.data.json`
- `./out/ObjectPerceptionObjectList.index.json`
- `./out/ObjectPerceptionObjectList.bin`
如果你想看可读结果,再检查:
- `./out/ObjectPerceptionObjectList.jl`