Files
2026-06-24 09:35:46 +08:00

324 lines
8.8 KiB
Markdown
Executable File
Raw Permalink 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.
# 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`