Inhaltsverzeichnis:

Autonomes spurhaltendes Auto mit Raspberry Pi und OpenCV - Gunook
Autonomes spurhaltendes Auto mit Raspberry Pi und OpenCV - Gunook

Video: Autonomes spurhaltendes Auto mit Raspberry Pi und OpenCV - Gunook

Video: Autonomes spurhaltendes Auto mit Raspberry Pi und OpenCV - Gunook
Video: Autonomes Fahren – Was können selbstfahrende Autos? | Umschau | MDR 2024, November
Anonim
Autonomes spurhaltendes Auto mit Raspberry Pi und OpenCV
Autonomes spurhaltendes Auto mit Raspberry Pi und OpenCV

In diesem Instructables wird ein autonomer Spurhalteroboter implementiert und durchläuft die folgenden Schritte:

  • Sammeln von Teilen
  • Softwarevoraussetzungen installieren
  • Hardware-Montage
  • Erster Test
  • Spurlinien erkennen und Leitlinie anzeigen mit openCV
  • Implementieren eines PD-Controllers
  • Ergebnisse

Schritt 1: Komponenten sammeln

Sammeln von Komponenten
Sammeln von Komponenten
Sammeln von Komponenten
Sammeln von Komponenten
Sammeln von Komponenten
Sammeln von Komponenten
Sammeln von Komponenten
Sammeln von Komponenten

Die obigen Bilder zeigen alle in diesem Projekt verwendeten Komponenten:

  • RC-Auto: Ich habe meins von einem lokalen Geschäft in meinem Land bekommen. Es ist mit 3 Motoren (2 für Drosselung und 1 für Lenkung) ausgestattet. Der Hauptnachteil dieses Autos ist, dass die Lenkung zwischen "keine Lenkung" und "Volllenkung" begrenzt ist. Mit anderen Worten, es kann im Gegensatz zu servolenkenden RC-Cars nicht in einem bestimmten Winkel lenken. Hier finden Sie ein ähnliches Car Kit, das speziell für Raspberry Pi entwickelt wurde.
  • Raspberry Pi 3 model b+: Dies ist das Gehirn des Autos, das viele Verarbeitungsschritte bewältigen wird. Es basiert auf einem Quad-Core-64-Bit-Prozessor mit einer Taktrate von 1,4 GHz. Ich habe meine von hier.
  • Raspberry Pi 5 MP Kameramodul: Es unterstützt 1080p @ 30 fps, 720p @ 60 fps und 640x480p 60/90 Aufnahmen. Es unterstützt auch eine serielle Schnittstelle, die direkt an den Raspberry Pi angeschlossen werden kann. Es ist nicht die beste Option für Bildverarbeitungsanwendungen, aber es ist für dieses Projekt ausreichend und sehr günstig. Ich habe meine von hier.
  • Motortreiber: Wird verwendet, um die Richtungen und Geschwindigkeiten der DC-Motoren zu steuern. Es unterstützt die Steuerung von 2 Gleichstrommotoren in einer Platine und hält 1,5 A stand.
  • Power Bank (optional): Ich habe eine Powerbank (bewertet mit 5 V, 3 A) verwendet, um den Himbeer-Pi separat einzuschalten. Ein Abwärtswandler (Abwärtswandler: 3A Ausgangsstrom) sollte verwendet werden, um den Himbeer-Pi aus einer Quelle zu betreiben.
  • 3s(12 V) LiPo-Akku: Lithium-Polymer-Akkus sind für ihre hervorragende Leistung im Robotikbereich bekannt. Es wird verwendet, um den Motortreiber mit Strom zu versorgen. Ich habe meine hier gekauft.
  • Überbrückungsdrähte männlich zu männlich und weiblich zu weiblich.
  • Doppelseitiges Klebeband: Wird verwendet, um die Komponenten am RC-Auto zu befestigen.
  • Blaues Band: Dies ist ein sehr wichtiger Bestandteil dieses Projekts, es wird verwendet, um die zweispurigen Linien zu erstellen, zwischen denen das Auto fahren wird. Sie können jede gewünschte Farbe wählen, aber ich empfehle, Farben zu wählen, die sich von denen in der Umgebung unterscheiden.
  • Kabelbinder und Holzstäbe.
  • Schraubenzieher.

Schritt 2: OpenCV auf Raspberry Pi installieren und Remote Display einrichten

OpenCV auf Raspberry Pi installieren und Remote Display einrichten
OpenCV auf Raspberry Pi installieren und Remote Display einrichten

Dieser Schritt ist etwas nervig und wird einige Zeit in Anspruch nehmen.

OpenCV (Open Source Computer Vision) ist eine Open-Source-Softwarebibliothek für Computer Vision und maschinelles Lernen. Die Bibliothek verfügt über mehr als 2500 optimierte Algorithmen. Befolgen Sie DIESE sehr einfache Anleitung, um die openCV auf Ihrem Himbeer-Pi zu installieren und das Himbeer-Pi-Betriebssystem zu installieren (falls Sie es immer noch nicht getan haben). Bitte beachten Sie, dass der Aufbau des openCV in einem gut gekühlten Raum ca. 1,5 Stunden dauern kann (da die Temperatur des Prozessors sehr hoch wird!), also Tee trinken und geduldig warten:D.

Befolgen Sie für die Fernanzeige auch DIESE Anleitung, um den Fernzugriff auf Ihren Raspberry Pi von Ihrem Windows/Mac-Gerät aus einzurichten.

Schritt 3: Teile miteinander verbinden

Teile miteinander verbinden
Teile miteinander verbinden
Teile miteinander verbinden
Teile miteinander verbinden
Teile miteinander verbinden
Teile miteinander verbinden

Die obigen Bilder zeigen die Verbindungen zwischen Raspberry Pi, Kameramodul und Motortreiber. Bitte beachten Sie, dass die von mir verwendeten Motoren jeweils 0,35 A bei 9 V aufnehmen, was es dem Motortreiber sicher macht, 3 Motoren gleichzeitig zu betreiben. Und da ich die Geschwindigkeit der 2 Drosselmotoren (1 hinten und 1 vorne) genau gleich steuern möchte, habe ich sie an den gleichen Port angeschlossen. Den Motortreiber habe ich mit Doppelklebeband auf der rechten Seite des Autos montiert. Was das Kameramodul betrifft, habe ich einen Kabelbinder zwischen die Schraubenlöcher eingefügt, wie das obige Bild zeigt. Dann befestige ich die Kamera an einer Holzstange, damit ich die Position der Kamera nach Belieben anpassen kann. Versuchen Sie, die Kamera so weit wie möglich in der Mitte des Autos zu installieren. Ich empfehle, die Kamera mindestens 20 cm über dem Boden zu platzieren, damit das Sichtfeld vor dem Auto besser wird. Das Fritzing-Schema ist unten angehängt.

Schritt 4: Erster Test

Erster Test
Erster Test
Erster Test
Erster Test

Kameratests:

Sobald die Kamera installiert und die OpenCV-Bibliothek erstellt ist, ist es an der Zeit, unser erstes Bild zu testen! Wir nehmen ein Foto von pi cam und speichern es als "original.jpg". Es kann auf 2 Arten erfolgen:

1. Verwenden von Terminalbefehlen:

Öffnen Sie ein neues Terminalfenster und geben Sie den folgenden Befehl ein:

Raspistille -o original.jpg

Dadurch wird ein Standbild erstellt und im Verzeichnis "/pi/original.jpg" gespeichert.

2. Verwenden einer beliebigen Python-IDE (ich verwende IDLE):

Öffnen Sie eine neue Skizze und schreiben Sie den folgenden Code:

CV2 importieren

video = cv2. VideoCapture(0) während True: ret, frame = video.read() frame = cv2.flip(frame, -1) # wird verwendet, um das Bild vertikal zu spiegeln cv2.imshow('original', frame) cv2. imwrite('original.jpg', frame) key = cv2.waitKey(1) if key == 27: break video.release() cv2.destroyAllWindows()

Sehen wir uns an, was in diesem Code passiert ist. Die erste Zeile importiert unsere openCV-Bibliothek, um alle ihre Funktionen nutzen zu können. Die Funktion VideoCapture(0) startet das Streamen eines Live-Videos von der von dieser Funktion bestimmten Quelle, in diesem Fall ist es 0, was raspi-Kamera bedeutet. Wenn Sie mehrere Kameras haben, sollten unterschiedliche Nummern platziert werden. video.read() liest jeden Frame, der von der Kamera kommt, und speichert ihn in einer Variablen namens "frame". Die Funktion flip () dreht das Bild in Bezug auf die y-Achse (vertikal) um, da ich meine Kamera umgekehrt befestige. imshow() zeigt unsere Frames mit dem Wort "original" an und imwrite() speichert unser Foto als original.jpg. waitKey(1) wartet 1 ms auf das Drücken einer beliebigen Tastaturtaste und gibt seinen ASCII-Code zurück. Wenn die Escape-Taste (esc) gedrückt wird, wird ein Dezimalwert von 27 zurückgegeben und die Schleife entsprechend unterbrochen. video.release() stoppt die Aufnahme und DestroyAllWindows() schließt jedes Bild, das durch die Funktion imshow() geöffnet wurde.

Ich empfehle, Ihr Foto mit der zweiten Methode zu testen, um sich mit den openCV-Funktionen vertraut zu machen. Das Bild wird im Verzeichnis "/pi/original.jpg" gespeichert. Das Originalfoto, das meine Kamera aufgenommen hat, ist oben gezeigt.

Motoren testen:

Dieser Schritt ist wichtig, um die Drehrichtung jedes Motors zu bestimmen. Lassen Sie uns zunächst eine kurze Einführung in das Funktionsprinzip eines Motortreibers geben. Das obige Bild zeigt die Pinbelegung des Motortreibers. Freigabe A, Eingang 1 und Eingang 2 sind mit der Steuerung von Motor A verbunden. Freigabe B, Eingang 3 und Eingang 4 sind mit der Steuerung von Motor B verbunden. Die Richtungssteuerung wird durch den "Input"-Teil und die Geschwindigkeitssteuerung durch den "Enable"-Teil hergestellt. Um beispielsweise die Drehrichtung von Motor A zu steuern, setzen Sie Eingang 1 auf HIGH (3,3 V in diesem Fall, da wir einen Himbeer-Pi verwenden) und setzen Sie Eingang 2 auf LOW, der Motor dreht sich in eine bestimmte Richtung und durch Einstellen der entgegengesetzten Werte an Eingang 1 und Eingang 2, dreht sich der Motor in die entgegengesetzte Richtung. Wenn Eingang 1 = Eingang 2 = (HIGH oder LOW), dreht sich der Motor nicht. Enable-Pins nehmen ein Pulsweitenmodulations-(PWM)-Eingangssignal von der Himbeere (0 bis 3,3 V) und betreiben die Motoren entsprechend. Ein PWM-Signal von 100 % bedeutet beispielsweise, dass wir mit der maximalen Geschwindigkeit arbeiten, und ein PWM-Signal von 0 % bedeutet, dass sich der Motor nicht dreht. Der folgende Code wird verwendet, um die Richtungen der Motoren zu bestimmen und ihre Geschwindigkeiten zu testen.

Importzeit

RPi. GPIO als GPIO importieren GPIO.setwarnings(False) # Pins des Lenkmotors Steering_enable = 22 # Physical Pin 15 in1 = 17 # Physical Pin 11 in2 = 27 # Physical Pin 13 #Throttle Motors Pinsthrottle_enable = 25 # Physical Pin 22 in3 = 23 # Physical Pin 16 in4 = 24 # Physical Pin 18 GPIO.setmode(GPIO. BCM) # GPIO-Nummerierung anstelle der physikalischen Nummerierung verwenden GPIO.setup(in1, GPIO.out) GPIO.setup(in2, GPIO.out) GPIO. setup(in3, GPIO.out) GPIO.setup(in4, GPIO.out) GPIO.setup(throttle_enable, GPIO.out) GPIO.setup(steering_enable, GPIO.out) # Lenkmotorsteuerung GPIO.output(in1, GPIO. HIGH) GPIO.output(in2, GPIO. LOW) Steering = GPIO. PWM(steering_enable, 1000) # setze die Schaltfrequenz auf 1000 Hz Steering.stop() # Throttle Motors Control GPIO.output(in3, GPIO. HIGH) GPIO.output(in4, GPIO. LOW)throttle = GPIO. PWM(throttle_enable, 1000) # Schaltfrequenz auf 1000 Hz setzen % PWM-Signal -> (0,25 * Batteriespannung) - Treiber Verlust Steering.start(100) # startet den Motor bei 100% PWM-Signal -> (1 * Batteriespannung) - Fahrerverlust time.sleep(3) Throttle.stop() Steering.stop()

Dieser Code lässt die Drosselmotoren und den Lenkmotor 3 Sekunden lang laufen und stoppt sie dann. Der (Fahrerverlust) kann mit einem Voltmeter bestimmt werden. Wir wissen zum Beispiel, dass ein 100% PWM-Signal die volle Batteriespannung an der Motorklemme liefern sollte. Aber indem ich PWM auf 100% eingestellt habe, habe ich festgestellt, dass der Treiber einen 3-V-Abfall verursacht und der Motor 9 V anstelle von 12 V erhält (genau das, was ich brauche!). Der Verlust ist nicht linear, d. h. der Verlust bei 100 % unterscheidet sich stark von dem Verlust bei 25 %. Nachdem ich den obigen Code ausgeführt hatte, waren meine Ergebnisse wie folgt:

Throttling-Ergebnisse: Wenn in3 = HIGH und in4 = LOW, werden die Drosselmotoren im Uhrzeigersinn (CW) rotieren, d.h. das Auto bewegt sich vorwärts. Andernfalls fährt das Auto rückwärts.

Lenkergebnisse: wenn in1 = HIGH und in2 = LOW, dreht der Lenkmotor maximal nach links, d.h. das Auto lenkt nach links. Andernfalls lenkt das Auto nach rechts. Nach einigen Experimenten habe ich festgestellt, dass sich der Lenkmotor nicht dreht, wenn das PWM-Signal nicht 100% war (dh der Motor lenkt entweder ganz nach rechts oder ganz nach links).

Schritt 5: Spurlinien erkennen und Kurslinie berechnen

Spurlinien erkennen und Kurslinie berechnen
Spurlinien erkennen und Kurslinie berechnen
Spurlinien erkennen und Kurslinie berechnen
Spurlinien erkennen und Kurslinie berechnen
Spurlinien erkennen und Kurslinie berechnen
Spurlinien erkennen und Kurslinie berechnen

In diesem Schritt wird der Algorithmus erklärt, der die Bewegung des Autos steuert. Das erste Bild zeigt den gesamten Vorgang. Die Eingabe des Systems sind Bilder, die Ausgabe ist Theta (Lenkwinkel in Grad). Beachten Sie, dass die Verarbeitung für 1 Bild erfolgt und für alle Frames wiederholt wird.

Kamera:

Die Kamera beginnt mit der Aufnahme eines Videos mit einer Auflösung von (320 x 240). Ich empfehle, die Auflösung zu verringern, damit Sie eine bessere Bildrate (fps) erzielen, da nach der Anwendung von Verarbeitungstechniken auf jeden Frame ein fps-Abfall auftritt. Der folgende Code ist die Hauptschleife des Programms und fügt jeden Schritt über diesen Code hinzu.

CV2 importieren

import numpy as np video = cv2. VideoCapture(0) video.set(cv2. CAP_PROP_FRAME_WIDTH, 320) # setze die Breite auf 320 p video.set(cv2. CAP_PROP_FRAME_HEIGHT, 240) # setze die Höhe auf 240 p # Die Schleife while True: ret, frame = video.read() frame = cv2.flip(frame, -1) cv2.imshow("original", frame) key = cv2.waitKey(1) if key == 27: break video.release () cv2.destroyAllWindows()

Der Code hier zeigt das in Schritt 4 erhaltene Originalbild und wird in den obigen Bildern angezeigt.

In HSV-Farbraum konvertieren:

Nachdem Sie nun die Videoaufnahme als Frames von der Kamera aufgenommen haben, besteht der nächste Schritt darin, jeden Frame in den Farbraum Hue, Saturation und Value (HSV) zu konvertieren. Der Hauptvorteil dabei besteht darin, Farben anhand ihrer Leuchtdichte unterscheiden zu können. Und hier ist eine gute Erklärung des HSV-Farbraums. Die Konvertierung in HSV erfolgt über die folgende Funktion:

def convert_to_HSV(frame):

hsv = cv2.cvtColor(frame, cv2. COLOR_BGR2HSV) cv2.imshow("HSV", hsv) return hsv

Diese Funktion wird von der Hauptschleife aufgerufen und gibt den Frame im HSV-Farbraum zurück. Der von mir erhaltene Frame im HSV-Farbraum ist oben abgebildet.

Blaue Farbe und Kanten erkennen:

Nach der Konvertierung des Bildes in den HSV-Farbraum ist es an der Zeit, nur die Farbe zu erkennen, die uns interessiert (d. h. blaue Farbe, da es die Farbe der Fahrspurlinien ist). Um blaue Farbe aus einem HSV-Rahmen zu extrahieren, sollten ein Bereich von Farbton, Sättigung und Wert angegeben werden. Lesen Sie hier, um eine bessere Vorstellung von HSV-Werten zu erhalten. Nach einigen Experimenten werden die oberen und unteren Grenzen der blauen Farbe im folgenden Code gezeigt. Und um die Gesamtverzerrung in jedem Frame zu reduzieren, werden Kanten nur mit dem Canny-Edge-Detektor erkannt. Mehr über Canny Edge finden Sie hier. Als Faustregel gilt, die Parameter der Canny()-Funktion mit einem Verhältnis von 1:2 oder 1:3 auszuwählen.

def Detect_edges(frame):

lower_blue = np.array([90, 120, 0], dtype = "uint8") # Untergrenze der blauen Farbe upper_blue = np.array([150, 255, 255], dtype="uint8") # Obergrenze von blaue Farbmaske = cv2.inRange(hsv, lower_blue, upper_blue) # diese Maske filtert alles außer Blau heraus # erkennt Kanten Kanten = cv2. Canny(mask, 50, 100) cv2.imshow("Kanten", Kanten) gibt Kanten zurück

Diese Funktion wird auch von der Hauptschleife aufgerufen, die als Parameter den HSV-Farbraumrahmen verwendet und den umrandeten Rahmen zurückgibt. Der umrandete Rahmen, den ich erhalten habe, ist oben zu finden.

Wählen Sie die Region von Interesse (ROI):

Die Auswahl des interessierenden Bereichs ist entscheidend, um nur auf einen Bereich des Rahmens zu fokussieren. In diesem Fall möchte ich nicht, dass das Auto viele Gegenstände in der Umgebung sieht. Ich möchte nur, dass sich das Auto auf die Spurlinien konzentriert und alles andere ignoriert. PS: Das Koordinatensystem (x- und y-Achse) beginnt in der oberen linken Ecke. Mit anderen Worten, der Punkt (0, 0) beginnt in der oberen linken Ecke. y-Achse ist die Höhe und x-Achse ist die Breite. Der folgende Code wählt den interessierenden Bereich aus, um sich nur auf die untere Hälfte des Bildes zu konzentrieren.

def region_of_interest(Kanten):

height, width = edge.shape # extrahiere die Höhe und Breite der Kanten frame mask = np.zeros_like(edges) # erzeuge eine leere Matrix mit den gleichen Abmessungen der Kanten frame # fokussiere nur die untere Hälfte des Bildschirms # spezifiziere die Koordinaten von 4 Punkte (unten links, oben links, oben rechts, unten rechts) polygon = np.array(

Diese Funktion nimmt den umrandeten Rahmen als Parameter und zeichnet ein Polygon mit 4 voreingestellten Punkten. Es konzentriert sich nur auf das, was sich innerhalb des Polygons befindet, und ignoriert alles außerhalb des Polygons. Mein Region-of-Interest-Frame wird oben angezeigt.

Liniensegmente erkennen:

Die Hough-Transformation wird verwendet, um Liniensegmente aus einem kantigen Rahmen zu erkennen. Die Hough-Transformation ist eine Technik, um jede beliebige Form in mathematischer Form zu erkennen. Es kann fast jedes Objekt erkennen, auch wenn es gemäß einer bestimmten Anzahl von Stimmen verzerrt ist. Eine großartige Referenz für die Hough-Transformation wird hier gezeigt. Für diese Anwendung wird die Funktion cv2. HoughLinesP() verwendet, um Linien in jedem Frame zu erkennen. Die wichtigen Parameter, die diese Funktion benötigt, sind:

cv2. HoughLinesP(frame, rho, theta, min_threshold, minLineLength, maxLineGap)

  • Frame: ist der Frame, in dem wir Linien erkennen möchten.
  • rho: Dies ist die Entfernungsgenauigkeit in Pixeln (normalerweise = 1)
  • theta: Winkelgenauigkeit im Bogenmaß (immer = np.pi/180 ~ 1 Grad)
  • min_threshold: minimale Stimme, die es erhalten sollte, damit es als Linie betrachtet wird
  • minLineLength: minimale Zeilenlänge in Pixeln. Jede Zeile, die kürzer als diese Zahl ist, wird nicht als Zeile betrachtet.
  • maxLineGap: maximale Lücke in Pixeln zwischen 2 Zeilen, die als 1 Zeile behandelt werden. (In meinem Fall wird es nicht verwendet, da die von mir verwendeten Fahrspuren keine Lücke haben).

Diese Funktion gibt die Endpunkte einer Linie zurück. Die folgende Funktion wird von meiner Hauptschleife aufgerufen, um Linien mit der Hough-Transformation zu erkennen:

def Detect_line_segments(cropped_edges):

rho = 1 theta = np.pi / 180 min_threshold = 10 line_segments = cv2. HoughLinesP(cropped_edges, rho, theta, min_threshold, np.array(), minLineLength=5, maxLineGap=0) return line_segments

Durchschnittliche Steigung und Schnittpunkt (m, b):

Denken Sie daran, dass die Liniengleichung durch y = mx + b gegeben ist. Dabei ist m die Steigung der Geraden und b der y-Achsenabschnitt. In diesem Teil wird der Durchschnitt von Steigungen und Achsenabschnitten von Liniensegmenten berechnet, die mit der Hough-Transformation erkannt wurden. Bevor wir dies tun, werfen wir einen Blick auf das oben gezeigte Original-Rahmenfoto. Die linke Fahrspur scheint nach oben zu gehen, hat also eine negative Neigung (erinnern Sie sich an den Startpunkt des Koordinatensystems?). Mit anderen Worten hat die linke Fahrspurlinie x1 < x2 und y2 x1 und y2 > y1, was eine positive Steigung ergibt. Alle Linien mit positiver Steigung gelten also als Punkte der rechten Fahrspur. Bei vertikalen Linien (x1 = x2) ist die Steigung unendlich. In diesem Fall überspringen wir alle vertikalen Linien, um einen Fehler zu vermeiden. Um dieser Erkennung mehr Genauigkeit zu verleihen, wird jeder Frame durch 2 Grenzlinien in zwei Bereiche (rechts und links) unterteilt. Alle Breitenpunkte (x-Achsenpunkte), die größer als die rechte Begrenzungslinie sind, werden der Berechnung der rechten Fahrspur zugeordnet. Und wenn alle Breitenpunkte kleiner als die linke Begrenzungslinie sind, werden sie der Berechnung der linken Fahrspur zugeordnet. Die folgende Funktion nimmt den verarbeiteten Rahmen und die mit der Hough-Transformation erkannten Fahrspursegmente und gibt die durchschnittliche Steigung und den Schnittpunkt von zwei Fahrspurlinien zurück.

def Average_slope_intercept(frame, line_segments):

