13-交通管理器
1 什么是交通管理器?
交通管理器(TM)是在仿真中控制车辆自动驾驶模式的模块。它的目标是模拟真实的城市交通状况。用户可以自定义一些行为,例如设置特定的学习环境。
1.1 结构化设计
TM建立在CARLA的客户端。执行流程分为几个阶段,每个阶段都有独立的操作和目标。每个阶段在不同的线程上运行。通过同步消息传递管理与其他阶段的通信。
1.2 用户自定义
用户可以通过设置参数来控制交通流。用户可以根据自己的喜好改变交通流行为。例如,汽车可以被允许忽略速度限制或强行改变车道。
2 体系结构
概述
上图是TM的内部架构的表示。每个组件的c++代码可以在LibCarla/source/carla/trafficmanager中找到。下面几节将详细解释每个组件。
1. 存储和更新模拟的当前状态
代理生命周期和状态管理(Agent Lifecycle & State Management (ALSM))扫描整个仿真世界,跟踪所有存在的车辆和行走者。所有数据都从服务端检索,ALSM是唯一调用服务器的组件。
车辆注册表(vehicle registry)包含自动驾驶(由TM控制)的车辆数组和非自动驾驶(不受TM控制)的行人和车辆列表。
仿真状态(simulation state)是仿真中所有车辆和行人的位置、速度和附加信息的缓存存储。
2. 计算每个自动驾驶车辆的运动。
TM根据仿真状态(simulation state)为车辆注册表(vehicle registry)中的所有车辆生成可行的命令。每辆车的计算是分开进行的。这些计算分为不同的阶段(stages)。控制循环(control loop)通过在阶段之间创建同步阻塞来确保所有计算都是一致的。在当前阶段所有车辆的计算完成之前,没有车辆移动到下一阶段。每辆车都要经过以下几个阶段:
2.1 定位阶段(Localization Stage)
路径是使用从In-Memory Map中收集的附近路点列表动态创建的,这是仿真地图作为路点网格的简化。路口的方向是随机选择的。每辆车的路径由路径缓冲和车辆跟踪(Path Buffers & Vehicle Tracking (PBVT))组件存储和维护,以便在未来阶段访问和修改。
2.2 碰撞阶段(Collision Stage)
边界框扩展到每辆车的路径上,以识别和导航潜在的碰撞危险。
2.3 交通灯阶段(Traffic Light Stage)
与碰撞阶段类似,由于交通灯的影响,停车标志和路口优先级,影响每辆车路径的潜在危险。
2.4 运动规划阶段(Motion Planner Stage)
车辆的移动是基于定义的路径计算的。PID控制器决定如何到达目标航路点。然后将其转换为CARLA命令,用于下一步的应用程序。
2.5 车辆灯光阶段(Vehicle Lights Stage)
车辆车灯会根据环境因素(例如,阳光、雾或雨)和车辆行为(例如,如果车辆在下一个路口左转/右转,则打开方向指示灯,或者在刹车时打开停车灯)动态地开关。
3. 在模拟中应用命令
在前一步中生成的命令被收集到命令数组中,并成批发送到CARLA服务器,以便在同一帧中应用。
下面几节将更详细地解释上面描述的TM逻辑中的每个组件和阶段。
ALSM
ALSM为代理生命周期和状态管理。它是TM逻辑循环的第一步,并提供仿真当前状态的上下文。
ALSM组件:
扫描世界,跟踪所有车辆和行人的位置和速度。如果启用了物理计算模式,则速度由Vehicle.get_velocity()检索得到。否则,速度是使用位置随时间更新的历史来计算的。
在仿真状态(simulation state)组件中存储每个车辆和行人的位置、速度和附加信息(交通灯影响、边界框等)。
在车辆注册表(vehicle registry)中更新tm控制的车辆列表。
更新控制回路和PBVT组件中的条目以匹配车辆注册表。
相关cpp文件:ALSM.h、ALSM.cpp。
车辆注册表(Vehicle registry)
记录仿真中的所有车辆和行人。
从ALSM传递最新的车辆和行人列表。
将注册到TM的车辆存储在单独的数组中,以便在控制循环期间进行迭代。
相关cpp文件:MotionPlannerStage.cpp。
仿真状态
仿真状态存储了仿真中所有车辆的信息,便于后期访问和修改。
接收来自ALSM的数据,包括当前角色位置、速度、交通灯影响、交通灯状态等。
将所有信息存储在缓存中,避免在控制循环期间对服务端的后续调用。
相关cpp文件:SimulationState.cpp、SimulationState.h。
控制循环
控制循环管理所有自动驾驶车辆的下一个命令的计算,以便它们同步执行。控制循环由五个不同的阶段组成;定位,碰撞,交通灯,运动规划和车辆灯。
从车辆登记接收tm控制的车辆数组。
通过循环遍历数组,分别对每辆车执行计算。
将计算分成一系列的阶段。
在阶段之间创建同步阻塞以保证一致性。所有车辆的计算在任何车辆移动到下一阶段之前完成,确保所有车辆在同一帧中更新。
协调各阶段之间的过渡,使所有计算同步完成。
当最后一个阶段(运动规划阶段(Motion Planner Stage)和车辆灯光阶段(Vehicle Lights Stage))完成时,将命令数组(command array)发送到服务器,因此在命令计算和命令应用之间没有帧延迟。
相关cpp文件:TrafficManagerLocal.cpp。
内存地图
内存地图是包含在PBVT中的辅助模块,在定位阶段Localization Stage使用。
将地图转换为离散路径点的网格。
包含特定数据结构中的路点,并提供更多信息来连接路点和识别道路、路口等。
通过识别这些建筑物的ID来快速定位附近区域的车辆。
相关cpp文件:InMemoryMap.cpp和SimpleWaypoint.cpp。
PBVT
PBVT为路径缓存和车辆跟踪模块。PBVT是一种数据结构,它包含每辆车的预期路径,并允许在控制回路(control loop)期间访问数据。
包含一个deque对象的地图,每辆车有一个入口。
包含每辆车的一组路点,描述其当前位置和近期路径。
包含定位阶段(Localization Stage)使用的内存地图(In-Memory Map),用于将每个车辆与最近的路点和可能的重叠路径关联起来。
PID控制器
PID控制器是在运动规划阶段(Motion Planner Stage)执行计算的辅助模块。
根据运动规划阶段收集的信息,计算达到目标值所需的油门、刹车和转向输入。
根据控制器的具体参数化进行调整。如果需要可以修改参数(PID controllers)。
相关的cpp文件:PIDController.cpp。
命令数组
命令数组代表TM逻辑循环的最后一步。它接收所有注册车辆的命令并应用它们。
收到一系列来自运动规划阶段(Motion Planner Stage)的carla.VehicleControl。
在同一帧内批量应用所有命令。
将批处理发送到CARLA服务器,在CARLA中调用apply_batch()或apply_batch_sync()。具体取决于模拟是在异步模式还是同步模式下运行。
相关的cpp文件:TrafficManagerLocal.cpp。
控制回路的各个阶段
阶段1-定位阶段
定位阶段定义了由TM控制的车辆在不久将来的路径。
从仿真状态(simulation state)中得到所有车辆的位置和速度。
使用内存地图将每个车辆与路径点列表联系起来,这些路径点根据其轨迹描述了其当前位置和近期路径。车辆跑得越快,列表就越长。
根据规划决策更新路径,如车道变化、速度限制、与领先车辆的距离参数化等。
在PBVT模块中存储所有车辆的路径。
相互比较路径以估计可能的碰撞情况。结果被传递到碰撞阶段。
相关的cpp文件:LocalizationStage.cpp和LocalizationUtils.cpp。
阶段2-碰撞阶段
碰撞阶段触发碰撞危险。
从定位阶段接收路径可能重叠的车辆对列表。
为每一对车辆沿着前方的路径扩展边界框,以检查它们是否确实重叠,并确定碰撞的风险是否真实存在。
将所有可能碰撞的危险发送到运动规划器阶段,以相应地修改路径。
相关的cpp文件:CollisionStage.cpp。
阶段3-红绿灯阶段
交通灯阶段由于交通灯、停车标志和十字路口的优先级等交通调节器而引发危险。
如果车辆受到黄色或红色交通灯或停车标志的影响,则会造成交通危险。
如果车辆处于无信号路口,则沿着车辆路径扩展边界框。有重叠路径的车辆遵循“先进先出”的顺序移动。等待时间设置为固定值。
相关的cpp文件:TrafficLightStage.cpp。
阶段4-运动规划阶段
运动规划阶段生成应用于车辆的CARLA命令。
收集车辆的位置和速度(仿真状态simulation state)、路径(PBVT))和危险(碰撞阶段和红绿灯阶段)。
对车辆应该如何行驶做出高级决策,例如,计算防止碰撞危险所需的刹车。采用PID控制器根据目标值对行为进行计算。
把想要的动作转换成carla.VehicleControl控制应用于车辆。
将生成的CARLA命令发送到命令数组(Command Array)。
阶段5-车辆灯光阶段
车辆灯光阶段根据车辆状况和周围环境激活灯光。
检索车辆的计划路径点车辆灯的信息(例如灯光状态和将要应用的规划命令)和天气条件。
确定车辆灯的新状态:
当车辆计划在下一个路口左转/右转时,开启行车灯。
如果应用命令要求车辆刹车,则打开停车灯。
从日落到黎明,或在大雨中打开低光束和位置灯。
在大雾条件下打开雾灯。
相关cpp文件:
使用交通流管理器
车辆行为
TM实现了在设置车辆为自动驾驶模式时必须考虑的一般行为模式:
车辆不是目标导向的,它们遵循动态生成的轨迹,在接近路口时随机选择路径。他们的道路没有尽头。
车辆的目标速度为当前速度限制的70%,除非设置任何其他值。
路口优先不遵守交通规则。TM在路口使用自己的优先级系统。例如,环岛内的车辆会向试图进入的车辆让行。
TM行为可以通过Python API进行调整。有关具体方法,请参阅Python API文档的TM部分。以下是通过该API可以实现的功能的概要:
话题/topic | 概述 |
General: | —创建与端口相连的TM实例。 —检索TM连接的端口。 |
Safety conditions: | -设定停车车辆之间的最小距离(适用于单个车辆或所有车辆)。 -将期望速度设置为当前速度限制的百分比(适用于单个车辆或所有车辆)。 -重置交通灯。 |
Collision managing: | -启用/禁用车辆与特定角色之间的碰撞。 -让一个车辆忽略所有其他车辆。 -使车辆忽略所有步行者。 -让车辆忽略所有交通灯。 |
Lane changes: | -强制换道,忽略可能发生的碰撞。 -允许/禁止车辆变道 |
Hybrid physics mode: | -启用/禁用混合物理模式。 -更改启用物理的半径。 |
创建交通流管理器
注意:TM被设计为在同步模式下工作。在异步模式下使用TM可能会导致意想不到的结果,可以在同步模式(Synchronous mode)一节中了解更多信息。
TM实例由carla客户端创建,需要传递使用的端口,默认端口号为8000。
创建一个TM实例:
tm = client.get_trafficmanager(port)
要启用一组车辆的自动驾驶模式,可以检索TM实例的端口并将set_autopilot设置为True,同时传递TM端口。如果没有提供端口,它将尝试连接到默认端口(8000)中的TM。如果TM不存在,它将创建一个:
tm_port = tm.get_port()
for v in vehicles_list:
v.set_autopilot(True,tm_port)
在多客户端情况下创建或连接到TM与上面的示例不同。在运行多个交通流管理器一节了解更多信息(Running multiple Traffic Managers)。
/PythonAPI/examples中的generate_traffic.py脚本提供了一个示例,说明如何使用作为脚本参数传递的端口创建TM实例,并通过批量将autopilot设置为True来注册生成的每个车辆:
traffic_manager = client.get_trafficmanager(args.tm-port)
tm_port = traffic_manager.get_port()
...
batch.append(SpawnActor(blueprint, transform).then(SetAutopilot(FutureActor, True,tm_port)))
...
traffic_manager.global_percentage_speed_difference(30.0)
设置自动驾驶行为
下面的示例创建了一个TM实例,并为特定车辆配置危险行为,使其忽略所有交通灯,不与其他车辆保持安全距离,并以比当前速度限制快20%的速度行驶:
tm = client.get_trafficmanager(port)
tm_port = tm.get_port()
for v in my_vehicles:
v.set_autopilot(True,tm_port)
danger_car = my_vehicles[0]
tm.ignore_lights_percentage(danger_car,100)
tm.distance_to_leading_vehicle(danger_car,0)
tm.vehicle_percentage_speed_difference(danger_car,-20)
下面的示例将相同的车辆列表设置为自动驾驶,但将其配置为合适的驾驶行为。车辆的行驶速度比目前的限速慢80%,与其他车辆之间至少保持5米的距离,并且从不进行车道变换。
tm = client.get_trafficmanager(port)
tm_port = tm.get_port()
for v in my_vehicles:
v.set_autopilot(True,tm_port)
danger_car = my_vehicles[0]
tm.global_distance_to_leading_vehicle(5)
tm.global_percentage_speed_difference(80)
for v in my_vehicles:
tm.auto_lane_change(v,False)
授权交通管理器自动更新车辆指示灯
默认情况下,由TM管理的车辆的车灯(刹车、转向指示灯等)永远不会更新。可以委托TM来更新给定车辆参与者的车辆灯:
tm = client.get_trafficmanager(port)
for actor in my_vehicles:
tm.update_vehicle_lights(actor, True)
车辆灯光管理必须在每辆车的基础上进行指定,并且在任何给定的时间都可能存在车辆有或没有自动灯光管理的情况。
停止交通管理器
TM不是一个需要被销毁的actor;它将在创建它的客户端停止时不再运行。这是由API自动管理的,用户不需要做任何事情。但是,当关闭TM时,用户必须销毁它所控制的车辆,否则它们将在地图上保持不动状态。脚本generate_traffic.py会自动完成:
client.apply_batch([carla.command.DestroyActor(x) for x in vehicles_list])
警告:关闭TM-Server将关闭与其连接的TM-Client。要了解TM-Server和TM-Client之间的区别,参考(Running multiple Traffic Managers)。
确定模式
在确定模式下,TM会在相同的条件下产生相同的结果和行为。记录器允许存储仿真的日志以回放它,但确定性确保只要保持相同的条件,TM在脚本的不同执行中始终具有相同的输出。
确定模式仅在同步模式下可用。在异步模式下,对仿真的控制较少,无法实现确定性。在开始之前,请参阅“同步模式”("Synchronous mode")一节了解更多信息。
启用deterministic模式的方法如下:
my_tm.set_random_device_seed(seed_value)
seed_value是一个int数,从中将生成随机数。值本身是不相关的,但是相同的值总是会产生相同的输出。在相同条件下使用相同种子值的两个仿真将是确定的。
为了在多个仿真运行中保持确定性,必须为每个仿真设置种子。例如,每次重新加载世界时,必须重新设置种子:
client.reload_world()
my_tm.set_random_device_seed(seed_value)
可以在generate_traffic.py示例脚本中通过传递一个种子值作为参数来测试确定性模式。下面的例子在同步模式下填充了一个有50个自动驾驶角色的地图,并将seed设置为任意值9:
cd PythonAPI/examples
python3 generate_traffic.py -n 50 --seed 9
警告:在启用确定模式之前,CARLA服务器和TM必须处于同步模式。阅读有关TM中的同步模式的更多信息参考here。
混合物理模式
混合模式允许用户禁用所有自动驾驶车辆的大多数物理计算,或者禁用标记为hero的车辆一定半径以外的自动驾驶车辆。这从仿真中消除了车辆物理瓶颈。保持线性加速度的基本计算,以确保更新位置和车辆速度,并且车辆的物理计算可以灵活切换。
混合模式使用Actor.set_simulate_physics()方法来切换物理计算。默认为关闭状态。有两个选项可以启用它:
TrafficManager.set_hybrid_physics_mode(True)该方法为调用它的交通流管理器对象启用混合模式。
使用标志位--hybrid运行generate_traffic.py。这个示例脚本创建一个交通流管理器(TM),并在自动驾驶模式下生成车辆。然后,当--hybrid标志位作为脚本参数传递时,它将这些车辆设置为混合模式。
修改hybrid模式的行为需要使用以下两个参数:
半径(默认为50米)--半径与标有hero的车辆相关。所有在这个半径内的车辆都将启用物理计算;半径之外的车辆将会失去物理计算功能。使用traffic_manager.set_hybrid_physics_radius(r)修改半径的大小。
hero车辆:标记为role_name=' Hero '的车辆,充当半径的中心。
如果没有hero车辆,所有车辆的物理计算都会失效。
如果有一个以上的hero车辆,考虑所有hero车辆的半径,创造不同的影响区域与物理启用。
运行多个交通流管理器
交通流管理器服务端和客户端
CARLA客户端通过向服务端指定要使用的端口来创建TM。如果未指定端口,则使用默认的8000。如果在同一端口上创建更多的TM,则它们将成为TM- client,而原始TM将成为TM- server。
TM-Server
如果TM- server是第一个连接到空闲端口的TM,然后其他TM (TM- client)连接到运行它的同一端口,则创建TM- server。TM-Server将决定所有TM- client的行为,例如,如果TM-Server停止,所有TM- client也将停止。
下面的代码创建了两个TM-Server。每一个都连接到一个不同的,未使用的端口:
tm01 = client01.get_trafficmanager() # tm01 --> tm01 (p=8000)
tm02 = client02.get_trafficmanager(5000) # tm02(p=5000) --> tm02 (p=5000)
TM-Client
当一个TM连接到另一个TM (TM-server)所占用的端口时,创建TM- client。TM-Client行为将由TM-Server决定。
下面的代码创建了两个tm - client,每个客户机都连接到上面小节中创建的TM-Server。
tm03 = client03.get_trafficmanager() # tm03 --> tm01 (p=8000).
tm04 = client04.get_trafficmanager(5000) # tm04(p=5000) --> tm02 (p=5000)
CARLA服务端通过存储连接到所有TM实例端口和客户端IP(对用户隐藏)来保存所有TM实例的寄存器。目前还没有办法检查到目前为止已经创建的TM实例。当尝试创建实例时,总是会尝试连接,并且它将创建一个新的TM-Server或TM-Client。
多客户端仿真
在多客户机仿真中,在同一端口上创建多个TM。第一个TM将是TM服务端,其余的将是连接到它的TM客户端。TM服务端将决定所有TM实例的行为:
terminal 1: ./CarlaUE4.sh -carla-rpc-port=4000
terminal 2: python3 generate_traffic.py --port 4000 --tm-port 4050 # TM-Server
terminal 3: python3 generate_traffic.py --port 4000 --tm-port 4050 # TM-Client
Multi-TM仿真
在多TM仿真中,在不同的端口上创建多个TM实例。每个TM实例将控制自己的行为:
terminal 1: ./CarlaUE4.sh -carla-rpc-port=4000
terminal 2: python3 generate_traffic.py --port 4000 --tm-port 4050 # TM-Server A
terminal 3: python3 generate_traffic.py --port 4000 --tm-port 4550 # TM-Server B
多个仿真
多个仿真是指多个CARLA服务器同时运行。TM需要连接到相应的CARLA服务器端口。只要计算能力允许,TM可以同时运行多个仿真而不会出现任何问题:
terminal 1: ./CarlaUE4.sh -carla-rpc-port=4000 # simulation A
terminal 2: ./CarlaUE4.sh -carla-rpc-port=5000 # simulation B
terminal 3: python3 generate_traffic.py --port 4000 --tm-port 4050 # TM-Server A connected to simulation A
terminal 4: python3 generate_traffic.py --port 5000 --tm-port 5050 # TM-Server B connected to simulation B
多个仿真的概念是独立于TM本身的。上面的示例并行运行两个CARLA仿真A和B。在每个仿真中,一个TM-Server是独立于另一个创建的。仿真A可以运行多客户端TM,而仿真B运行多客户端TM,或者根本不运行TM。
上述设置中最可能出现的问题是客户端试图连接到已存在的TM,该TM没有在所选仿真上运行。如果发生这种情况,将出现错误消息,连接将被中止,以防止仿真之间的干扰。
同步模式
TM被设计为在同步模式下工作。为了正常工作,应该将CARLA服务器和TM设置为同步。在异步模式下使用TM可能会导致意想不到的和不希望的结果,但是,如果需要异步模式,则仿真应该至少以20-30 fps的速度运行。
下面的脚本演示了如何将服务器和TM设置为同步模式:
...
# Set the simulation to sync mode
init_settings = world.get_settings()
settings = world.get_settings()
settings.synchronous_mode = True
# After that, set the TM to sync mode
my_tm.set_synchronous_mode(True)
...
# Tick the world in the same client
world.apply_settings(init_settings)
world.tick()
...
# Always disable sync mode before the script ends to prevent the server blocking whilst waiting for a tick
settings.synchronous_mode = False
my_tm.set_synchronous_mode(False)
generate_traffic.py示例脚本启动一个TM,并用车辆和行人填充地图。自动将TM与CARLA服务器设置为同步模式:
cd PythonAPI/examples
python3 generate_traffic.py -n 50
如果需要异步模式,在运行上述命令时使用--async标志。
如果多个TM设置为同步模式,则同步将失败。遵循以下准则可以避免这些问题:
在多客户端情况下,应该只将TM-Server设置为同步模式。
在多tm环境下,只有一个TM-Server被设置为同步模式。
警告:在脚本完成之前禁用同步模式(对于世界和TM),以防止服务器阻塞,永远等待一个标记。
大地图中的交通管理器
要理解TM如何在大型地图上工作,请务必首先通过阅读文档here来熟悉大地图的工作原理。
自动驾驶车辆在大地图上的表现取决于是否有hero车存在:
hero车不存在
所有自动驾驶车辆都将被视为休眠角色。休眠的自动驾驶角色将在地图上移动,就像在混合模式下一样。车辆不会被渲染,因为没有hero车辆来触发地图贴图流。
hero车出现
当自动驾驶车辆超过actor_active_distance定义的值时,自动驾驶车辆将进入休眠状态。要设置此值,请使用Python API:
settings = world.get_settings()
# Actors will become dormant 2km away from the ego vehicle
settings.actor_active_distance = 2000
world.apply_settings(settings)
在TM中,休眠角色可以被配置为在hero车辆周围不断重生,而不是在地图的其他地方休眠。这个选项可以在Python API中使用set_respawn_dormant_vehicles方法来配置。车辆将在hero车辆的用户可定义距离内重生。respawnable距离的上下边界可以使用set_boundaries_respawn_dormant_vehicles方法设置。注意上距离不会大于大地图的贴图流距离,最小下距离为20米。
允许hero车辆25米和700米范围内的休眠车辆重生:
my_tm.set_respawn_dormant_vehicles(True)
my_tm.set_boundaries_respawn_dormant_vehicles(25,700)
如果碰撞阻止休眠角色重生,则TM将在下一个仿真步骤中重试。
如果休眠车辆没有重生,它们的行为将取决于是否启用混合模式。如果混合模式已经启用,那么休眠的actor将被传送到地图周围。如果混合模式没有启用,那么休眠actor的物理属性将不会被计算,他们将保持在原地,直到他们不再休眠。