(WS16-01) Autonomes Modellfahrzeug/KI

Aus Verteilte Systeme - Wiki
Wechseln zu: Navigation, Suche

Inhaltsverzeichnis

Team

Name Zuständigkeit
Lucas Noack Programmgerüst, Geschwindigkeit/Lenkeinschlag, Systemschnittstelle, Parken2
Tu Xu Parken
Ales Koblizek Spur folgen
Thomas Czezor Startbox, Intersection, Overtake, End
Stefan Weiß Neuronale Netze (Optional), Logging(System)

Allgemeines

Aufgabenstellung

Meisterung der dynamischen Disziplinen I-III durch geschickte Steuerung des Modellfahrzeugs. Dabei war die Herangehensweise vollkommen uns überlassen, da zuvor noch kein Code dazu vorhanden war.

Unsere Herangehensweise

Zugunsten besserer Kontrollierbarkeit und Nachvollziehbarkeit haben wir uns zum erfolgreichen Bezwingen der dynmaischen Disziplinen zunächst für den symbolischen Ansatz entschieden. Mit den so erhebbaren Trainingsdaten wollen wir dann neuronale Netze einlernen, in der Hoffnung, dass diese sich zur Steuerung von Lenkeinschlag und Geschwindigkeit noch besser eignen.

Die ausgemachten Zustände entsprechen den kleinsten sinnvollen funktionalen Einheiten, die abhängig von der jeweiligen Disziplin richtig miteinander interagieren müssen. Die Interaktion sieht so aus, dass entweder infolge einer Zustandsprüfung ein Zustandsübergang erfolgt oder Zustände die Funktionalität eines anderes Zustände nutzen können, wobei hier vor allem "Spur folgen" hervorzuheben ist. Dieser kann frei auf normaler Strecke vorkommen, wo er den Übergang in "Kreuzung" und "Überholen" prüft, oder kontrolliert innerhalb von z.B. "Parken", wo langsam an der Spur orientiert gefahren und gleichzeitig eine Parklücke gefunden werden muss. Es macht keinen Sinn, diese Funktionalität situationsbedingt immer wieder neu zu implementieren. Das Privileg des Zustandsüberganges obliegt immer einzig dem obersten aktiven Zustand.

Zustandsdiagramm

States.png

Grobe Zeitplanung

  • Analyse/Design (Abschluss 30.11.2016)
  • Implementierung (erster Prototyp 21.12.2016, Abschluss 18.01.2017)
  • Test/Validierung (Abschluss 02.02.2017)

Festlegungen

  • Programmiersprache: C/C++
  • ...

Schnittstellen

Anmerkung: Die Kommunikation mit dem System und der Bildverarbeitung erfolgt ausschließlich über das SHM-Segment.

Abmachungen mit System

Wir bekommen

  • Geschwindigkeit in m/s
  • durch die echo-Distanzsensoren (vorne & hinten) ermittelte Entfernung in cm
  • Objektsensor hinten rechts
  • gefahrene Distanz (verworfen)
  • Fehrnbedienung an/aus
  • über Taster die Disziplin

Wir übergeben

  • Lenkeinschlag
  • Geschwindigkeit in m/s
  • Signal für das Blink- und Bremslicht (noch nicht implementiert)

Abmachungen mit Bildverarbeitung

Allgmein

  • Das Koordinatenkreuz liegt so, dass (0,0) in der Mitte vor dem Fahrzeug ist

Wir bekommen

1     bool obstacles;                     /** obstacles on our lane = TRUE else FASLE **/
2     struct Lane lane;                   /** the left and the right line of the street **/
3     struct Line stop_line;              /** if there is an intersection the stopline else NaN/NaN **/
4     struct Line start_line;             /** if there is the start line the line else NaN/NaN **/
5     struct Line vertical_obstacle;      /** if there is a car the line where it sarts or end else NaN/NaN  **/
6     int counter;			/** to check if the struct has changed since the last call 1. value should be set on 0 **/

Wir übergeben

  • nichts (für eine bessere Performance/Erkennung könnte man allerdings situationsbedingt bestimmte Inputdaten anfordern/die region of interest vorgeben)

Programmgerüst

Status: Fertig

Überblick der Programmaufrufe

Überischt

Aufgabe

  • Verwalten der einzelnen Methoden:
    • Aus der Startbox fahren
    • Parken
    • der Straße folgen
    • an einer Kreuzung halten
    • Überholen / Hindernissen ausweichen
  • Inizialisierung der Schnittstellen
  • Updaten der benötigten Daten
  • Loggen der eigenen Daten
  • Soweit möglich Fehler abfangen (z.B. zu kleine oder große Lenkeinschläge)
  • Bestimmen einer Zykluszeit
  • Abfragen der Taster

Einzelheiten

von außen

Die main Methode der KI bekommt mittgeteilt ob ein neuronales Netz zum lernen verwendet werden soll.

  • -n aktiviert das neuronale Netz

Es könnten leicht weitere Optionen eingebaut werden, da ein argparser implementiert ist. Es besteht jedoch momentan kein Bedarf. Man könnte allerdings noch einen Testmodus einführen oder eine maximal Geschwindigkeit angeben

Die Regelung der Disziplien läuft über die Taster am Auto

intern

Es werden alle benötigten Komponenten initialisiert und danach die Logik verwaltet. Die Unterprogramme haben als Rückgabe, was als nächstes aufgerufen werden muss. Mit jedem Durchlauf wird auf neue Bilddaten überprüft. Über einen counter in diesen Daten wird gecheckt ob die Daten neu sind. Falls zu lange keine neuen Daten kommen wird zuerst langsamer gefahren und dann angehalten. Der Zykluszeit liegt bei 10 ms. Könnte aber beliebig angepasst werden, da die Durchlaufzeit unter 1ms liegt. Die Zykluszeit wird erreicht durch bestimmen der Laufzeit, welche dann von der angestebten wartezeit abgezogen wird. Der entstehende Wert wird als sleeptime verwendet. Da bei einer negativen sleeptime unendlich geschlafen werden würde wird dies zuvor auf >0 geprüft, da einige Funktionen intern Wartezeiten haben.

Es wird jeden Zyklus abgefragt welcher Taster aktiviert ist und falls kein Taster aktiviert ist geht die KI in einen warte Zustand und wartet welcher Modus als nächstes gestartet wird. Beim auslösen des Tasters werden alle Variablen für den Start diesen Zustand gesetzt.

Daten

Die Daten von der Bildverarbeitung wird mit einer Funktion aus der Libcarolo ausgelesen. Die Geschwindigkeit und der Lenkeinschlag werden direkt in der Geschwindigkeits- und Lenkungsreglung an das System übergeben. Die Bildverarbeitung gibt uns:

  • linke und rechte Außenlinie
  • Haltelinie
  • Hindernisse
  • Hindernisse auf einer Kreuzung
  • und die Startlinie

Jeden Zyklus werden die Telemetriedaten und die Bildverarbeitungsdaten neu ausgelesen. Anhand eines counters kann überprüft werden ob dies neue Daten sind.

Geschwindigkeit/Lenkeinschlag

Status: Fertig

Aufgabe

Eine Methode, die einen Punkt und evtl eine maximal Geschwindigkeit übergeben bekommt und anhand dessen die Geschwindigkeit und den Lenkeinschlag regelt. Diese Funktion wird innerhalb der anderen Funktionen wie zum Beispiel dem Überholen aufgerufen. Es wird überprüft, ob fixe Werte genommen werden sollen oder die Werte, die von einem neuronalen Netzwerk bestimmt wurden.

Einzelheiten

Anhand eines übergebenen x Wertes zwischen -1 und 1 wird der Lenkeinschlag und die Geschwindigkeit ausgerechnet. Die Maximalgeschwindigkeit kann jedoch zuvor beschrenkt werden. Momentan wird nur in 3 verschiedene Geschwindigkeiten eingeteilt. Der Lenkeinschlag wird mit 500 + x*500 berechnet. Die Geschwindigkeit wird in m/s und der Lenkeinschlag mit einem Wert zwischen 0(Links) und 1000(Rechts) an die main weitereleitet, welche die Systemfunktion carolo_control mit den entsprechenden Werten aufruft. Diese Funktion wird jeweils von den Funktionen aufgerufen, die zuvor den aktuellen x Wert berechnet haben. Parken gibt direkt den Lenkeinschalg und die Geschwindigkeit (per max_speed) an, da dort immer die gewünschte Geschwindigkeit gefahren werden soll.

Da beim Überholen der Straße gefolgt werden muss, dabei aber auch noch die Spur gewechselt werden muss, kann noch ein Offset zum gegeben x hinzugefügt werden.

Optimierung

  • Fertigstellen des Neuronalen Netzes, dass die optimale Geschwindigkeit ermitteln soll.
  • Lenkung auch über ein Neuronales Netz steuern.

Startbox

Status: fertig

siehe Testergebnisse

Aufgabe

Dieser Zustand ist in den beiden dynamischen Disziplinen 2 + 3 wichtig, da hier aus der Startbox gestartet wird. Diese kann gemäß RegelwerkCaroloCup2016 folgendermaßen aussehen:

Startbox.png

Man sieht, dass hier die Außenlinie der rechten Fahrspur aus der Kurve die Spur der Startbox schneidet. Das ist die einzige Stelle innerhalb der Strecke, wo "schräg" verlaufende Linien überfahren werden müssen. Würde man bloß immer auf den Schnittpunkt der erkannten linken und rechten Linie zufahren ("FollowRoad"), so würde man möglicherweise noch vor der Startlinie rechts von der Spur abkommen. Um das zu verhindern, braucht es diesen Zustand. Er muss die Orientierung innerhalb der Startbox vorgeben und die Kontrolle an "FollowRoad" abgeben, sobald die Startlinie überfahren wurde.

Funktionsweise

An dieser Stelle weise ich auf unser globales Zustandsdiagramm und die prinzipielle Code-Struktur hin. Alles weitere ergibt sich logisch aus dem jeweiligen Kontext, den Kommentierungen des Quellcodes und den nachfolgenden Erklärungen zu den internen Zuständen im Detail.

SB_INIT

Bei Auswahl von Disziplin 2 + 3 ist "Startbox" der erste aktive Zustand und "SB_INIT" sein erster Unterzustand. "FollowRoad" wird mit den Argumenten MAX_SPEED und LOCK_STEERING (ursprünglich ORIENTATE_RIGHT) genutzt, um sicher und gerade zur Startlinie zu gelangen. Bei LOCK_STEERING muss sichergestellt sein, dass das Fahrzeug absolut gerade in der Startbox positioniert wird. Praktische Tests haben ergeben, dass ein natürlicher "Drift" (Fahrzeug fährt trotz einer geraden anfänglichen Ausrichtung ganz von alleine zunehmend eine Kurve) auf dem ersten Meter eigentlich nicht zu beobachten und deswegen vernachlässigbar ist ~. Jeden Zyklus schaut "Startbox" im "FromImage"-struct nach, ob die Startlinie erkannt wurde. Diese wird nur wenige Zyklen zu sehen sein, da die entsprechende region of interest der Bildverarbeitung Performance bedingt sehr klein sein muss und zum jetzigen Zeitpunkt nicht nachjustiert werden kann. Theoretisch reicht die einmalige Tiefeninformation (Abstand zur Startlinie) aber aus, siehe "SB_LINE_FOUND". Die Technik zur Extraktion der Tiefeninformation aus einem Bild zu einem Feature wird im Zustand "Intersection" näher beschrieben, da dort Gleiches für die Haltelinie benötigt wird. Bei gefundener Startlinie wird die Entfernung zu ihr ermittelt und "SB_LINE_FOUND" als Unterzustand gesetzt.

SB_LINE_FOUND

Die Nutzung von "FollowRoad" erfolgt hier unverändert. Die Entfernung zur Startlinie ist bekannt und wird stetig über Geschwindigkeit und verstrichene Zeit intern nachvollzogen. Wenn diese einen negativen Wert annimmt (Startlinie wird gerade überfahren), wird "Startbox" zurückgesetzt und der Rückgabewert von "FollowRoad" zurückgegeben.

Erkannte Probleme:

  • ...

Verworfenes:

  • Umschaltung in "FollowRoad" nach negativer Flanke der Startlinie im "FromImage"-struct (kann wegen kleiner ROI nicht lange beobachtet werden -> Umschaltung wäre viel zu früh)
  • mehrere Frames die negative Flanke beobachten für bessere Stabilität (wäre hier vielleicht noch gut machbar, aber pauschal alle bereitgestellten Informationen der Bildverarbeitung und des Systems zu hinterfragen macht vieles grundlos kompliziert)
  • startender Timer beim Überfahren der Startlinie, um das Ende zu bestimmen (siehe Zustand "End")

