-
Notifications
You must be signed in to change notification settings - Fork 367
/
simple_vehicle_control.py
330 lines (269 loc) · 14.6 KB
/
simple_vehicle_control.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
#!/usr/bin/env python
# Copyright (c) 2020-2021 Intel Corporation
#
# This work is licensed under the terms of the MIT license.
# For a copy, see <https://opensource.org/licenses/MIT>.
"""
This module provides an example control for vehicles which
does not use CARLA's vehicle engine.
Limitations:
- Does not respect any traffic regulation: speed limit, priorities, etc.
- Can only consider obstacles in forward facing reaching (i.e. in tight corners obstacles may be ignored).
"""
from distutils.util import strtobool
import math
import carla
from srunner.scenariomanager.actorcontrols.basic_control import BasicControl
from srunner.scenariomanager.actorcontrols.visualizer import Visualizer
from srunner.scenariomanager.carla_data_provider import CarlaDataProvider
from srunner.scenariomanager.timer import GameTime
class SimpleVehicleControl(BasicControl):
"""
Controller class for vehicles derived from BasicControl.
The controller directly sets velocities in CARLA, therefore bypassing
CARLA's vehicle engine. This allows a very precise speed control, but comes
with limitations during cornering.
In addition, the controller can consider blocking obstacles, which are
classified as dynamic (i.e. vehicles, bikes, pedestrians). Activation of this
features is controlled by passing proper arguments to the class constructor.
The collision detection uses CARLA's obstacle sensor (sensor.other.obstacle),
which checks for obstacles in the direct forward channel of the vehicle, i.e.
there are limitation with sideways obstacles and while cornering.
The controller can also respect red traffic lights, and use a given deceleration
value for more realistic behavior. Both behaviors are activated via the corresponding
controller arguments.
Args:
actor (carla.Actor): Vehicle actor that should be controlled.
args (dictionary): Dictonary of (key, value) arguments to be used by the controller.
May include: (consider_obstacles, true/false) - Enable consideration of obstacles
(proximity_threshold, distance) - Distance in front of actor in which
obstacles are considered
(consider_trafficlights, true/false) - Enable consideration of traffic lights
(max_deceleration, float) - Use a reasonable deceleration value for
this vehicle
(attach_camera, true/false) - Attach OpenCV display to actor
(useful for debugging)
Attributes:
_generated_waypoint_list (list of carla.Transform): List of target waypoints the actor
should travel along. A waypoint here is of type carla.Transform!
Defaults to [].
_last_update (float): Last time step the update function (tick()) was called.
Defaults to None.
_consider_obstacles (boolean): Enable/Disable consideration of obstacles
Defaults to False.
_proximity_threshold (float): Distance in front of actor in which obstacles are considered
Defaults to infinity.
_cv_image (CV Image): Contains the OpenCV image, in case a debug camera is attached to the actor
Defaults to None.
_camera (sensor.camera.rgb): Debug camera attached to actor
Defaults to None.
_obstacle_sensor (sensor.other.obstacle): Obstacle sensor attached to actor
Defaults to None.
_obstacle_distance (float): Distance of the closest obstacle returned by the obstacle sensor
Defaults to infinity.
_obstacle_actor (carla.Actor): Closest obstacle returned by the obstacle sensor
Defaults to None.
"""
def __init__(self, actor, args=None):
super(SimpleVehicleControl, self).__init__(actor)
self._generated_waypoint_list = []
self._last_update = None
self._consider_traffic_lights = False
self._consider_obstacles = False
self._proximity_threshold = float('inf')
self._max_deceleration = None
self._max_acceleration = None
self._obstacle_sensor = None
self._obstacle_distance = float('inf')
self._obstacle_actor = None
self._visualizer = None
if args and 'consider_obstacles' in args and strtobool(args['consider_obstacles']):
self._consider_obstacles = strtobool(args['consider_obstacles'])
bp = CarlaDataProvider.get_world().get_blueprint_library().find('sensor.other.obstacle')
bp.set_attribute('distance', '250')
if args and 'proximity_threshold' in args:
self._proximity_threshold = float(args['proximity_threshold'])
bp.set_attribute('distance', str(max(float(args['proximity_threshold']), 250)))
bp.set_attribute('hit_radius', '1')
bp.set_attribute('only_dynamics', 'True')
self._obstacle_sensor = CarlaDataProvider.get_world().spawn_actor(
bp, carla.Transform(carla.Location(x=self._actor.bounding_box.extent.x, z=1.0)), attach_to=self._actor)
self._obstacle_sensor.listen(lambda event: self._on_obstacle(event)) # pylint: disable=unnecessary-lambda
if args and 'consider_trafficlights' in args and strtobool(args['consider_trafficlights']):
self._consider_traffic_lights = strtobool(args['consider_trafficlights'])
if args and 'max_deceleration' in args:
self._max_deceleration = float(args['max_deceleration'])
if args and 'max_acceleration' in args:
self._max_acceleration = float(args['max_acceleration'])
if args and 'attach_camera' in args and strtobool(args['attach_camera']):
self._visualizer = Visualizer(self._actor)
def _on_obstacle(self, event):
"""
Callback for the obstacle sensor
Sets _obstacle_distance and _obstacle_actor according to the closest obstacle
found by the sensor.
"""
if not event:
return
self._obstacle_distance = event.distance
self._obstacle_actor = event.other_actor
def reset(self):
"""
Reset the controller
"""
if self._visualizer:
self._visualizer.reset()
if self._obstacle_sensor:
self._obstacle_sensor.destroy()
self._obstacle_sensor = None
if self._actor and self._actor.is_alive:
self._actor = None
def run_step(self):
"""
Execute on tick of the controller's control loop
If _waypoints are provided, the vehicle moves towards the next waypoint
with the given _target_speed, until reaching the final waypoint. Upon reaching
the final waypoint, _reached_goal is set to True.
If _waypoints is empty, the vehicle moves in its current direction with
the given _target_speed.
For further details see :func:`_set_new_velocity`
"""
if self._reached_goal:
# Reached the goal, so stop
velocity = carla.Vector3D(0, 0, 0)
self._actor.set_target_velocity(velocity)
return
if self._visualizer:
self._visualizer.render()
self._reached_goal = False
if not self._waypoints:
# No waypoints are provided, so we have to create a list of waypoints internally
# get next waypoints from map, to avoid leaving the road
self._reached_goal = False
map_wp = None
if not self._generated_waypoint_list:
map_wp = CarlaDataProvider.get_map().get_waypoint(CarlaDataProvider.get_location(self._actor))
else:
map_wp = CarlaDataProvider.get_map().get_waypoint(self._generated_waypoint_list[-1].location)
while len(self._generated_waypoint_list) < 50:
map_wps = map_wp.next(2.0)
if map_wps:
self._generated_waypoint_list.append(map_wps[0].transform)
map_wp = map_wps[0]
else:
break
# Remove all waypoints that are too close to the vehicle
while (self._generated_waypoint_list and
self._generated_waypoint_list[0].location.distance(self._actor.get_location()) < 0.5):
self._generated_waypoint_list = self._generated_waypoint_list[1:]
direction_norm = self._set_new_velocity(self._offset_waypoint(self._generated_waypoint_list[0]))
if direction_norm < 2.0:
self._generated_waypoint_list = self._generated_waypoint_list[1:]
else:
# When changing from "free" driving without pre-defined waypoints to a defined route with waypoints
# it may happen that the first few waypoints are too close to the ego vehicle for obtaining a
# reasonable control command. Therefore, we drop these waypoints first.
while self._waypoints and self._waypoints[0].location.distance(self._actor.get_location()) < 0.5:
self._waypoints = self._waypoints[1:]
self._reached_goal = False
if not self._waypoints:
self._reached_goal = True
else:
direction_norm = self._set_new_velocity(self._offset_waypoint(self._waypoints[0]))
if direction_norm < 4.0:
self._waypoints = self._waypoints[1:]
if not self._waypoints:
self._reached_goal = True
def _offset_waypoint(self, transform):
"""
Given a transform (which should be the position of a waypoint), displaces it to the side,
according to a given offset
Args:
transform (carla.Transform): Transform to be moved
returns:
offset_location (carla.Transform): Moved transform
"""
if self._offset == 0:
offset_location = transform.location
else:
right_vector = transform.get_right_vector()
offset_location = transform.location + carla.Location(x=self._offset*right_vector.x,
y=self._offset*right_vector.y)
return offset_location
def _set_new_velocity(self, next_location):
"""
Calculate and set the new actor veloctiy given the current actor
location and the _next_location_
If _consider_obstacles is true, the speed is adapted according to the closest
obstacle in front of the actor, if it is within the _proximity_threshold distance.
If _consider_trafficlights is true, the vehicle will enforce a stop at a red
traffic light.
If _max_deceleration is set, the vehicle will reduce its speed according to the
given deceleration value.
If the vehicle reduces its speed, braking lights will be activated.
Args:
next_location (carla.Location): Next target location of the actor
returns:
direction (carla.Vector3D): Length of direction vector of the actor
"""
current_time = GameTime.get_time()
target_speed = self._target_speed
if not self._last_update:
self._last_update = current_time
current_speed = math.sqrt(self._actor.get_velocity().x**2 + self._actor.get_velocity().y**2)
if self._consider_obstacles:
# If distance is less than the proximity threshold, adapt velocity
if self._obstacle_distance < self._proximity_threshold:
distance = max(self._obstacle_distance, 0)
if distance > 0:
current_speed_other = math.sqrt(
self._obstacle_actor.get_velocity().x**2 + self._obstacle_actor.get_velocity().y**2)
if current_speed_other < current_speed:
acceleration = -0.5 * (current_speed - current_speed_other)**2 / distance
target_speed = max(acceleration * (current_time - self._last_update) + current_speed, 0)
else:
target_speed = 0
if self._consider_traffic_lights:
if (self._actor.is_at_traffic_light() and
self._actor.get_traffic_light_state() == carla.TrafficLightState.Red):
target_speed = 0
if target_speed < current_speed:
self._actor.set_light_state(carla.VehicleLightState.Brake)
if self._max_deceleration is not None:
target_speed = max(target_speed, current_speed - (current_time -
self._last_update) * self._max_deceleration)
else:
self._actor.set_light_state(carla.VehicleLightState.NONE)
if self._max_acceleration is not None:
target_speed = min(target_speed, current_speed + (current_time -
self._last_update) * self._max_acceleration)
# set new linear velocity
velocity = carla.Vector3D(0, 0, 0)
direction = next_location - CarlaDataProvider.get_location(self._actor)
direction_norm = math.sqrt(direction.x**2 + direction.y**2)
velocity.x = direction.x / direction_norm * target_speed
velocity.y = direction.y / direction_norm * target_speed
self._actor.set_target_velocity(velocity)
# set new angular velocity
current_yaw = CarlaDataProvider.get_transform(self._actor).rotation.yaw
# When we have a waypoint list, use the direction between the waypoints to calculate the heading (change)
# otherwise use the waypoint heading directly
if self._waypoints:
delta_yaw = math.degrees(math.atan2(direction.y, direction.x)) - current_yaw
else:
new_yaw = CarlaDataProvider.get_map().get_waypoint(next_location).transform.rotation.yaw
delta_yaw = new_yaw - current_yaw
if math.fabs(delta_yaw) > 360:
delta_yaw = delta_yaw % 360
if delta_yaw > 180:
delta_yaw = delta_yaw - 360
elif delta_yaw < -180:
delta_yaw = delta_yaw + 360
angular_velocity = carla.Vector3D(0, 0, 0)
if target_speed == 0:
angular_velocity.z = 0
else:
angular_velocity.z = delta_yaw / (direction_norm / target_speed)
self._actor.set_target_angular_velocity(angular_velocity)
self._last_update = current_time
return direction_norm