# Carla简单入门-4 pygame与场景细节

Carla简单入门-4 pygame与场景细节

本文写于2023年7月,文中所展示的版本为Ubuntu20.04以及Carla0.9.14,不同版本可能有一定的不同,欢迎各位伙伴们把遇到的问题和解决办法与其他人分享。

上篇文档中我们详细了解了多种传感器以及他们所对应的各种属性以及方法,这篇文章会介绍如何使用pygame来进行多传感器同时监控,同时还会介绍一些在场景搭建时候的细节,例如天气的控制等。

1. Pygame综述

Pygame本身是一个跨平台的游戏开发Python模块,我们想要在Carla中使用Pygame是因为它提供了非常有用且方便的图像实时渲染手段,这为我们实时可视化Carla中的各个传感器提供了很大的便利。而且Pygame作为游戏开发模块还提供了键盘的监听,这让我们也可以手动操控主车和其他车辆。下面就利用官方的简单demo来讲一下如何利用pygame实现最基础的传感器监控。

a. 第一步仍然是设定好整个模拟环境,包括加载地图,同步设置,交通管理器设置,生成车辆等等一系列设置:

import carla
import random
import pygame
import numpy as np

# 连接到客户端并检索世界对象
client = carla.Client('localhost', 2000)
world = client.get_world()

# 将模拟环境设定为同步模式
settings = world.get_settings()
settings.synchronous_mode = True
settings.fixed_delta_seconds = 0.05
world.apply_settings(settings)

# 将交通管理器设为同步模式
traffic_manager = client.get_trafficmanager()
traffic_manager.set_synchronous_mode(True)

# 设置种子,以便必要时行为可以重复
traffic_manager.set_random_device_seed(0)
random.seed(0)

# 设置观察者视角
spectator = world.get_spectator()

# 获取地图的生成点
spawn_points = world.get_map().get_spawn_points()

# 选择车辆模型范围
models = ['dodge', 'audi', 'model3', 'mini', 'mustang', 'lincoln', 'prius', 'nissan', 'crown', 'impala']
blueprints = []
for vehicle in world.get_blueprint_library().filter('*vehicle*'):
    if any(model in vehicle.id for model in models):
        blueprints.append(vehicle)

# 设置车辆生成数量上限
max_vehicles = 50
max_vehicles = min([max_vehicles, len(spawn_points)])
vehicles = []

# 在随机生成点位生成随机车辆
for i in range(0,max_vehicles):
    spawn_point = random.choice(spawn_points)
    temp = world.try_spawn_actor(random.choice(blueprints), spawn_point)
    if temp is not None:
        vehicles.append(temp)

# 将生成的车辆设定为自动驾驶模式
for vehicle in vehicles:
    vehicle.set_autopilot(True)

b. 在对模拟环境进行完基础的设置之后我们设置后续carla中传感器的回调函数,以及pygame的渲染函数:

# 相机传感器回调,将相机的原始数据重塑为2D RGB,并应用于PyGame表面
def pygame_callback(data, obj):
    img = np.reshape(np.copy(data.raw_data), (data.height, data.width, 4))
    img = img[:,:,:3]
    img = img[:, :, ::-1]
    obj.surface = pygame.surfarray.make_surface(img.swapaxes(0,1))

# 渲染物体
class RenderObject(object):
    def __init__(self, width, height):
        init_image = np.random.randint(0,255,(height,width,3),dtype='uint8')
        self.surface = pygame.surfarray.make_surface(init_image.swapaxes(0,1))

