What is Scenic?
In der offiziellen Dokumentation, heißt es: "Scenic ist eine domänenspezifische probabilistische Programmiersprache zur Modellierung der Umgebung von cyber-physischen Systemen wie Robotern und autonomen Fahrzeugen. Ein Scenic-Programm definiert eine Verteilung über Szenen, Konfigurationen von physischen Objekten und Agenten; Stichproben aus dieser Verteilung ergeben konkrete Szenen, die simuliert werden können, um Trainings- oder Testdaten zu erzeugen. Scenic kann auch (probabilistische) Richtlinien für dynamische Agenten definieren, was die Modellierung von Szenarien ermöglicht, in denen Agenten im Laufe der Zeit als Reaktion auf den Zustand der Welt Aktionen ausführen."
Unser Hauptziel bei der Verwendung von Scenic ist der Entwurf von Szenarien - nicht die Datengenerierung. Dieser Blog-Beitrag befasst sich mit Scenic - einem praktischen und nützlichen Tool, mit dem Sie festlegen können, wie Ihre virtuelle Umgebung aussehen soll. Genauer gesagt nutzen wir flexible Möglichkeiten, um Objekte in der Szene zu platzieren und benutzerdefinierte Eigenschaften in Scenic zu implementieren.
Um Ihre eigene Verkehrssimulation mit Scenic zu erstellen, sind 5 Schritte zu beachten. Alle Schritte folgen einem Beispiel und setzen die Verwendung von CARLA voraus, einer Open-Source-Simulationsplattform, die die Entwicklung, das Training und die Validierung des autonomen Fahrens unterstützt. Weitere Details finden Sie in dem Beitrag Was ist CARLA und wie nutzt man es um autonomes Fahren zu simulieren?
Schauen wir uns ein Beispiel eines Überholvorgangs an, um die Schritte in einer Verkehrssimulation mit Scenic zu sehen. Das autonome Fahrzeug, das wir in diesem Beitrag ego vehicle nennen, überholt ein anderes Fahrzeug und wechselt zurück auf seine ursprüngliche Fahrspur.
Schritt 1: Platzieren von Autos in der Szene
Scenic bietet eine sehr flexible Möglichkeit, Objekte in der Welt zu platzieren. Der einfachste Weg dies zu tun ist die Erstellung von exakten Punkten, an denen das autonome Auto, oder wie wir es nennen, das“ego vehicle”, platziert werden kann. Zum Beispiel: Car at -50 @ 1.75
. Kurz gesagt, es werden Punkte definiert und Objekte auf diesen Punkten platziert. Die genauen Koordinaten für einen Punkt können direkt im UE4-Editor abgerufen werden oder indem man ein Scenic Skript ohne das Flag --simulate
startet.
scenic my_traffic_sim.scenic
Code-Sprache: CSS (css)
Sie können sehen, dass die Koordinaten in der unteren rechten Ecke der Ansicht angezeigt werden. Drücken Sie CTL+C, um die Ansicht zu schließen. Platzieren wir das ego vehicle an einem Punkt, der durch seine x- und y-Koordinaten definiert ist.
# my_traffic_sim.scenic
EGO_MODEL = "vehicle.audi.tt"
ego = Car at -50 @ 1.75,
with blueprint EGO_MODEL,
with behavior EgoBehavior()
Code-Sprache: PHP (php)
Nachdem das Ego Vehicle definiert ist, können andere Fahrzeuge einfacher platziert werden. Sie können relativ zum Ego Vehicle definiert werden, z. B. mit "ahead of“
CAR_1_MODEL = "vehicle.ford.mustang"
car_1 = Car ahead of ego by (0, 10),
with blueprint CAR_1_MODEL,
with behavior Car1Behavior()
Code-Sprache: JavaScript (javascript)
Scenic ist sehr anschaulich und die natürliche Sprache macht es einfach zu verstehen, wie die Autos platziert sind.
Der Zugriff auf eine bestimmte Straße/Spur in Scenic ist möglich, da das hinterliegende Netzwerk eine Dictionary-Struktur mit Schlüsseln und Werten hat. Wir verwenden einen online OpenDRIVE viewer , um die Straßennamen schnell abzurufen und sie Variablen zuzuordnen.
# the network variable can be directly used without special initialization
road4 = network.elements['road4']
road7 = network.elements['road7']
road2 = network.elements['road2']
Code-Sprache: PHP (php)
Schritt 2: Benutzerdefinierte Verhaltensweisen erstellen - Beispiel: Überholen
Sie können zwei weitere Zeilen in dem Codeblock sehen, in denen wir das Auto an einem bestimmten Punkt platziert haben.
car_1 = Car ahead of ego by (0, 10),
with blueprint CAR_1_MODEL,
with behavior Car1Behavior()
Code-Sprache: JavaScript (javascript)
In Zeile 2 wird das 3D-Modell des Fahrzeugs zugewiesen, ein sogenannter Blueprint (spezifisch für Unreal Engine 4). In Zeile 3 wird dem Auto ein Verhalten zugewiesen. Die so genannten Behaviors sind für die Aktionen verantwortlich, die ein Auto ausführt. Sie können grundlegende Verhaltensweisen wieFollowLaneBehavior
und FollowTrajectoryBehavior
direkt verwenden. Für komplexere Verhaltensweisen müssen Sie jedoch Ihre eigenen erstellen. Dies ist höchstwahrscheinlich der Fall, wenn Sie eine Verkehrssimulation erstellen möchten. Verhaltensweisen können mit dem Schlüsselwort do.
Hier ein Beispiel dafür, wie ein Überholverhalten implementiert werden kann.
behavior Overtake(leadCar, target_speed, change_back_distance=10, is_oppositeTraffic=False):
"""
leadCar: Car object of the leading car
target_speed: Integer of the target speed to overtake
change_back_distance: Integer of the distance after overtaking to change back to initial lane
is_oppositeTraffic: Boolean if there is opposite traffic
"""
# get the left lane of the current lane section
leftLane = self.laneSection.laneToLeft
# blink before overtaking
take SetVehicleLightStateAction(VehicleLightState.LeftBlinker)
# follow lane for 2 seconds
do FollowLaneAndDoNotCollideWith(leadCar, target_speed=target_speed, is_oppositeTraffic=is_oppositeTraffic) for 2 seconds
# change to left lane for overtaking and turn of the blinker
do LaneChangeBehavior(laneSectionToSwitch=leftLane, target_speed=target_speed, is_oppositeTraffic=is_oppositeTraffic)
take SetVehicleLightStateAction(VehicleLightState.NONE)
# follow the overtaking lane until there is enough distance to the leading car, the car you want to overtake
do FollowLaneBehavior(target_speed=target_speed, laneToFollow=leftLane.lane, is_oppositeTraffic=is_oppositeTraffic) \
until (distance from ego to leadCar) > change_back_distance
# change back to the initial lane and set the right blinker
take SetVehicleLightStateAction(VehicleLightState.RightBlinker)
do FollowLaneBehavior(target_speed=target_speed, laneToFollow=leftLane.lane, is_oppositeTraffic=is_oppositeTraffic) for 1 seconds
if is_oppositeTraffic:
rightLane = self.laneSection.laneToLeft
else:
rightLane = self.laneSection.laneToRight
do LaneChangeBehavior(laneSectionToSwitch=rightLane, target_speed=target_speed)
# turn of blinker
take SetVehicleLightStateAction(VehicleLightState.NONE)
Code-Sprache: PHP (php)
Schritt 3: Checkpoints setzen
Alle Fahrzeuge sind in der Szene platziert und die Verhaltensweisen sind definiert. Nun wollen wir die Verhaltensweisen kontrolliert auslösen. Dies gewährleistet eine konsistentere Art der Simulationen. Der Trigger, an dem ein Verhalten ausgelöst wird, ist genau definiert und reduziert das Risiko von unerwünschten und zufälligen Verhaltensmustern während der Simulation. Beachten Sie, dass Scenic Simulationen zulässt, die mehr oder weniger zufällig sind. Der Auslöser kann mit einem Punkt implementiert werden. Wir nennen ihn einen Checkpoint. Für das vorgestellte Beispiel definieren wir die Checkpoints.
# checkpoint when to start overtaking
CHECKPOINT_1 = Point at -320 @ 5.25
# this is needed to terminate the simulation
EGO_GOAL = Point at -890 @ 5.25
Code-Sprache: PHP (php)
Checkpoints können zum Beispiel ausgelöst werden, wenn das Auto nahe genug ist. Auch hier macht die natürliche Sprache von Scenic dies deutlich:
# example of how to use checkpoint to interrupt behaviors
behavior ExampleBehavior():
try
# default behavior
do ...
interrupt when (distance from self to CHECKPOINT_1) <= 2:
# default behavior is interrupted
# self refers to the object this behavior is assigned to
take ...
do ...
# this terminates the simulation and is absolutetly required in the scenic script
terminate when (distance from ego to EGO_GOAL) <= 2
Code-Sprache: PHP (php)
Fassen wir die bisher gecodeten Dinge zusammen. Verhaltensweisen, die nicht im Detail erwähnt werden, sind höchstwahrscheinlich integrierte Verhaltensweisen, die Sie direkt verwenden können.
### my_traffic_sim.scenic
## CAR MODELS AND MORE
EGO_MODEL = "vehicle.audi.tt"
# it is not clear which unit the speed is given in but we think it is m/s
EGO_SPEED = 10
CAR_1_MODEL = "vehicle.ford.mustang"
CAR_1_SPEED = 10
# checkpoints
CHECKPOINT_1 = Point at -320 @ 5.25
EGO_GOAL = Point at -890 @ 5.25
CAR_1_GOAL = Point at -360 @ 5.25
# define the ego trajectory that is followed
road4 = network.elements['road4']
road7 = network.elements['road7']
road2 = network.elements['road2']
ego_trajectory = [road4.lanes[1], road7.lanes[0], road2.lanes[0]]
## BEHAVIORS
behavior FollowLaneAndDoNotCollideWith(leadCar, target_speed=10, speedier_threshold=15, speed_threshold=12, brake_threshold = 7, is_oppositeTraffic=False):
"""
leadCar: Car object of the leading car
target_speed: Integer of the target speed to overtake
speedier_treshold: Integer of distance to lead car (far away)
speed_threshold: Integer of distance to lead car (closer)
brake_treshold: Integer of distance to lead car (too close)
is_oppositeTraffic: Boolean if there is opposite traffic
"""
try:
print("The fast xor furious!")
do FollowLaneBehavior(target_speed=target_speed, is_oppositeTraffic=is_oppositeTraffic)
interrupt when (distance from self to leadCar) <= speedier_threshold:
try:
print("What a sloth in front of me!")
do FollowLaneBehavior(target_speed=leadCar.speed + 1, is_oppositeTraffic=is_oppositeTraffic) until (distance from self to leadCar) > speedier_threshold
interrupt when (distance from self to leadCar) <= speed_threshold:
try:
print("I'll better go as fast as this snail!")
do FollowLaneBehavior(target_speed=leadCar.speed, is_oppositeTraffic=is_oppositeTraffic) until (distance from self to leadCar) > speed_threshold + 2
interrupt when (distance from self to leadCar) <= brake_threshold:
print("Ooof, I must brake! Out of the way!")
brake_intensity = min(max(math.exp(-0.1535 * (distance to leadCar)), 0), 1)
take SetBrakeAction(brake_intensity)
take SetThrottleAction(0.0)
behavior Overtake(leadCar, target_speed, change_back_distance=10, is_oppositeTraffic=False):
"""
leadCar: Car object of the leading car
target_speed: Integer of the target speed to overtake
change_back_distance: Integer of the distance after overtaking to change back to initial lane
is_oppositeTraffic: Boolean if there is opposite traffic
"""
# get the left lane of the current lane section
leftLane = self.laneSection.laneToLeft
# blink before overtaking
take SetVehicleLightStateAction(VehicleLightState.LeftBlinker)
# follow lane for 2 seconds
do FollowLaneAndDoNotCollideWith(leadCar, target_speed=target_speed, is_oppositeTraffic=is_oppositeTraffic) for 2 seconds
# change to left lane for overtaking and turn of the blinker
do LaneChangeBehavior(laneSectionToSwitch=leftLane, target_speed=target_speed, is_oppositeTraffic=is_oppositeTraffic)
take SetVehicleLightStateAction(VehicleLightState.NONE)
# follow the overtaking lane until there is enough distance to the leading car, the car you want to overtake
do FollowLaneBehavior(target_speed=target_speed, laneToFollow=leftLane.lane, is_oppositeTraffic=is_oppositeTraffic) \
until (distance from ego to leadCar) > change_back_distance
# change back to the initial lane and set the right blinker
take SetVehicleLightStateAction(VehicleLightState.RightBlinker)
do FollowLaneBehavior(target_speed=target_speed, laneToFollow=leftLane.lane, is_oppositeTraffic=is_oppositeTraffic) for 1 seconds
if is_oppositeTraffic:
rightLane = self.laneSection.laneToLeft
else:
rightLane = self.laneSection.laneToRight
do LaneChangeBehavior(laneSectionToSwitch=rightLane, target_speed=target_speed)
# turn of blinker
take SetVehicleLightStateAction(VehicleLightState.NONE)
behavior Car1Behavior():
"""
car1 follows the same trajectory as the ego vehicle and slows down after the goal is reached
"""
take SetSpeedAction(10)
do FollowTrajectoryBehavior(trajectory= ego_trajectory, target_speed=CAR_1_SPEED) until (distance from self to CAR_1_GOAL) <= 2
do FollowLaneBehavior(target_speed=5)
behavior EgoBehavior():
"""
the ego vehicle follows its trajectory and overtakes after checkpoint 1 is reached
"""
take SetSpeedAction(10)
try:
do FollowTrajectoryBehavior(trajectory=ego_trajectory, target_speed=EGO_SPEED, turn_speed=EGO_SPEED)
interrupt when (distance from self to CHECKPOINT_1) <= 2:
take SetVehicleLightStateAction(VehicleLightState.NONE)
do Overtake(car_1, target_speed=EGO_SPEED*1.2, is_oppositeTraffic=False, change_back_distance=10)
FollowLaneBehavior(target_speed=EGO_SPEED)
## DEFINE CARS
ego = Car at -50 @ 1.75,
with blueprint EGO_MODEL,
with behavior EgoBehavior()
car_1 = Car ahead of ego by (0, 10),
with blueprint CAR_1_MODEL,
with behavior Car1Behavior()
# terminate condition
terminate when (distance from ego to EGO_GOAL) <= 2
Code-Sprache: PHP (php)
Schritt 4: Starten der Scenic Simulation
Bei der Ausführung einer Scenic Simulation sucht Scenic lokal nach dem CARLA-Server. Weitere Informationen zur Einrichtung eines CARLA-Servers für den Fernzugriff finden Sie in unserem Blog-Beitrag “Einrichten von CARLA auf einem Server für den Fernzugriff”.
Um die Simulation auszuführen, müssen Sie Parameter entweder an die CLI oder direkt im Skript angeben. Fügen Sie oben in Ihrem Scenic-Skript Folgendes hinzu:
## SET MAP AND MODEL
# required
param map = localPath('../to/opendrive_map.xodr')
param carla_map = 'name of your carla map'
model scenic.simulators.carla.model
# optional
param timestep = 0.02
param timeout = 20
Code-Sprache: PHP (php)
Für Scenic-spezifische Parameter führen Sie einfach scenic --help
aus oder sehen Sie hierFür CARLA-spezifische Parameter sehen Sie sich die Tabelle hieran. Nun können Sie Ihre Scenic-Simulation mit dem Flag --simulate
starten. Es sollte ein Fenster mit der gerenderten Simulation erscheinen.
scenic path/to/my_simulation.scenic --simulate
Schritt 5: Aufnahme speichern (optional)
Wir haben Scenic zusätzliche Parameter hinzugefügt, um eine Aufnahme direkt zu speichern. Die Höhe und Breite des Fensters sind wichtige Parameter. Auch der Parameter timestep hatte einen erheblichen Einfluss auf die Simulation. Für weitere Details empfehlen wir Ihnen unseren Blog-Beitrag “Videoaufzeichnung von benutzerdefinierten Szenarien mit CARLA“ zu lesen.