• [carla入门教程]-3 在carla中遥控汽车并采集传感器数据(一个简单Demo,附代码)


    本专栏教程将记录从安装carla到调用carla的pythonAPI进行车辆操控并采集数据的全流程,带领大家从安装carla开始,到最终能够熟练使用carla仿真环境进行传感器数据采集和车辆控制.

    第三节 在carla中遥控一台车并采集数据

    本小节的目标是在carla中生成一台可操控的车辆,并且添加一个RGB相机到车辆上,然后操控车辆采集图像数据。本教程主要分成3步进行,第1步,我们要在地图中生成一台车辆。第2步,我们需要在车辆上添加一个传感器。第3步,我们需要添加一个键盘控制器来操控车辆。

    本案例中会涉及到很多carla pythonAPI的调用,但是由于本案例比较简单,所以设计到的接口用法比较有限,更多的内容需要移步到carla的官方pythonAPI的网站上详细了解。在浏览下面的教程时,遇到难懂的函数用法,也可以去pythonAPI的网站上查阅。


    章节内容介绍:
    第1小节介绍了一些涉及carla的重要概念。第2小节介绍了准备工作。第3小节介绍了主要内容。第4小节介绍了一些代码和文档链接。

    1. 基础概念

    本小节简要介绍一下carla中定义物体的一些用法,包括Actors,blueprint。

    1.1 Actors

    Actors,中文翻译为演员,是CARLA中的参与者是在模拟中执行动作的元素,他们可以影响其他参与者。CARLA的演员包括车辆和步行者,还有传感器、交通标志、交通灯和观众。充分了解如何对其进行操作至关重要。我们重点了解车辆和传感器。

    1.2 blueprint

    blueprint,中文翻译为蓝图,它们已经是带有动画和一系列属性的模型,帮助用户在模拟中顺利地加入新角色。这些属性包括车辆颜色、激光雷达传感器中的通道数量、步行者的速度等等。

    蓝图库( blueprint library)中存放了很多可以用的蓝图。我们在创建演员时,可以直接从蓝图库中获取蓝图。

    1.3 Spawning

    Spawning,翻译为生成,用于创建演员。世界对象负责生成演员并跟踪这些演员。Spawning演员时需要只需要一张演员的蓝图( blueprint)和一个演员的坐标(Transform)。坐标(Transform)说明了演员的位置和旋转。

    2. 准备工作

    我们将从头讲解本脚本的编写过程。本小节主要介绍导入第三方库,定义全局变量,创建客户端等准备工作,为3,4,5小节铺垫。

    2.1 导入第三方库

    首先我们需要添加路径,以便python脚本运行时能够找到 CARLA module相关库的路径。

    import glob
    import os
    import sys
    # ==============================================================================
    # -- Find CARLA module ---------------------------------------------------------
    # ==============================================================================
    
    try:
        sys.path.append(glob.glob('../carla/dist/carla-*%d.%d-%s.egg' % (
            sys.version_info.major,
            sys.version_info.minor,
            'win-amd64' if os.name == 'nt' else 'linux-x86_64'))[0])
    except IndexError:
        pass
    
    # ==============================================================================
    # -- Add PythonAPI for release mode --------------------------------------------
    # ==============================================================================
    try:
      sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + '/carla')
        sys.path.append("../examples/")
    except IndexError:
        pass
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    接着,我们需要import本脚本所需要的模块。
    车辆控制模块我们直接从examples/manual_control.py模块中导入。

    
    from manual_control import HUD,KeyboardControl
    from manual_control import CameraManager,get_actor_display_name,find_weather_presets
    from manual_control import CollisionSensor,LaneInvasionSensor,GnssSensor,IMUSensor
    import numpy as np
    import carla
    import random
    import time
    import cv2
    import pygame
    import argparse
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    接着,为了能够实现可复现性,我们设置随机数种子

    random.seed(123456)
    
    • 1

    2.2 定义全局变量

    接下来我们需要定义一些全局变量:

    # 设置演员列表
    actors_list = []
    #设置是否采用同步模式
    synchronous_master=True 
    #设置汽车数量
    num_vehicles=20 
    
    #设置采集的图片的参数
    IM_WIDTH = 640
    IM_HEIGHT = 480
    
    # 设置显示窗口大小
    windos_width=1280
    windos_height=720
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    2.3 创建表达世界对象的类

    为了便于对世界对象的光照、天气等信息进行控制,添加一个World类。这个类是由examples/manual_control.py中的World类修改而来,主要修改的部分是,原脚本的车辆是随机选择的,而这里我们是对自己定义的车辆进行控制。

    
    class World(object):
        def __init__(self, carla_world, hud, args,player):
            self.world = carla_world
            self.sync = args.sync
            self.actor_role_name = args.rolename
            try:
                self.map = self.world.get_map()
            except RuntimeError as error:
                print('RuntimeError: {}'.format(error))
                print('  The server could not send the OpenDRIVE (.xodr) file:')
                print('  Make sure it exists, has the same name of your town, and is correct.')
                sys.exit(1)
            self.hud = hud
            self.player = player
            self.collision_sensor = None
            self.lane_invasion_sensor = None
            self.gnss_sensor = None
            self.imu_sensor = None
            self.radar_sensor = None
            self.camera_manager = None
            self._weather_presets = find_weather_presets()
            self._weather_index = 0
            self._actor_filter = args.filter
            self._actor_generation = args.generation
            self._gamma = args.gamma
            self.restart()
            self.world.on_tick(hud.on_world_tick)
            self.recording_enabled = False
            self.recording_start = 0
            self.constant_velocity_enabled = False
            self.show_vehicle_telemetry = False
            self.current_map_layer = 0
            self.map_layer_names = [
                carla.MapLayer.NONE,
                carla.MapLayer.Buildings,
                carla.MapLayer.Decals,
                carla.MapLayer.Foliage,
                carla.MapLayer.Ground,
                carla.MapLayer.ParkedVehicles,
                carla.MapLayer.Particles,
                carla.MapLayer.Props,
                carla.MapLayer.StreetLights,
                carla.MapLayer.Walls,
                carla.MapLayer.All
            ]
    
        def restart(self):
            self.player_max_speed = 1.589
            self.player_max_speed_fast = 3.713
            # Keep same camera config if the camera manager exists.
            cam_index = self.camera_manager.index if self.camera_manager is not None else 0
            cam_pos_index = self.camera_manager.transform_index if self.camera_manager is not None else 0
    
            self.collision_sensor = CollisionSensor(self.player, self.hud)
            self.lane_invasion_sensor = LaneInvasionSensor(self.player, self.hud)
            self.gnss_sensor = GnssSensor(self.player)
            self.imu_sensor = IMUSensor(self.player)
            self.camera_manager = CameraManager(self.player, self.hud, self._gamma)
            self.camera_manager.transform_index = cam_pos_index
            self.camera_manager.set_sensor(cam_index, notify=False)
            actor_type = get_actor_display_name(self.player)
            self.hud.notification(actor_type)
    
            if self.sync:
                self.world.tick()
            else:
                self.world.wait_for_tick()
    
        def next_weather(self, reverse=False):
            self._weather_index += -1 if reverse else 1
            self._weather_index %= len(self._weather_presets)
            preset = self._weather_presets[self._weather_index]
            self.hud.notification('Weather: %s' % preset[1])
            self.player.get_world().set_weather(preset[0])
    
        def next_map_layer(self, reverse=False):
            self.current_map_layer += -1 if reverse else 1
            self.current_map_layer %= len(self.map_layer_names)
            selected = self.map_layer_names[self.current_map_layer]
            self.hud.notification('LayerMap selected: %s' % selected)
    
        def load_map_layer(self, unload=False):
            selected = self.map_layer_names[self.current_map_layer]
            if unload:
                self.hud.notification('Unloading map layer: %s' % selected)
                self.world.unload_map_layer(selected)
            else:
                self.hud.notification('Loading map layer: %s' % selected)
                self.world.load_map_layer(selected)
    
        def toggle_radar(self):
            if self.radar_sensor is None:
                self.radar_sensor = RadarSensor(self.player)
            elif self.radar_sensor.sensor is not None:
                self.radar_sensor.sensor.destroy()
                self.radar_sensor = None
    
        def modify_vehicle_physics(self, actor):
            #If actor is not a vehicle, we cannot use the physics control
            try:
                physics_control = actor.get_physics_control()
                physics_control.use_sweep_wheel_collision = True
                actor.apply_physics_control(physics_control)
            except Exception:
                pass
    
        def tick(self, clock):
            self.hud.tick(self, clock)
    
        def render(self, display):
            self.camera_manager.render(display)
            self.hud.render(display)
    
        def destroy_sensors(self):
            self.camera_manager.sensor.destroy()
            self.camera_manager.sensor = None
            self.camera_manager.index = None
    
        def destroy(self):
            if self.radar_sensor is not None:
                self.toggle_radar()
            sensors = [
                self.camera_manager.sensor,
                self.collision_sensor.sensor,
                self.lane_invasion_sensor.sensor,
                self.gnss_sensor.sensor,
                self.imu_sensor.sensor]
            for sensor in sensors:
                if sensor is not None:
                    sensor.stop()
                    sensor.destroy()
            if self.player is not None:
                self.player.destroy()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134

    2.4 添加可传入的参数

    为了在使用python xxxx.py命令时可以传入ip地址等参数,我们使用argparser库创建了一个存储参数的变量args。添加这个参数主要是因为我们后面的编程依赖于manual_control.py中的库,而这个库中传入了args参数。如果在本地运行carla服务器则参数无需深入了解。

    
        argparser = argparse.ArgumentParser(
            description='CARLA Manual Control Client')
        argparser.add_argument(
            '-v', '--verbose',
            action='store_true',
            dest='debug',
            help='print debug information')
        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(
            '-a', '--autopilot',
            action='store_true',
            help='enable autopilot')
        argparser.add_argument(
            '--res',
            metavar='WIDTHxHEIGHT',
            default='1280x720',
            help='window resolution (default: 1280x720)')
        argparser.add_argument(
            '--filter',
            metavar='PATTERN',
            default='vehicle.*',
            help='actor filter (default: "vehicle.*")')
        argparser.add_argument(
            '--generation',
            metavar='G',
            default='2',
            help='restrict to certain actor generation (values: "1","2","All" - default: "2")')
        argparser.add_argument(
            '--rolename',
            metavar='NAME',
            default='hero',
            help='actor role name (default: "hero")')
        argparser.add_argument(
            '--gamma',
            default=2.2,
            type=float,
            help='Gamma correction of the camera (default: 2.2)')
        argparser.add_argument(
            '--sync',
            action='store_true',
            help='Activate synchronous mode execution',
            default=True)
        args = argparser.parse_args()
        args.width, args.height = [int(x) for x in args.res.split('x')]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    2.5 创建客户端,配置仿真参数和同步模式

    下面就是创建一个客户端来运行本次脚本,参数为ip和端口号,都为默认值,无需修改。

    client = carla.Client('127.0.0.1', 2000)
    
    • 1

    从客户端中获取世界对象:

        #获取世界对象
        world_sim = client.get_world()
    
    • 1
    • 2

    接下来使用try except finally构成主处理流程:

    设置同步模式,carla服务器默认运行在异步模式,由于我们需要在脚本中运行一个AI,所以需要使用同步模式synchronous_master = True.仿真步长设置为0.05s,使用 world_sim.apply_settings(settings)应用配置。

     #设置同步模式,carla服务器默认运行在异步模式,由于我们需要在脚本中运行一个AI,所以需要使用同步模式.
            init_setting=world_sim.get_settings()
            settings=world_sim.get_settings()
            if not settings.synchronous_mode:
                synchronous_master = True #同步标志位
                settings.synchronous_mode = True
                # 仿真步长设置为0.05s
                settings.fixed_delta_seconds = 0.05
            # 设置正常运行渲染,交通流复杂时可以关闭渲染
            settings.no_rendering_mode=False
            # 将设置应用于世界
            world_sim.apply_settings(settings)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    3.关键内容

    3.1 生成车辆

    本小节将结合代码讲解如何构建一个自己的客户端,并且实现车辆的生成。

    通过第2节,我们做好了前期的准备工作。通过1.1我们了解到车辆属于carla中的演员,而一个演员是由一个蓝图和一个坐标组成的,下面就开始着手从蓝图和坐标创建一个车辆演员。

            # 车辆蓝图
            vehicles_blueprints=world_sim.get_blueprint_library().filter('*vehicle*')
            ego_blueprint = random.choice(vehicles_blueprints)
            
            # 车辆坐标
            spawn_points = world_sim.get_map().get_spawn_points()
            ego_spawn_point=random.choice(spawn_points)
    		
    		# 生成车辆
            ego_vehicleActor   = world_sim.spawn_actor(ego_blueprint,ego_spawn_point)
            print(f"ID: {ego_vehicleActor.id}  ,create ego vehicle sussesed!")
            actors_list.append(ego_vehicleActor)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    step1:获取蓝图ego_blueprint。我们通过world_sim.get_blueprint_library()可以获得所有的蓝图库,然后.filter('*vehicle*')就可以把所有车辆的蓝图过滤出来,得到了车辆的蓝图列表vehicles_blueprints,这个列表中有很多不同种类的车,例如大众,奥迪,特斯拉等等,我们从这个车辆列表中随机选择一个 random.choice(vehicles_blueprints)

    step2:获得坐标ego_spawn_point。我们通过world_sim.get_map().get_spawn_points()可以获得当前地图提供的所有生成点坐标,然后通过random.choice(spawn_points)随机选择一个坐标作为生成点坐标。

    step3:生成演员ego_vehicleActor。通过world_sim.spawn_actor(ego_blueprint,ego_spawn_point)就创建了车辆演员。

    3.2 添加相机传感器

    如何从carla 中实时获取前是相机数据,便于下一步进行图像处理.
    相机也同样是carla中的一个演员,所以也是通过world_sim.spawn_actor创建的,不同的是,这里的相机需要安装在车上,所以多了一个参数attach_to=ego_vehicleActor,意思是依附在车辆演员ego_vehicleActor上。

    另外,在我们创建相机蓝图camera_bp时,可以通过.set_attribute()对其中的参数进行修改:camera_bp.set_attribute('image_size_x', f'{IM_WIDTH}') #图像宽度

    生成相机演员camera_actor之后,使用camera_actor.listen(lambda image:process_img(image))对消息进行监听,受到消息后会在process_img()函数中处理。

            # ==============================================================================
            # --create a Image sensor  actor---------------------------------------------------------
            # ==============================================================================
    
                #下面要添加传感器,这里以添加RGB相机为例:
            # Create a transform to place the camera on top of the vehicle
            camera_init_trans = carla.Transform(carla.Location(z=1.5))
            
            # We create the camera through a blueprint that defines its properties
            camera_bp = world_sim.get_blueprint_library().find('sensor.camera.rgb')
            
            # get the blueprint for this sensor
            # change the dimensions of the image
            camera_bp.set_attribute('image_size_x', f'{IM_WIDTH}') #图像宽度
            camera_bp.set_attribute('image_size_y', f'{IM_HEIGHT}')#图像高度
            camera_bp.set_attribute('fov', '110')#水平视长角 (度)
            camera_bp.set_attribute('sensor_tick','1.0')#消息间隔时间 (s)
    
    
            # We spawn the camera and attach it to our ego vehicle
            camera_actor = world_sim.spawn_actor(camera_bp, camera_init_trans, attach_to=ego_vehicleActor)
            actors_list.append(camera_actor)
            
            # # 有了RGB相机,下面我们需要对数据进行订阅和存储
            # # Start camera with PyGame callback
            if listen_flag:
                camera_actor.listen(lambda image:process_img(image))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    在消息处理的process_img函数中,我们把接收到的图片保存在out/下,命名为当前图片的帧。并且,我们也提供了转换成numpy的方法image.raw_data,可以用于在线处理。

    def process_img(image):
        print("get a img!")
        image.save_to_disk('out/%06d.png' % image.frame)
        i = np.array(image.raw_data)  # convert to an array
        i2 = i.reshape((IM_HEIGHT, IM_WIDTH, 4))  # was flattened, so we're going to shape it.
        i3 = i2[:, :, :3]  # remove the alpha (basically, remove the 4th index  of every pixel. Converting RGBA to RGB)
        return i3/255.0  # normalize
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    3.3.添加控制器

    使用pygame创建显示界面。

            # ==============================================================================
            # --create a pygame display  windows---------------------------------------------------------
            # ==============================================================================
    
            # 这里使用pygame模块
            pygame.init()
            pygame.font.init()
            # 使用pygame创建显示窗口
            display=pygame.display.set_mode((windos_width,windos_height),pygame.HWSURFACE | pygame.DOUBLEBUF)
            display.fill((0,0,0))
            pygame.display.flip()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    调用automatic_control.py中的库创建车辆信息打印窗口hud,创建世界变量控制器world, 创建键盘控制器controller

    
            # ==============================================================================
            # --create a infomation windos and controler---------------------------------------------------------
            # ==============================================================================
    
            # 创建车辆信息打印窗口
            hud = HUD(windos_width,windos_height)
            # 创建世界变量控制器,用于控制天气、地图等信息(禁用了车辆信息更换)
            world = World(world_sim, hud, args,ego_vehicleActor)
            # 创建键盘控制器,用于操控车辆
            controller = KeyboardControl(world, args.autopilot)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    3.4.世界运行及其后处理

    默认以同步方式运行世界模拟,也就是客户端的更新时间和服务器的更新时间保持一致。

            # 真实世界更新
            if args.sync:
                world_sim.tick()
            else:
                world_sim.wait_for_tick()
    
            clock = pygame.time.Clock()
            
            while True:
                if args.sync:
                    world_sim.tick()
                clock.tick_busy_loop(60)
                if controller.parse_events(client, world, clock, args.sync):
                    break
                world.tick(clock)
                world.render(display)
                pygame.display.flip()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    在代码最后,我们需要写一个后处理来销毁演员。由于演员创建后不会自动销毁,会在后台占用内存和计算资源,所以在推出程序时,我们需要销毁已经创建的演员。

            # 摧毁已经创建的actors
            # camera_actor.destroy()
            print('\ndestroying %d actors' % len(actors_list))
            client.apply_batch([carla.command.DestroyActor(x) for x in actors_list])
           
            if world is not None:
                world.destroy()
            pygame.quit()
    
    
            print('\nCancelled by user. Bye!')
            settings.synchronous_mode = False
            world_sim.apply_settings(settings)
            print("finally processed!")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    4.相关链接

    配套代码:
    gitte代码库

    官方指南入口:

    1. Python API reference
    2. Actors and blueprints
    3. World and client
    4. World

    其他教程:

    1. Alex_996

    2. 一骑红尘荔枝来

  • 相关阅读:
    1665. 完成所有任务的最少初始能量-快速排序+贪心算法
    【机器学习】拉格朗日对偶性
    【Linux】|开发工具介绍 | yum |vim | gcc/g++ | gdb | git
    使用MindStudio的X2MindSpore工具进行训练脚本转换
    重采样--学习笔记
    Codechef [June Long Two 2022] 题解
    基于OpenFeign实现服务调用
    分享一个java技术开发的springboot线上问卷调查可视化系统源码 lw 调试
    文件的操作方法
    Linux开发工具之调试器gdb
  • 原文地址:https://blog.csdn.net/condom10010/article/details/127627347