Prinzipielle Code-Struktur

Die hier vorgestellte Struktur der Hauptmethode gilt ebenso für die Zustände "Intersection" und "Overtake". Alle drei Zustände bestehen aus zwei bis fünf Unterzuständen, welche nicht mit "FollowRoad" zu verwechseln sind (die Nutzung von "FollowRoad" meint einfach nur, dass dessen Hauptmethode aufgerufen wird). Die jeweiligen Unterzustände sind in "shared.h" im "libcarolo"-Projekt definiert. Sie spalten die eigentlichen Zustände noch einmal auf, sodass jeden Zyklus aufgabenbedingt nur sehr wenige Zeilen abgearbeitet werden müssen.

 1 /**
 2  * Controls the activity in state "STARTBOX"
 3  *
 4  * @return the active state in the next cycle
 5  */
 6 States Startbox::run() {
 7 
 8 	struct Line startLine;
 9 	float meanY;
10 	double now;
11 
12 	switch (m_internState)
13 	{
14 		/* look for the start line */
15 		case startboxStates::SB_INIT:
16 			startLine = m_rFromImage.start_line;
17 			/* if there is no startline a NAN filled struct is expected, else a relative value between 0 (down) and 1 (up) is expected */
18 			meanY = (startLine.start.y + startLine.end.y) / 2;
19 			if (std::isnan(meanY)) {
20 				break;
21 			} else {
22 				m_lineDistance = m_rDisCalc.getDistanceFromImage(meanY);
23 				m_timestamp = m_rDisCalc.getTime();
24 				m_internState = startboxStates::SB_LINE_FOUND;
25 				m_rData.startboxState = m_internState;
26 			}
27 		/* wait until the car is driving over */
28 		case startboxStates::SB_LINE_FOUND:
29 			now = m_rDisCalc.getTime();
30 			m_lineDistance -= (now - m_timestamp) * m_rCaroloTelemetry.speed_ms;
31 			m_timestamp = now;
32 			if (m_lineDistance < 0) {
33 				reset();
34 				return m_rFollowRoad(10.0f, false);
35 			}
36 	}
37 
38 	/* "orientate only on the right line" */
39 	m_rFollowRoad(10.0f, true);
40 
41 	return States::STARTBOX;
42 }

Jeden Zyklus entscheidet abhängig vom aktuellen Zustand ein übergeordnetes switch, welcher case (Unterzustand) angesprungen wird. Die meisten Unterzustände sind so aufgebaut, wie hier in "SB_INIT" zu sehen. Die freistehenden Zeilen oben werden immer ausgeführt. Sie prüfen, ob ein Zustandswechsel eingeleitet werden soll. Wird kein Wechsel eingeleitet, wird der if-Block aktiv, der auch immer ein break am Ende enthält. Im Falle eines Zustandswechsels wird der else-Block aktiv, der die Initialisierung für den nächsten Unterzustand vornimmt. Hier existiert kein break, sodass im gleichen Zyklus direkt der nächste case (nächte aktive Unterzustand) abgearbeitet wird. Wenn der letzte Unterzustand (hier "SB_LINE_FOUND") fertig ist, wird immer ein Reset durchgeführt, bei dem sämtliche Variablen für den nächsten Aufruf zurückgesetzt werden. Danach wird immer der Rückgabewert des Folgeszustandes zurückgegeben, damit dieser die gleiche Sicht auf die zuletzt aktuellen Strukturen erhält. Ansonsten wird immer der eigene Zustand zurückgegeben.

Spur folgen

Status: Probably finished

Follow road

  • uses calc_steering to calculate the correct steering and speed_reg to regulate the speed and set the steering
  • handles switching between the states by checking for obstacles / intersections if enabled and returns the new state

Anforderungen (weil sehr zentrale Funktionalität)

  • Argument, ob eigener Zustand (normale Strecke) oder Nutzung innerhalb eines anderen Zustandes (hat Einfluss auf die Prüfung von Zustandsübergängen - darf immer nur im obersten Zustand bestimmt werden)
    • following the lane is not a separate state - its used during drive with and without obstacles and when crossing the intersection
  • Argument für die Geschwindigkeit, entweder maximal (normale Strecke) oder vorgegeben (Parkplatz suchen, an Haltelinie bremsen, am Ende bremsen)
    • when looking for a parking place, control logic sets a speed limit, which is then passed to "Geschwindigkeit/Lenkeinschlag"
  • Argument für die Orientierung an Spurlinien/Hindernissen, entweder frei (normale Strecke) oder gebunden (z.B. Fahren aus der Startbox: Orientierung rechts)
    • disable overtaking? follow_lane is not called during start_box state
  • TODOS:
    • disable checking for obstacles while in intersection state?
    • halve the maxspeed when both lines are missing? might be useful during debugging - to avoid damage to the car (I think this is already done somewhere else.)
    • is there a situation when we should not overtake when we see and obstacle?

Calculating the steering

Status: Almost finished - waiting for better image processing

Principle

  • we use multiple algorithms and do weighted average between their results (one based on current position of the car, one based on line direction)
  • function calc_steering(Lane)
    • returns steering coefficient (how much the car is off the lane): <-1, 1> (= -1 means that car is centered on the left line, 1 .. on the right line)
    •  !! when both lines are missing, the function returns NAN

Algorithm based on current position of the car

  •  !! initialization: when both lines are valid, the lane width is stored (distance between lines at y=0)
  • basically we are trying to center the car on the lane
  • for more details see image description
  • the speed of the car should not matter
  • there might be a problem if the loop was slow

Calc steering car pos.png

Algorithm based on direction of the lines

  • just return average of normalized x portions of the line direction vectors
  • when there is only one line the perspective would push the car away from the line ... so this algorithm can be used only when we have both lines
  • also since the regions of interests are short and at the bottom of the image, the curves probably won't be detected as on the image below, but rather initially as straight lines and later when the car is in the curve, it will simply look like the car is driving out of the lane, therefore this algorithm is probably useless for now (maybe just for damping / smoothing the results of another algorithm)
  • for details see image description

Calc steering line dir.png

What won't work

  • ad Algorithm based on current position of the car
    • (do not) measure distance between lines at y>0 (instead of y=0) - because the angle between lines changes so the distance between them at certain y is not always the same ... but it should be pretty consistent at y=0
  • ad Alg. based on direction of the line
    • if only one of the lines is visible, (do not) use it's angle - because the angle depends on the position of the car on the lane, not just on the real angle of the line

Suggestions

  • since BV is slower than expected (long interval between updated lines), we might try to damp the steering a little so the car does not turn so sharply
  • TODO - algorithm that works without initialization and with only one line (as backup or main)
    • maybe try to maintain the x_intersection value (see calc_steeting.cpp file) at some y (probably 0) for the visible line (but this requires initialization)
  • create a map of the track (see ORB-SLAM) and learn the correct steering for whole track
    • very complex
    • does the car have sufficient HW?
    • also the car can sometimes drive pretty fast, which might be a problem
    • i think obstacles can change positions - the track must be independent of overtaking

Parken

Status: bereit zum Testen

Infos

  • Autoslänge 400 mm, Breite 200 mm
  • JuniorCupRegel(2016)
    • 300mm breiter Parksstreifen
    • Parklücken 600 mm,700 mm,800 mm
    • Strafen

Strafen.png

Parklücken suchen

  • Input
  1. Die Information, ob etwas neben dem Auto steht, von seitlichen Sensor bekommen.
  2. gelaufen Distanz (tele->distance)
  • Output
  1. ob eine Parklücke finden, die länger als 600 mm.
  • Idee

Falls nichts daneben steht, wird die laufende Distanz gezählt. Jede Zeitframe wird seitlicher Sensor und die laufende Distanz abgefragt, bis Distanz länger als 600 mm oder was daneben kommt.

Einparken

  • Input
  1. Distanzsensor hintens (tele->us[0])
  • Output
  1. ob Einparken fertig
  2. Geschwindigkeit, vorwärts oder rückwarts (s_s->reg(steering, speed))
  3. Einschlag (s_s->reg(steering, speed))
  • Idee

Das Auto wird gesteurt je nach der Distanz von dem hinteren Hindernis.

Diagramm

218.png

Parken 2

status: fertig

Infos

  • Autoslänge 400 mm, Breite 200 mm
  • JuniorCupRegel(2016)
    • 300mm breiter Parksstreifen
    • Parklücken 600 mm,700 mm,800 mm
    • Strafen

Strafen.png

Input

  1. Ultraschallsensoren vorne und hinten
  2. seitlicher Sensor, welcher erkennt ob dort ein Hindernis ist
  3. Radsensoren um die gefahrene Distanz zu messen

Idee

Finden der Parklücke mit Hilfe des Seitsensors. Wenn wir an einem Gegenstand vorbei gefahren sind fangen wir nach diesem an die Distanz bis zu dem nächsten Gegenstand zu messen. Danach entscheiden ob diese Distanz groß genug zum Einparken ist bzw. beim Finden der Besten Lücke entscheiden ob dies die Richtige ist. Wenn eine angestrebte Parklücke gefunden wird, wird immer die selbe S-Kurve rückwärts gefahren. Falls die Lücke zu klein ist um komplett mit rückwärts fahren einzuparken wird noch einmal vorwärts gefahren. Wie weit hängt davon ab, wie weit der Fortschritt beim rückwärts fahren war.

Ablauf erste Lücke parken

Parken.png

Ablauf beste aus 3

Die einzige Änderung ist das suchen der Lücke. Es wird so lange gesucht bis eine Lücke zwischen 560 mm und 660 mm gefunden wird. Parken2.png

Optimierungsvorschläge

  • Einbindung von Kameradaten
  • Seitlicher Sensor sollte den Abstand erkennen können

Mit Hilfe dieser beiden neuen Informationsquellen sollte man besser einschätzen können ob man tief genug in der Parklücke steht bzw. schon vor dem Einparkvorgang abschätzen können, wie weit nach rechts man fahren muss.

Intersection

Status: fertig (aber keine Möglichkeit, Hindernisse zu erkennen - BV)

siehe Testergebnisse

Aufgabe

Kreuzung.png

Bei den dynamischen Disziplin 2 + 3 sind Kreuzungen teil der Strecke. Anders als bei Disziplin 2 dürfen diese bei Disziplin 3 nicht ignoriert werden. Gemäß Regelwerk muss 0 bis 15cm vor jeder Haltelinie gehalten werden. Mindestens 2 Sekunden muss das Fahrzeug dort still stehen. Jetzt gilt es, die Vorfahrtsregelung zu beachten, insbesondere für von rechts kommende "Hindernisse". Erst wenn die Kreuzung frei ist und kein von rechts kommendes Hindernis noch passieren möchte, darf die eigene Fahrt fortgesetzt werden. All diese Anforderungen machen diesen Zustand notwendig.

Funktionsweise

An dieser Stelle weise ich auf unser globales Zustandsdiagramm und die prinzipielle Code-Struktur hin. Alles weitere ergibt sich logisch aus dem jeweiligen Kontext, den Kommentierungen des Quellcodes und den nachfolgenden Erklärungen zu den internen Zuständen im Detail.

IS_INIT

"Intersection" wird aktiv, nachdem im Zustand "FollowRoad" eine Haltelinie - keine Startlinie - erkannt wurde. Anders als in "Startbox" muss hier die Linie also nicht erst noch gesucht werden. Innerhalb von "IS_INIT" wird die Entfernung zur dieser ermittelt (siehe Extraktion der Tiefeninformation aus einem Bild) und direkt danach "IS_CHECK_FOR_STOPPING" als Unterzustand gesetzt.

IS_CHECK_FOR_STOPPING

Jetzt wird "blind" weitergefahren, die Entfernung zur Haltelinie wird hier wie in "Startbox" über Geschwindigkeit und verstrichene Zeit intern nachvollzogen. Und auch "FollowRoad" wird hier wieder genutzt, um sicher in der Spur zu bleiben, diesmal mit den Argumenten MAX_SPEED und FREE_ORIENTATION. Hintergrund für das erste Argument ist, dass die ermittelte Entfernung relativ groß im Vergleich zur momentanen Geschwindigkeit sein kann und man nicht unbedingt zur Haltelinie schleichen möchte. Hier wird zusätzlich also noch geprüft, wie stark die mittlere Bremsverzögerung (negative Beschleunigung) sein müsste, um knapp 8cm vor der Haltelinie stehen zu bleiben:

FormelBremsverzoegerung.png

Und bis diese nicht einen kritischen Wert (ist noch empirisch zu bestimmen, etwa 80% Bremsleistung) überschreitet, ist Vollgas freigegeben. Erst bei Überschreiten des kritischen Wertes wird die errechnete mittlere Bremsverzögerung gesetzt und unter anderem die aktuelle Geschwindigkeit als Referenzwert für den Folgezustand "IS_STOPPING" festgehalten.

IS_STOPPING

Hier gilt bis zum Stillstand des Fahrzeugs folgender Programmausschnitt:

1 				now = m_rDisCalc.getTime();
2 				speed = m_refSpeed - (now - m_timestamp) * m_deceleration;
3 				m_rFollowRoad(std::max(speed, 0.0f), false);

Dabei wird die maximale Geschwindigkeit für "FollowRoad" proportional zur Zeit verringert (v=a*t). Beim dargestellten Ansatz kann es zu keinen Folgefehlern kommen. Ist die Geschwindigkeit klein genug, wird Unterzustand "IS_WAITING" gesetzt.

IS_WAITING

Jetzt läuft die Wartezeit von 2 Sekunden. Die wird nicht mit sleep(2) umgesetzt, sondern mit "aktivem Warten", um Main nicht über einen so langen Zeitraum die Kontrolle zu nehmen. Prinzipiell wäre es jetzt schon möglich, nach Hindernissen Ausschau zu, die sich auf der Kreuzung befinden oder die diese passieren wollen. Darauf wurde verzichtet, da es für das Prüfen auf Hindernisse nur einen Zyklus braucht. Es gibt also kein Beobachten des "FromImage"-structs, wie ursprünglich angedacht. Die Bildverarbeitung läuft im Hintergrund aber weiter und so ist dank der langen Wartezeit sichergestellt, dass kein Hindernis unerkannt bleiben kann. Nach Ablauf der Wartezeit folgt Unterzustand "IS_TRY_PASSING".

IS_TRY_PASSING

Bei erkanntem Hindernis wird solange gewartet, bis dieses verschwunden ist. Momentan finden die Distanzsensoren zur Hinderniserkennung keine Anwendung, da der Öffnungswinkel des echo-Kegels nicht abschließend in der Praxis getestet wurde. Sein alleiniger Einsatz macht nur Sinn, wenn er mindestens den gleichen Horizont wie die Bildverarbeitung abdecken kann. Eventuell müssen beide Sensorinformationen auch kombiniert ausgewertet werden. Momentan wird seitens der Bildverarbeitung für eine Kreuzung genau eine vertikale Hindernislinie gesetzt. Sind deren Werte nicht NAN, wird eine freie Kreuzung angenommen, resettet und in den Folgezustand "FollowRoad" umgeschalten. Zurzeit werden noch andere Techniken zur Hinderniserkennung diskutiert, wodurch sich auch die Auswertung hier ändern würde.

Erkannte Probleme:

  • ein Hindernis steht direkt hinter der Kreuzung, was die Bildverarbeitung fälschlicherweise als Hindernis auf der Kreuzung interpretieren könnte (man müsste sich über deren "Rat" hinwegsetzen, wenn der Distanzsensor nicht anschlägt; dieser schlägt aber vielleicht auch nicht an, wenn erkanntes Hindernis ganz rechts im Bild... - besser: Mindestgröße in der Akkumulatormatrix der BV, zu weit entfernte Bildteile von der Hough-Transformation ausschließen)
  • wenn mehrere Hindernisse versetzt auf der Kreuzung stehen würden, wäre durch den fehlenden Kontrast zum Hintergrund vielleicht keine vertikale Hindernislinie erkennbar (Spezialfall, statt Linienerkennung weiße "Kästen" auswerten - zurzeit in Diskussion)
  • nach dem Halten könnte die Haltelinie noch unten im Bild zu sehen sein, wodurch der Folgezustand "FollowRoad" direkt wieder in "Intersection" schalten würde (bisher noch nicht abgefangen aber auch nicht schwer, relevant ist vor allem der Neigungswinkel der Kamera, Testläufe: nicht kritisch)

Verworfenes:

  • Hindernisse und deren Bewegungsrichtung über mehrere Frames beobachten (Hindernis, das nur da steht und selbst nicht passieren will, gibt es nicht)
  • Umgang mit Hindernissen, die sich von links der Kreuzung nähern (normale Hindernislinie, Behandlung wie von rechts kommende Hindernisse)
  • wenn Hindernis Kreuzung passiert, "switched" unter Umständen dessen Außenkante (weiterhin normale Hindernislinie, erkannt wenn nicht NANs)
  • zwei vertikale Hindernislinien nötig damit Hindernis eindeutig lokalisierbar (eine Linie reicht aus, ob linke oder rechte Kante ist egal, erkannt wenn nicht NANs)
  • durch starkes Bremsen geht das Fahrzeug vorne runter, sodass die Haltelinie bei der Entfernungsermittlung weiter weg angenommen wird als sie eigentlich ist (Linie wird nicht mehr getrackt, wäre umgekehrt nur noch bei starker Beschleunigung denkbar, viel zu früh für derartige Gedanken)

Extraktion der Tiefeninformation aus einem Bild

Schon relativ früh kam das Problem auf, wie denn die reale Entfernung zwischen Fahrzeug und einem Feature, welches die Bildverarbeitung findet - vorrangig Start- und Haltelinie -, gemessen werden könnte. Eine Lösung wäre eine perspektivische Abbildung der Bildverarbeitung gewesen ("Sicht von oben"), wodurch man die reale Entfernung einfach aus der Pixelweite hätte ablesen können, weil proportional. Doch diese Abbildung wäre unheimlich teuer gewesen und stand von Beginn an nicht zur Diskussion. Also musste eine andere Lösung gefunden werden. Lange Zeit wurde versucht, eine einfache mathematische Abbildung zu finden, die gemäß der nachfolgenden GeoGebra-Darstellung ausgehend von Augpunkt der Kamera (C) das Kamerabild (AE) auf die reale Strecke (AB) abbildet. Wie zu sehen ist, wird z.B. der Bildpunkt F auf Punkt G abgebildet, wodurch sich eine Entfernung von 2.91f zu A ergibt. (Anmerkung: das Modellfahrzeug befindet sich links auf der negativen X-Achse und die Kamera schaut nach rechts unten auf den Boden; seitliche Betrachtung der Szene)

Getdistance.png

Eine einfache, direkte Abbildung konnte nicht gefunden werden, sodass eine algebraische Lösung hergeleitet werden musste. Die Funktionalität zur Entfernungsbestimmung befindet sich in der "DisCalc"-Klasse. Jedes mal, wenn sich der Neigungswinkel der Kamera mechanisch verändert, müssen hier die Punkte B und C und die auf der Darstellung nicht sichtbare Distanz BUMPER_A_DISTANCE (Distanz zwischen Punkt A und der Stoßstange des Fahrzeugs) aufs Neue vorgegeben werden. Alles orientiert sich an Punkt A, der als Nullpunkt des Koordinatensystems angenommen wird. Bei der "Kalibrierung" geht man von folgendermaßen vor:

  1. Fahrzeug auf freie Ebene stellen und Kamerabild in Echtzeit ausgeben lassen
  2. Punkt direkt vor dem Fahrzeug finden, welcher nur noch im untersten mittigen Pixel des Bildes zu sehen ist (=A, Fahrzeug nicht mehr bewegen)
  3. Punkt vor dem Fahrzeug finden, welcher gerade noch im obersten mittigen Pixel des Bildes zu sehen ist (relativ zu A messen, =B)
  4. Augpunkt der Kamera relative zu A messen (=C)
  5. Distanz zwischen A und der Stoßstange des Fahrzeugs messen (=BUMPER_A_DISTANCE)

Innerhalb des Konstruktors wird anhand der eingestellten Werte der Vektor AE errechnet. Danach wird abhängig vom Y-Wert entdeckter Features ein Faktor [0.0, 1.0] (Pixel ganz unten im Bild -> 0.0, Pixel ganz oben im Bild -> 1.0, ...) bestimmt und mit diesem ein Punkt F auf AE. Abschließend muss nur noch der X-Wert des Schnittpunktes der Geraden CF und y=0 ausgerechnet werden.

Wichtiger Hinweis

Wenn die Kamera nicht voll auf den Boden ausgerichtet sein soll (B wäre dann sehr weit entfernt oder schon in der Luft), muss ein Teilbild als neues Bild bestimmt werden, das immer den Boden zeigt.

Overtake

Status: grundsätzlich fertig (es fehlen noch Tests für Kurven und Abbruch)

siehe Testergebnisse

Aufgabe

Overtake.png

In der dynamischen Disziplin 3 können vor dem Modellfahrzeug auf der rechten Spur stehende und sogar fahrende Hindernisse auftauchen, die zu überholen sind. Sie können auf gerader Strecke und in Kurven vorkommen, nicht aber auf Kreuzungen. Es können mehrere zu überholende Hindernisse unterschiedlicher Größe aufeinander folgen, wobei dann das Einscheren abgebrochen und der Überholvorgang fortgesetzt werden muss. Der Überholweg kann immer als frei angenommen werden. Beim Ausscheren darf das zu überholende Hindernis nicht berührt werden und spätestens 2m nach Passieren des letzten Hindernisses muss der Vorgang des Einscherens abgeschlossen sein. Ebenso sind die Blinker des Modellfahrzeugs während der Spurwechsel zu benutzen. All diese Anforderungen machen diesen Zustand notwendig.

Funktionsweise

An dieser Stelle weise ich auf unser globales Zustandsdiagramm und die prinzipielle Code-Struktur hin. Alles weitere ergibt sich logisch aus dem jeweiligen Kontext, den Kommentierungen des Quellcodes und den nachfolgenden Erklärungen zu den internen Zuständen im Detail.

Vorher braucht es aber noch eine kurze Einleitung - das Konzept betreffend -, damit gefundene Lösungswege überhaupt erst ersichtlich werden und nachvollzogen werden können. Das Überholen ist sehr schwierig, wenn man sich alle möglichen Variationen und Umstände vor Augen hält: auf gerader Strecke/in Kurve, unterschiedlich große Hindernisse, Hindernisse stehend/fahrend, Abbruch des Einscherens, zwei beliebige Spurlinien können gleichzeitig auf 1m fehlen. Deswegen wurde sich für "blinde" Spurwechsel entschieden, bei denen insbesondere die Informationen der Bildverarbeitung nicht berücksichtigt werden. Für das Fahren auf der Gegenspur wird "FollowRoad" genutzt, was zwischen den Spurwechseln für Sicherheit sorgt, sollte eine abgefahrene Bahnkurve einmal nicht ganz so perfekt sein. Ohne blinde Spurwechsel müsste die Umgebung richtig nachvollzogen werden. Dazu gehört das Schließen von Geschwindigkeiten und Lenkeinschlägen auf Bilder im nächsten Frame, damit die ROIs der Bildverarbeitung angepasst werden können, wodurch sich erst z.B. das Überfahren der Spurlinie in der Mitte erkennen lässt. Zu Bedenken dabei ist außerdem die schräge Kameraperspektive, die Alles nochmal viel schwieriger macht, und die Tatsache, dass bei fehlenden Spurlinien diese Rechnerei erst gar nicht weiterhilft. Das einzige, was definitiv immer vorhanden ist, ist ein Hindernis, wenn auch von potentiell unterschiedlicher Größe und mit einer eigenen Geschwindigkeit. Bei näherem darüber Nachdenken fällt aber auch hier auf, dass das Orientieren an einer Hinderniskante mindestens ähnlich schwierig ist. Hinzukommt, dass in einer Kurve das Hindernis schräg von Hinten zu sehen ist und dabei alle Außenseiten die gleiche weiße Farbe aufweisen, sodass eine vormalig erkannte hintere Außenkante plötzlich einer vorderen Außenkante weicht (Linkskurve, anders Rechtskurve). Ebenso sind die Hinderniskanten je nach Kurve unterschiedlich lange zu beobachten. Um all dem aus dem Weg zu gehen, wird stattdessen für die Spurwechsel eine s-Kurve (tanh-Funktion) abgefahren. Bei Kurven wird einfach ein initialer Lenkeinschlag hinzuaddiert. Auch dieses System ist nicht perfekt, insbesondere wenn man das Zusammenspiel mit "FollowRoad" bei Situationen wie im Einführungsbild bedenkt - gleichzeitig muss gesagt werden, dass eine solche Anordnung von Hindernissen und weggelassenen Spurlinien auf diversen YouTube-Videos nicht gefunden wurde. Weitere kritische Punkte sind der initiale Lenkeinschlag beim Überholen (Überholvorgang startet z.B. auf einer Geraden kurz vor dem Beginn einer scharfen Kurve) und auch der Abbruch eines Überholmanövers in einer Kurve. Weil "Overtake" lange Zeit nicht in der Praxis getestet werden konnte, wurde sich intensiv um eine offline-Validierung bemüht wurde, um die Korrektheit diverser Methoden und Algorithmen sicherzustellen.