c. 接下来我们来看主车的控制系统,因为pygame可以方便的进行键盘监听,所以我们在这里加入手动键盘控制主车的选项:

    # 检测键盘输入并且根据输入来设定主车各个控制属性的状态
    def parse_control(self, event):
        if event.type == pygame.KEYDOWN:
            # 当按下回车键时切换自动驾驶状态
            if event.key == pygame.K_RETURN:
                self._autopilot = not self._autopilot
                self._vehicle.set_autopilot(self._autopilot)
            # 按下方向键上键时将油门属性设定为True
            if event.key == pygame.K_UP:
                self._throttle = True
            # 按下方向键下键时将刹车属性设定为True
            if event.key == pygame.K_DOWN:
                self._brake = True
            # 按下方向键右键时将方向盘属性设定为1
            if event.key == pygame.K_RIGHT:
                self._steer = 1
            # 按下方向键左键时将方向盘属性设定为-1
            if event.key == pygame.K_LEFT:
                self._steer = -1

        # 抬起按键时将属性重设为默认值
        if event.type == pygame.KEYUP:
            if event.key == pygame.K_UP:
                self._throttle = False
            if event.key == pygame.K_DOWN:
                self._brake = False
                self._control.reverse = False
            if event.key == pygame.K_RIGHT:
                self._steer = None
            if event.key == pygame.K_LEFT:
                self._steer = None

    # 将当前控制属性转化为carla.VehicleControl()的控制信息以应用
    def process_control(self):
        if self._throttle:
            self._control.throttle = min(self._control.throttle + 0.05, 1)
            self._control.gear = 1
            self._control.brake = False
        elif not self._brake:
            self._control.throttle = 0.0

        if self._brake:
            # 当按住方向下键并且车辆处于静止状态,切换到倒车档位并加速
            if self._vehicle.get_velocity().length() < 0.01 and not self._control.reverse:
                self._control.brake = 0.0
                self._control.gear = 1
                self._control.reverse = True
                self._control.throttle = min(self._control.throttle + 0.1, 1)
            elif self._control.reverse:
                self._control.throttle = min(self._control.throttle + 0.1, 1)
            else:
                self._control.throttle = 0.0
                self._control.brake = min(self._control.brake + 0.3, 1)
        else:
            self._control.brake = 0.0

        if self._steer is not None:
            if self._steer == 1:
                self._steer_cache += 0.03
            if self._steer == -1:
                self._steer_cache -= 0.03
            min(0.7, max(-0.7, self._steer_cache))
            self._control.steer = round(self._steer_cache,1)
        else:
            if self._steer_cache > 0.0:
                self._steer_cache *= 0.2
            if self._steer_cache < 0.0:
                self._steer_cache *= 0.2
            if 0.01 > self._steer_cache > -0.01:
                self._steer_cache = 0.0
            self._control.steer = round(self._steer_cache,1)

        # 将存储在self._control的控制信息应用
        self._vehicle.apply_control(self._control)

d. 下来我们需要生成主车并且将摄像头绑定到主车上:

# 为主车随机选择一个蓝图
ego_vehicle = random.choice(vehicles)

# 生成绑定到主车的摄像头
camera_init_trans = carla.Transform(carla.Location(x=-5, z=3), carla.Rotation(pitch=-20))
camera_bp = world.get_blueprint_library().find('sensor.camera.rgb')
camera = world.spawn_actor(camera_bp, camera_init_trans, attach_to=ego_vehicle)

# 设定camera读取数据后调用回调函数
camera.listen(lambda image: pygame_callback(image, renderObject))

# 获得相机画面尺寸
image_w = camera_bp.get_attribute("image_size_x").as_int()
image_h = camera_bp.get_attribute("image_size_y").as_int()

# 初始化渲染的物体和控制的物体
renderObject = RenderObject(image_w, image_h)
controlObject = ControlObject(ego_vehicle)

e. 接下来我们就可以初始化pygame窗口了:

# 初始化pygame视窗,在这里会弹出一个新的pygame弹窗
pygame.init()
gameDisplay = pygame.display.set_mode((image_w,image_h), pygame.HWSURFACE | pygame.DOUBLEBUF)
gameDisplay.fill((0,0,0))
gameDisplay.blit(renderObject.surface, (0,0))
pygame.display.flip()

f. 最后我们来看game loop部分,也就是控制模拟进程的部分,在这个部分里我们实现了切换视角的功能,按下Tab键后就可以将视角切换到另一个随机车辆上,按下回车键就可以将该车切换成手动模式,再次按下回车键会切换回自动驾驶模式。这样我们可以通过手动控制环境车辆来为自动驾驶的主车设置挑战课题,这段代码如下:


# Game loop
crashed = False

while not crashed:
    # 更新模拟环境时间
    world.tick()
    # 更新pygame现实
    gameDisplay.blit(renderObject.surface, (0,0))
    pygame.display.flip()
    # 应用当前控制状态
    controlObject.process_control()
    # 监听按键信息
    for event in pygame.event.get():
        # 窗口关闭则退出循环
        if event.type == pygame.QUIT:
            crashed = True

        # 将监听到的按键信息转化成控制信息
        controlObject.parse_control(event)
        if event.type == pygame.KEYUP:
            # 按下tab键切换到其他车辆
            if event.key == pygame.K_TAB:
                ego_vehicle.set_autopilot(True)
                ego_vehicle = random.choice(vehicles)
                # 确保车辆仍存活
                if ego_vehicle.is_alive:
                    # 清除原本摄像头
                    camera.stop()
                    camera.destroy()

                    # 生成新摄像头并绑定到新主车上
                    controlObject = ControlObject(ego_vehicle)
                    camera = world.spawn_actor(camera_bp, camera_init_trans, attach_to=ego_vehicle)
                    camera.listen(lambda image: pygame_callback(image, renderObject))

                    # 更新pygame窗口
                    gameDisplay.fill((0,0,0))
                    gameDisplay.blit(renderObject.surface, (0,0))
                    pygame.display.flip()

# 停止摄像头并退出pygame
# 销毁所有车辆
for vehicle in world.get_actors().filter('*vehicle*'):
    vehicle.destroy()
# 获得当前模拟世界设定
settings = world.get_settings()
# 设定为异步模式
settings.synchronous_mode = False
# 设定为可变时间步长
settings.fixed_delta_seconds = None
# 应用设定
world.apply_settings(settings)
camera.stop()
pygame.quit()

最后我们会得到这样的结果:

pygame_edited

Demo使用代码基于官方文档代码简单修改而成,文档链接:Pygame for vehicle control

2. Carla的天气系统

Carla中的天气系统并不是一个单独的类,而是属于carla.world的参数,所以我们可以通过调整参数来控制天气,调整有两种方式,一种是手动设定每种天气属性的参数,另一种则是直接套用官方给出的预设,先看第一种:

weather = carla.WeatherParameters(
    cloudiness=80.0,
    precipitation=30.0,
    sun_altitude_angle=70.0)

world.set_weather(weather)

print(world.get_weather())

另外一种使用预设的设置方法如下:

world.set_weather(carla.WeatherParameters.WetCloudySunset)

对于自定义手动设置天气参数的选项,官方文档也给出了每种属性的详细说明:

a. 天气参数

  • cloudiness (float)
    云量,范围0-100,值为0时无云,值为100时天空完全被云覆盖
  • precipitation (float)
    降水量,范围0-100,值为0时没有降雨,值为100时为大雨
  • precipitation_deposits (float)
    积水量,范围0-100,决定了道路上积水水洼的产生,值为0时完全没有积水,为100时道路完全被水覆盖,注意水洼会在固定的地点生成,没有随机性
  • wind_intensity (float)
    风强,范围0-100,控制风的强度,从0-100为无风到强风,风会影响雨和树叶的效果。
  • sun_azimuth_angle (float - degrees)
    太阳方位角,范围0-360,单位为角度,值为0时为UE引擎定义的天空球原点
  • sun_altitude_angle (float - degrees)
    太阳高度角,范围-90-+90,单位为角度,-90时代表午夜,+90时代表正午
  • fog_density (float)
    雾气浓度,范围0-100,注意此属性只影响RGB相机的输出
  • fog_distance (float - meters)
    雾气距离,范围0-inf,雾气到摄像机的距离
  • wetness (float)
    湿度,范围0-100,可能会引起镜头水雾等,注意此属性只影响RGB相机的输出
  • fog_falloff (float)
    云雾密度,范围0-inf,该值越大云雾越浓同时云雾高度越低,详情可参考 Fog Height Falloff
  • scattering_intensity (float)
    散射强度,控制被散射而在雾中形成光柱的光的比例,值为0时完全不散射
  • mie_scattering_scale (float)
    米氏散射强度,控制米氏散射的强度(出现圆环型光晕的概率),值为0时完全不散射
  • rayleigh_scattering_scale (float)
    瑞利散射强度,控制瑞利散射强度,影响傍晚天空红色等
  • dust_storm (float)
    沙尘暴,范围0-100,控制沙尘暴天气的强度

这些就是所有可以设置的天气参数了,用户可以通过这些属性自定义出所需要的天气,不过要注意的是很多天气属性都只会对RGB相机生效,而其他的传感器则无法模拟被天气影响的情况。

b. 代码实现

下面我们就借助着基于官方例子中dynamic_weather.py修改的代码来简单看一下如何在Carla中创建一个不断变化的天气系统。

# 限值函数,将输入的参数限制在0到100中间后输出
def clamp(value, minimum=0.0, maximum=100.0):
    return max(minimum, min(value, maximum))

# 太阳类,用以控制太阳方位角和太阳高度较
class Sun(object):
    def __init__(self, azimuth, altitude):
        self.azimuth = azimuth
        self.altitude = altitude
        self._t = 0.0
    # 步进更新,更新太阳高度,方位角
    def tick(self, delta_seconds):
        self._t += 0.008 * delta_seconds
        self._t %= 2.0 * math.pi
        self.azimuth += 0.25 * delta_seconds
        self.azimuth %= 360.0
        self.altitude = (70 * math.sin(self._t)) - 20

    # 输出当前太阳高度,方位角
    def __str__(self):
        return 'Sun(alt: %.2f, azm: %.2f)' % (self.altitude, self.azimuth)

# 风暴类用于控制降水,云雾,湿度,积水,风等
class Storm(object):
    def __init__(self, precipitation):
        self._t = precipitation if precipitation > 0.0 else -50.0
        self._increasing = True
        self.clouds = 0.0
        self.rain = 0.0
        self.wetness = 0.0
        self.puddles = 0.0
        self.wind = 0.0
        self.fog = 0.0

    # 更新风暴类中的所有参数,降水云雾等
    def tick(self, delta_seconds):
        delta = (1.3 if self._increasing else -1.3) * delta_seconds
        self._t = clamp(delta + self._t, -250.0, 100.0)
        self.clouds = clamp(self._t + 40.0, 0.0, 90.0)
        self.rain = clamp(self._t, 0.0, 80.0)
        delay = -10.0 if self._increasing else 90.0
        self.puddles = clamp(self._t + delay, 0.0, 85.0)
        self.wetness = clamp(self._t * 5, 0.0, 100.0)
        self.wind = 5.0 if self.clouds <= 20 else 90 if self.clouds >= 70 else 40
        self.fog = clamp(self._t - 10, 0.0, 30.0)
        if self._t == -250.0:
            self._increasing = True
        if self._t == 100.0:
            self._increasing = False
    # 输出当前云量,降水和风强度
    def __str__(self):
        return 'Storm(clouds=%d%%, rain=%d%%, wind=%d%%)' % (self.clouds, self.rain, self.wind)

# 天气类,包含太阳和风暴
class Weather(object):
    def __init__(self, weather):
        self.weather = weather
        self._sun = Sun(weather.sun_azimuth_angle, weather.sun_altitude_angle)
        self._storm = Storm(weather.precipitation)

    # 将自定义类太阳和风暴的参数传给weather
    def tick(self, delta_seconds):
        self._sun.tick(delta_seconds)
        self._storm.tick(delta_seconds)
        self.weather.cloudiness = self._storm.clouds
        self.weather.precipitation = self._storm.rain
        self.weather.precipitation_deposits = self._storm.puddles
        self.weather.wind_intensity = self._storm.wind
        self.weather.fog_density = self._storm.fog
        self.weather.wetness = self._storm.wetness
        self.weather.sun_azimuth_angle = self._sun.azimuth
        self.weather.sun_altitude_angle = self._sun.altitude

    # 输出当前天气参数
    def __str__(self):
        return '%s %s' % (self._sun, self._storm)

# 初始化weather
weather = Weather(world.get_weather())
# 将时间步长设置为0
elapsed_time = 0.0

# Game loop
crashed = False

while not crashed:
    # 更新模拟环境
    world.tick()
    # 时间步长设定
    elapsed_time += 0.05
    # 天气更新到时间步长之后的时间
    weather.tick(elapsed_time)
    # 应用更新的天气
    world.set_weather(weather.weather)
    # 输出天气信息
    sys.stdout.write('\r' + str(weather) + 12 * ' ')
    # 重置时间步长
    elapsed_time = 0.0

最后搭建好动态天气的效果如图:

pygame_weather_edited

3. Carla的车灯系统

根据Carla官方文档的说法,Carla中所有车辆的车灯都可以被交通管理器自动管理,但是笔者并没有找到如何使用交通管理器来进行车辆灯光控制,这里自己对于Carla的车灯系统进行了一点研究,也分享出来。

a. 车灯系统概述:

在Carla中,每种灯光都有所属于的组别,分为vehicle,street,building和other,除了vehicle以外的所有灯光都可以通过carla.LightManager来直接进行控制,而vehicle类的所有灯光需要 carla.Vehiclecarla.VehicleLightState来控制。 VehicleLightState是一个flag,通过carla.Vehicle中的set_light_state方法将VehicleLightState传入就可以改变车灯状态。

b. carla.VehicleLightState:

在Carla中,车辆灯光的状态是由一个flag表示,flag在二进制下共计11位,每一位所代表的灯光状态如下:

  • Position 位置灯

  • LowBeam 近光灯

  • HighBeam 远光灯

  • Brake 刹车灯

  • RightBlinker 右转向灯

  • LeftBlinker 左转向灯

  • Reverse 倒车灯

  • Fog 雾灯

  • Interior 内部灯

  • Special1 特殊灯光,特殊车辆例如警车警灯

  • Special2 特殊灯光,特殊车辆例如警车警灯

例如carla.VehicleLightState为0b11111111111时为全部灯光打开,为0b00000000000时灯光全部关闭。

c. 使用方法:

我们可以读取当前的天气,以及车辆信息,然后使用二进制运算的方式来对车辆灯光进行操作,代码如下:

def update_light_state(world):
    # 获取天气信息
    weather = world.get_weather()
    # 获取每辆车
    for vehicle in world.get_actors().filter('*vehicle*'):
        # 获取每辆车的控制信息
        control = vehicle.get_control()
        # 获取每辆车的灯光信息
        current_lights = vehicle.get_light_state()
        # 当进入夜晚打开位置灯,打开远光灯以及内部灯
        if weather.sun_altitude_angle < 0:
            current_lights |= carla.VehicleLightState.Position
            current_lights |= carla.VehicleLightState.HighBeam
            current_lights |= carla.VehicleLightState.Interior
        # 进入白天,关掉远灯及内部灯
        if weather.sun_altitude_angle > 0:
            current_lights &= 0b11011111011
        # 当雾气浓度大于30或者降雨量大于30时,打开位置灯,近光灯以及雾灯
        if weather.fog_density > 30 or weather.precipitation > 30:
            current_lights |= carla.VehicleLightState.Position
            current_lights |= carla.VehicleLightState.LowBeam
            current_lights |= carla.VehicleLightState.Fog
        # 当降雨小于30并且雾气浓度小于30时关闭近光灯以及雾灯
        if weather.fog_density < 30 and weather.precipitation < 30:
            current_lights &= 0b11101111101
        # 当踩下刹车时,亮起刹车灯
        if control.brake > 0.1:
            current_lights |= carla.VehicleLightState.Brake
        # 刹车松开,关闭刹车灯
        if control.brake <= 0.1:
            current_lights &= 0b11111110111
        # 方向盘左转,亮起左转向灯
        if control.steer < -0.1:
            current_lights |= carla.VehicleLightState.LeftBlinker
        # 方向盘右转,亮起右转向灯
        if control.steer > 0.1:
            current_lights |= carla.VehicleLightState.RightBlinker
        # 方向回正,关闭所有转向灯
        if abs(control.steer) < 0.1:
            current_lights &= 0b11111001111
        # 处于倒档时,亮起倒车灯
        if control.reverse:
            current_lights |= carla.VehicleLightState.Reverse
        # 不处于倒档时,关闭倒车灯
        if not control.reverse:
            current_lights &= 0b11110111111

        # 应用车灯信息
        vehicle.set_light_state(VehicleLightState(current_lights))

定义好更新车灯的方法后我们只需要在while loop中简单调用即可,最终效果图如下:
vehicle_light_edited

4. 多传感监控

在文章的最开始,我们利用官方文档中的案例进行了pygame的简单理解,并且实现了将一个传感器的读取信息在pygame窗口中渲染出来,下面我们进行一下拓展,同时将多个传感器的数据全部渲染在pygame窗口中以实现多传感器监控的效果。碍于篇幅限制,这里就将多传感器同窗口监视的核心代码直接放出,
下文中代码是基于Carla官方示例中的visualize_multiple_sensors.py修改而来,笔者在中间加入了自己的注释,希望能够帮助理解:

# 计时器
class CustomTimer:
    # 使用本机时间作为计时器,如果报错则改用浮点秒数计时
    def __init__(self):
        try:
            self.timer = time.perf_counter
        except AttributeError:
            self.timer = time.time
    # 返回计时器
    def time(self):
        return self.timer()

# 显示控制器
class DisplayManager:
    # 根据输入窗口大小初始化现实控制器并启用硬件加速,双缓冲
    def __init__(self, grid_size, window_size):
        pygame.init()
        pygame.font.init()
        self.display = pygame.display.set_mode(window_size, pygame.HWSURFACE | pygame.DOUBLEBUF)
        self.grid_size = grid_size
        self.window_size = window_size
        self.sensor_list = []

    # 返回整个窗口大小
    def get_window_size(self):
        return [int(self.window_size[0]), int(self.window_size[1])]

    # 返回单个监视窗的大小
    def get_display_size(self):
        return [int(self.window_size[0] / self.grid_size[1]), int(self.window_size[1] / self.grid_size[0])]

    #返回单个监视窗的位置
    def get_display_offset(self, gridPos):
        dis_size = self.get_display_size()
        return [int(gridPos[1] * dis_size[0]), int(gridPos[0] * dis_size[1])]

    # 添加传感器
    def add_sensor(self, sensor):
        self.sensor_list.append(sensor)

    # 获取传感器列表
    def get_sensor_list(self):
        return self.sensor_list

    # 渲染画面并将画面输出
    def render(self):
        if not self.render_enabled():
            return

        for s in self.sensor_list:
            s.render()

        pygame.display.flip()

    # 销毁传感器
    def destroy(self):
        for s in self.sensor_list:
            s.destroy()

    # 渲染开关
    def render_enabled(self):
        return self.display != None


