400 lines
13 KiB
Python
Executable File
400 lines
13 KiB
Python
Executable File
#!/usr/bin/env python
|
|
|
|
# Copyright (c) 2019 Aptiv
|
|
#
|
|
# This work is licensed under the terms of the MIT license.
|
|
# For a copy, see <https://opensource.org/licenses/MIT>.
|
|
|
|
"""
|
|
An example of client-side bounding boxes with basic car controls.
|
|
|
|
Controls:
|
|
|
|
W : throttle
|
|
S : brake
|
|
AD : steer
|
|
Space : hand-brake
|
|
|
|
ESC : quit
|
|
"""
|
|
|
|
# ==============================================================================
|
|
# -- find carla module ---------------------------------------------------------
|
|
# ==============================================================================
|
|
|
|
|
|
import glob
|
|
import os
|
|
import sys
|
|
|
|
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
|
|
|
|
|
|
# ==============================================================================
|
|
# -- imports -------------------------------------------------------------------
|
|
# ==============================================================================
|
|
|
|
import carla
|
|
|
|
import weakref
|
|
import random
|
|
|
|
try:
|
|
import pygame
|
|
from pygame.locals import K_ESCAPE
|
|
from pygame.locals import K_SPACE
|
|
from pygame.locals import K_a
|
|
from pygame.locals import K_d
|
|
from pygame.locals import K_s
|
|
from pygame.locals import K_w
|
|
except ImportError:
|
|
raise RuntimeError('cannot import pygame, make sure pygame package is installed')
|
|
|
|
try:
|
|
import numpy as np
|
|
except ImportError:
|
|
raise RuntimeError('cannot import numpy, make sure numpy package is installed')
|
|
|
|
VIEW_WIDTH = 1920//2
|
|
VIEW_HEIGHT = 1080//2
|
|
VIEW_FOV = 90
|
|
|
|
BB_COLOR = (248, 64, 24)
|
|
|
|
# ==============================================================================
|
|
# -- ClientSideBoundingBoxes ---------------------------------------------------
|
|
# ==============================================================================
|
|
|
|
|
|
class ClientSideBoundingBoxes(object):
|
|
"""
|
|
This is a module responsible for creating 3D bounding boxes and drawing them
|
|
client-side on pygame surface.
|
|
"""
|
|
|
|
@staticmethod
|
|
def get_bounding_boxes(vehicles, camera):
|
|
"""
|
|
Creates 3D bounding boxes based on carla vehicle list and camera.
|
|
"""
|
|
|
|
bounding_boxes = [ClientSideBoundingBoxes.get_bounding_box(vehicle, camera) for vehicle in vehicles]
|
|
# filter objects behind camera
|
|
bounding_boxes = [bb for bb in bounding_boxes if all(bb[:, 2] > 0)]
|
|
return bounding_boxes
|
|
|
|
@staticmethod
|
|
def draw_bounding_boxes(display, bounding_boxes):
|
|
"""
|
|
Draws bounding boxes on pygame display.
|
|
"""
|
|
|
|
bb_surface = pygame.Surface((VIEW_WIDTH, VIEW_HEIGHT))
|
|
bb_surface.set_colorkey((0, 0, 0))
|
|
for bbox in bounding_boxes:
|
|
points = [(int(bbox[i, 0]), int(bbox[i, 1])) for i in range(8)]
|
|
# draw lines
|
|
# base
|
|
pygame.draw.line(bb_surface, BB_COLOR, points[0], points[1])
|
|
pygame.draw.line(bb_surface, BB_COLOR, points[0], points[1])
|
|
pygame.draw.line(bb_surface, BB_COLOR, points[1], points[2])
|
|
pygame.draw.line(bb_surface, BB_COLOR, points[2], points[3])
|
|
pygame.draw.line(bb_surface, BB_COLOR, points[3], points[0])
|
|
# top
|
|
pygame.draw.line(bb_surface, BB_COLOR, points[4], points[5])
|
|
pygame.draw.line(bb_surface, BB_COLOR, points[5], points[6])
|
|
pygame.draw.line(bb_surface, BB_COLOR, points[6], points[7])
|
|
pygame.draw.line(bb_surface, BB_COLOR, points[7], points[4])
|
|
# base-top
|
|
pygame.draw.line(bb_surface, BB_COLOR, points[0], points[4])
|
|
pygame.draw.line(bb_surface, BB_COLOR, points[1], points[5])
|
|
pygame.draw.line(bb_surface, BB_COLOR, points[2], points[6])
|
|
pygame.draw.line(bb_surface, BB_COLOR, points[3], points[7])
|
|
display.blit(bb_surface, (0, 0))
|
|
|
|
@staticmethod
|
|
def get_bounding_box(vehicle, camera):
|
|
"""
|
|
Returns 3D bounding box for a vehicle based on camera view.
|
|
"""
|
|
|
|
bb_cords = ClientSideBoundingBoxes._create_bb_points(vehicle)
|
|
cords_x_y_z = ClientSideBoundingBoxes._vehicle_to_sensor(bb_cords, vehicle, camera)[:3, :]
|
|
cords_y_minus_z_x = np.concatenate([cords_x_y_z[1, :], -cords_x_y_z[2, :], cords_x_y_z[0, :]])
|
|
bbox = np.transpose(np.dot(camera.calibration, cords_y_minus_z_x))
|
|
camera_bbox = np.concatenate([bbox[:, 0] / bbox[:, 2], bbox[:, 1] / bbox[:, 2], bbox[:, 2]], axis=1)
|
|
return camera_bbox
|
|
|
|
@staticmethod
|
|
def _create_bb_points(vehicle):
|
|
"""
|
|
Returns 3D bounding box for a vehicle.
|
|
"""
|
|
|
|
cords = np.zeros((8, 4))
|
|
extent = vehicle.bounding_box.extent
|
|
cords[0, :] = np.array([extent.x, extent.y, -extent.z, 1])
|
|
cords[1, :] = np.array([-extent.x, extent.y, -extent.z, 1])
|
|
cords[2, :] = np.array([-extent.x, -extent.y, -extent.z, 1])
|
|
cords[3, :] = np.array([extent.x, -extent.y, -extent.z, 1])
|
|
cords[4, :] = np.array([extent.x, extent.y, extent.z, 1])
|
|
cords[5, :] = np.array([-extent.x, extent.y, extent.z, 1])
|
|
cords[6, :] = np.array([-extent.x, -extent.y, extent.z, 1])
|
|
cords[7, :] = np.array([extent.x, -extent.y, extent.z, 1])
|
|
return cords
|
|
|
|
@staticmethod
|
|
def _vehicle_to_sensor(cords, vehicle, sensor):
|
|
"""
|
|
Transforms coordinates of a vehicle bounding box to sensor.
|
|
"""
|
|
|
|
world_cord = ClientSideBoundingBoxes._vehicle_to_world(cords, vehicle)
|
|
sensor_cord = ClientSideBoundingBoxes._world_to_sensor(world_cord, sensor)
|
|
return sensor_cord
|
|
|
|
@staticmethod
|
|
def _vehicle_to_world(cords, vehicle):
|
|
"""
|
|
Transforms coordinates of a vehicle bounding box to world.
|
|
"""
|
|
|
|
bb_transform = carla.Transform(vehicle.bounding_box.location)
|
|
bb_vehicle_matrix = ClientSideBoundingBoxes.get_matrix(bb_transform)
|
|
vehicle_world_matrix = ClientSideBoundingBoxes.get_matrix(vehicle.get_transform())
|
|
bb_world_matrix = np.dot(vehicle_world_matrix, bb_vehicle_matrix)
|
|
world_cords = np.dot(bb_world_matrix, np.transpose(cords))
|
|
return world_cords
|
|
|
|
@staticmethod
|
|
def _world_to_sensor(cords, sensor):
|
|
"""
|
|
Transforms world coordinates to sensor.
|
|
"""
|
|
|
|
sensor_world_matrix = ClientSideBoundingBoxes.get_matrix(sensor.get_transform())
|
|
world_sensor_matrix = np.linalg.inv(sensor_world_matrix)
|
|
sensor_cords = np.dot(world_sensor_matrix, cords)
|
|
return sensor_cords
|
|
|
|
@staticmethod
|
|
def get_matrix(transform):
|
|
"""
|
|
Creates matrix from carla transform.
|
|
"""
|
|
|
|
rotation = transform.rotation
|
|
location = transform.location
|
|
c_y = np.cos(np.radians(rotation.yaw))
|
|
s_y = np.sin(np.radians(rotation.yaw))
|
|
c_r = np.cos(np.radians(rotation.roll))
|
|
s_r = np.sin(np.radians(rotation.roll))
|
|
c_p = np.cos(np.radians(rotation.pitch))
|
|
s_p = np.sin(np.radians(rotation.pitch))
|
|
matrix = np.matrix(np.identity(4))
|
|
matrix[0, 3] = location.x
|
|
matrix[1, 3] = location.y
|
|
matrix[2, 3] = location.z
|
|
matrix[0, 0] = c_p * c_y
|
|
matrix[0, 1] = c_y * s_p * s_r - s_y * c_r
|
|
matrix[0, 2] = -c_y * s_p * c_r - s_y * s_r
|
|
matrix[1, 0] = s_y * c_p
|
|
matrix[1, 1] = s_y * s_p * s_r + c_y * c_r
|
|
matrix[1, 2] = -s_y * s_p * c_r + c_y * s_r
|
|
matrix[2, 0] = s_p
|
|
matrix[2, 1] = -c_p * s_r
|
|
matrix[2, 2] = c_p * c_r
|
|
return matrix
|
|
|
|
|
|
# ==============================================================================
|
|
# -- BasicSynchronousClient ----------------------------------------------------
|
|
# ==============================================================================
|
|
|
|
|
|
class BasicSynchronousClient(object):
|
|
"""
|
|
Basic implementation of a synchronous client.
|
|
"""
|
|
|
|
def __init__(self):
|
|
self.client = None
|
|
self.world = None
|
|
self.camera = None
|
|
self.car = None
|
|
|
|
self.display = None
|
|
self.image = None
|
|
self.capture = True
|
|
|
|
def camera_blueprint(self):
|
|
"""
|
|
Returns camera blueprint.
|
|
"""
|
|
|
|
camera_bp = self.world.get_blueprint_library().find('sensor.camera.rgb')
|
|
camera_bp.set_attribute('image_size_x', str(VIEW_WIDTH))
|
|
camera_bp.set_attribute('image_size_y', str(VIEW_HEIGHT))
|
|
camera_bp.set_attribute('fov', str(VIEW_FOV))
|
|
return camera_bp
|
|
|
|
def set_synchronous_mode(self, synchronous_mode):
|
|
"""
|
|
Sets synchronous mode.
|
|
"""
|
|
|
|
settings = self.world.get_settings()
|
|
settings.synchronous_mode = synchronous_mode
|
|
self.world.apply_settings(settings)
|
|
|
|
def setup_car(self):
|
|
"""
|
|
Spawns actor-vehicle to be controled.
|
|
"""
|
|
|
|
car_bp = self.world.get_blueprint_library().filter('vehicle.*')[0]
|
|
location = random.choice(self.world.get_map().get_spawn_points())
|
|
self.car = self.world.spawn_actor(car_bp, location)
|
|
|
|
def setup_camera(self):
|
|
"""
|
|
Spawns actor-camera to be used to render view.
|
|
Sets calibration for client-side boxes rendering.
|
|
"""
|
|
|
|
camera_transform = carla.Transform(carla.Location(x=-5.5, z=2.8), carla.Rotation(pitch=-15))
|
|
self.camera = self.world.spawn_actor(self.camera_blueprint(), camera_transform, attach_to=self.car)
|
|
weak_self = weakref.ref(self)
|
|
self.camera.listen(lambda image: weak_self().set_image(weak_self, image))
|
|
|
|
calibration = np.identity(3)
|
|
calibration[0, 2] = VIEW_WIDTH / 2.0
|
|
calibration[1, 2] = VIEW_HEIGHT / 2.0
|
|
calibration[0, 0] = calibration[1, 1] = VIEW_WIDTH / (2.0 * np.tan(VIEW_FOV * np.pi / 360.0))
|
|
self.camera.calibration = calibration
|
|
|
|
def control(self, car):
|
|
"""
|
|
Applies control to main car based on pygame pressed keys.
|
|
Will return True If ESCAPE is hit, otherwise False to end main loop.
|
|
"""
|
|
|
|
keys = pygame.key.get_pressed()
|
|
if keys[K_ESCAPE]:
|
|
return True
|
|
|
|
control = car.get_control()
|
|
control.throttle = 0
|
|
if keys[K_w]:
|
|
control.throttle = 1
|
|
control.reverse = False
|
|
elif keys[K_s]:
|
|
control.throttle = 1
|
|
control.reverse = True
|
|
if keys[K_a]:
|
|
control.steer = max(-1., min(control.steer - 0.05, 0))
|
|
elif keys[K_d]:
|
|
control.steer = min(1., max(control.steer + 0.05, 0))
|
|
else:
|
|
control.steer = 0
|
|
control.hand_brake = keys[K_SPACE]
|
|
|
|
car.apply_control(control)
|
|
return False
|
|
|
|
@staticmethod
|
|
def set_image(weak_self, img):
|
|
"""
|
|
Sets image coming from camera sensor.
|
|
The self.capture flag is a mean of synchronization - once the flag is
|
|
set, next coming image will be stored.
|
|
"""
|
|
|
|
self = weak_self()
|
|
if self.capture:
|
|
self.image = img
|
|
self.capture = False
|
|
|
|
def render(self, display):
|
|
"""
|
|
Transforms image from camera sensor and blits it to main pygame display.
|
|
"""
|
|
|
|
if self.image is not None:
|
|
array = np.frombuffer(self.image.raw_data, dtype=np.dtype("uint8"))
|
|
array = np.reshape(array, (self.image.height, self.image.width, 4))
|
|
array = array[:, :, :3]
|
|
array = array[:, :, ::-1]
|
|
surface = pygame.surfarray.make_surface(array.swapaxes(0, 1))
|
|
display.blit(surface, (0, 0))
|
|
|
|
def game_loop(self):
|
|
"""
|
|
Main program loop.
|
|
"""
|
|
|
|
try:
|
|
pygame.init()
|
|
|
|
self.client = carla.Client('127.0.0.1', 2000)
|
|
self.client.set_timeout(2.0)
|
|
self.world = self.client.get_world()
|
|
|
|
self.setup_car()
|
|
self.setup_camera()
|
|
|
|
self.display = pygame.display.set_mode((VIEW_WIDTH, VIEW_HEIGHT), pygame.HWSURFACE | pygame.DOUBLEBUF)
|
|
pygame_clock = pygame.time.Clock()
|
|
|
|
self.set_synchronous_mode(True)
|
|
vehicles = self.world.get_actors().filter('vehicle.*')
|
|
|
|
while True:
|
|
self.world.tick()
|
|
|
|
self.capture = True
|
|
pygame_clock.tick_busy_loop(20)
|
|
|
|
self.render(self.display)
|
|
bounding_boxes = ClientSideBoundingBoxes.get_bounding_boxes(vehicles, self.camera)
|
|
ClientSideBoundingBoxes.draw_bounding_boxes(self.display, bounding_boxes)
|
|
|
|
pygame.display.flip()
|
|
|
|
pygame.event.pump()
|
|
if self.control(self.car):
|
|
return
|
|
|
|
finally:
|
|
self.set_synchronous_mode(False)
|
|
self.camera.destroy()
|
|
self.car.destroy()
|
|
pygame.quit()
|
|
|
|
|
|
# ==============================================================================
|
|
# -- main() --------------------------------------------------------------------
|
|
# ==============================================================================
|
|
|
|
|
|
def main():
|
|
"""
|
|
Initializes the client-side bounding box demo.
|
|
"""
|
|
|
|
try:
|
|
client = BasicSynchronousClient()
|
|
client.game_loop()
|
|
finally:
|
|
print('EXIT')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|