OT_INIT

"Overtake" wird aktiv, wenn in Zustand "FollowRoad" ein Hindernis auf der rechten Spur erkannt wurde. Damit nicht "FollowRoad" bestimmt, wann der Überholvorgang, also das Ausscheren, eingeleitet wird, existiert hier die Möglichkeit, das Hindernis zunächst mit einer IGNORE_OBSTACLE_DISTANCE zu ignorieren, indem "FollowRoad" bis zum Unterschreiten dieses Wertes kontrolliert genutzt wird, um sich dem Hindernis weiter zu nähern. Denkbar wäre auch, das Ausscheren in einer Linkskurve früher zu initiieren als in Rechtskurven, damit spätere Lenkeinschläge (in Linkskurve nochmal nach links ziehen) nicht ganz so stark ausfallen, siehe Validierungskurven unten. Bei Unterschreiten von IGNORE_OBSTACLE_DISTANCE wird eine tanh-Funktion definiert, die im folgenden Unterzustand "OT_CHANGE_RL" von links nach rechts abzufahren ist (Ansicht von oben, Quelle: https://www.desmos.com/calculator):

Tanh-function.png

Der Faktor 0.21 rührt daher, dass die gesamte Fahrbahn 820mm (Innenkanten der beiden äußeren Spurlinien) breit ist, wovon der Mittelstreifen 20mm ausmacht. Jede Fahrspur ist demnach 400mm breit, sodass deren Mittellinien 200mm+200mm+20mm=420mm auseinanderliegen - der Wertebereich der tanh-Funktion ist also immer ]-0.21, 0.21[. Jetzt wird angenommen, dass das Fahrzeug richtig in der rechten Fahrspur positioniert und ausgerichtet ist. Bestimmt werden muss zuerst ein "swerveFactor", hier oben der Faktor 6 (blau) bzw. 12 (rot) vor dem x im Exponenten der e-Funktion, welcher die Härte der s-Kurve angibt und allein abhängig von der momentanen Geschwindigkeit ist. Je schneller das Fahrzeug ist, desto weniger soll während dem Überholen stark gelenkt werden müssen. Momentan ist die Zuordnung linear (das heißt Speed 0 -> Faktor 12, MAX_SPEED -> Faktor 6, Rest dazwischen kontinuierlich), was noch ausbaufähig ist. Jetzt muss die Startposition (x-Wert) auf der Funktion bestimmt werden, wozu der Wert OBSTACLE_CURVE_DISTANCE existiert. Mit diesem wird die x-Position des Hindernisses errechnet, es gilt tanh(x)=f(x)=OBSTACLE_CURVE_DISTANCE nach x aufzulösen. Hintergrund ist ein maximal breit angenommenes Hindernis ((x_errechnet, 0) ist seine linke hintere Außenkante), an welchem weit genug ohne zu kollidieren vorbeigefahren werden soll. Es wird erst das Hindernis im Koordinatenraum berechnet und davon abhängig die eigene Position, welche auf der Kurve angenommen wird. Die Berechnung hat die kleine Schwäche, dass nur die vertikale Distanz angenommen wird und keine radiale. Eine radiale Distanz allerdings würde die Berechnung noch einmal viel komplizierter machen (Verschieben der Kreisfunktion bis genau ein Schnittpunkt ...), zumal graphisch ein maximaler Fehler von etwa 1cm gezeigt werden konnte und die Linienbreite noch nicht einmal beachtet wurde (Abstand +1cm). Normalerweise wird für das Hindernis ein x-Wert zwischen 0.15 und 0.4 angenommen werden, die eigene x-Position wäre demnach x_ermittelt-DistanzZumHindernis. Außerdem wird hier noch die Steigung in x=0 errechnet und der initiale Lenkeinschlag festgehalten, bevor an Unterzustand "OT_CHANGE_RL" übergeben wird.

OT_CHANGE_RL

Jetzt kann mit dem Ausscheren begonnen werden, wobei zu diesem Zeitpunkt noch gar nicht klar ist, wie eine solche Kurve überhaupt abgefahren werden kann, also welcher Lenkeinschlag und welche Geschwindigkeit wann und wo vorzugeben sind. Dazu ein paar Annahmen, welche so auch durch die Validierung bestätigt werden konnten (siehe Validierung):

  • das Fahrzeug befindet sich zu jedem Zeitpunkt immer auf der Kurve
  • die vorzugebende Geschwindigkeit ist irrelevant und wird immer auf MAX_SPEED gesetzt (wird sowieso abgeschnitten, wenn mit dem Lenkeinschlag nicht vereinbar)
  • entscheidend ist allein der Lenkeinschlag, welcher aus der aktuellen Position resultiert
  • der Lenkeinschlag ist nur für x=0 Null, für alle anderen x-Werte entspricht der kleinste Winkel zwischen zwei Steigungsvektoren einem relativen Lenkeinschlag (Winkel werden zwischen Steigungsvektor der Ist-Position einmal gegen Steigung=0 und einmal gegen Steigungsvektor in x=0 bestimmt), dabei sind alle Lenkeinschläge vor x=0 negativ und danach positiv

Während dem Spurwechsel muss "SpeedSteering" direkt aufgerufen werden, wofür es ein Argument für die Geschwindigkeit (MAX_SPEED), eines für den Lenkeinschlag (Wert zwischen -1 und +1) und eines für den initialen Lenkeinschlag braucht. Der Lenkeinschlag ist kontinuierlich zwischen MIN und MAX, weshalb die relativen Lenkeinschläge einfach mit einem noch empirisch zu bestimmenden MAGIC_FACTOR multipliziert werden können (nur die relativen Lenkeinschläge sind bekannt - nicht die absoluten!). Beim initialen Lenkeinschlag zeigt sich eine Besonderheit, siehe Validierung. Fehlt nur noch, wie die eigene Position auf der tanh-Funktionskurve bestimmt und nachvollzogen werden kann. Jeden Zyklus wird eine Kreisfunktion mit der gefahrenen Distanz (grün) auf die vormals aktuelle Position gelegt. Der Schnittpunkt beider Funktionen entspricht dabei dem x-Wert der neuen Position:

PosOnTanh.png

Leider wird schnell ersichtlich, dass durch Äquivalenzumformung keine Lösung gefunden werden kann. Allerdings kann durch Subtrahieren der Formeln voneinander das Problem zu einem Nullstellensuchproblem (orange) umgewandelt werden, sodass Verfahren zur Nullstellensuche wie Regula Falsi zur Anwendung kommen können. Auch sieht man oben, dass sich die Nullstellen bei Steigungen nahe Null und sehr kurzen gefahrenen Distanzen sehr schnell sehr dicht an der äußeren Grenze b befinden. Diese musste ohnehin minimal nach links verschoben werden, um NAN-Ergebnissen vorzubeugen. Aus diesem Grund müsste in jedem Zyklus gewartet werden, bis knapp 1mm gefahren ist. Weil "Main" aber eine Zykluszeit von etwa 10ms aufweist, wurde darauf verzichtet. Abbruchkriterium neben einem sehr kleinen Epsilon ist die Prüfung, ob sich die Grenzen a und b überhaupt verändern, ohne welche häufig Endlosschleifen das Resultat waren. Es wird solange gefahren, bis x_Ist > -x_Start, erst dann wird "OT_PASS_OBSTACLE" gesetzt.

OT_PASS_OBSTACLE

Die Bahnkurve ist abgefahren und das Fahrzeug befindet sich nun richtig ausgerichtet auf der linken Spur, weshalb jetzt "FollowRoad" kontrolliert genutzt wird, um am Hindernis vorbeizuziehen. Gleichzeitig wird wie im Vorzustand der Objektsensor hinten rechts am Fahrzeug auf eine negative Flanke beobachtet. Ist diese gegeben, kann das Einscheren eingeleitet werden, indem an Unterzustand "OT_CHANGE_LR" übergeben wird. Vorher findet aber noch die gleiche Initialisierung wie in "OT_INIT" statt, bei der lediglich IGNORE_OBSTACLE_DISTANCE als die Distanz zum Hindernis angenommen wird, weil ein Hindernis eigentlich gar nicht vorhanden ist.

OT_CHANGE_LR

Dieser Unterzustand entspricht weitestgehend "OT_CHANGE_RL" mit zwei kleinen Abweichungen. Der finale Lenkeinschlag wird mit -1 multipliziert (es wird nicht die tanh-Kurve mit -1 umgedreht...) und es wird mit dem Distanzsensor vorne geprüft, ob während dem Einscheren ein Hindernis vor der Fahrzeug auftaucht, in welchem Fall das Einscheren abgebrochen und Unterzustand "OT_CHANGE_LR_ABORT" gesetzt wird. Ohne ein weiteres Hindernis wird nach vollendeter s-Kurve "Overtake" resettet und die Kontrolle an "FollowRoad" abgegeben.

OT_CHANGE_LR_ABORT

Wenn dieser Unterzustand aktiv wird, befindet sich das Fahrzeug mitten im Einscheren und hat situationsabhängig (Gerade, Linkskurve, Rechtskurve) eine unbekannte Position und Ausrichtung auf der tanh-Funktion. Sicher können nur Tendenzen angegeben werden: in einer Linkskurve wird das Hindernis wesentlich früher entdeckt werden als auf einer Geraden oder gar in einer Rechtskurve. Das Fahrzeug muss in jedem Fall aber wieder sicher auf die linke Spur gelangen und das ebenfalls "blind". Im Falle einer Rechtskurve kann die Abbruch-Bahnkurve abhängig vom vormaligen "swerveFactor" mehr als 1.5m betragen, auf welchen sich die Fahrbahnstrecke in ihrem bisherigen Verlauf besser nicht ändern sollte (z.B. Start einer neuen Kurve, Schlenker-Kurve, ...), weil einfach nicht erkennbar. Ausgehend von einer Linkskurve, wo zum Zeitpunkt des Abbruchs nur sehr wenig der s-Kurve abgefahren wurde, macht es Sinn, den bisherigen Weg einfach gespiegelt zurückzufahren. Graphisch zeigt sich aber ein Knickpunkt, der unmöglich befahrbar ist.

IdeaAbortLR.png

Die Situation ist sogar noch sehr viel schlimmer. Beide Kurven weisen die gleiche Richtung beim Lenkeinschlag auf: rechts. Der einzige Unterschied besteht in der initialen Ausrichtung des Modellfahrzeugs, welche praktisch nur sehr schwer nachvollzogen werden kann. Deswegen wird zum Überbrücken stattdessen zunächst ein tangential anschließender Kreisbogen (Kreisfunktion) abgefahren, dessen konstante Form mit MIN_DRIVABLE_RADIUS hartkodiert vorzugeben ist:

AbortLR CircularArc.png

An dieser Stelle muss gesagt werden, dass nicht beliebige Funktionsverläufe abgefahren werden können, da es verdammt schwierig ist, den Lenkeinschlag an den jeweiligen Positionen zu bestimmen (die s-Kurve ist da eine Ausnahme). Hier lässt sich nicht die gleiche Technik wie bei der s-Kurve anwenden, da mit dieser unten in der Mitte des Kreisbogens der Lenkeinschlag 0 wäre und das soll nicht sein - es muss dauerhaft stark nach links eingeschlagen werden. Der Kreisbogen ist deshalb vorteilhaft, weil hier ein konstanter Lenkeinschlag angenommen werden kann. Außerdem verfügt er über alle Steigungen und sein "Umfang" (die abzufahrende Strecke) lässt sich leicht berechnen. Aber auch diese Variante hat den Fehler, dass die "Trägheit" der Hinterachse des Fahrzeugs nicht mit einbezogen wurde. Hier spielt auch die Dimension der Szene eine Rolle, also wie groß das Fahrzeug relativ zu den Funktionsverläufen ist, was bei allen Berechnungen von "Overtake" nicht berücksichtigt wurde, weil einfach viel zu schwer (siehe auch Validierung). Nach dem Kreisbogen folgt das letzte Stück vom Rechts-Links-Wechsel. Um hier an der richtigen Position fortzufahren, kann dank der symmetrischen tanh-Funktion für x_ist einfach -x_abort angenommen werden. Das Überholen auf einer Geraden und in einer Rechtskurve folgt analog dazu. Nur eben, dass hier die korrigierenden Bahnkurven vergleichsweise sehr viel länger sind und Ungenauigkeiten sehr viel weniger verziehen werden. Unbekannt ist aufgrund nicht soweit fortgeschrittener praktischer Tests, wie sich der Kreisbogen mit initialen Lenkeinschlägen in Kurven verträgt - der Abbruch wurde auch nicht validiert, die Validierung lässt jedoch Nachbesserungsbedarf vermuten. Es ist sogar möglich, dass der Kreisbogen einer ganz anderen Technik weichen muss, wenn auf diese Weise die invertierte Ausrichtung des Fahrzeugs nicht sicher gestellt werden kann, weil z.B. das Auto viel zu lang für den Kreisbogen ist... Der interne Folgezustand ist "OT_PASS_OBSTACLE", wobei dieser im gleichen Zyklus bedingt durch die case-Reihenfolge nicht angesprungen werden kann.

Erkannte Probleme:

  • initiale Ausrichtung und initialer Lenkeinschlag müssen bei "FollowRoad" stimmen, insbesondere in Rechtskurven (keine Möglichkeit der Nachjustierung)
  • Prinzip hinter dem Abbruch des Einscherens (nur in der Praxis sinnvoll testbar wegen Ausrichtung des Fahrzeugs)
  • Orientierung von "FollowRoad" nach dem Überholen bei fehlenden Spurlinien (vielleicht Spezialfall, keine Orientierung innerhalb der Spurwechsel möglich)
  • in scharfen Linkskurven könnte zum Ausscheren ein stärkerer Lenkeinschlag als möglich nötig werden, Kollision unvermeidlich (IGNORE_OBSTACLE_DISTANCE erhöhen, MAX_SWERVE_HARDNESS senken)
  • entgegen kommendes Hindernis während dem Überholen (auch erstmal ein Spezialfall)

Verworfenes:

  • beim Ausscheren solange nach links lenken, bis das Objekt vom Distanzsensor aus verschwindet und dann ab Überfahren der Mittellinie gespiegelt zurück (weil Stärke und Dauer des Einlenkens schwierig zu bestimmen, Sensor vermutlich zu breit, man verpasst den Zeitpunkt zum Gegenlenken - 2x pro Spurwechsel, ungenaue Bestimmung der Mittellinie in Kurven..)
  • Orientierung an stehenden oder fahrenden Hindernissen (schwierig deren Ausrichtung richtig zu interpretieren, Kombination mit Bildverarbeitung)
  • weitere Hindernisse mit der Bildverarbeitung erkennen, kein Abbruch des Einscherens mehr notwendig (Problem ist ein horizontal zu schmales Sichtfeld - speziell in Rechtskurven, Auswertung mittels Distanzsensoren viel einfacher und so auch mehrfach gesehen auf YouTube)

Validierung

Anmerkung: der gesamte Code hierzu befindet sich in "overtake_valid.cpp" im KI-Projekt

Ziel dieses Punktes war es, das Fahren einer s-Kurve und das auch mit einem initialen Lenkeinschlag für das Überholen in einer Kurve zu validieren - ohne das Fahrzeug. Dabei haben sich das grundlegende Prinzip und die Funktionalität diverser Methoden und Algorithmen als richtig erwiesen, wobei gesagt sein muss, dass zur Überprüfung keine physikalisch korrekte Fahrsimulation geschrieben werden konnte. Vielmehr wurden zu Beginn ausgehend von einer festen tanh-Kurve die relativen Lenkeinschläge ermittelt. Nur anhand dieser konnte die s-Kurve wieder rekonstruiert werden. Danach folgte die Bestimmung eines initialen Lenkeinschlags auf einer Kurve und deren Überlagerung mit den Lenkeinschlägen der s-Kurve. Dabei hat sich erstaunlicherweise herausgestellt, dass innerhalb einer Kurve für den Spurwechsel immer der Lenkeinschlag der äußeren Spur zu addieren ist und nicht pauschal der aktuelle.

Relative Lenkeinschläge

Die Startposition und der "swerveFactor" wurden zunächst fest vorgegeben. Jetzt wurde angenommen, dass jeden Zyklus genau ein 1mm weit gefahren wird. Nachfolgend sind die ermittelten relativen Lenkeinschläge (Y-Achse) abhängig von der zurückgelegten Wegstrecke (X-Achse) zu sehen:

OvertakeSteering.png

Früh war klar, dass der Plot im Groben einer Sinus-Kurve entsprechen würde. Die spitzen Extrema sind im ersten Moment verblüffend aber eigentlich leicht zu erklären. Klar ist: auf der tanh-Kurve muss ganz Vorne, in der Mitte und ganz Hinten der Lenkeinschlag 0 sein. Jeweils dazwischen gibt es genau eine Stelle, wo der Lenkeinschlag maximal ist (Extrema im Plot). Die beiden Stellen teilen jeweils ihre Rechts-/Linkskurve der tanh-Funktion in zwei Hundekurven auf. Hundekurven sind Kurven, die leicht anfangen und zunehmend immer stärker werden. Bis zum ersten Extrema wird in eine linke Hundekurve hinein gefahren und dann von einem auf den anderen Moment sofort wieder aus einer linken heraus. Jetzt ist man auf der Geraden im Nullpunkt angenommen und der gleiche Vorgang wiederholt sich gespiegelt in einer Rechtskurve. Einen ähnlichen Plot erhält man übrigens auch, wenn man die tanh-Funktion zweimal ableitet, wobei der Ansatz schon falsch ist. Die Lenkeinschläge müssen sich nämlich auch einem rotierten Funktionsverlauf entnehmen lassen, was mit den Winkeln zwischen Steigungsgeraden funktioniert, mit Ableitungen aber nicht.

Gelegentliche Peaks genau in der Mitte des Plots zeigten Fehler, welche darauf zurückzuführen waren, dass in der Methode "calcAngle()" acos minimal größere Werte als 1 erhielt und somit NAN zurückgab. min wiederum wählte von von einem großen Wert und einem NAN-Wert den großen Wert. Seitdem befindet sich hier hinter jedem acos-Aufruf eine isnan-Abfrage.

Rekonstruktion einer einfachen tanh-Kurve

Nur anhand der vorherig ermittelten Lenkeinschläge wurde jetzt versucht, eine einfache s-Kurve zu rekonstruieren. Die Fahrsimulation sieht so aus, dass ausgehend von einer alten Position mithilfe eines Vektors der Länge 1mm eine neue Position ermittelt wird. Dabei wird der Vektor jedes mal um einen Winkel gedreht, welcher dem jeweiligen Lenkeinschlags entspricht - multipliziert mit einem MAGIC_FACTOR. Wählt man falsch skalierte relative Lenkeinschläge, ergeben sich Fahrwege der folgenden Form (Faktor 0.5f):

WrongMagicFactor.png

Solche Fahrwege sind auf einem so kleinen Raum natürlich nicht zu fahren, schon gar nicht wenn man die Länge des Fahrzeugs bedenkt. Würde die s-Kurve allerdings Maße von 1km aufweisen, sieht die Situation bei einem leicht zu starken relativen Lenkeinschlag anders aus. Empirisch wurde mit 0.0105f ein sehr guter Faktor für die Simulation gefunden:

StraightMagic.png

Gleichzeitig ist hier zu sehen, wie auch nachfolgend der Fahrweg relativ zur Fahrbahn interpretiert werden kann. Zunächst wurden die jeweiligen Fahrbahnteile mit Gimp gezeichnet. Über diese wurden dann die nicht verzerrten Diagramm-Ausgaben aus LibreOffice mit einer Deckungskraft von 50% gelegt und gleichmäßig so groß skaliert, bis die Fahrbahnbreiten übereinstimmten (eine graphische Ungenauigkeit ist immer dabei). Gestartet wird von links und Ziel ist natürlich, mittig auf der Gegenseite mit der richtigen Ausrichtung anzukommen. Bleibt noch zu erwähnen, dass auf allen Darstellungen die Mittellinie ein wenig zu schmal geraten ist, sodass die Spurmitte überall ein wenig weiter außen anzunehmen ist.

Hier wurde noch ein Fehler im Regula Falsi - Verfahren entdeckt. Aufgrund des zu "groß" gewählten Epsilons ergab sich ein nicht rotationssymmetrischer Fahrweg. Die linke Teil war perfekt nur rechts "konvergierte" der Wert etwa gegen 0.24.

Rekonstruktion eines Spurwechsels beim Überholen

Hier galt es zunächst, Werte für die initialen Lenkeinschläge einer Links- und einer Rechtskurve herauszufinden - alleine. Dafür wurden wieder mit Gimp Fahrbahnen gezeichnet (Innenradius 1m, danach die Radien für eine 820mm Breite Fahrbahn):

LinksInitial.png RechtsInitial.png

Wie zu sehen ist, stimmen die Spurbreiten nicht perfekt überein, wodurch auch die initial gleich bleibenden Lenkeinschläge nicht perfekt sind. Deutlich soll nur das Prinzip werden. Die Lenkeinschläge wurde experimentell immer wieder minimal angepasst und leicht verändert. Jetzt folgt der eigentlich interessante Teil, die Überlagerung von s-Kurve und initialem Lenkeinschlag:

LinksUeberlagert.png RechtsUeberlagert.png

Wieder können grafische Unsauberkeiten nicht ausgeschlossen werden. Dennoch fällt sofort auf, dass der Überholweg in der Linkskurve sehr viel besser passt, als in der Rechtskurve, wo der initial stärkere Lenkeinschlag auf die Länge der Bahnkurve viel schwerer wiegt als umgekehrt. Eine Erklärung dafür konnte nicht gefunden werden. Als nächstes kam die Idee auf, den mittleren Lenkeinschlag von Innen- und Außenkurve (Mittellinie, stimmt aber auch nicht ganz) zu wählen, weil die Bahnkurve in beiden etwa zu gleichen Teilen verläuft. Daraus resultierte, dass die vormals perfekte linke Überholkurve viel zu stark war, sodass das Fahrzeug falsch ausgerichtet die linke Gegenspur zu verlassen drohte. Auf dem zweiten Blick fiel auf, dass das tangentiale Verlassen und Ankommen im linken Schaubild gegeben war und damit auch der Lenkeinschlag stimmte, wenn das Fahrzeug von oben auf der rechten Innenspur kommen und überholen würde. Daraus konnte nur geschlossen werden, dass der initiale Lenkeinschlag für die Innen- und Außenspur gleich sein musste. Jetzt wurde mit einer größeren Kurve (Innenradius 2m) überprüft, ob immer der äußere Lenkeinschlag zu wählen ist oder ob der perfekte Lenkeinschlag hier oben einfach nur Zufall war:

LinksGrossUeberlagert.png

Es zeigt sich wieder eine sehr gut aussehende Überholkurve, die ein wenig schlechter als die mit dem kleineren Kurvenradius zu bewerten ist - grafischer Fehler?. Allerdings wurde bei allen Beispielen bisher nicht die Länge der Überholkurve insgesamt betrachtet. Eine weitere Kurve macht eine höhere Geschwindigkeit und damit auch eine längere s-Kurve (kleinerer "swerveFactor") mit einer leicht anderen Form wahrscheinlich. Prinzipiell muss jedes Überholmanöver auch mit der niedrigsten Geschwindigkeit zu bewerkstelligen sein. Trotzdem ist die Kurvenlänge eine immer mit einzubeziehende Größe. Bei einer zur langen Bahnkurve muss deshalb auch der äußere Lenkeinschlag falsch sein.

Erkenntnisse und Fazit

Bleibt festzuhalten, dass für beide Fahrspuren - bei gleicher Geschwindigkeit - der gleicher initiale Lenkeinschlag gebraucht wird (tangentiales Verlassen und Ankommen - in beide Richtungen auf derselben Bahnkurve!). Unerklärlicherweise liefert der initiale Lenkeinschlag der äußeren Spur sehr viel bessere Ergebnisse, die aber auch nicht unumstritten sind. Trotzdem wurde eine Methode implementiert, die abhängig vom momentanen Lenkeinschlag den initial äußeren Lenkeinschlag liefert - die Fahrbahn ist ja normiert. Inwieweit die Simulation Rückschlüsse auf die Realität erlaubt, müssen weitergehende praktische Testläufe zeigen.

Betrachtet wurden lediglich Spurwechsel der Form Rechts-Links und das auf kontinuierlichen Kurven, keine Hundekurven. Das Einscheren funktioniert analog zum Ausscheren. Der Abbruch des Einscherens wurde nicht validiert, da hier die Ausrichtung des Fahrzeugs eine fundamentale Rolle spielt und die bei dieser einfachen Simulation grundsätzlich nicht überprüft werden kann. Vielleicht macht es Sinn, andere Bahnkurven und andere Systeme auszuprobieren. Aber auch diese werden mit ähnlichen Schwierigkeiten zu kämpfen haben.

End

Momentan wird der Lauf in den dynamischen Disziplinen manuell abgebrochen, was diesen Zustand unnötig macht. Das Ende war kein vordergründiges Problem, sodass hier lediglich Ideen existieren:

  • Abbruch immer von Hand (leicht umsetzbar und gut zu kontrollieren, momentan)
  • nach abgelaufener Zeit - Timer würde in "Startbox" oder "Parking" gestartet werden - unabhängig von Ort und Zustand Vollbremsung (ebenfalls kein Zustand "End" notwendig, aufwändiger und in mancher Situation vielleicht ungünstig)
  • Runde fertig fahren und bremsen nach Startlinie (nicht möglich beim Parken, unnötig kompliziert)

Neuronale Netze (Optional)

Die Neuronalen Netze sind als Verbesserung gedacht, für die Aufgaben des Lenkens und der Geschwindigkeitskontrolle. Dabei ist die Idee, die Neuronalen Netze mit den Daten zu trainieren, die aus den Fahrten mit der symbolischen KI gesammelt werden. Um zu schauen ob wir mit den Neuronalen Netzen bessere Ergebnisse erzielen können.

Aufbau

Trainings-Software

Zum trainieren der Netze gibt es eine Trainings-Software die mit der Fast Artificial Neural Network Library (FANN) arbeitet und alle 5 Trainingsalgorithmen ausprobiert. Des weiteren können auch die Aktivitätsfunktionen für Output- und Hiddenschicht eingestellt werden.


----------Menu----------

1. Normale Training
2. Cascade Training
3. Validation

----------------------------------------

4. Change activation function
5. Change number of input units
6. Change number of output units
7. Change number of layer
8. Change number of hidden units
9. Change desired error
10. Change max epochs
11. Change epochs between reports
12. Change learning rate
13. Change connection rate(only cascade training)

----------------------------------------

0. End

1. Führt eine normales Training mit den eingestellten Parametern aus und zwar mit allen 5 Lernalgorithmen. Die Augabedateien werden wie folgt benannt "network_t_%d.net", wobei %d = der Verwendete Algorithmus ist.
2. Führt ein Kaskadentraining aus mit den eingestellten Parameter aus und zwar mit 1,2,3,4,...,9 Hidden-Schichten. Die Augabedateien werden wie folgt benannt "network_c_%d.net", wobei %d = die Anzahl der Schichten plus Input- und Outputschicht ist.
3. Kann verwendet werden um den Error eines Netzes auf Test bzw. Validierungsdaten zu bestimmen.

4. Bei dieser Option werden die Aktivitätsfunktionen in Hidden- und Output-Schicht eingestellt. Es werden auch zurzeit für beide Schichten das selbe eingestellt.
5. Hier kann die Anzahl der Input-Units eingestellt werden, diese muss an den Trainingsdatensatz angepasst werden. (Min. 1) (Default=2)
6. Hier kann die Anzahl der Output-Units eingestellt werden, diese muss an den Trainingsdatensatz angepasst werden. (Min. 1) (Default=1)
7. Einstellen der Layer Anzahl für das Neuronale Netz. (Min. 2) (Default=3)
8. Hier können die Anzahl an Hidden-Units pro Hidden-Schicht eingestellt werden. (Min. 1) (Default=3)
9. Hier kann der maximal erwünschten Fehler eingestellt werden. (Default=0.0000000001)
10. Hier kann die maximale Anzahl an Lerndurchgängen eingestellt werden. (Default=50000)
11. Hier kann die Anzahl an Lerndurchgängen eingestellt werden, nachdem ein Report ausgegeben wird. (Default=10000)
12. Hier kann die verwendete Lernrate eingestellt werden. (Default=0.7)
13. Hier kann die Rate eingestellt werden wie stark die Schichten mit einander vernetzt sind. (Default=1.0)
Verwendung

Um ein neues Neuronales Netz zu trainieren sind folgende Schritte nötig:

  1. Einstellen der Anzahl der Input und Output Units mit Menüpunkt 5 und 6.
  2. Netzdesign wählen durch einstellen der Hidden-Layer-Anzahl und der Anzahl an Hidden-Units pro Layer. Außerdem können noch die Lernrate sowie die Aktivitätsfunktionen eingestellt werden.
  3. Danach kann das Netz trainiert werden mit Normalen Training oder Casscaden Trainig.
  4. Man sollte versuchen mehrere Netze zu erstellen und mit einer Menge an Validierungsdaten ausprobieren welches Netz den geringsten Error hat. Dafür kann die Validierungsfunktion verwendet werden, mit Menüpunkt 3
Die Validierungsmenge darf nicht die gleiche Menge wie die Trainingsmenge sein !

Neuronale Netze Interface

Zur Benutzung der Neuronalen Netze wird ein Interface, der restlichen KI-Gruppe zur Verfügung gestellt.

Beispiel

 1 #include "neural_network.h"
 2 
 3 
 4 int main () {
 5 
 6     NeuralNetwork neuralNatwork();
 7 
 8     float speed = 0.0;
 9     float steering = 0.0;
10 
11 
12     while(true) {
13         
14         neuralNetwork.setSpeedInput(1, 2, 0.1);
15         neuralNetwork.setSteeringInput(1, 2, 0.1);
16         
17         speed = neuralNetwork.getSpeed();
18         steering = neuralNetwork.getSteering();
19     }
20 
21 }

Datenanalyse-Software

Die Datenanalyse-Software ist im Logging-Client mit integriert und stellt einige Funktionen bereit um mit den Daten zu arbeiten. Der Logging-Teil der Software wird hier erklärt. Für den Analyseteil werden vor allem die Libraries JFreeChart (zum ertsellen von Charts) und mxparser (zum errechnen neuer Spalten) verwendet.

Analyse.png

  1. Hauptfunktions-Buttons:
    • Verwendung:
      • Load
    1. Load-Button drücken und es öffnet sich ein Auswahlmenü.
    2. Danach die gewünschte Datei im Dateisystem auswählen.
    3. Datei wird automatisch geladen und in 6. angezeigt.
      • Save
    1. Save-Button drücken und es öffnet sich ein Auswahlmenü.
    2. Danach den Ordner auswählen in den die Datei gespeichert werden soll. Der Dateiname ist der Name des TabbedPanes.
      • Remove
    Entfernt die ausgewählte Tabelle aus der Anzeige.
  2. Convert-Bereich:
    • Verwendung:
    1. Einen Convertion-String für die ausgewählte Tabelle angeben. (i:gyro0;i:gyro1;o:accel0)Weitere Informationen hier.
    2. Danach den Convert-Button drücken und es öffnet sich ein Auswahlmenü.
    3. Ordner auswählen wo die Trainingsdatei gespeichert werden soll.
    4. Es wird eine Trainingsdatei mit dem Name "tarining.data" erstellt, die als Trainingsdatei für die Tariningssoftware verwendet werden kann.
  3. Plot-Range:
    • Verwnedung:
    1. Eingeben des Anfangsdateneintrag aus der Tabelle z.B. Dateneintrag 3.
    2. Eingeben des Enddateneintarg aus der Tabelle z.B. Dateneintrag 1000.
    3. Danach wird beim Ploten eines oder mehrerer Werte nur die Dateneinträge geplottet die sich in diesem Intervall befinden.
  4. Plot:
    • Verwendung:
    1. Angeben welche Werte geplottet werden sollen. Es können auch mehrere Werte mit , getrennt angegeben werden.
    2. Danach den Plot-Button drücken und es öffnet sich ein extra Fenster mit den geplotteten Daten.
  5. Calculation:
    • Verwendung:
    1. Angeben eines richtigen Calculation-Ausdruckes. (gyro0|gyro1|newColumn|mean(gyro0_a) + gyro1) Weitere Informationen hier.
    2. Danach den Calculate-Button drücken und es wird automatisch für alle Einträge der neu Wert berechnet.
  6. Aktuell ausgewählte Tabelle
Convert-Funktion

Die Convert-Funktion hilft einem dabei eine rainingsdatei zu erstellen. In diesem Abschnitt möchte ich kurz erklären wie der Syntax des Convertion-Strings aufgebaut ist.

Der Convertion-String besteht aus Input und Output Einheiten, also die späteren Input-Werten des Neuronalen Netzes und den Output-Werten des Neuronalen Netzes.

  • Input:
    • Wenn eine Spalte ein Input-Wert sein soll dann wird dieser folgendermaßen im Convertion-String definiert i:NameDerSpalte. Wobei das i für Input steht.
  • Output:
    • Wenn eine Spalte ein Output-Wert sein soll dann wird dieser folgendermaßen im Convertion-String definier o:NameDerSpalte. Wobei das o für Output steht.

Die einzelnen Input und Output-Werte werden mit ; getrennt und es entsteht z.B. folgender Convertion-String i:Spalte1;i:Spalte2;o:Spalte3;o:Spalte4

Calculation-Funktion

Die Calculation-Funktion ist dafür gedacht neue Werte aus den bestehenden zu berechnen. Dazu stehen einem einige mathematische Funktionen zur Verfügung. Zum errechnen dieser Funktionen wird der mxparser verwendet.

Der Calculation-String besteht aus 4 Teilen. Dazu gehören zwei Angaben von Spalten, die sich darin unterscheiden das bei der ersten Angabe die komplette Spalte gemeint ist und bei der zweiten Angabe von Variablen um den Wert der Spalte in der jeweiligen Zeile. Des Weiteren muss noch der Name der neuen Spalte angegeben werden und die eigentliche mathematische Formel.

  • Variablen die eine Spalte repräsentieren:
    • Im Calculation-String stehen sie an erster Stelle und sind quasi nur die Spaltennamen der Spalten die verwendet werden. Wenn mehrere Spalten verwendet werden sollen müssen sie mit , getrennt werden. Z.B. (Spalte1,Spalt2, Spalte3)
    • Die Variablen die hier angegeben werden sind für folgende Funktionen gedacht und müssen in der mathematischen Formel mit dem folgenden Suffix erweitert werden _a. Z.B. mean(Spalte1_a)
  • Variablen die den Werte einer Spalte in der aktuellen Zeile repräsentieren:
    • Im Calculation-String stehen sie an zweiter Stelle und werden wie die oben genannten Variablen einfach durch den Spaltenname getrennt von einem , angegeben.
    • In der mathematischen Formel werden sie nur mit dem Spaltennamen angesprochen wie z.B. Spalte1 + Spalte2
  • Der dritte Eintrag im Calculation-String ist der Name der neuen Spalte in die, die neu errechneten Werte eingetragen werden.

Die 4 Teile eine Calculation-Strings werden mit | getrennt z.B. Spalte1,Spalte2|Spalte3,Spalte4|NeueSpalte|mean(Spalte1_a) * Spalte3 + min(Spalte2_a) + sin(Spalte4)

Ende

Die neuronalen Netze sind so weit es möglich war implementiert. Leider sind wir nicht mehr dazu gekommen Netze zu Trainieren, weshalb die Mehtoden setSpeedInput und setSteeringInput noch nicht implementiert sind und noch Parameter als Platzhalter haben.

Tipps für die nächste Gruppe

  1. Die oben angesprochenen Set-Methoden müssen angepasst werden je nachdem welchen Input die Neuronalen Netze am Schluss bekommen. Des Weiteren müssen die Methoden das speedInput und das steeringInput Array setzen.
  2. Falls die Datenanalysesoftware weiter verwendet wird, wäre es ratsam die Convert-Funktion zu erweitern. Man könnte nicht nur eine Trainingsdatei erstellen sondern gleich die Daten in Trainingsdaten, Validierungsdaten und Testdaten einteilen. Außerdem wäre es ratsam das man Input und Output-Daten nicht nur aus der aktuell ausgewählten Tabelle wählen kann, sondern aus allen zur Zeit geöffneten Tabellen.

Dynamische Disziplinen

Regelwerk des Carlo-Cup von der TU Braunschweig

Disziplin 1: Einparken

Beteiligte Zustände:

  • Spur folgen
  • Parken
  • (Ende)

Reglement zusammengefasst (keine Garantie!!!):

  • Start unmittelbar vor der Startlinie
  • Einparkvorgang dauert maximal 30 Sekunden
  • Abstand zum vorderen und hinteren Hindernis > 10 mm
  • das Fahrzeug muss innerhalb der weißen Linien stehen

Disziplin 2: Rundlauf ohne Hindernisse

Beteiligte Zustände:

  • Startbox
  • Spur folgen
  • (Ende)

Reglement zusammengefasst (keine Garantie!!!):

  • Start aus der Startbox
  • in 2 min soll eine möglichst weite Strecke zurückgelegt werden (Bewertung: gefahrene Meter - Strafmeter)
  • Haltelinien an Kreuzungen sind zu ignorieren
  • alle drei Spurlinien können an beliebigen Stellen auf 1000mm unterbrochen sein (aber nur zwei dürfen gleichzeitig fehlen)
  • wenn das Fahrzeug von der Strecke abkommt, kann es im RC-Modus wieder auf die Strecke zurückgeführt werden

Disziplin 3: Rundlauf mit Hindernissen

Beteiligte Zustände:

  • Startbox
  • Spur folgen
  • Kreuzung
  • Überholen
  • (Ende)

Reglement zusammengefasst (keine Garantie!!!):

  • Start aus der Startbox
  • in 3 min soll eine möglichst weite Strecke zurückgelegt werden (Bewertung: gefahrene Meter - Strafmeter)
  • 0-15cm vor der Haltelinie muss gehalten werden, mindestens 2 Sekunden stehen, Kreuzung auf Hindernisse prüfen
  • alle drei Spurlinien können an beliebigen Stellen auf 1000mm unterbrochen sein (aber nur zwei dürfen gleichzeitig fehlen)
  • Hindernisse können einem auf beiden Seiten begegnen, stehend oder fahrend
  • der Überholvorgang muss spätestens 2m nach Passieren des letzten zu überholendes Hindernisses abgeschlossen sein
  • bei zwei Hindernissen hintereinander muss das Einscheren abgebrochen werden können
  • wenn das Fahrzeug von der Strecke abkommt, kann es im RC-Modus wieder auf die Strecke zurückgeführt werden

Protokoll

bis zum 28.02.2017

28.02.2017

  • finale Anpassungen im Wiki/am Code
  • Projektabschluss

27.02.2017

  • erste Testläufe von Überholen
    • leichte Anpassungen am Code
    • scheint grundsätzlich auf der Geraden zu funktionieren
    • keine Zeit mehr für umfangreichere Tests mit Kurven

26.02.2017

  • das Parken wurde soweit angepasst, dass es mittlerweile funktioniert
  • erstmals Tests mit Startbox und Kreuzung
    • keine Probleme, scheinen zu funktionieren
  • zwischenzeitlich Vorbereitung für den Projektabschluss
    • Dokumentation im Wiki
    • doxygen-konformer Quellcode

20.02.2017

  • Spur folgen scheint grundsätzlich zu funktionieren
    • Lenkeinschläge aber sehr abrupt
      • Fehler bei uns oder den erkannten Linien?

18.02.2017

  • Bildverarbeitung ist auf dem Fahrzeug viel zu langsam
  • Fertigstellung der offline-Validierung beim Überholen

08.02.2017

  • letztes regelmäßiges Treffen
  • zeitlicher Engpass wegen anstehender Klausuren

06.02.2017

  • erste praktische Testläufe mit Spur folgen

04.02.2017

  • erstmals kann KI prinzipiell anfangen zu testen

01.02.2017

Aktuelles

  • PID-Rgeler ist "fahrtüchtig"
  • KI ist lauffähig auf dem Auto aber bisher noch keine Tests mit den Daten der Bildverarbeitung
  • immer noch keine Tests möglich

25.01.2017

Aktuelles

  • Situation unverändert (PID-Regler/Geschwindigkeitsmessung funktioniert weiterhin nicht)
  • Start mit offline-Validierung von Überholen

18.01.2017

Aktuelles/Gesprächsbedarf

  • Wie realisiert man, ob überholt werden soll?
    • in Rechtskurve ist mit dem Distanzsensor nicht ersichtlich, ob Hindernis auf der eigenen oder der Gegenspur
    • Abhilfe nur mit der Bildverarbeitung möglich
      • Geeinigt auf: BV gibt uns einen bool-Wert ob Hindernis auf unserer oder der Gegenspur
      • Realisierungsmöglichkeiten
  • KI ist bereit fürs Tests, Testen aber nicht möglich weil Fahrzeug nicht funktionstüchtig
    • Aushelfen beim System
    • Testen rückt in noch weitere ferne weil Geschwindigkeitsmessung tiefgreifend fehlerhaft

11.01.2017

Aktuelles

  • alle Kontrollstrukturen in Main eingebaut
  • Auslesen aus dem Shared Memory Bereich funktioniert
  • Prototyp für das Überholen steht

21.12.2016

Aktuelles

  • bis auf das Überholen haben alle Klassen fertige Prototypen
  • Testen ist nach wie vor nicht in Sicht

14.12.2016

Gesprächsbedarf

  • Bei bekanntem Zustandswechsel den Folgezustand aus sich heraus einmal aufrufen, damit wichtige Informationen innerhalb eines Zyklus nicht verloren gehen - auch wenn die Zykluszeit sehr klein ist (oder wir brauchen ein Spezial-Struct für sowas oder main müsste nochmal aufrufen.. ). Wichtig wäre das zumindest für "Spur folgen" -> "Kreuzung", wo die Haltelinie verschwinden könnte.
    • Nachtrag: Wird so zurzeit nicht überall praktiziert. Es gab aber auch keine Probleme bei der Haltelinienerkennung.

07.12.2016

Aktuelles

  • es wurde autotools für das Projekt eingeführt, neue Ordnerstruktur
    • alle .cpp Dateien in den /src Ordner
    • alle .h Dateien in den /include Ordner

30.11.2016

Gesprächsbedarf

  • Bekommt die Lenkungs-/Geschwindigkeitsreglung von allen ein Punkt übergeben oder soll auch ein Lenkeinschlag und/oder eine Geschwindigkeit übergeben werden können?
    • In welcher Größenordnung liegen die Punkte die übergeben werden? (y:-10/10, -100/100)(x:0/10,0/100)
    • Vorschlag: Es gibt eine Geschwindikeitsvorgabe (z.B. Parkplatzsuchen) - die aber noch oben begrenzt werden muss. Beim Bild 800x600 und der (0,0) unten in der Mitte: x:-400 bis +400, y: 0 bis 600
    • Nachtrag: "SpeedSteering" erhält einen x-Wert [-1.0, 1.0] für den Lenkeinschlag, eine maximale Geschwindigkeit für das Bremsen und einen initialen Lenkeinschlag - wie x-Wert
  • Struct in der Main Funktion auch zum loggen nehmen? Heißt, immer alle wichtigen Werte werden in das Struct geschrieben.
    • Vorschlag: Ein gemeinsames Struct oder die einzelnen Structs von jedem loggen. Letzteres macht wegen der Datenkapselung Sinn, damit nicht jeder alles darf.
    • Nachtrag: Für ein einfacheres Logging existiert nur Struct. Jeder muss aufpassen, dass nur die eigenen Werte geändert werden. Keine Kommunikation über dieses Struct.
  • Neuronales Netz steuert direkt oder soll es nochmal überprüft werden?
    • Vorschlag: Nicht prüfen (außer die Geschwindigkeitslimitierung), weil sonst keiner mehr nachvollziehen kann, wer eigentlich steuert. (am besten Umschaltung mit Status-Bit am Controller - sofern noch eines frei ist)
    • Nachtrag: irrelevant, da nicht abschließend realisiert
  • Streichung des Ende-Zustandes
  • Rolle von Start-Zustand beim Einparken, da Start von der Startlinie aus und Linie nicht sichtbar
    • Geeinigt auf: Einparken ohne "Startbox"
  • Braucht wer die Rad-Drehzahlsensoren?
    • allgemein zugreifbar über das Telemetrie-Struct
    • Nachtrag: Keine Verwendung innerhalb der KI. Durchdrehen der Räder ist kein vordergründiges Thema.

23.11.2016

Aktuelles/Gesprächsbedarf

  • jeder hat an seinem Part weitergearbeitet
  • Abstimmungen mit der BV bezüglich dem Abfangen möglicher Fehlerwerte
    • BV sorgt dafür, dass uns kleine Geraden (kleine Werte in der Hough-Matrix) nicht erreichen
    • alle anderen möglichen Probleme fallen in unsere Zuständigkeit
    • Erkenntnis: Haltlinienerkennung ist aufgrund der kleinen region of interest eingeschränkt, wir werden wahrscheinlich nur wenige Frames die Haltelinie sehen und blind bremsen müssen (wenn das Scheitern sollte, braucht es eine Nachjustierung der ROI, die wir dann vorgeben)
    • noch offen: spezieller Fehlerwert, der nicht im Bild liegen kann, z.B. 10000
      • Nachtrag: Fehlerwert ist immer NAN

16.11.2016

Fragen/Gesprächsbedarf

  • Wenn das Fahrzeug die Strecke verlässt... wie nach dem RC-Modus den richtigen Zustand wiederfinden?
    • KI wird nur pausiert, bleibt in der selben Disziplin und sucht auf welcher Spur es steht
    • evtl läuft KI einfach weiter und es rechnet weiter was es machen soll und ist immer im richtigen Zustand. Muss man aber Zustände auch außerhalb der Fahrbahn einbauen???
    • entweder aus den Input-Daten den richtigen Zustand erkennen oder nach dem RC-Modus wird immer der Zustand "Spur folgen" auf normaler Strecke angenommen (am einfachsten).
    • Geeinigt auf: Nach RC-Modus immer in rechter Spur und Zustand "Spur folgen
  • Wie das Ende realisieren - durch einen Timer (müsste im RC-Modus weiter laufen)?
    • KI liefert einfach keine neuen Daten. RC-Modus geht immer und ist auch schon so implementiert in der Firmeware.
    • Geeinigt auf: Abbruch durch Special-Knopf auf dem Controller (Status bits)
  • Wie werden wir nach dem RC-Modus wieder gestartet? - ohne kill... müssen wir mit dem System über das SHM-Segment kommunizieren können...
    • Wir laufen dauerhaft weiter nur unsere Signale werden ignoriert. Heißt wir müssen nicht neu gestartet werden.
    • Geeinigt auf: Wir warten auf Taster-Signale im SHM wenn kein Lauf - Taster-Signale während Lauf werden ignoriert
  • Wer realisiert die maximale Geschwindigkeit von 0,3m/s und die 1 Sek. Stillstandszeit im RC-Modus beim Wettbewerb (im Training: 1,0m/s, Stop aber keine Stillstandszeit) - wir oder System?
    • mit Ben geklärt: RC-Modus überschreibt uns, es gibt in der Firmware schon einen Anfang dazu aber der ist momentan auskommentiert.
    • Geeinigt auf: System ist zuständig
  • Lenkeinschlag - wer??
    • Geeinigt auf: Lucas - Geschwindigkeit/Lenkung (sind direkt miteinander verknüpft)
  • Wie unterscheiden wir RC-Modus + danach weitermachen und dauerhafter Abbruch voneinander?
    • oben geklärt (mit den Knöpfen mit denen man die Disziplinen auswählen kann, kann man auch abbrechen)
  • Strukturen für den Austausch mit System/Bildverarbeitung, Organisation unserer eigenen internen Struktur(en)
    • Vorschlag: In jedem Frame kopieren wir zu Beginn einmal aus dem SHM in unsere Struktur und am Ende wieder zurück (erspart viele kleine Zugriffe).
    • interne Strukturen nach dem RC-Modus wieder zurücksetzen
  • Wollen wir unsere eigenen Daten zur Validierung loggen (Zustandswechsel und zugehörige Inputdaten, interne Zustände, regelmäßig Output)?
    • Wird von Tobi bereitgestellt - trotzdem später schwer nachvollziehbar.
  • Manche Funktionalitäten (fehlende Außenlinien bei Spur folgen, Kreuzung, Überholen...) werden losgelöst von Inputdaten der BV realisiert werden müssen -> unvollständiger Trainingsdatensatz...
    • Geeinigt auf: neuronale Netzen steuern nur Geschwindigkeit und Lenkeinschlag

09.11.2016

Ideen/Fragen/Probleme

  • sollen wir die Linien, die wir von der BV erhalten, auf Sinn prüfen (mit Tolleranz und Straßenbreite) - oder immer als korrekt annehmen?
    • Nachtrag: kein Prüfen der Linien (ROIs sind klein genug), NANs als Fehlerwert
  • wenn Linien fehlen evtl. an Hindernissen orientieren (->Rücksprache mit BV halten), betrifft Spezialfall beim Überholen (siehe Regelwerk S. 24)
    • Nachtrag: verworfen, da zu kompliziert
  • im Zustand Startbox: Orientierung nur an der rechten Spurlinie (siehe Regelwerk S. 23)
    • Nachtrag: realisiert mit LOCK_STEERING, anfängliche gerade Ausrichtung wichtig
  • Mehrfachverwendung von Methoden - z.B. "Spur folgen" findet Anwendung in den Zuständen Start, Rechts fahren, an Haltelinie halten, Links fahren... (je nach dem, in welcher Disziplin und in welchem Zustand)
  • Zustände entsprechen Klassen
  • abhängig von der Disziplin ein eigenes SwitchCase in Schleife, von wo aus die Zustandsmethoden aufgerufen werden
  • Prüfung auf Zustandswechsel könnte in den einzelnen Zustandsfunktionen erfolgen... bei Wechsel gibt der Rückgabewert den nachfolgenden Zustand an (aber dann keine Möglichkeit mehr Methode und damit den Zustand vorzeitig wieder verlassen...)
  • Zustände werden mit Präprozessor-Defines in einem globalen Header bestimmt (weniger Verwechslungsgefahr)
    • Nachtrag: realisiert mit enums
  • evtl. Timer, um die 2 bzw 3 Minuten für die jeweilige max. Disziplindauer zu begrenzen
    • Nachtrag: verworfen, da kein vordergründiges Problem

Aufgaben bis zum nächsten Termin

  • Nachdenken
  • Vorschläge zum grundsätzlichen Programmaufbau

02.11.2016

Aufteilung der ausgemachten Arbeitspakete

  • Straße folgen / evtl. Bilddaten kompensieren falls Linie nicht da
  • Überholen / Blinken
  • Haltelinie
  • Einparken
  • Bremsen vor Kurve / Bremslichter

siehe Team

Fragen/Probleme

  • welche Sonsoredaten kommen vorverarbeitet an die KI (besonders Distanzsensor infinity-Problem)?
  • kann das Auto eventuell selbst feststellen, ob es zu schnell ist? Räder rutschen etc.
  • was sind sinnvolle Geschwindigkeiten/Lenkeinschläge?
  • wie Objektsensor hinten rechts oder Rad-Drehzahlsensoren auslesen (Format)?

Erster Entwurf zu den dynamischen Disziplinen 2 + 3 Dynamisch23.png

Aufgaben bis zum nächsten Termin die jeweils eigene Aufgabe durchdenken, Lösungsansätze erarbeiten

26.10.2016

Mögliche Input-/Outputdaten

Input:

  • echo - Distanzsensoren (vorne, hinten)
  • Objektsensor (hinten rechts)
  • Rad-Drehzahlsensoren (4x)
  • vorverarbeitete Bilddaten...

Output:

  • Lenkeinschlag
  • Motorleistung/-drehzahl
  • Brems- und Blinklichter

Welche Technik???

Probleme/Fragen

  • Inputdaten von der Bildverarbeitung noch nicht bekannt - was brauchen wir?
  • inwieweit ist die KI für die Bildverarbeitung verantwortlich (Stichwort: ORB-SLAM)?
  • welche Programmiersprache - C/C++, OpenCL (C Erweiterung), Python, ...?
  • regeln wir die Raddrehzahl oder macht das die Regeltechnik (wie das Durchdrehen der Räder erkennen)?
  • wie und wo könnten Fuzzyregler zum Einsatz kommen?

Aufgaben bis zum nächsten Termin

  • Gedanken machen über Aufteilung in kleinere Arbeitspakete
  • Interessen überlegen

Testergebnisse

Es gibt zu allen folgenden Funktionen auch jeweils ein Video eines Testlaufs.

Parken

Funktioniert sehr gut, nur um auch noch in kleinere Parklücken fahren zu können sollte man die Bildverarbeitung noch mit einbeziehen um sehen zu können ob man rechts von der Straßenbegrenzungslinie steht. Außerdem wird follow road noch nicht verwendet, man muss also das Auto beim Start so ausrichten, das es parallel an der Parklücke vorbei fährt.

Spur folgen

Es wurden einige gute Ergebnisse erzielt. Es sieht aber danach aus, dass es besser klappt, wenn nur eine Linie gesehen wird. Es ist noch nicht ganz klar, ab dies an unsere Funktion liegt oder der noch nicht perfekt funktionierenden Linienerkennung liegt. Es wurde auch festgestellt, das die Lichtverhältnisse einen sehr starken Einfluss haben.

Startbox

Das gerade Überfahren der Kurvenaußenlinie und das Umschalten in "FollowRoad" funktionierten problemlos. Der Umschaltzeitpunkt jedoch ließ darauf schließen, dass seitens der Bildverarbeitung die Kurvenlinie selbst als Startlinie erkannt wurde. Als Startlinie wurde "holdLine" der BV angenommen.

Kreuzung

Lediglich das Anhalten, Warten und Abgeben wurde getestet, da Hindernisse nicht erkannt werden konnten (mit der BV). "FollowRoad" lenkte mittig auf der Spur in Richtung Haltelinie teils zu stark, wodurch früh Geschwindigkeit verloren ging. Gleichzeitig brachten die immer kleiner werdenden vorgegebenen Geschwindigkeiten den Motor zum Stottern, sodass das Fahrzeug häufig 30cm vor der Haltelinie zum Stehen kam. Bessere Ergebnisse (etwa 10cm vor der Haltelinie) lieferten Starts nahe an einer der Außenlinien, wodurch kaum gelenkt wurde. Das Warten und Weiterfahren - die Kreuzung wurde immer als frei angenommen - funktionierten einwandfrei.

Überholen

Getestet wurde das Überholen ausschließlich auf der Geraden und das überwiegend nur mit fest vorgegebenen Werten (Kurvenhärte, x-Startwert, initialer Lenkeinschlag) und auch ohne "FollowRoad" wegen zu starkem Lenken beim Vorbeiziehen. Und selbst hiermit zeigte sich ein nicht deterministisches Verhalten des Fahrzeugs, insbesondere was die Ausrichtung des Fahrzeugs vor und nach einem Spurwechsel betrifft. Trotz einer nachweislich umgekehrt gespiegelten Lenkeinschlagskurve (geloggter Output) lenkte das Fahrzeug meist stärker nach links als nach rechts. Der Fehler verschärfte sich durch den zweiten Spurwechsel und ohne "FollowRoad" dazwischen deutlich (aber: "SpeedSteering" wurde für die Gegenspur nur behelfsmäßig eingesetzt - ist für die finale Version nicht geplant!). Jeder Spurwechsel für sich getestet war deutlich besser zu bewerten, aber auch hier verlief der gefahrene Weg nicht immer gleich. An ein Überholen in der Kurve war zeitlich nicht mehr zu denken.

Erwähnenswert ist noch, dass die s-Kurve bei eingeschalteter Bildverarbeitung deutlich flacher ausfiel, obwohl die Daten der Bildverarbeitung während dem Spurwechsel keine Auswertung erfahren und interne Einstellungen nicht verändert wurden.

Projektabschluss

Produktabnahme

Welche Punkte wurden fertig entwickelt und im Auto eingebaut und implementiert?

  • außer den neuronalen Netzen wurde soweit alles Vorgesehene auch umgesetzt
  • die geforderten Funktionalitäten sind aber keineswegs pefekt und teils kann keine Aussage getroffen werden
    • der Straße nach fahren funktioniert zufriedenstellend
    • Parken ist ebenfalls auf einem guten Weg
    • Startbox und Kreuzung funktionieren solange sie die richtigen Daten von der Bildverarbeitung bekommen
    • beim Überholen
      • funktionieren die Spurwechsel auf der Geraden bei richtiger initialer Ausrichtung
      • Schwierigkeiten in Kombination mit "FollowRoad" beim Vorbeiziehen
      • keine Aussage zu Verhalten in Kurven möglich

Bei welchem Entwicklungsstand befinden sich andere Projektpunkte?

  • Trainingsdaten können für das neuronale Netz erhoben werden, sobald Spur folgen besser funktioniert
  • keine Möglichkeit, das Blinklicht zu setzen

Projektabschlussanalyse

Welche Probleme waren zu bewältigen?

  • Lösungswege erarbeiten, obwohl keine Möglichkeit bestand, diese frühzeitig in der Praxis zu testen (erst ganz am Ende)
  • fehlende Erfahrung und anfängliche Schwierigkeiten mit der Hardware
  • eingeschränktes Testen bzw. keine Parallelität
    • das System muss richtig funktionieren
    • die Bildverarbeitung muss richtig funktionieren
    • erst jetzt kann sinnvoll die KI getestet werden

=> flexiblere Teams von Anfang an, Fahrzeug hätte früher funktionieren müssen

Ist das Zeitmanagement aufgegangen? - bedingt

  • innerhalb der KI: größtenteils ja
    • die meisten Funktionalitäten waren relativ früh fertig implementiert und bereit zum Testen
    • zunehmend gegen Ende aber immer wieder auch Anpassungen (Schnittstellen, Sensoren, ...)
    • teils Arbeiten bis zum letzten Tag
    • für das neuronale Netz blieb am Ende keine Zeit mehr, weil gute Trainingsdaten nicht mehr zu beschaffen waren
  • Insgesamt (in Zusammenspiel mit den anderen Teams): eher weniger gut
    • schleppende Absprachen zu Beginn, keine festen Zusagen/spätere Änderungen (aber auch verständlich)
    • sehr lange Anlaufphase bis richtig mit der Hardware gearbeitet werden konnte (im Prinzip erst nach der Abschlusspräsentation)
      • kein genauer Plan wie gut was funktioniert
      • Engpass gegen Ende am Auto

Ursachen für zeitliche Abweichungen?

  • teils anfangs zu wenig Zeit investiert
  • teils deutlich mehr Aufwand als erwartet
  • Abhängigkeiten unterschätzt, wodurch manche Teams nicht viel machen konnten bis andere fertig waren

Was man am Anfang wissen sollte

Welche Informationen hätte ich am Anfang gerne gehabt?

  • zeitintensives Projekt
  • man muss sich an den Möglichkeiten der Hardware orientieren und kann manches evtl. nicht so umsetzten wie man es gern tun würde
  • man muss sich auf andere Leute verlassen, da man sehr abhängig untereinander ist

Was für Voraussetzungen müssen mitgebracht werden?

  • sich nicht frustrieren lassen, Ausdauer
  • ins Blaue programmieren können, ohne zu wissen, wann das Programmierte getestet werden wird/kann (lange Zeit keine Rückmeldung über die Funktionalität des eigenen Codes)
  • lange Einarbeitungszeit in bereits Geschriebenes
  • lange Fehlersuche, man muss auch mit Hardwarefehlern rechnen

Zusammenfassung und Ausblick

das Modellfahrzeug

  • kann eine Parklücke erkennen und in diese fahren
  • kann einer einfachen Spur folgen
  • kann aus der Startbox fahren
  • kann vor der Haltelinie an einer Kreuzung halten
  • kann Spurwechsel auf einer Geraden vollziehen

Das Übergreifen der einzelnen Funktionalitäten ist fehleranfällig, besonders jenseits der Teststrecke. Positive Testläufe waren nicht immer direkt auf Anhieb reproduzierbar. Die neuronalen Netze konnten zeitlich bedingt nicht mehr umgesetzt werden, ebenso weitergehende Testläufe das Überholen in Kurven betreffend. Es fehlen noch wichtige Daten der Bildverarbeitung (Hindernis auf der Kreuzung, Hindernis auf der eigenen oder der Gegenspur, Startlinie!=Haltelinie), die zum gegenwärtigen Zeitpunkt nicht richtig ausgewertet werden können.

Man könnte noch implementieren, dass die KI das Statusbit der Taster zurücksetzen kann.

Fazit

Die Resultate können sich sehen lassen, insbesondere wenn man den Ausgangszustand des Fahrzeugs zu Projektbeginn bedenkt. Dennoch sind wir nicht soweit gekommen, wie wir ursprünglich geplant hatten. Ein nicht sehr geringen Anteil daran hat die Tatsache, dass technisch bedingt zu spät mit dem Fahrzeug wirklich gearbeitet werden konnte. Es blieb einfach zu wenig Zeit für ausgiebige, stressfreie Testläufe und ein ausreichendes Überarbeiten der eigenen Konzepte. Ebenso war trotz des Loggings nicht immer klar, woran es im Detail hing. Waren wir es, die falschen Daten der Bildverarbeitung oder Unsauberkeiten des Systems?