3. 时间戳与帧率:仿真时间戳概念、固定时间步长、帧率控制、时间戳对齐策略

做自动驾驶仿真,最头疼的问题是什么?

我个人觉得,不是传感器噪声,也不是物理引擎不准,而是——时间对不上。

你想想看,激光雷达说「我在 10.000 秒扫了一帧」,相机说「我在 10.015 秒拍了一张图」,控制模块说「我在 10.020 秒发出了指令」。这三个时间点,到底哪个才是「真实」的?如果不对齐,你拿什么去训练你的模型?

这一章,我们就来把时间戳这件事彻底讲透。

3.1 仿真时间戳:到底什么是「仿真时间」?

先说一个基本概念。CARLA 里的时间,不是你的电脑时间,也不是墙上挂钟的时间。它叫仿真时间(Simulation Time)。

说白了,就是 CARLA 自己维护的一个计数器。每走一步,这个计数器就加一点。它跟你的 CPU 跑多快、显卡渲染多流畅,没有半毛钱关系。

举个例子:

  • 你设置固定步长为 0.05 秒(50ms)
  • 仿真跑了 100 步
  • 那么仿真时间就是 100 × 0.05 = 5.0 秒

哪怕你的电脑卡成 PPT,这 5.0 秒依然是 5.0 秒。这就是仿真时间的核心价值——可复现。

关键点:仿真时间 ≠ 真实时间。仿真时间是逻辑时间,真实时间是物理时间。两者可以不同步,甚至应该不同步(比如你想跑得比真实快)。

我在项目中遇到过一个问题:有人把仿真时间当成了真实时间,结果录制的数据在回放时完全对不上。嗯,这就是没搞懂「仿真时间」的本质。

3.2 固定时间步长:为什么必须用 Fixed Time Step?

CARLA 默认是「尽力而为」模式。什么意思?就是每帧能跑多快跑多快,时间步长不固定。这在玩游戏时没问题,但在仿真里——这是灾难。

为什么?

  • 传感器数据的时间戳会乱跳
  • 物理引擎的积分步长不一致,导致车辆轨迹漂移
  • 不同机器上跑出来的结果不一样

所以,做数据对齐的第一步:开启固定时间步长。

# 开启固定时间步长
settings = world.get_settings()
settings.fixed_delta_seconds = 0.05  # 50ms,即 20 FPS
settings.synchronous_mode = True      # 同步模式
world.apply_settings(settings)

这里我建议你记住一个原则:固定步长 + 同步模式,是数据对齐的基石。缺一个都不行。

我的经验:固定步长选多少?看你的传感器需求。激光雷达一般 10Hz(0.1s),相机 20Hz(0.05s),控制模块 50Hz(0.02s)。选最小的公倍数,或者直接 0.05s,兼容大多数场景。

3.3 帧率控制:你真的需要 60 FPS 吗?

很多新手一上来就问:「怎么让 CARLA 跑到 60 FPS?」

我的回答是:你为什么要 60 FPS?

自动驾驶仿真不是打游戏。60 FPS 意味着每帧 16.6ms,你的传感器、控制模块、数据记录全都要在这个时间内完成。一旦超时,整个仿真就卡住了。

实际上,大多数场景下 20 FPS(50ms 步长)就完全够用。原因有三:

  1. 传感器频率限制:激光雷达通常 10Hz,相机 20Hz,你跑 60 FPS 也没用,数据还是 20Hz
  2. 物理精度:50ms 的步长对于车辆动力学已经足够精确
  3. 计算资源:省下来的算力可以跑更多的 NPC 车辆和更复杂的传感器

避坑指南:我曾经为了追求高帧率,把步长设成了 0.01s(100 FPS)。结果呢?物理引擎倒是准了,但数据量暴增 5 倍,磁盘写爆了,而且大部分数据是冗余的。后来我学乖了——够用就好,别贪心。

3.4 时间戳对齐策略:让所有传感器「说同一种时间」

好了,现在我们有固定步长和同步模式。但问题来了:每个传感器都有自己的时间戳,怎么对齐?

我常用的策略有三种:

策略一:统一使用仿真时间戳

最简单粗暴的方法。所有传感器数据都打上当前仿真时间的戳。

# 获取当前仿真时间
sim_time = world.get_snapshot().timestamp.elapsed_seconds

# 给传感器数据打时间戳
sensor_data.timestamp = sim_time

优点:简单,所有数据天然对齐。
缺点:如果传感器有内部延迟(比如相机曝光时间),这个时间戳就不准。

策略二:传感器时间 + 偏移补偿

每个传感器记录自己的时间戳,然后加上一个固定的偏移量。

# 相机延迟 30ms,激光雷达延迟 10ms
camera_offset = 0.030
lidar_offset = 0.010

camera_data.timestamp = camera_raw_timestamp + camera_offset
lidar_data.timestamp = lidar_raw_timestamp + lidar_offset

这个方法更精确,但需要你事先标定每个传感器的延迟。我在实际项目中用过,效果不错,就是标定过程比较麻烦。

策略三:插值对齐

这是最精确的方法。把低频传感器(比如激光雷达 10Hz)的数据,通过插值对齐到高频传感器(比如相机 20Hz)的时间轴上。

# 伪代码:线性插值对齐
def interpolate_lidar_to_camera(lidar_data, camera_timestamps):
    aligned_data = []
    for cam_ts in camera_timestamps:
        # 找到前后两帧激光雷达数据
        before = find_closest_before(lidar_data, cam_ts)
        after = find_closest_after(lidar_data, cam_ts)
        # 线性插值
        ratio = (cam_ts - before.timestamp) / (after.timestamp - before.timestamp)
        interpolated = before.data * (1 - ratio) + after.data * ratio
        aligned_data.append(interpolated)
    return aligned_data

我的建议:

  • 如果只是做数据记录和回放,用策略一就够了
  • 如果要做传感器融合,用策略二
  • 如果要做高精度的时间同步(比如 V2X 场景),用策略三

3.5 知识体系:时间戳与帧率核心逻辑

下面这张图,是我自己总结的时间戳对齐核心逻辑。你看一遍,基本就全通了。

时间戳与帧率核心逻辑 固定时间步长 同步模式 帧率控制 仿真时间戳(Simulation Time) 逻辑时间,可复现,与真实时间解耦 策略一:统一仿真时间 简单,适合数据记录 策略二:偏移补偿 精确,需标定延迟 策略三:插值对齐 最高精度,适合融合 ✅ 数据对齐完成,可复现、可回放

这张图的核心逻辑很简单:固定步长 + 同步模式 → 统一仿真时间 → 选择对齐策略 → 数据对齐完成。每一步都是下一步的前提,缺一不可。

3.6 实战中的几个坑

最后,分享几个我踩过的坑,希望能帮你省点时间。

坑一:忘记设置同步模式

只设了固定步长,没设同步模式。结果呢?CARLA 还是异步跑,时间戳乱跳。我排查了整整一天才发现问题。记住:固定步长和同步模式,必须同时开启。

坑二:传感器回调时间戳不一致

每个传感器的回调函数里,拿到的 world snapshot 可能不是同一帧。解决方案:在回调里统一用 world.get_snapshot() 获取当前帧的时间戳,而不是用传感器自带的时间。

坑三:回放时时间轴错位

录数据时用了固定步长 0.05s,回放时却用了默认的异步模式。结果数据全对不上。回放时也要保持相同的步长设置,这是基本常识,但我见过不少人犯这个错。

好了,时间戳和帧率这部分就讲到这里。记住一句话:时间不对齐,数据就是垃圾。下一章我们聊聊传感器标定,那是另一个容易踩坑的地方。


可可精品资料库