本专栏教程将记录从安装carla到调用carla的pythonAPI进行车辆操控并采集数据的全流程,带领大家从安装carla开始,到最终能够熟练使用carla仿真环境进行传感器数据采集和车辆控制.
本小节的目标是在carla中生成一台可操控的车辆,并且添加一个RGB相机到车辆上,然后操控车辆采集图像数据。本教程主要分成3步进行,第1步,我们要在地图中生成一台车辆。第2步,我们需要在车辆上添加一个传感器。第3步,我们需要添加一个键盘控制器来操控车辆。
本案例中会涉及到很多carla pythonAPI的调用,但是由于本案例比较简单,所以设计到的接口用法比较有限,更多的内容需要移步到carla的官方pythonAPI的网站上详细了解。在浏览下面的教程时,遇到难懂的函数用法,也可以去pythonAPI的网站上查阅。
本小节简要介绍一下carla中定义物体的一些用法,包括Actors,blueprint。
Actors,中文翻译为演员,是CARLA中的参与者是在模拟中执行动作的元素,他们可以影响其他参与者。CARLA的演员包括车辆和步行者,还有传感器、交通标志、交通灯和观众。充分了解如何对其进行操作至关重要。我们重点了解车辆和传感器。
blueprint,中文翻译为蓝图,它们已经是带有动画和一系列属性的模型,帮助用户在模拟中顺利地加入新角色。这些属性包括车辆颜色、激光雷达传感器中的通道数量、步行者的速度等等。
蓝图库( blueprint library)中存放了很多可以用的蓝图。我们在创建演员时,可以直接从蓝图库中获取蓝图。
Spawning,翻译为生成,用于创建演员。世界对象负责生成演员并跟踪这些演员。Spawning演员时需要只需要一张演员的蓝图( blueprint)和一个演员的坐标(Transform)。坐标(Transform)说明了演员的位置和旋转。
我们将从头讲解本脚本的编写过程。本小节主要介绍导入第三方库,定义全局变量,创建客户端等准备工作,为3,4,5小节铺垫。
首先我们需要添加路径,以便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
接着,我们需要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
接着,为了能够实现可复现性,我们设置随机数种子
random.seed(123456)
接下来我们需要定义一些全局变量:
# 设置演员列表
actors_list = []
#设置是否采用同步模式
synchronous_master=True
#设置汽车数量
num_vehicles=20
#设置采集的图片的参数
IM_WIDTH = 640
IM_HEIGHT = 480
# 设置显示窗口大小
windos_width=1280
windos_height=720
为了便于对世界对象的光照、天气等信息进行控制,添加一个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()
为了在使用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')]
下面就是创建一个客户端来运行本次脚本,参数为ip和端口号,都为默认值,无需修改。
client = carla.Client('127.0.0.1', 2000)
从客户端中获取世界对象:
#获取世界对象
world_sim = client.get_world()
接下来使用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)
本小节将结合代码讲解如何构建一个自己的客户端,并且实现车辆的生成。
通过第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)
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)
就创建了车辆演员。
如何从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))
在消息处理的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
使用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()
调用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)
默认以同步
方式运行世界模拟,也就是客户端的更新时间和服务器的更新时间保持一致。
# 真实世界更新
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()
在代码最后,我们需要写一个后处理来销毁演员。由于演员创建后不会自动销毁,会在后台占用内存和计算资源,所以在推出程序时,我们需要销毁已经创建的演员。
# 摧毁已经创建的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!")
配套代码:
gitte代码库
官方指南入口:
其他教程: