Traffic Manager基本架构
Traffic Manager的最大作用就是可以帮助用户集体管理一个车群,定义群体的共性行为。
TIPS:在同步模式下,设置AutoPilot的车辆必须依附于设置为同步模式的traffic manager才能跑起来,即tm也需设置为同步模式。(具体实现方法见后文)
TM主要分为六个阶段(此处的解释尽量简单,省略掉了一些模块的阐述):
①Vehicle Registry——扫描仿真环境中的所有行人汽车信息,并选择出(用户控制)由TM管理的车辆。
②Localization Stage——读取所管辖车辆的位置与路线规划等信息。
③Collision Stage——根据所管辖车辆的周边环境与路线规划判断是否有发生车祸的可能。
④Traffic Light Stage——判断所管辖车辆周边是否有红绿灯。
⑤Motion Planner——将③④的信息整合,制定车辆下一步的举动。
⑥Send Command Array——将上述每一辆车的控制命令一次性分发到每辆车上,实现控制。
TM的基本使用方法
创建Traffic Manager:
world = client.get_world()
# 可以使用命令行'python traffic manager.py --tm_port 8001'来设置TM的端口号,若不设置则默认为8000
traffic_manager = client.get_trafficmanager(args.tm_port)
TIPS:才弄明白argparse库到底是干啥的,当运行python脚本时,如果想要改变一些参数,除了进入硬代码中更改之外的另一种方法是在执行时使用命令行设置(就行上述代码中注释所展现的),argparse库的作用就是让我们可以通过命令行对参数进行配置。
具体怎么使用呢,请见以下代码:
#导入argparse库 import argparse # 创建 ArgumentParser 对象,在这个对象上定义我们希望通过命令行接受的参数 # description仅为解读、注释效果,对代码整体功能无影响 parser = argparse.ArgumentParser(description='Carla Traffic Manager Example') # 添加 tm_port 参数 parser.add_argument('--tm_port', type=int, default=8000, help='Traffic Manager port') # 解析用户输入的命令行参数(若要实现命令行读取配置参数功能,则此行必需) args = parser.parse_args() #之后即可连接上一段代码,用args.tm_port获取TM连接的端口号了
设置tm里的车辆整体行为模式
# tm里的每一辆车都要和前车保持至少3m的距离来保持安全
traffic_manager.set_global_distance_to_leading_vehicle(3.0)
# tm里面的每一辆车都是混合物理模式(只有在ego_vehicle附近的车辆会开启物理特性)
traffic_manager.set_hybrid_physics_mode(True)
# tm里面每一辆车都是默认速度的80%(即默认速度*80%,默认速度为30km/h,若为-80,则为30km/h*180%)
traffic_manager.global_percentage_speed_difference(80)
设置tm为同步模式:
# 如果命令行中要求设置同步模式
if args.sync:
settings = world.get_settings()
# 设置tm的同步模式
traffic_manager.set_synchronous_mode(True)
# 具体为什么要加这个if不是很理解,大概是节省计算消耗(?)
if not settings.synchronous_mode:
# 此变量在后面执行tm指令时会用到
synchronous_master = True
# 正常设置settings的同步模式
settings.synchronous_mode = True
settings.fixed_delta_seconds = 0.05
world.apply_settings(settings)
生成TM中的车辆:
# 用command指令将carla.SpawnActor赋值给SpawnActor变量(SetAutopilot同理),这里能让所有TM种的小车一同被设置
SpawnActor = carla.command.SpawnActor
SetAutopilot = carla.command.SetAutopilot
# FutureActor指正在生成实体的异步任务,当生成完成后,实体对象将会分配给FutureActor
FutureActor = carla.command.FutureActor
batch = []
for n, transform in enumerate(spawn_points):
if n >= args.number_of_vehicles:
break
blueprint = random.choice(blueprints_vehicle)
# 'recommended_values'指获取属性的推荐值
if blueprint.has_attribute('color'):
color = random.choice(blueprint.get_attribute('color').recommended_values)
blueprint.set_attribute('color', color)
if blueprint.has_attribute('driver_id'):
driver_id = random.choice(blueprint.get_attribute('driver_id').recommended_values)
blueprint.set_attribute('driver_id', driver_id)
# 设置'role_name'属性(autopilot非主角小车,仅是一个名称还是会影响功能还不确定,后续我会试一下)
blueprint.set_attribute('role_name', 'autopilot')
# spawn the cars and set their autopilot all together
# 'then'用于指定在前一个异步任务完成后执行的后续操作,即SpawnActor生成实体的异步任务完成后,执行FutureActor调用出来生成的实体(将后续操作续加到了then前的任务上)
batch.append(SpawnActor(blueprint, transform)
.then(SetAutopilot(FutureActor, True, traffic_manager.get_port())))
TIPS:异步任务与异步模式是两个不一样的名词,异步任务指可以在后台进行而不阻塞主程序执行的任务,
执行设定好的指令:
# enumerate括号内的函数:synchronous_master为bool值,若为True,则按batch执行;若为False则不执行;均有反馈
# synchronous_master是在前面设置tm的同步模式中设置过的
for (i, response) in enumerate(client.apply_batch_sync(batch, synchronous_master)):
if response.error:
# 在日志中记录错误
logging.error(response.error)
else:
# actor_id大概是response内容中的一部分
print("Fucture Actor", response.actor_id)
vehicles_id_list.append(response.actor_id)
以上便是Traffic Manager的基本应用,正在阅读一个来自知乎“叶小飞”GitHub里的源代码,阅读过程中的任何新收获和问题都会在这里更新,欢迎大家指正!
2.17更新:加过注释的完整代码&上面内容的一些更正
读完了上面所说的源代码,发现有一些在上述讲解中自己也不太理解的地方在完整代码中就懂了很多,我把自己加过注释讲解的代码放在这里,没有我注释的版本在文末链接的知乎博主文章里的GitHub里面有。
import argparse
import carla
import cv2
import logging
import time
import numpy as np
from numpy import random
from queue import Queue
from queue import Empty
def parser():
# 创建一个ArgumentParser对象,可以定义、解析命令行参数
argparser = argparse.ArgumentParser(
description=__doc__)
# 用户可以用‘--host’指定IP地址,若不指定则默认为127.0.0.1,metavar = 'H'是占位符,告诉用户在使用--host时应提供一个IP地址,像这样: --host H(IP)
argparser.add_argument(
'--host',
metavar='H',
default='127.0.0.1',
help='IP of the host server (default: 127.0.0.1)')
argparser.add_argument(
'-p', '--port',
metavar='P',
default=2000,
type=int,
help='TCP port to listen to (default: 2000)')
argparser.add_argument(
'-n', '--number-of-vehicles',
metavar='N',
default=20,
type=int,
help='number of vehicles (default: 30)')
argparser.add_argument(
'-d', '--number-of-dangerous-vehicles',
metavar='N',
default=1,
type=int,
help='number of dangerous vehicles (default: 3)')
argparser.add_argument(
'--tm-port',
metavar='P',
default=8000,
type=int,
help='port to communicate with TM (default: 8000)')
# 与前面的不同,这里没有metavar而是有"action = 'store_true'",指用户只要输入了--sync,就设置同步模式为true,而无需提供其它任何信息
argparser.add_argument(
'--sync',
action='store_true',
default=True,
help='Synchronous mode execution')
# 解析用户在命令行中输入的参数并返回
return argparser.parse_args()
def sensor_callback(sensor_data, sensor_queue):
# 将传感器的原始数据转换为数据类型为unint8(无符号 8 位整数)的一维数组,‘.raw_data’获取的是sensor_data(一张照片)的原始数据
array = np.frombuffer(sensor_data.raw_data, dtype=np.dtype("uint8"))
# 将array重新构造成三维数组,三个维的长度分别为照片的高度、宽度和4(4表示照片的每个像素由4个值组成:RGBA,即红绿蓝透明度)
array = np.reshape(array, (sensor_data.height, sensor_data.width, 4))
# array切片,去掉透明度
array = array[:, :, :3]
# 把帧号(frame)和array组成的元组放入队列,以便后续处理
sensor_queue.put((sensor_data.frame, array))
def main():
args = parser() # 即args为解析用户命令行输入的结果
# 配置基本的日志系统,format为日志的格式,level为日志级别:如INFO, WARNING, ERROR,此处的level设置为仅记录INFO及以上级别的日志消息
logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.INFO)
vehicles_id_list = []
client = carla.Client(args.host, args.port)
# 如果该操作花费时间超过10s,则触发超时
client.set_timeout(10.0)
synchronous_master = False
try:
world = client.get_world()
origin_settings = world.get_settings()
traffic_manager = client.get_trafficmanager(args.tm_port)
# every vehicle keeps a distance of 3.0 meter
traffic_manager.set_global_distance_to_leading_vehicle(3.0)
# Set physical mode only for cars around ego vehicle to save computation
traffic_manager.set_hybrid_physics_mode(True)
# default speed is 30, so the speed here is 30*80% = 24 (km/h)
traffic_manager.global_percentage_speed_difference(80)
# Suggest using syncmode
if args.sync:
settings = world.get_settings()
traffic_manager.set_synchronous_mode(True)
# 如果仅看这个代码,这里的if是不必要的,但是如果有多个代码一起运行,那么这里的if检测就可以减少冗余的消耗了
if not settings.synchronous_mode:
# 由这里可以看出,synchronous_master是同步模式是否是由此代码设置的flag,如果为true,那么这里的代码就要对同步模式的其它设置和调整负责
synchronous_master = True
settings.synchronous_mode = True
settings.fixed_delta_seconds = 0.05 # 20fps
world.apply_settings(settings)
blueprints_vehicle = world.get_blueprint_library().filter("vehicle.*")
# sort the vehicle list by id(bp.id的意思是获取bp的id这一项内容), bp is the element in blueprints_vehicle
# lambda一般输入bp,输出bp.id
blueprints_vehicle = sorted(blueprints_vehicle, key=lambda bp: bp.id)
# 获取可以放置vehicle的地方
spawn_points = world.get_map().get_spawn_points()
number_of_spawn_points = len(spawn_points)
if args.number_of_vehicles < number_of_spawn_points:
# 将spawn_points重新随机排序(且在spawn_points中替换原序列)
random.shuffle(spawn_points)
elif args.number_of_vehicles >= number_of_spawn_points:
# 出现异常,反馈给日志,并减少要求的vehicle数量
msg = 'requested %d vehicles, but could only find %d spawn points'
logging.warning(msg, args.number_of_vehicles, number_of_spawn_points)
args.number_of_vehicles = number_of_spawn_points - 1 # 减1是为了后面给hero vehicle留一个spawn_points位置
# Use command to apply actions on batch of data
SpawnActor = carla.command.SpawnActor
SetAutopilot = carla.command.SetAutopilot
# this is equal to int 0
FutureActor = carla.command.FutureActor
# batch是一个批量命令组成的数组
batch = []
for n, transform in enumerate(spawn_points):
if n >= args.number_of_vehicles:
break
blueprint = random.choice(blueprints_vehicle)
if blueprint.has_attribute('color'):
color = random.choice(blueprint.get_attribute('color').recommended_values)
blueprint.set_attribute('color', color)
if blueprint.has_attribute('driver_id'):
driver_id = random.choice(blueprint.get_attribute('driver_id').recommended_values)
blueprint.set_attribute('driver_id', driver_id)
# 设置role_name属性为‘autopilot’(非主角车辆)
blueprint.set_attribute('role_name', 'autopilot')
# spawn the cars and set their autopilot all together
batch.append(SpawnActor(blueprint, transform)
.then(SetAutopilot(FutureActor, True, traffic_manager.get_port())))
# excute the command
for (i, response) in enumerate(client.apply_batch_sync(batch, synchronous_master)):
if response.error:
logging.error(response.error)
else:
print("Future Actor", response.actor_id)
vehicles_id_list.append(response.actor_id)
vehicles_list = world.get_actors().filter('vehicle.*')
# wait for a tick to ensure client receives the last transform of the vehicles we have just created
if not args.sync or not synchronous_master:
# 如果不是同步模式则要等待一下,确保客户端收到了最新的车辆位置信息
world.wait_for_tick()
else:
# 如果是同步模式,那么一定已经收到了,因此不用等待,推进仿真时间步骤即可
world.tick()
# set several of the cars as dangerous car
for i in range(args.number_of_dangerous_vehicles):
danger_car = vehicles_list[i]
# crazy car ignore traffic light, do not keep safe distance, and very fast
# 100%忽略交通信号灯
traffic_manager.ignore_lights_percentage(danger_car, 100)
# 与前方车辆保持的安全距离为0
traffic_manager.distance_to_leading_vehicle(danger_car, 0)
# 设置速度为30km/h*150%(很快)
traffic_manager.vehicle_percentage_speed_difference(danger_car, -50)
print('spawned %d vehicles , press Ctrl+C to exit.' % (len(vehicles_list)))
# create ego vehicle(玩家操控的车辆)
ego_vehicle_bp = world.get_blueprint_library().find('vehicle.mercedes-benz.coupe')
# green color
ego_vehicle_bp.set_attribute('color', '0, 255, 0')
# 设置role_name属性为‘hero’(主角车辆)
ego_vehicle_bp.set_attribute('role_name', 'hero')
# 获取一个还没有被使用过的车辆放置位置
transform = spawn_points[len(vehicles_id_list)]
ego_vehicle = world.spawn_actor(ego_vehicle_bp, transform)
ego_vehicle.set_autopilot(True, args.tm_port)
vehicles_id_list.append(ego_vehicle.id)
# 创建了一个队列对象,并指定最大长度为10,队列是一种数据结构,遵循先进先出(FIFO)的原则
sensor_queue = Queue(maxsize=10)
# create camera
camera_bp = world.get_blueprint_library().find('sensor.camera.rgb')
# camera relative position related to the vehicle
camera_transform = carla.Transform(carla.Location(x=1.5, z=2.4))
camera = world.spawn_actor(camera_bp, camera_transform, attach_to=ego_vehicle)
# set the callback function, 同样输入image,输出sensor_callback(image, sensor_queue)
camera.listen(lambda image: sensor_callback(image, sensor_queue))
while True:
# 这里的if和else与前面的原理一样
if args.sync and synchronous_master:
world.tick()
try:
# 按队列的进入顺序获取元素,1.0代表最长获取时间为1s,若超时则退出
s_frame = sensor_queue.get(True, 1.0)
# 打印帧数
print("Camera Frame: %d" % (s_frame[0]))
# show image in a poping window
cv2.imshow('camera', s_frame[1])
# 检测是否按下键盘q键,cv2.waitKey等待按键按下,按下后将返回Unicode码值,参数1为等待1ms,看是否有按键按下
# '& 0xFF'为位运算操作,只保留最后八位;ord('q')返回‘q’的Unicode数值
if cv2.waitKey(1) & 0xFF == ord('q'):
break
except Empty:
print("Some of the sensor information is missed")
else:
world.wait_for_tick()
finally:
# 一切初始化
world.apply_settings(origin_settings)
print('\ndestroying %d vehicles' % len(vehicles_id_list))
client.apply_batch([carla.command.DestroyActor(x) for x in vehicles_id_list])
camera.destroy()
cv2.destroyAllWindows()
time.sleep(0.5)
if __name__ == '__main__':
try:
main()
except KeyboardInterrupt:
pass
finally:
print('\ndone.')
另外这里强推知乎“叶小飞”写的“史上最全Carla教程”,我的自学整理大多是在此教程的基础上进行注释补充和一些不懂的名词注释,这里贴上Traffic Manager这一章的,大家可以去他的主页查看全部教程~
史上最全Carla教程 |(五)交通管理器https://zhuanlan.zhihu.com/p/346636395