324 lines
8.8 KiB
Markdown
324 lines
8.8 KiB
Markdown
|
|
# 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`
|