Lane_lines = if line_segments is None: print("kein Liniensegment erkannt") return lane_lines height, width, _ = frame.shape left_fit = right_fit = Boundary = left_region_boundary = width * (1 - Boundary) right_region_boundary = Breite * Grenze für line_segment in line_segments: für x1, y1, x2, y2 in line_segment: if x1 == x2: print("Skipping vertikale Linien (Slope = unendlich)") continue fit = np.polyfit((x1, x2), (y1 append((slope, intercept)) left_fit_average = np.average(left_fit, axis=0) if len(left_fit) > 0: lane_lines.append(make_points(frame, left_fit_average)) right_fit_average = np.average(right_fit, axis=0) if len(right_fit) > 0: lane_lines.append(make_points(frame, right_fit_average)) # lane_lines ist ein 2D-Array bestehend aus den Koordinaten der rechten und linken Spurlinie # zum Beispiel: lan e_lines =

make_points() ist eine Hilfsfunktion für die Funktion mean_slope_intercept(), die die begrenzten Koordinaten der Fahrspurlinien (von unten bis zur Mitte des Rahmens) zurückgibt.

def make_points(Rahmen, Linie):

Höhe, Breite, _ = Rahmen. Form Steigung, Schnittpunkt = Linie y1 = Höhe # Unterkante des Rahmens y2 = int(y1 / 2) # Punkte von der Mitte des Rahmens nach unten setzen, wenn Steigung == 0: Steigung = 0.1 x1 = int((y1 - Achsenabschnitt) / Steigung) x2 = int((y2 - Achsenabschnitt) / Steigung) Rückgabe

Um eine Division durch 0 zu verhindern, wird eine Bedingung angegeben. Wenn Steigung = 0, was y1 = y2 (horizontale Linie) bedeutet, geben Sie der Steigung einen Wert nahe 0. Dies hat keinen Einfluss auf die Leistung des Algorithmus und verhindert den unmöglichen Fall (Teilung durch 0).

Um die Fahrspurlinien auf den Rahmen anzuzeigen, wird die folgende Funktion verwendet:

def display_lines(frame, lines, line_color=(0, 255, 0), line_width=6): # Linienfarbe (B, G, R)

line_image = np.zeros_like(frame) wenn Zeilen nicht None ist: für Zeile in Zeilen: für x1, y1, x2, y2 in Zeile: cv2.line(line_image, (x1, y1), (x2, y2), line_color, line_width) line_image = cv2.addWeighted(frame, 0.8, line_image, 1, 1) return line_image

Die Funktion cv2.addWeighted() nimmt die folgenden Parameter an und wird verwendet, um zwei Bilder zu kombinieren, wobei jedoch jedem eine Gewichtung zugewiesen wird.

cv2.addWeighted(image1, alpha, image2, beta, gamma)

Und berechnet das Ausgabebild mit der folgenden Gleichung:

Ausgabe = Alpha * Bild1 + Beta * Bild2 + Gamma

Weitere Informationen über die Funktion cv2.addWeighted() werden hier abgeleitet.

Kopfzeile berechnen und anzeigen:

Dies ist der letzte Schritt, bevor wir Geschwindigkeiten auf unsere Motoren anwenden. Die Kurslinie ist dafür verantwortlich, dem Lenkmotor die Richtung zu geben, in die er sich drehen soll, und den Drosselmotoren die Geschwindigkeit, mit der sie arbeiten sollen. Die Berechnung der Kurslinie ist reine Trigonometrie, es werden die trigonometrischen Funktionen tan und atan (tan^-1) verwendet. Einige Extremfälle sind, wenn die Kamera nur eine Fahrspurlinie erkennt oder wenn sie keine Linie erkennt. Alle diese Fälle werden in der folgenden Funktion angezeigt:

def get_steering_angle(frame, lane_lines):

height, width, _ = frame.shape if len(lane_lines) == 2: # wenn zwei Lane-Linien erkannt werden _, _, left_x2, _ = Lane_lines[0][0] # linke x2 aus dem Lane_lines-Array extrahieren _, _, right_x2, _ = Lane_lines[1][0] # rechts x2 aus dem Lane_lines-Array extrahieren mid = int(width / 2) x_offset = (left_x2 + right_x2) / 2 - mid y_offset = int(height / 2) elif len(lane_lines) == 1: # wenn nur eine Linie erkannt wird x1, _, x2, _ = Lane_lines[0][0] x_offset = x2 - x1 y_offset = int(height / 2) elif len(lane_lines) == 0: # wenn keine Linie erkannt wird x_offset = 0 y_offset = int(height / 2) angle_to_mid_radian = math.atan(x_offset / y_offset) angle_to_mid_deg = int(angle_to_mid_radian * 180.0 / math.pi) Steering_angle = angle_to_mid_deg. + 90 return Steering_angle

x_offset ist im ersten Fall, wie stark sich der Durchschnitt ((rechts x2 + links x2) / 2) von der Mitte des Bildschirms unterscheidet. y_offset wird immer als Höhe / 2 angenommen. Das letzte Bild oben zeigt ein Beispiel für eine Kurslinie. angle_to_mid_radians ist das gleiche wie "theta", das im letzten Bild oben gezeigt wird. Wenn Steering_angle = 90, bedeutet dies, dass das Auto eine Kurslinie senkrecht zur Linie "Höhe / 2" hat und das Auto ohne Lenkung vorwärts fährt. Wenn Lenkwinkel > 90, sollte das Auto nach rechts lenken, ansonsten sollte es nach links lenken. Um die Kopfzeile anzuzeigen, wird die folgende Funktion verwendet:

def display_heading_line(frame, Steering_angle, line_color=(0, 0, 255), line_width=5)

header_image = np.zeros_like(frame) height, width, _ = frame.shape Steering_angle_radian = Steering_angle / 180.0 * math.pi x1 = int(width / 2) y1 = height x2 = int(x1 - height / 2 / math.tan (steering_angle_radian)) y2 = int(height / 2) cv2.line(heading_image, (x1, y1), (x2, y2), line_color, line_width) header_image = cv2.addWeighted(frame, 0.8, header_image, 1, 1) Überschrift_Bild zurückgeben

Die obige Funktion verwendet den Rahmen, in dem die Kurslinie gezeichnet wird, und den Lenkwinkel als Eingabe. Es gibt das Bild der Überschriftslinie zurück. Der in meinem Fall aufgenommene Überschriftslinienrahmen ist im obigen Bild gezeigt.

Kombinieren des gesamten Codes:

Der Code kann nun zusammengestellt werden. Der folgende Code zeigt die Hauptschleife des Programms, das jede Funktion aufruft:

CV2 importieren

import numpy as np video = cv2. VideoCapture(0) video.set(cv2. CAP_PROP_FRAME_WIDTH, 320) video.set(cv2. CAP_PROP_FRAME_HEIGHT, 240) während True: ret, frame = video.read() frame = cv2.flip(frame, -1) #Aufruf der Funktionen hsv = convert_to_HSV(frame) edge = detect_edges(hsv) roi = region_of_interest(edges) line_segments = detect_line_segments(roi) lane_lines = mean_slope_intercept(frame, line_segments) lane_lines_image = display_lines)(frame,angle lane_lines) = get_steering_angle(frame, lane_lines) header_image = display_heading_line(lane_lines_image, Steering_angle) key = cv2.waitKey(1) if key == 27: break video.release() cv2.destroyAllWindows()

Schritt 6: Anwenden der PD-Kontrolle

Anwenden der PD-Kontrolle
Anwenden der PD-Kontrolle

Jetzt haben wir unseren Lenkwinkel bereit, um den Motoren zugeführt zu werden. Wie bereits erwähnt, sollte das Auto bei einem Lenkwinkel von mehr als 90 nach rechts abbiegen, ansonsten sollte es nach links abbiegen. Ich habe einen einfachen Code angewendet, der den Lenkmotor nach rechts dreht, wenn der Winkel über 90 liegt, und nach links, wenn der Lenkwinkel bei einer konstanten Drosselgeschwindigkeit von (10% PWM) kleiner als 90 ist, aber ich habe viele Fehler bekommen. Der Hauptfehler, den ich erhalten habe, ist, dass der Lenkmotor direkt wirkt, wenn sich das Auto einer Kurve nähert, aber die Drosselmotoren blockieren. Ich habe versucht, die Drosselungsgeschwindigkeit in Kurven auf (20% PWM) zu erhöhen, endete jedoch damit, dass der Roboter die Fahrspuren verließ. Ich brauchte etwas, das die Drosselgeschwindigkeit stark erhöht, wenn der Lenkwinkel sehr groß ist, und die Geschwindigkeit etwas erhöht, wenn der Lenkwinkel nicht so groß ist, und dann die Geschwindigkeit auf einen Anfangswert verringert, wenn sich das Auto 90 Grad nähert (geradeaus). Die Lösung bestand darin, einen PD-Controller zu verwenden.

PID-Regler steht für Proportional-, Integral- und Differenzialregler. Diese Art von Linearsteuerungen wird häufig in Robotikanwendungen verwendet. Das obige Bild zeigt den typischen PID-Regelkreis. Das Ziel dieses Reglers ist es, den "Sollwert" auf möglichst effiziente Weise zu erreichen, im Gegensatz zu "Ein-Aus"-Reglern, die die Anlage je nach Bedingungen ein- oder ausschalten. Einige Stichworte sollten bekannt sein:

  • Sollwert: ist der gewünschte Wert, den Ihr System erreichen soll.
  • Istwert: Ist der vom Sensor erfasste Istwert.
  • Fehler: ist die Differenz zwischen Soll- und Istwert (Fehler = Sollwert - Istwert).
  • Kontrollierte Variable: aus ihrem Namen die Variable, die Sie kontrollieren möchten.
  • Kp: Proportionale Konstante.
  • Ki: Integralkonstante.
  • Kd: Ableitungskonstante.

Kurz gesagt funktioniert der PID-Regelkreis wie folgt:

  • Der Benutzer definiert den Sollwert, den das System erreichen muss.
  • Der Fehler wird berechnet (Fehler = Sollwert - Ist).
  • Der P-Regler erzeugt eine Aktion proportional zum Fehlerwert. (Fehler steigt, P-Aktion steigt auch)
  • Der I-Controller integriert den Fehler im Laufe der Zeit, wodurch der stationäre Fehler des Systems beseitigt wird, aber sein Überschwingen erhöht wird.
  • D-Regler ist einfach die zeitliche Ableitung für den Fehler. Mit anderen Worten, es ist die Steigung des Fehlers. Es führt eine Aktion proportional zur Ableitung des Fehlers aus. Dieser Controller erhöht die Stabilität des Systems.
  • Die Ausgabe des Controllers ist die Summe der drei Controller. Der Ausgang des Controllers wird 0, wenn der Fehler 0 wird.

Eine großartige Erklärung zum PID-Regler finden Sie hier.

Zurück zum Spurhalteauto war meine gesteuerte Variable die Drosselgeschwindigkeit (da die Lenkung nur zwei Zustände hat, entweder rechts oder links). Zu diesem Zweck wird ein PD-Regler verwendet, da D-Aktion die Drosselgeschwindigkeit stark erhöht, wenn die Fehleränderung sehr groß ist (dh große Abweichung) und das Auto verlangsamt, wenn diese Fehleränderung gegen 0 geht. Ich habe die folgenden Schritte durchgeführt, um ein PD zu implementieren Regler:

  • Setze den Sollwert auf 90 Grad (ich möchte immer, dass das Auto geradeaus fährt)
  • Berechnet den Abweichungswinkel von der Mitte
  • Die Abweichung gibt zwei Informationen: Wie groß der Fehler ist (Abweichungsbetrag) und welche Richtung der Lenkmotor einschlagen muss (Abweichungszeichen). Bei positiver Abweichung soll das Auto nach rechts lenken, ansonsten nach links.
  • Da die Abweichung entweder negativ oder positiv ist, wird eine "Fehler"-Variable definiert und immer gleich dem Absolutwert der Abweichung.
  • Der Fehler wird mit einem konstanten Kp multipliziert.
  • Der Fehler erfährt eine Zeitdifferenzierung und wird mit einer konstanten Kd multipliziert.
  • Die Motordrehzahl wird aktualisiert und die Schleife beginnt erneut.

Der folgende Code wird in der Hauptschleife verwendet, um die Geschwindigkeit der Drosselmotoren zu steuern:

Drehzahl = 10 # Betriebsdrehzahl in % PWM

#Variablen, die in jeder Schleife aktualisiert werden sollen lastTime = 0 lastError = 0 # PD-Konstanten Kp = 0,4 Kd = Kp * 0,65 Während True: now = time.time() # aktuelle Zeitvariable dt = now - lastTime Abweichung = Steering_angle - 90 # Äquivalent to angle_to_mid_deg variable error = abs(deviation) if deviation -5: # nicht lenken bei 10 Grad Fehler Bereichsabweichung = 0 error = 0 GPIO.output(in1, GPIO. LOW) GPIO.output(in2, GPIO. LOW) Steering.stop() Elif-Abweichung > 5: # Nach rechts lenken, wenn die Abweichung positiv ist -5: # bei negativer Abweichung nach links lenken GPIO.output(in1, GPIO. HIGH) GPIO.output(in2, GPIO. LOW) Steering.start(100) Ableitung = kd * (error - lastError) / dt proportional = kp * Fehler PD = int(Drehzahl + Ableitung + Proportional) spd = abs(PD) wenn spd> 25: spd = 25 Drossel.start(spd) lastError = Fehler lastTime = time.time()

Wenn der Fehler sehr groß ist (die Abweichung von der Mitte ist groß), sind die proportionalen und abgeleiteten Aktionen hoch, was zu einer hohen Drosselgeschwindigkeit führt. Wenn sich der Fehler 0 nähert (Abweichung von der Mitte ist gering), wirkt die Differentialwirkung umgekehrt (Steigung ist negativ) und die Drosselgeschwindigkeit wird niedrig, um die Stabilität des Systems aufrechtzuerhalten. Der vollständige Code ist unten angehängt.

Schritt 7: Ergebnisse

Die Videos oben zeigen die Ergebnisse, die ich erhalten habe. Es braucht mehr Tuning und weitere Anpassungen. Ich habe den Himbeer-Pi an meinen LCD-Bildschirm angeschlossen, weil das Video-Streaming über mein Netzwerk eine hohe Latenz hatte und die Arbeit sehr frustrierend war. Aus diesem Grund sind im Video Kabel mit dem Himbeer-Pi verbunden. Ich benutzte Schaumstoffplatten, um die Spur zu zeichnen.

Ich warte auf Ihre Empfehlungen, um dieses Projekt zu verbessern! Da ich hoffe, dass diese instructables gut genug waren, um Ihnen einige neue Informationen zu geben.

Empfohlen: