5 minute read

El objetivo de esta semana era estudir la PythonAPI de CARLA para crear un coche teleoperable y que tuviese el sensor LiDAR.

Tutorial PythonAPI

Primero se siguió un pequeño tutorial para entender los principios de la biblioteca de CARLA, investigando como conectar el cliente al servidor, genera un vehiculo, hacer que se mueva de forma automática por los caminos en CARLA, etc.

Script Creado

Se hicieron varios scripts de prueba para ir añadiendo el control manual, la cámara de visualización, tocar la físicas de distintos elementos para probar su funcionamiento, etc. Finalmente se hizo el script de prueba MovementLidar.py con todo.

control_vehicle()

Se trata de la función que detecta las teclas pulsadas en el teclado mediante Pygame y realiza una función dependiendo de cual tecla se haya presionado. Con W se acelera, con la S se frena, con la A se gira a la izquierda, la D para girar a la derecha y el ESPACIO para el freno de mano.

def control_vehicle(vehicle):
    keys = pygame.key.get_pressed()
    
    control = carla.VehicleControl()
    control.throttle = 0.0
    control.steer = 0.0
    control.brake = 0.0
    control.hand_brake = False
    
    if keys[pygame.K_w]:  # Acelerar
        control.throttle = 1.0
    if keys[pygame.K_s]:  # Frenar
        control.brake = 1.0
    if keys[pygame.K_a]:  # Girar a la izquierda
        control.steer = -0.3
    if keys[pygame.K_d]:  # Girar a la derecha
        control.steer = 0.3
    if keys[pygame.K_SPACE]:  # Freno de mano
        control.hand_brake = True

    # Aplicar el control al vehículo
    vehicle.apply_control(control)

process_img()

Es la función que procesa el formato de las imágenes recibidas por la camara para hacerlas visualizables y las muestra en la pantalla mediante Pygame. Primero pasa la imagen sin procesar a uint8, se reorganiza el array en una estrictira de 3 dimensiones con 4 canales de color, se elimina el canal alfa, se pasa de BGR a RGB, se crea la superficie de Pygame y se dibuja por pantalla.

# Función de callback para recibir las imágenes de la cámara
def process_img(image, display):
    array = np.frombuffer(image.raw_data, dtype=np.dtype("uint8"))
    array = np.reshape(array, (image.height, image.width, 4))
    array = array[:, :, :3]  # Quitar el canal alfa
    array = array[:, :, ::-1]  # Convertir de BGRA a RGB
    surface = pygame.surfarray.make_surface(array.swapaxes(0, 1))
    display.blit(surface, (0, 0))  # Dibujar en la pantalla

process_lidar()

Se encarga de procesar los datos recibido por el sensor LiDAR, extrayendo las coordenas y la intensidad de cada punto. Primero lee los datos LiDAR y los convierte a float32, se reorganizan los datos y se indica el número de puntos procesados.

# Función de callback para procesar los datos del LiDAR
def process_lidar(data):
    points = np.frombuffer(data.raw_data, dtype=np.float32)
    points = np.reshape(points, (int(points.shape[0] / 4), 4))  # [x, y, z, intensity]
    print(f'Recibidos {points.shape[0]} puntos de LiDAR')

main()

Es el código principal del script se encarga de la conexión al servidor, la creación de un vehículo aleatoria y su aparición en uno de los spawn points, se añadio un sensor de colisión que indicase cada vez que habia una Y se configuró el modo síncrono, la cámara y el sensor LiDAR, que también guarda los datos .ply generados. Finalmente, tiene el bucle principal de simulación que escucha eventos de pygame y llama a control_vehicle() para manejar el vehículo generado y a world.tick() para avanzar un tick en la simulación de manera constante. También se hicieron algunas pruebas de las físicas del vehículo y de las ruedas que no se han añadido aqui.

def main():
    # Conectar al servidor CARLA
    client = carla.Client('localhost', 2000)
    client.set_timeout(10.0)
    world = client.get_world()

    # Obtener el blueprint library para los vehículos
    blueprint_library = world.get_blueprint_library()
    vehicle_bp = blueprint_library.filter('vehicle.*')[0]  # Selecciona un coche específico que tenga un modelo de físicas realista

    # Obtener un spawn point aleatorio
    spawn_points = world.get_map().get_spawn_points()
    spawn_point = random.choice(spawn_points)

    # Spawnear el vehículo
    vehicle = world.try_spawn_actor(vehicle_bp, spawn_point)

    if vehicle is not None:
        print('Vehículo spawneado correctamente')

        ##################
        # Configuracion de las colisiones del vehiculo
        collision_bp = blueprint_library.find('sensor.other.collision')
        collision_sensor = world.spawn_actor(collision_bp, carla.Transform(), attach_to=vehicle)

        # Ejemplo de escucha del sensor de colisión
        collision_sensor.listen(lambda event: print("Colisión detectada"))
        #########################

        # Configuración del vehículo
        vehicle.set_autopilot(False)  # Desactivar el piloto automático

        ######################## Modo sincrónico
        settings = world.get_settings()
        settings.synchronous_mode = True  # Habilitar modo sincrónico

        # Establecer delta de tiempo fijo (por ejemplo, 0.03 segundos)
        settings.fixed_delta_seconds = 0.03
        world.apply_settings(settings)

        # Configuración de la cámara en tercera persona
        camera_bp = blueprint_library.find('sensor.camera.rgb')
        camera_bp.set_attribute('image_size_x', f'{width}')
        camera_bp.set_attribute('image_size_y', f'{height}')
        camera_bp.set_attribute('fov', '110')  # Campo de visión amplio

        # Posición de la cámara detrás y encima del coche
        camera_transform = carla.Transform(carla.Location(x=-4.0,z=2.5), carla.Rotation(pitch=-15))
        
        # Crear y spawnear la cámara
        camera = world.spawn_actor(camera_bp, camera_transform, attach_to=vehicle)

        # Configurar la cámara para enviar imágenes a la función process_img
        camera.listen(lambda image: process_img(image, screen))

        ############################################### Añadir un sensor LiDAR
        lidar_bp = blueprint_library.find('sensor.lidar.ray_cast')
        lidar_bp.set_attribute('range', '50')  # Rango del LiDAR en metros
        lidar_bp.set_attribute('channels', '32')  # Número de canales
        lidar_bp.set_attribute('points_per_second', '56000')  # Resolución del LiDAR
        lidar_bp.set_attribute('rotation_frequency', '10')  # Frecuencia de rotación en Hz

        # Colocar el LiDAR en el techo del vehículo
        lidar_transform = carla.Transform(carla.Location(x=0.0, z=2.5))
        lidar = world.spawn_actor(lidar_bp, lidar_transform, attach_to=vehicle)

        # Configurar el callback del LiDAR
        lidar.listen(lambda data: process_lidar(data))

        # Configurar el callback del LiDAR y guardar datos en disco
        lidar.listen(lambda point_cloud: point_cloud.save_to_disk('/home/yuriy/Lidar/%.6d.ply' % point_cloud.frame))

        try:
            # Bucle principal para control del coche
            running = True
            while running:
                for event in pygame.event.get():
                    if event.type == pygame.QUIT:
                        running = False

                # Controlar el coche con el teclado
                control_vehicle(vehicle)

                # Avanzar un tick en la simulación
                world.tick()

                # Obtener la velocidad del vehículo
                velocity = vehicle.get_velocity()  # Devuelve un vector (x, y, z)
                speed = np.sqrt(velocity.x**2 + velocity.y**2 + velocity.z**2)  # Magnitud de la velocidad
                speed_kmh = speed * 3.6  # Convertir a km/h

                # Mostrar la velocidad en pantalla
                speed_text = font.render(f'Velocidad: {speed_kmh:.2f} km/h', True, (255, 255, 255))
                screen.blit(speed_text, (50, 50))

                pygame.display.flip()  # Actualizar la pantalla

                # Pequeño delay
                time.sleep(0.05)

        finally:
            # Restaurar los ajustes originales
            settings.synchronous_mode = False
            world.apply_settings(settings)

            # Destruir el vehículo, la cámara y el LiDAR
            lidar.destroy()
            camera.destroy()
            vehicle.destroy()
            print("Simulación terminada, vehículo, cámara y LiDAR destruidos.")
    
    else:
        print('Error: No se pudo spawnear el vehículo.')

Resultado

Problemas

  • Reinstalación: Debido a que al intentar iniciar el script en CARLA saltaba un error, al igual que con el manual_control.py proporcionado por CARLA, se buscaron soluciones en distintos foros, pero debido a la solución que proporcionaba un usuario se estropeo la instalación, por lo que se tuvo que volver a hacer toda la instalación de CARLA y Unreal Engine en la que surgieron algunos problemas de permisos que se acabaron solucionando y finalmente si se podían ejecutar los scripts, pero se perdió mucho tiempo debido a ello.

  • Open3D: Se intento visualizar en tiempo real con Open3D los puntos LiDAR generados, pero salia un erro de Violación de Segmentación tanto en el script de prueba MovementLidarVisualizer.py como en los scripts de compañeros que probaron su funcionamiento, pero todavía no se ha encontrado una solución.