本节介绍CARLA中的两个基本概念。它们的配置定义了仿真中的时间如何流逝,以及服务器如何使仿真向前发展。
1 仿真时间步长
实时时间和仿真时间是有区别的。仿真世界有自己的时钟和时间,由服务器管理。计算两个仿真步骤需要一些时间。然而,这两个仿真时刻之间也存在时间跨度,即时间步长。服务器可能需要几毫秒来计算仿真的两个步骤。然而,这两个仿真时刻之间的时间步长可以配置为,例如,总是1秒。
时间步长可以是固定的,也可以是可变的,这取决于用户的偏好。
1.1 可变时间步长
CARLA中的默认模式。步骤之间的仿真时间将是服务器计算这些步骤所需的时间。
settings = world.get_settings()
settings.fixed_delta_seconds = None # Set a variable time-step
world.apply_settings(settings)
PythonAPI/util/config.py使用参数设置时间步长。零等于可变时间步长。
cd PythonAPI/util && python3 config.py --delta-seconds 0
1.2 固定的时间步
步骤之间的运行时间保持不变。如果设置为0.5秒,则每仿真1秒将有两帧。在每个步骤上使用相同的时间增量是从仿真中收集数据的最佳方法。物理和传感器数据将对应于易于理解的仿真时刻。此外,如果服务器足够快,就可以在较短的实时时间内仿真较长的时间段。
固定的增量秒可以在世界设置中设置。要以0.05秒的固定时间步长运行仿真,请应用以下设置。在这种情况下,仿真器将采取20个步长(1/0.05)来重新创建仿真世界的1秒钟。
settings = world.get_settings()
settings.fixed_delta_seconds = 0.05
world.apply_settings(settings)
这也可以使用提供的脚本PythonAPI/util/config.py进行设置。
cd PythonAPI/util && python3 config.py --delta-seconds 0.05
1.3 物理子步长
物理学必须在非常低的时间步长内进行精确计算。在为我们的仿真选择增量时间时,这可能是一个问题,我们通常在每一帧执行多个计算,例如传感器渲染。由于这种限制只发生在物理仿真中,我们可以将子步长仅应用于物理计算。默认情况下,这是启用的,并且设置为最多有10个物理子步长,最大物理增量时间为0.01。
这些选项可以通过API在世界设置中更改为:
settings = world.get_settings()
settings.substepping = True
settings.max_substep_delta_time = 0.01
settings.max_substeps = 10
world.apply_settings(settings)
请注意,如果您设置了同步模式和固定时间步长,则子步长选项需要与固定增量秒的值保持一致。需要满足的条件是:
fixed_delta_seconds <= max_substep_delta_time * max_substeps
为了获得最佳的物理仿真,子步骤增量时间至少应低于0.01666,理想情况下应低于0.01。
2 客户端-服务端同步
CARLA建立在客户端-服务端架构之上。服务端运行仿真。客户端检索信息,并要求改变世界。本节处理客户端和服务端之间的通信。
默认情况下,CARLA以异步模式运行。服务端尽可能快地运行仿真,而不需要等待客户端。在同步模式下,服务端在更新到下一个仿真步骤之前等待客户端标记,即“准备就绪”消息。
2.1 设置同步模式
在同步和异步模式切换只需要设置布尔状态。
settings = world.get_settings()
settings.synchronous_mode = True # Enables synchronous mode
world.apply_settings(settings)
如果启用了同步模式,并且有一个交通流管理器在运行,那么也必须将其设置为同步模式,参见该文档(this)。
要禁用同步模式,只需将变量设置为false或使用脚本PythonAPI/util/config.py。
cd PythonAPI/util && python3 config.py --no-sync # Disables synchronous mode
同步模式不能通过脚本启用,只能禁用。启用同步模式将使服务端等待客户端的响应。
2.1 使用同步模式
同步模式特别适用于速度较慢的客户端应用程序,以及需要在不同元素(如传感器)之间实现同步的情况。如果客户端太慢,服务器不等待,就会出现信息溢出。客户端将无法管理所有内容,并且会丢失或混淆。同样,对于许多传感器和异步,不可能知道是否所有传感器都在使用仿真中同一时刻的数据。
下面的代码片段扩展了前面的代码片段。客户端创建相机传感器,将当前步骤的图像数据存储在队列中,并在从队列中检索图像数据后勾选服务器。在这里可以找到一个关于多个传感器的更复杂的示例here。
settings = world.get_settings()
settings.synchronous_mode = True
world.apply_settings(settings)
camera = world.spawn_actor(blueprint, transform)
image_queue = queue.Queue()
camera.listen(image_queue.put)
while True:
world.tick()
image = image_queue.get()
来自基于gpu的传感器(主要是相机)的数据通常会延迟几帧生成。同步在这里是必不可少的。
当然,也存在异步方法可以让客户端等待服务器的标记,或者在接收到它时做一些事情。
# Wait for the next tick and retrieve the snapshot of the tick.
world_snapshot = world.wait_for_tick()
# Register a callback to get called every time we receive a new snapshot.
world.on_tick(lambda world_snapshot: do_something(world_snapshot))
3 可能的配置
时间步长和同步的配置,导致不同的设置。以下是对这些可能性的简要总结。
固定时间步长 | 变步长 | |
同步 | 客户端完全控制仿真及其信息。 | 有仿真不可靠的风险。 |
异步 | 很好的时间参考。服务器运行得尽可能快。 | 仿真不容易重复。 |
同步模式+可变时间步长。这几乎肯定是一种不可取的状态。当时间步长大于0.1s时,物理计算无法正常运行。如果服务端必须等待客户端计算完成,则很可能发生这种情况。仿真时间和物理计算将不同步。仿真将不可靠。
异步模式+可变时间步长。这是默认的CARLA状态。客户端和服务器是异步的。仿真时间按实时性流动。重新仿真需要考虑浮点算术误差,以及服务器之间可能的时间步长差异。
异步模式+固定时间步长。服务端将尽可能快地运行。检索到的信息很容易与仿真中的精确时刻相关联。如果服务端足够快,这种配置可以在较短的实时时间内仿真长时间。
同步模式+固定时间步长。客户端将控制仿真。时间步长是固定的。在客户端发送一个标记之前,服务器不会计算接下来的步骤。当同步和精度相关时,这是最佳模式。特别是在处理缓慢的客户端或不同的元素检索信息时。
注意:在同步模式下,始终使用固定的时间步长。如果服务端必须等待用户,并且使用可变的时间步长,那么时间步长就太大了。物理学计算将不可靠。
4 物理设置
必须启用同步模式和固定增量秒:确定性要求客户机端与服务端完全同步,以确保正确应用命令并生成准确且可重复的结果。必须通过设置fixed_delta_seconds强制执行恒定的时间步长。如果没有设置,时间步长将根据模拟性能在每一步自动计算。
在加载或重新加载世界之前必须启用同步模式:如果世界从一开始就没有处于同步模式,则可能会出现不同的时间戳。这可以在物理仿真和交通信号灯等对象的生命周期中产生微小的差异。
每次新的重复都必须重新加载世界:每次你想要重现仿真时都要重新加载世界。
应该批处理命令,而不是一次发出一个命令: 在繁忙的仿真或过载的服务端中,单个发出的命令可能会丢失。如果在apply_batch_sync命令中对命令进行批处理,则保证该命令被执行或返回失败响应。
下面是上述设置的一个例子:
client = carla.Client(HOST, PORT) # connect to the server
client.set_timeout(10.0)
world = client.get_world()
# Load the desired map
client.load_world("Town10HD_Opt")
# Set synchronous mode settings
new_settings = world.get_settings()
new_settings.synchronous_mode = True
new_settings.fixed_delta_seconds = 0.05
world.apply_settings(new_settings)
client.reload_world(False) # reload map keeping the world settings
# Set up the traffic manager
traffic_manager = client.get_trafficmanager(TM_PORT)
traffic_manager.set_synchronous_mode(True)
traffic_manager.set_random_device_seed(SEED) # define TM seed for determinism
# Spawn your vehicles, pedestrians, etc.
# Simulation loop
while True:
# Your code
world.tick()
回放功能的一个特殊例子:
client = carla.Client(HOST, PORT) # connect to the server
client.set_timeout(10.0)
world = client.get_world()
# Load the desired map
client.load_world("Town10HD_Opt")
# Set synchronous mode settings
new_settings = world.get_settings()
new_settings.synchronous_mode = True
new_settings.fixed_delta_seconds = 0.05
world.apply_settings(new_settings)
client.reload_world(False) # reload map keeping the world settings
client.replay_file(FILE_TO_PLAY, 0, 0, 0, False)
world.tick() # a tick is necessary for the server to process the replay_file command
# Simulation loop
while True:
# Your code
world.tick()
运行这些步骤将确保每次仿真运行都得到相同的结果。