09实时与离线

平台门口的形式决定了它能接什么、能吐给谁。这一页先看"三件套"为什么长这样, 再看那两条链路到底差在哪,以及差出来的东西为什么会静默地咬人。

三件套 丢了时间轴 丢帧不可见

一、进来的是 pcd + jpg + json

前面几页讲的是平台内部怎么运转,这一节是它的门口。门口的形式很重要, 因为它决定了后面所有事的上限。

进平台的是三样东西: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 的本质是一个"带名字的定长记录数组"。它只能装每个点都有的、定长的字段。

而相机内参是非逐点的,它是整幅图共享的一个换算关系,不属于任何一个点。 所以 PCD 装不下标定,因为它不是容器,没有放元数据的地方。

于是标定只能外挂,json 就是那个外挂位。

二、更本质的说法:json 是 MCAP 丢掉的东西

这一点是理解三件套的钥匙。先看一段 MCAP 里本来有什么:

MCAP 自带的,自描述
  话题名        每帧一个时刻
  时间戳        每个传感器坐标系的名字
  坐标系名      传感器之间的变换
  标定话题      多路传感器之间的同步关系

一旦拆成裸文件,这些东西大部分就散了:

pcd   只有几何    没有话题、没有时间戳、没有坐标系名
jpg   只有像素    同上
json  捡回一部分

但要注意它捡得回什么、捡不回什么:

MCAP 里的东西拆文件后
标定,也就是内外参✅ 能进 json
文件配对关系✅ 能进 json
坐标系约定🟡 能靠约定写死
每帧的时间戳❌ 彻底丢了
时间丢了意味着什么
pcd 里没有时间戳
jpg 里没有传感器时间戳,EXIF 那个不是
camera_config 虽然是逐帧的,但里面没有时间字段
result/*.json 里也没有时间

整个三元组没有时间轴
"时间"只能靠文件名排序隐式表达

于是那个"33 毫秒对应 102 像素"的时间同步误差,在平台内部完全不可见。

这还是私有约定,不是标准

PCDcamera_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 公里每小时。而且不报错, 数据里有三千帧,帧帧看起来都正常。

五、根因在采集端的缓冲

环形缓冲 / ring buffer 一块固定大小的内存,写满之后从头覆盖。 实时系统用它来保存"最近 N 秒"的数据,因为不可能把整趟数据都放在内存里。 它同时决定了你能"回看多久"。

采集端的缓冲策略直接决定了会不会丢、丢多少:

采集端做了什么下游付出什么
环形缓冲够大、落盘跟得上✅ 数据完整,跨帧算得对
缓冲不够、落盘跟不上❌ 丢帧,离线侧看不见,速度和推演全错

所以它不是"一个代价",是整条链路的根因。前面所有关于时间一致性的讨论, 在数据进来的那一刻就已经被决定了。

六、统一接入层要暴露的三件事

如果要在代码上把两条链路收进一个接口,必须暴露三样东西。第三样是最容易被漏掉的。

#抽象关键点
①通道名字、类型、语义。不是一个话题字符串,而是"这个名字下的数据是什么、什么单位、什么坐标系"
②时间用采集时刻,不是接收时刻。而且要说清"现在"是一个位置还是最新时刻
③完整性这段数据有没有断、断在哪、丢了多久
第三样为什么是它

到了标注平台,数据都已经落盘了,"能不能回放"是白送的,不需要声明。

标注侧真正需要知道的是"这段数据可不可信"。完整性声明就是回答这个的。

少了它,上层会按"最理想的那条链路"写代码,然后在另一条上静默失败。

七、这一页带走什么

三件套是工作副本,不是归档格式。它的关键缺陷是没有时间轴, 所以它本质上是一个"静态快照格式",不是"数据格式"。

实时和离线不是两条标注链路,是采集和标注。它们统一格式,不统一保证。

最该记住的是丢帧:它在离线侧完全不可见,而它会让所有基于时间差的计算悄悄算错。 根因在采集端的缓冲,不在标注端。

← 上一页:点云格式与 PCD ·  下一篇:内外参与投影 →
京ICP备19013239号