Carla自学整理——Traffic Manager

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教程 |(五)交通管理器icon-default.png?t=N7T8https://zhuanlan.zhihu.com/p/346636395

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值