# 传感器控制器
class SensorManager:
    # 初始化传感器,计时器等
    def __init__(self, world, display_man, sensor_type, transform, attached, sensor_options, display_pos):
        self.surface = None
        self.world = world
        self.display_man = display_man
        self.display_pos = display_pos
        self.sensor = self.init_sensor(sensor_type, transform, attached, sensor_options)
        self.sensor_options = sensor_options
        self.timer = CustomTimer()
        self.time_processing = 0.0
        self.tics_processing = 0
        self.display_man.add_sensor(self)

    # 初始化传感器方法
    def init_sensor(self, sensor_type, transform, attached, sensor_options):
        # 当输入传感器类型为RGBCamera时
        if sensor_type == 'RGBCamera':
            camera_bp = self.world.get_blueprint_library().find('sensor.camera.rgb')
            disp_size = self.display_man.get_display_size()
            camera_bp.set_attribute('image_size_x', str(disp_size[0]))
            camera_bp.set_attribute('image_size_y', str(disp_size[1]))

            for key in sensor_options:
                camera_bp.set_attribute(key, sensor_options[key])

            camera = self.world.spawn_actor(camera_bp, transform, attach_to=attached)
            camera.listen(self.save_rgb_image)

            return camera

        # 当输入传感器类型为LiDAR时
        elif sensor_type == 'LiDAR':
            lidar_bp = self.world.get_blueprint_library().find('sensor.lidar.ray_cast')
            lidar_bp.set_attribute('range', '100')
            lidar_bp.set_attribute('dropoff_general_rate',
                                   lidar_bp.get_attribute('dropoff_general_rate').recommended_values[0])
            lidar_bp.set_attribute('dropoff_intensity_limit',
                                   lidar_bp.get_attribute('dropoff_intensity_limit').recommended_values[0])
            lidar_bp.set_attribute('dropoff_zero_intensity',
                                   lidar_bp.get_attribute('dropoff_zero_intensity').recommended_values[0])

            for key in sensor_options:
                lidar_bp.set_attribute(key, sensor_options[key])

            lidar = self.world.spawn_actor(lidar_bp, transform, attach_to=attached)

            lidar.listen(self.save_lidar_image)

            return lidar

        # 当输入传感器类型为SemanticLiDAR时
        elif sensor_type == 'SemanticLiDAR':
            lidar_bp = self.world.get_blueprint_library().find('sensor.lidar.ray_cast_semantic')
            lidar_bp.set_attribute('range', '100')

            for key in sensor_options:
                lidar_bp.set_attribute(key, sensor_options[key])

            lidar = self.world.spawn_actor(lidar_bp, transform, attach_to=attached)

            lidar.listen(self.save_semanticlidar_image)

            return lidar

        # 当输入传感器类型为Radar时
        elif sensor_type == "Radar":
            radar_bp = self.world.get_blueprint_library().find('sensor.other.radar')
            for key in sensor_options:
                radar_bp.set_attribute(key, sensor_options[key])

            radar = self.world.spawn_actor(radar_bp, transform, attach_to=attached)
            radar.listen(self.save_radar_image)

            return radar

        # 当输入传感器类型为DepthCamera时
        elif sensor_type == "DepthCamera":
            depth_camera_bp = self.world.get_blueprint_library().find('sensor.camera.depth')
            for key in sensor_options:
                depth_camera_bp.set_attribute(key, sensor_options[key])

            depth_camera = self.world.spawn_actor(depth_camera_bp, transform, attach_to=attached)
            depth_camera.listen(self.save_depth_image)

            return depth_camera

        # 当输入传感器类型为SemanticSegmentationCamera时
        elif sensor_type == "SemanticSegmentationCamera":
            semantic_segmentation_camera_bp = self.world.get_blueprint_library().find('sensor.camera.semantic_segmentation')
            for key in sensor_options:
                semantic_segmentation_camera_bp.set_attribute(key, sensor_options[key])

            semantic_segmentation_camera = self.world.spawn_actor(semantic_segmentation_camera_bp, transform, attach_to=attached)
            semantic_segmentation_camera.listen(self.save_semantic_segmentation_image)

            return semantic_segmentation_camera

        # 当输入传感器类型为InstanceSegmentationCamera时
        elif sensor_type == "InstanceSegmentationCamera":
            instance_segmentation_camera_bp = self.world.get_blueprint_library().find(
                'sensor.camera.instance_segmentation')
            for key in sensor_options:
                instance_segmentation_camera_bp.set_attribute(key, sensor_options[key])

            instance_segmentation_camera = self.world.spawn_actor(instance_segmentation_camera_bp, transform,
                                                                  attach_to=attached)
            instance_segmentation_camera.listen(self.save_instance_segmentation_image)

            return instance_segmentation_camera
        else:
            return None

    # 获取传感器
    def get_sensor(self):
        return self.sensor

    # 转化rgb图像,渲染并更新时间
    def save_rgb_image(self, image):
        t_start = self.timer.time()

        image.convert(carla.ColorConverter.Raw)
        array = np.frombuffer(image.raw_data, dtype=np.dtype("uint8"))
        array = np.reshape(array, (image.height, image.width, 4))
        array = array[:, :, :3]
        array = array[:, :, ::-1]

        if self.display_man.render_enabled():
            self.surface = pygame.surfarray.make_surface(array.swapaxes(0, 1))

        t_end = self.timer.time()
        self.time_processing += (t_end - t_start)
        self.tics_processing += 1




    # 转化depth图像,渲染并更新时间
    def save_depth_image(self, image):
        t_start = self.timer.time()

        image.convert(carla.ColorConverter.LogarithmicDepth)
        array = np.frombuffer(image.raw_data, dtype=np.dtype("uint8"))
        array = np.reshape(array, (image.height, image.width, 4))
        array = array[:, :, :3]
        array = array[:, :, ::-1]

        if self.display_man.render_enabled():
            self.surface = pygame.surfarray.make_surface(array.swapaxes(0, 1))

        t_end = self.timer.time()
        self.time_processing += (t_end - t_start)
        self.tics_processing += 1

    # 转化semantic_segmentation图像,渲染并更新时间

    def save_semantic_segmentation_image(self, image):
        t_start = self.timer.time()

        image.convert(carla.ColorConverter.CityScapesPalette)
        array = np.frombuffer(image.raw_data, dtype=np.dtype("uint8"))
        array = np.reshape(array, (image.height, image.width, 4))
        array = array[:, :, :3]
        array = array[:, :, ::-1]

        if self.display_man.render_enabled():
            self.surface = pygame.surfarray.make_surface(array.swapaxes(0, 1))

        t_end = self.timer.time()
        self.time_processing += (t_end - t_start)
        self.tics_processing += 1

    # 转化instance_segmentation图像,渲染并更新时间
    def save_instance_segmentation_image(self, image):
        t_start = self.timer.time()

        image.convert(carla.ColorConverter.Raw)
        array = np.frombuffer(image.raw_data, dtype=np.dtype("uint8"))
        array = np.reshape(array, (image.height, image.width, 4))
        array = array[:, :, :3]
        array = array[:, :, ::-1]

        if self.display_man.render_enabled():
            self.surface = pygame.surfarray.make_surface(array.swapaxes(0, 1))

        t_end = self.timer.time()
        self.time_processing += (t_end - t_start)
        self.tics_processing += 1

    # 转化lidar数据,渲染并更新时间
    def save_lidar_image(self, image):
        t_start = self.timer.time()

        disp_size = self.display_man.get_display_size()
        lidar_range = 2.0 * float(self.sensor_options['range'])

        points = np.frombuffer(image.raw_data, dtype=np.dtype('f4'))
        points = np.reshape(points, (int(points.shape[0] / 4), 4))
        lidar_data = np.array(points[:, :2])
        lidar_data *= min(disp_size) / lidar_range
        lidar_data += (0.5 * disp_size[0], 0.5 * disp_size[1])
        lidar_data = np.fabs(lidar_data)  # pylint: disable=E1111
        lidar_data = lidar_data.astype(np.int32)
        lidar_data = np.reshape(lidar_data, (-1, 2))
        lidar_img_size = (disp_size[0], disp_size[1], 3)
        lidar_img = np.zeros((lidar_img_size), dtype=np.uint8)

        lidar_img[tuple(lidar_data.T)] = (255, 255, 255)

        if self.display_man.render_enabled():
            self.surface = pygame.surfarray.make_surface(lidar_img)

        t_end = self.timer.time()
        self.time_processing += (t_end - t_start)
        self.tics_processing += 1

    # 转化semanticlidar数据,渲染并更新时间
    def save_semanticlidar_image(self, image):
        t_start = self.timer.time()

        disp_size = self.display_man.get_display_size()
        lidar_range = 2.0 * float(self.sensor_options['range'])

        points = np.frombuffer(image.raw_data, dtype=np.dtype('f4'))
        points = np.reshape(points, (int(points.shape[0] / 6), 6))
        lidar_data = np.array(points[:, :2])
        lidar_data *= min(disp_size) / lidar_range
        lidar_data += (0.5 * disp_size[0], 0.5 * disp_size[1])
        lidar_data = np.fabs(lidar_data)  # pylint: disable=E1111
        lidar_data = lidar_data.astype(np.int32)
        lidar_data = np.reshape(lidar_data, (-1, 2))
        lidar_img_size = (disp_size[0], disp_size[1], 3)
        lidar_img = np.zeros((lidar_img_size), dtype=np.uint8)

        lidar_img[tuple(lidar_data.T)] = (255, 255, 255)

        if self.display_man.render_enabled():
            self.surface = pygame.surfarray.make_surface(lidar_img)

        t_end = self.timer.time()
        self.time_processing += (t_end - t_start)
        self.tics_processing += 1

    # 转换radar数据并更新时间
    def save_radar_image(self, radar_data):
        t_start = self.timer.time()
        points = np.frombuffer(radar_data.raw_data, dtype=np.dtype('f4'))
        points = np.reshape(points, (len(radar_data), 4))

        t_end = self.timer.time()
        self.time_processing += (t_end - t_start)
        self.tics_processing += 1

    # 渲染屏幕输出
    def render(self):
        if self.surface is not None:
            offset = self.display_man.get_display_offset(self.display_pos)
            self.display_man.display.blit(self.surface, offset)

    # 摧毁传感器
    def destroy(self):
        self.sensor.destroy()

      display_manager = DisplayManager(grid_size=[2, 3], window_size=[1920,1080])

        # 传感器管理器负责根据我们输入的属性去初始化传感器并且将最终的显示输出的位置进行管理,这里初始化了
        # RGBCamera,DepthCamera,SemanticSegmentationCamera,InstanceSegmentationCamera
        # LiDAR以及SemanticLiDAR共6个传感器,具体传感器属性参数含义请参考上一篇文章
        SensorManager(world, display_manager, 'DepthCamera',
                      carla.Transform(carla.Location(x=0, z=2.4), carla.Rotation(yaw=+00)),
                      ego_vehicle, {'fov':'90'}, display_pos=[0, 0])
        SensorManager(world, display_manager, 'RGBCamera',
                      carla.Transform(carla.Location(x=0, z=2.4), carla.Rotation(yaw=+00)),
                      ego_vehicle, {'fov':'90'}, display_pos=[0, 1])
        SensorManager(world, display_manager, 'SemanticSegmentationCamera',
                      carla.Transform(carla.Location(x=0, z=2.4), carla.Rotation(yaw=+00)),
                      ego_vehicle, {'fov':'90'}, display_pos=[0, 2])
        SensorManager(world, display_manager, 'InstanceSegmentationCamera',
                      carla.Transform(carla.Location(x=0, z=2.4), carla.Rotation(yaw=+00)),
                      ego_vehicle, {'fov':'90'}, display_pos=[1, 1])

        SensorManager(world, display_manager, 'LiDAR', carla.Transform(carla.Location(x=0, z=2.4)),
                      ego_vehicle,
                      {'channels': '64', 'range': '100', 'points_per_second': '250000', 'rotation_frequency': '20'},
                      display_pos=[1, 0])
        SensorManager(world, display_manager, 'SemanticLiDAR', carla.Transform(carla.Location(x=0, z=2.4)),
                      ego_vehicle,
                      {'channels': '64', 'range': '100', 'points_per_second': '100000', 'rotation_frequency': '20'},
                      display_pos=[1, 2])

现在我们就构建好整个多传感器监控的架构了,下面只需要在while循环中调用render方法即可:

while True:
    #你的代码
    ...
    # 渲染画面
    display_manager.render()

最后多传感器监视与动态天气和车灯的效果统一起来就是这样:
1_edtied

5.后话:

a. 完整代码:

b. 参考文档:

CARLA Simulator

对于Carla传感器的各种用法,无论是可视化还是多窗口监视,这些都只是笔者或者官方的一两次尝试,想要深入彻底的了解这些内容,还是需要伙伴们自己上手写码尝试,文中的代码只做抛砖引玉作用,希望大家有好的内容不吝分享!
Carla的官方文档写的非常详细和完善,英语能力强的伙伴们一定要活用Cltr+f,很多问题都能在其中找到答案。另外,笔者也在积极学习Carla当中,如果文章有错处希望大家指出,有好的观点以及Carla的经验也都十分欢迎分享,让我们一起共同学习进步,共勉!

2 个赞