Carla简单入门-1 基本的API使用
本文写于2023年7月,文中所展示的版本为Ubuntu20.04以及Carla0.9.14,不同版本可能有一定的不同,欢迎各位伙伴们把遇到的问题和解决办法与其他人分享。
在上篇文档中,我们完成了Carla的下载和安装并且初步体验了官方所给出的演示案例,了解了Carla的一些基本功能,这篇文档中,我们将学习如何自己使用PythonAPI去实现之前所演示的功能并依照自己的需求去自定义它。
1. 核心概念
首先我们要理解两个概念,分别是 client 和 world,这两个概念是构成整个Carla拟真的基础,并且绝大多数的功能都是基于这两个概念之上。
首先是client,client 是用户同整个拟真环境进行信息交互的一个模块,用户对于拟真环境的控制基本都要依托于它。Client的运行需要一个IP地址和一个端口来和server进行通信,在一个server上同时可以有多个clent运行。想要控制多client同时运行需要对Carla和同步有深入了解,这里暂时不进行展开。我们可以用以下方法简单setup我们的client:
client = carla.Client('localhost', 2000)
如果carla.Client( )方法参数留空的话,将会默认在localhost和端口 2000上运行,同时,我们也可以修改第一个参数来进行远程连接进行模拟。如果有伙伴发现自己的PythonAPI无法运行,可以检查端口2000是否被占用,如果被占用的话,可以在启动Carla时输入这个参数来改变端口:
/CarlaUE4.sh -world-port=5000
同时我们在PythonAPI里setup我们的client时把端口更改为启动Carla时所用的端口即可。
接下来是world,world是整个拟真环境的代表,可以简单的理解为world即为这个拟真环境。world包含了很多重要的方法比如说生成车辆行人等物品,改变天气,获得环境中各种车辆的信息等。与可以多个同时运行的client不同,每次模拟中只会存在一个world,并且在我们通过client更换地图时,这个world将被销毁并由新的代表新地图的world代替。我们可以通过client的方法来获得现在这个client所处的world:
world = client.get_world()
当我们获得world之后,我们就可以通过它去访问其他在模拟中的物品,例如观察者,地图,天气等:
spectator = world.get_spectator()
map = world.get_map()
weather = world.get_weather()
除此之外还有一些概念我们需要简单知道,以便理解后续内容:
- Actor: Actor在Carla中是所有可以变化影响其他Actor并且对其他Actor作出反应的元素,包括:传感器(sensor), 交通标志和信号灯(Traffic lights and signs), 车辆(Vehicle), 行人(Walker)。
- Blueprints: Blueprints是创建生成Actor所需要的数据,包括模型,模型对应动画等。
- Traffic manager: Traffic manager在Carla中负责管理交通,所有被设置到自动驾驶(autopilot)的车辆都由Traffic manager管理, 通过Traffic manager,用户可以控制自动驾驶车辆的行为模式等,对于traffic manager感兴趣的伙伴可以去官方文档里看详细的原理以及用法说明:Traffic Manager - CARLA Simulator
2.地图环境的搭建
大致了解了最为核心的world以及client之后,我们就可以开始进行我们自己的尝试了。有些细心的伙伴们可能已经发现在官方的API例子中都有很长的一段代码用于导入Carla自己的Python api库:
在我们写自己的代码之前,我们可以先用pip去安装这个Python库,这样就不用每次用这么长的代码去导入库了:
pip3 install carla
注意pip install方法只适用于0.9.13及以上版本,老版本可以复制整段代码并粘贴到头:
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
import carla
a.加载地图
有两种办法加载我们想要的地图,第一种会使用官方给好的API,我们来到/PythonAPI/util目录下,运行:
./config.py --map Town04
将Town04换成我们想要加载的地图即可,但是这个方法比较麻烦,每次模拟之前需要手动加载需要的地图,所以下面介绍第二种方法,将加载地图的代码写到我们自己的代码里,第二种方法也很简单:
# setup client并且加载我们所需要的地图(Town01)
client = carla.Client('localhost', 2000)
client.load_world('Town01')
这样每次当我们运行我们自己的PythonAPI的时候就会自动加载到我们的Town01图了。
b.设置观察者
在我们把地图加载好之后,我们的视角会出现在观察者的默认位置上,我们可以通过W,A,S,D,E,Q 六个键去移动观察者,也可以在我们的代码中直接设定观察者方位:
# 获取我们client所对应的world
world = client.get_world()
# 获得这个world中的观察者
spectator = world.get_spectator()
# 获得观察者的方位信息
transform = spectator.get_transform()
# 根据观察者默认方位设置新的方位
location = transform.location + carla.Location(x=-30,z=20)
rotation = carla.Rotation(pitch=-20, yaw=-20, roll=0)
new_transform = carla.Transform(location, rotation)
# 将观察者设置到新方位上
spectator.set_transform(new_transform)
经过新的设定,我们就可以把观察者设定到一个新的鸟瞰视角
c.添加NPCs
现在我们的基本地图已经加载出来了,搭建一个最基础的拟真环境我们还需要向地图中添加各种道路交通参与者,无论是汽车还是行人。想要生成对应的NPC,我们就需要这个NPC的数据,于是我们的第一步就是要从world中获得这些汽车和行人的数据,也被称之为blueprint:
#获得整个的blueprint库并从中筛选出车辆
vehicle_blueprints = world.get_blueprint_library().filter('*vehicle*')
# 获得整个的blueprint库并从中筛选出行人
ped_blueprints = world.get_blueprint_library().filter('*pedestrian*')
在建立好我们所需的车辆和行人蓝图库之后,我们需要为这些车辆和行人设定各自的生成地点:
# 通过world获得map并获得所有可以生成车辆的地点
vehicle_spawn_points = world.get_map().get_spawn_points()
# 通过world获得所有可以生成行人的地点并存储
ped_spawn_points = []
for i in range(num_walkers):
spawn_point = carla.Transform()
loc = world.get_random_location_from_navigation()
if (loc != None):
spawn_point.location = loc
ped_spawn_points.append(spawn_point)
现在我们拥有了分别存储了车辆生成点和行人生成点的两个list以及记录着所有车辆和行人蓝图的蓝图库,接下来就可以生成了:
# 在地图上随机生成num_vehicle辆车,每辆车为车辆蓝图库中的随机车辆
for i in range(0, num_vehicle):
world.try_spawn_actor(random.choice(vehicle_blueprints), random.choice(vehicle_spawn_points))
# 在地图上随机生成num_walkers个行人,每个行人为行人蓝图库中的随机行人
for j in range(0, num_walkers):
world.try_spawn_actor(random.choice(ped_blueprints), random.choice(ped_spawn_points))
d.生成交通流
现在我们的地图当中已经拥有了行人,车辆等等必需的要素,但是现在他们还不会动,我们需要让他们动起来来形成真正的交通流。
对于行人和车辆,Carla各自有不同的方法来控制,我们先让车辆动起来,让车辆动起来是相对简单的,我们只需要从所有的actor中找到车辆,并且开启autopilot即可:
# 从world中获取车辆,并将每辆车的autopilot模式设置为打开
for vehicle in world.get_actors().filter('*vehicle*'):
vehicle.set_autopilot()
注意我们需要让client保持运行来使autopilot生效,我们可以在代码最后添加一个while loop来保持client的运行:
while True:
world.wait_for_tick()
现在我们已经成功让车辆开始运动起来了,像下图中演示的,车辆已经可以正常的通过红绿灯路口,同时我们也发现行人还是处于原地不动的状态,所以接下来我们写控制行人的代码。
与控制车辆的代码不同,控制行人的代码会略微复杂一些,因为车辆是由traffic manager控制,而行人的行动逻辑是由 controller.ai.walker控制。controller.ai.walker是一类特殊的actor,它没有实体,而且它在生成时需要绑定到一个parent actor上,而这个parent actor就是这个控制器所控制的行人。下面附上controller的官方文档: Python API - CARLA Simulator
创建行人的代码如下:
# 创建用来存储行人,行人速度设置和行人控制器的list
walker_batch = []
walker_speed = []
walker_ai_batch = []
# 在地图上随机生成num_walkers个行人,每个行人为行人蓝图库中的随机行人,并设定行人的移动速度
for j in range(num_walkers):
walker_bp = random.choice(ped_blueprints)
# 取消行人无敌状态
if walker_bp.has_attribute('is_invincible'):
walker_bp.set_attribute('is_invincible', 'false')
# 设置行人的移动速度
if walker_bp.has_attribute('speed'):
if random.random() > percentage_pedestrians_running:
# 将对应行人速度设置为走路速度
walker_speed.append(walker_bp.get_attribute('speed').recommended_values[1])
else:
# 将对应行人速度设置为跑步速度
walker_speed.append(walker_bp.get_attribute('speed').recommended_values[2])
# 从可生成行人的生成点中随机选择并生成随机行人,之后将生成的行人添加到同一批中
walker_batch.append(world.try_spawn_actor(walker_bp, random.choice(ped_spawn_points)))
行人一共有6种属性,其中有三种是用户可以自定义的,分别是是否为无敌状态,角色名,移动速度。在上面的代码中,我们对行人的两个参数做了自定义,一个是行人是否为无敌状态,另一个是行人的移动速度。
行人的属性设定结束后,我们就可以为每个行人生成对应的控制器了,以下是生成控制器的代码:
# 从蓝图库中寻找控制行人行动逻辑的控制器
walker_ai_blueprint = world.get_blueprint_library().find('controller.ai.walker')
# 为整批行人各自生成对应的控制器,并把控制器添加到代表批量控制器的列表中
for walker in world.get_actors().filter('*pedestrian*'):
walker_ai_batch.append(world.try_spawn_actor(walker_ai_blueprint, carla.Transform(), walker))
# 批量启动行人控制器,并设置控制器参数
for i in range(len(walker_ai_batch)):
# 启动控制器
walker_ai_batch[i].start()
# 通过控制器设置行人的目标点
walker_ai_batch[i].go_to_location(world.get_random_location_from_navigation())
# 通过控制器设置行人的行走速度
walker_ai_batch[i].set_max_speed(float(walker_speed[i]))
这样我们的行人的行动控制就基本设置好了,最后我们可以再对于行人横穿马路的参数进行调整:
# 设置行人横穿马路的参数
world.set_pedestrians_cross_factor(percentage_pedestrians_crossing)
经过以上的步骤,我们的地图环境就搭建好了,大概的效果如下图所示:
3. 测试主车的设置
我们的外部环境已经基本搭建完成了,接下来我们需要设置我们学习的对象,也就是测试主车。
a.生成主车并设置自动驾驶模式
主车的生成和生成其他车辆一样,这里就不细说了,直接上代码:
# 从可生成车辆地点中随机选择一个地点并记录
ego_spawn_point = random.choice(vehicle_spawn_points)
# 从蓝图库中挑选我们需要的主车蓝图
ego_bp = world.get_blueprint_library().find('vehicle.mini.cooper_s_2021')
# 设置主车蓝图的属性中的角色名
ego_bp.set_attribute('role_name', 'hero')
# 生成主车
ego_vehicle = world.spawn_actor(ego_bp, ego_spawn_point)
# 将主车设置为自动驾驶模式
ego_vehicle.set_autopilot()
当然,我们也可以选择其他的蓝图作为主车,官方的文档里提供了我们所有的可选蓝图:Blueprint library - CARLA Simulator
b.设置视角
因为每次生成车辆的位置都是随机的,我们开始模拟之后还需要手动找到主车并且手动控制观察者视角进行跟踪,这很不方便,所以下面我们将我们的观察者视角绑定在我们的主车上。这样我们就可以非常直观且方便的看到我们的仿真测试进程。
想要将我们的视角绑定到主车上也非常的简单直观:
- 选定一个传感器蓝图(例如rgb相机)
- 通过与主车绑定的方式生成(在生成时设置 AttachmentType)
- 每一帧读取传感器位置并将观察者视角(spectator)设定到该传感器位置
备注:任何actor都可以在生成时绑定其他actor,这里选用传感器是为了方便后续通过传感器记录仿真过程。
Carla提供了三种不同的 AttachmentType,这里大致说明一下,对细节感兴趣的伙伴们可以看下面官方文档的英文版:
- Rigid: 这种绑定方式是紧密固定被绑定物体与绑定物体的方法,是完全固定的绑定方式,如果将相机绑定到车辆上,那么车辆颠簸等都会被传递到相机上,适合想要在仿真中获得准确数据的情况。
- SpringArm: 这种绑定方式会根据不同情况伸长或者缩短与被绑定物体的距离,就像弹簧一样,适合想要获得没有抖动的平滑录像时使用。
- SpringArmGhost: 这种绑定方和第二种方式很相似,但是这种方式不会进行碰撞检测,意味着通过这个方式绑定的actor可以穿过墙体或者其他模型,同样也是适合想要获得没有抖动的平滑录像时使用。
了解了大致流程,我们现在就开始设置视角的流程,首先是第一步选定传感器蓝图:
# 从蓝图库中寻找rgb相机
camera_bp = world.get_blueprint_library().find('sensor.camera.rgb')
这里我们选择了rgb相机,当然还有很多其他选择例如深度相机sensor.camera.depth等,感兴趣的可以去官方文档里查看其他提供的传感器种类:Blueprint library - CARLA Simulator
接下来我们就可以生成相机并且绑定到我们的主车上去了,这里选用的是SpringArmGhost的AttachmentType,因为现在设置的第一个相机主要负责在第三人称视角记录仿真流程,我们需要记录的视频尽量平稳没有抖动。
# 设置rgb相机的方位信息
camera_transform = carla.Transform(carla.Location(x=-8, y=0, z=5),
carla.Rotation(pitch=10, yaw=0, roll=0))
# 生成rgb相机并用SpringArmGhost的方式绑定到主车上
camera = world.spawn_actor(camera_bp, camera_transform,
attach_to=ego_vehicle, attachment_type=carla.libcarla.AttachmentType.SpringArmGhost)
最后我们把每一帧读取相机方位并根据这个方位信息更新观察者视角的代码添加到我们的while loop中就成功了:
# 从world中获取观察者视角,并将观察者视角的方位信息设置为相机的对应方位信息
world.get_spectator().set_transform(camera.get_transform())
现在我们的测试主车和测试环境就已经基本设置完成了,现在我们再在代码的末尾添加退出时回收我们所生成的actor的代码:
finally:
# 停止并销毁所有controller
for controller in world.get_actors().filter('*controller*'):
controller.stop()
# 销毁所有车辆
for vehicle in world.get_actors().filter('*vehicle*'):
vehicle.destroy()
# 销毁所有行人
for walker in world.get_actors().filter('*walker*'):
walker.destroy()
这样我们就有了一个最基础的拟真环境了,大致的效果如下:
4.后话:
a. 完整代码分享:
import random
import carla
def main():
try:
# setup client并且加载我们所需要的地图
client = carla.Client('localhost', 2000)
client.load_world('Town01')
num_walkers = 200
num_vehicle = 20
# 设置跑步的行人比例
percentage_pedestrians_running = 0.25
# 设置横穿马路的行人比例
percentage_pedestrians_crossing = 0.15
# 获取我们client所对应的world
world = client.get_world()
# 获得这个world中的观察者
spectator = world.get_spectator()
# 获得观察者的方位信息
transform = spectator.get_transform()
# 根据观察者默认方位设置新的方位
location = transform.location + carla.Location(x=-30, z=20)
rotation = carla.Rotation(pitch=-20, yaw=-20, roll=0)
new_transform = carla.Transform(location, rotation)
# 将观察者设置到新方位上
spectator.set_transform(new_transform)
# 获得整个的blueprint库并从中筛选出车辆
vehicle_blueprints = world.get_blueprint_library().filter('*vehicle*')
# 获得整个的blueprint库并从中筛选出行人
ped_blueprints = world.get_blueprint_library().filter('*pedestrian*')
# 通过world获得map并获得所有可以生成车辆的地点
vehicle_spawn_points = world.get_map().get_spawn_points()
# 通过world获得所有可以生成行人的地点并存储
ped_spawn_points = []
for i in range(num_walkers):
spawn_point = carla.Transform()
loc = world.get_random_location_from_navigation()
if loc is not None:
spawn_point.location = loc
ped_spawn_points.append(spawn_point)
# 在地图上随机生成num_vehicle辆车,每辆车为车辆蓝图库中的随机车辆
for i in range(0, num_vehicle):
world.try_spawn_actor(random.choice(vehicle_blueprints),
random.choice(vehicle_spawn_points))
# 创建用来存储行人,行人速度设置和行人控制器的list
walker_batch = []
walker_speed = []
walker_ai_batch = []
# 在地图上随机生成num_walkers个行人,每个行人为行人蓝图库中的随机行人,并设定行人的移动速度
for j in range(num_walkers):
walker_bp = random.choice(ped_blueprints)
# 取消行人无敌状态
if walker_bp.has_attribute('is_invincible'):
walker_bp.set_attribute('is_invincible', 'false')
# 设置行人的移动速度
if walker_bp.has_attribute('speed'):
if random.random() > percentage_pedestrians_running:
# 将对应行人速度设置为走路速度
walker_speed.append(walker_bp.get_attribute('speed').recommended_values[1])
else:
# 将对应行人速度设置为跑步速度
walker_speed.append(walker_bp.get_attribute('speed').recommended_values[2])
# 从可生成行人的生成点中随机选择并生成随机行人,之后将生成的行人添加到同一批中
walker_batch.append(world.try_spawn_actor(walker_bp, random.choice(ped_spawn_points)))
# 从蓝图库中寻找控制行人行动逻辑的控制器
walker_ai_blueprint = world.get_blueprint_library().find('controller.ai.walker')
# 为整批行人各自生成对应的控制器,并把控制器添加到代表批量控制器的列表中
for walker in world.get_actors().filter('*pedestrian*'):
walker_ai_batch.append(world.spawn_actor(walker_ai_blueprint, carla.Transform(), walker))
# 批量启动行人控制器,并设置控制器参数
for i in range(len(walker_ai_batch)):
# 启动控制器
walker_ai_batch[i].start()
# 通过控制器设置行人的目标点
walker_ai_batch[i].go_to_location(world.get_random_location_from_navigation())
# 通过控制器设置行人的行走速度
walker_ai_batch[i].set_max_speed(float(walker_speed[i]))
# 设置行人横穿马路的参数
world.set_pedestrians_cross_factor(percentage_pedestrians_crossing)
# 从world中获取车辆,并将每辆车的autopilot模式设置为打开
for vehicle in world.get_actors().filter('*vehicle*'):
vehicle.set_autopilot()
# 从可生成车辆地点中随机选择一个地点并记录
ego_spawn_point = random.choice(vehicle_spawn_points)
# 从蓝图库中挑选我们需要的主车蓝图
ego_bp = world.get_blueprint_library().find('vehicle.mini.cooper_s_2021')
# 设置主车蓝图的属性中的角色名
ego_bp.set_attribute('role_name', 'hero')
# 生成主车
ego_vehicle = world.spawn_actor(ego_bp, ego_spawn_point)
# 将主车设置为自动驾驶模式
ego_vehicle.set_autopilot()
# 从蓝图库中寻找rgb相机
camera_bp = world.get_blueprint_library().find('sensor.camera.rgb')
# 设置rgb相机的方位信息
camera_transform = carla.Transform(carla.Location(x=-8, y=0, z=5),
carla.Rotation(pitch=10, yaw=0, roll=0))
# 生成rgb相机并用SpringArmGhost的方式绑定到主车上
camera = world.spawn_actor(camera_bp, camera_transform, attach_to=ego_vehicle,
attachment_type=carla.libcarla.AttachmentType.SpringArmGhost)
while True:
# 从world中获取观察者视角,并将观察者视角的方位信息设置为相机的对应方位信息
world.get_spectator().set_transform(camera.get_transform())
# 等待server更新world状态
world.wait_for_tick()
finally:
# 停止并销毁所有controller
for controller in world.get_actors().filter('*controller*'):
controller.stop()
# 销毁所有车辆
for vehicle in world.get_actors().filter('*vehicle*'):
vehicle.destroy()
# 销毁所有行人
for walker in world.get_actors().filter('*walker*'):
walker.destroy()
if __name__ == '__main__':
try:
main()
except KeyboardInterrupt:
pass
finally:
print('\ndone.')
b. 参考文档:
Carla的官方文档写的非常详细和完善,英语能力强的伙伴们一定要活用Cltr+f,很多问题都能在其中找到答案。另外,笔者也在积极学习Carla当中,如果文章有错处希望大家指出,有好的观点以及Carla的经验也都十分欢迎分享,让我们一起共同学习进步,共勉!
下期预告:
相信观察细致的伙伴们已经发现了,在演示的动态图中,主车的行动看起来有掉帧,卡顿的情况。我们下期就要着手解决这个问题,并且引入一些更加细致高级的控制,如traffic manager。