平台门口的形式决定了它能接什么、能吐给谁。这一页先看"三件套"为什么长这样, 再看那两条链路到底差在哪,以及差出来的东西为什么会静默地咬人。
前面几页讲的是平台内部怎么运转,这一节是它的门口。门口的形式很重要, 因为它决定了后面所有事的上限。
进平台的是三样东西:pcd 是点云,jpg 是图像,都好懂。
但 json 是什么?答案是它不止一种,同一个扩展名,位置和作用完全不同。
| 位置 | 内容 | 作用 |
|---|---|---|
camera_config/*.json | 相机内参、外参 4×4、图像尺寸、一个转置标志 | 建立点云与图像的投影关系,三件套里的 json 指的就是它 |
lidar_config/*.json | 雷达自身参数 | 雷达侧配置 |
data/*.json | 数据信息 | 官方注明仅用于导出 |
result/*.json | 框、类别、属性、身份、来源 | 真值本身,也就是出口那一侧 |
而且目录名在上传和导出时还不一样:上传时是 lidar_point_cloud_0 和
camera_image_0,导出时变成 point_cloud 和 image0。
同一套数据,两套命名。
PCD 的本质是一个"带名字的定长记录数组"。它只能装每个点都有的、定长的字段。
而相机内参是非逐点的,它是整幅图共享的一个换算关系,不属于任何一个点。 所以 PCD 装不下标定,因为它不是容器,没有放元数据的地方。
于是标定只能外挂,json 就是那个外挂位。
这一点是理解三件套的钥匙。先看一段 MCAP 里本来有什么:
MCAP 自带的,自描述
话题名 每帧一个时刻
时间戳 每个传感器坐标系的名字
坐标系名 传感器之间的变换
标定话题 多路传感器之间的同步关系
一旦拆成裸文件,这些东西大部分就散了:
pcd 只有几何 没有话题、没有时间戳、没有坐标系名
jpg 只有像素 同上
json 捡回一部分
但要注意它捡得回什么、捡不回什么:
| MCAP 里的东西 | 拆文件后 |
|---|---|
| 标定,也就是内外参 | ✅ 能进 json |
| 文件配对关系 | ✅ 能进 json |
| 坐标系约定 | 🟡 能靠约定写死 |
| 每帧的时间戳 | ❌ 彻底丢了 |
pcd 里没有时间戳
jpg 里没有传感器时间戳,EXIF 那个不是
camera_config 虽然是逐帧的,但里面没有时间字段
result/*.json 里也没有时间
整个三元组没有时间轴
"时间"只能靠文件名排序隐式表达
于是那个"33 毫秒对应 102 像素"的时间同步误差,在平台内部完全不可见。
| PCD | camera_config.json | |
|---|---|---|
| 谁定的 | 有公开规范 | 平台自己 |
| 自描述吗 | ✅ 头部写清字段名和类型 | ❌ 字段名靠文档 |
| 换平台 | 通用 | 字段名要改 |
税不是交在格式转换上,是交在每家的 json 约定都不一样上。
PCD 至少是自描述的,谁都能解出结构。而 json 是私有约定,必须知道对方怎么命名。
所以一条工程结论:三件套是标注用的工作副本,不是数据的事实来源。 真正的归档格式是 MCAP 或者 bag。
到这一步就能看清"实时"和"离线"的分工了。它们不是两条标注链路,而是采集侧和标注侧。
| 实时链路 | 离线链路 | |
|---|---|---|
| 干什么 | 看、筛选、触发采集 | 标注 |
| 谁在用 | 车上或远程的人,只看不画 | 标注团队 |
| 产出 | 一段值得标的数据 | 标签,进训练集 |
| 数据完整吗 | ❌ 丢是常态 | ✅ 看起来完整 |
数据一帧帧过去,没有人能停下来画框。所以想"把 bag 包装成一个能 seek 的实时流" 这种统一方案是错的方向。
真实的分工是:实时负责采集和筛选,离线负责标注。 两条链路统一的是格式,不是接口能力。
采集侧会丢帧。网络跟不上、磁盘写不过来、CPU 打满,都会丢。
你拿到的 bag 看起来是连续的 3000 帧
但车当时可能确实看到了 3003 帧,中间少了 3 帧
没有报错,没有间隙,帧号是连着的。丢帧是一个静默事件。
跨帧推演要算位置差除以时间差。如果代码假设了恒定帧率:
实际:两帧间隔 0.1 秒,位移 1 米 → 真实速度 10 m/s
代码:时间差硬写成 1/30 秒
速度 = 1 ÷ 0.033 = 30 m/s
一辆 36 公里每小时的车,被算成 108 公里每小时。而且不报错, 数据里有三千帧,帧帧看起来都正常。
采集端的缓冲策略直接决定了会不会丢、丢多少:
| 采集端做了什么 | 下游付出什么 |
|---|---|
| 环形缓冲够大、落盘跟得上 | ✅ 数据完整,跨帧算得对 |
| 缓冲不够、落盘跟不上 | ❌ 丢帧,离线侧看不见,速度和推演全错 |
所以它不是"一个代价",是整条链路的根因。前面所有关于时间一致性的讨论, 在数据进来的那一刻就已经被决定了。
如果要在代码上把两条链路收进一个接口,必须暴露三样东西。第三样是最容易被漏掉的。
| # | 抽象 | 关键点 |
|---|---|---|
| ① | 通道 | 名字、类型、语义。不是一个话题字符串,而是"这个名字下的数据是什么、什么单位、什么坐标系" |
| ② | 时间 | 用采集时刻,不是接收时刻。而且要说清"现在"是一个位置还是最新时刻 |
| ③ | 完整性 | 这段数据有没有断、断在哪、丢了多久 |
到了标注平台,数据都已经落盘了,"能不能回放"是白送的,不需要声明。
标注侧真正需要知道的是"这段数据可不可信"。完整性声明就是回答这个的。
少了它,上层会按"最理想的那条链路"写代码,然后在另一条上静默失败。
三件套是工作副本,不是归档格式。它的关键缺陷是没有时间轴, 所以它本质上是一个"静态快照格式",不是"数据格式"。
实时和离线不是两条标注链路,是采集和标注。它们统一格式,不统一保证。
最该记住的是丢帧:它在离线侧完全不可见,而它会让所有基于时间差的计算悄悄算错。 根因在采集端的缓冲,不在标注端。