Inhoudsopgave:
- Stap 1: Componenten verzamelen
- Stap 2: OpenCV installeren op Raspberry Pi en Remote Display instellen
- Stap 3: Onderdelen met elkaar verbinden
- Stap 4: Eerste test
- Stap 5: Rijstrooklijnen detecteren en koerslijn berekenen
- Stap 6: PD-controle toepassen
- Stap 7: Resultaten
Video: Autonome Lane-Keeping Car met Raspberry Pi en OpenCV - Ajarnpa
2024 Auteur: John Day | [email protected]. Laatst gewijzigd: 2024-01-30 11:16
In deze instructable wordt een autonome rijbaanrobot geïmplementeerd die de volgende stappen doorloopt:
- Onderdelen verzamelen
- Vereisten voor het installeren van software
- Hardware-assemblage:
- Eerste test
- Rijstrooklijnen detecteren en de hulplijn weergeven met openCV
- Een PD-controller implementeren
- Resultaten
Stap 1: Componenten verzamelen
De afbeeldingen hierboven tonen alle componenten die in dit project worden gebruikt:
- RC-auto: ik heb de mijne gekocht bij een plaatselijke winkel in mijn land. Hij is uitgerust met 3 motoren (2 voor smoren en 1 voor sturen). Het grootste nadeel van deze auto is dat de besturing beperkt is tussen "niet sturen" en "vol sturen". Met andere woorden, hij kan niet in een bepaalde hoek sturen, in tegenstelling tot servogestuurde RC-auto's. U kunt hier vergelijkbare carkits vinden die speciaal voor Raspberry Pi zijn ontworpen.
- Raspberry pi 3 model b+: dit is het brein van de auto dat veel verwerkingsstadia aankan. Het is gebaseerd op een quad-core 64-bits processor geklokt op 1,4 GHz. Ik heb de mijne hier vandaan.
- Raspberry pi 5 mp cameramodule: het ondersteunt 1080p @ 30 fps, 720p @ 60 fps en 640x480p 60/90 opname. Het ondersteunt ook een seriële interface die rechtstreeks op de Raspberry Pi kan worden aangesloten. Het is niet de beste optie voor beeldverwerkingstoepassingen, maar het is voldoende voor dit project en het is ook erg goedkoop. Ik heb de mijne hier vandaan.
- Motor Driver: Wordt gebruikt om de richtingen en snelheden van de DC-motoren te regelen. Het ondersteunt de aansturing van 2 gelijkstroommotoren in 1 bord en is bestand tegen 1,5 A.
- Powerbank (optioneel): ik heb een powerbank (nominaal 5V, 3A) gebruikt om de Raspberry Pi afzonderlijk van stroom te voorzien. Een step down converter (buck converter: 3A uitgangsstroom) moet gebruikt worden om de Raspberry Pi van 1 bron op te laden.
- 3s (12 V) LiPo-batterij: Lithium-polymeerbatterijen staan bekend om hun uitstekende prestaties op het gebied van robotica. Het wordt gebruikt om de motordriver van stroom te voorzien. Ik heb de mijne hier gekocht.
- Overbruggingsdraden van mannelijk naar mannelijk en vrouwelijk naar vrouwelijk.
- Dubbelzijdige tape: wordt gebruikt om de componenten op de RC-auto te monteren.
- Blauwe tape: Dit is een zeer belangrijk onderdeel van dit project, het wordt gebruikt om de twee rijstroken te maken waar de auto tussen zal rijden. Je kunt elke gewenste kleur kiezen, maar ik raad aan om andere kleuren te kiezen dan die in de omgeving.
- Ritssluitingen en houten staven.
- Schroevendraaier.
Stap 2: OpenCV installeren op Raspberry Pi en Remote Display instellen
Deze stap is een beetje vervelend en zal enige tijd duren.
OpenCV (Open source Computer Vision) is een open source computer vision en machine learning softwarebibliotheek. De bibliotheek heeft meer dan 2500 geoptimaliseerde algoritmen. Volg DEZE zeer eenvoudige handleiding om de openCV op je Raspberry Pi te installeren en om het Raspberry Pi OS te installeren (als je dat nog niet deed). Houd er rekening mee dat het proces van het bouwen van de openCV ongeveer 1,5 uur kan duren in een goed gekoelde ruimte (aangezien de temperatuur van de processor erg hoog zal worden!) dus drink wat thee en wacht geduldig:D.
Voor de externe weergave volgt u ook DEZE gids om externe toegang tot uw raspberry pi in te stellen vanaf uw Windows/Mac-apparaat.
Stap 3: Onderdelen met elkaar verbinden
De afbeeldingen hierboven tonen de verbindingen tussen raspberry pi, cameramodule en motordriver. Houd er rekening mee dat de motoren die ik heb gebruikt 0,35 A bij 9 V absorberen, waardoor het veilig is voor de motorbestuurder om 3 motoren tegelijkertijd te laten draaien. En omdat ik de snelheid van de 2 smoormotoren (1 achter en 1 voor) op precies dezelfde manier wil regelen, heb ik ze op dezelfde poort aangesloten. Ik monteerde de motor driver aan de rechterkant van de auto met behulp van dubbele tape. Wat de cameramodule betreft, heb ik een ritssluiting tussen de schroefgaten gestoken, zoals de afbeelding hierboven laat zien. Vervolgens plaats ik de camera op een houten balk, zodat ik de positie van de camera naar wens kan aanpassen. Probeer de camera zoveel mogelijk in het midden van de auto te installeren. Ik raad aan om de camera minimaal 20 cm boven de grond te plaatsen zodat het gezichtsveld voor de auto beter wordt. Het Fritzing-schema is hieronder bijgevoegd.
Stap 4: Eerste test
Cameratesten:
Zodra de camera is geïnstalleerd en de openCV-bibliotheek is gebouwd, is het tijd om onze eerste afbeelding te testen! We nemen een foto van pi cam en slaan deze op als "origineel.jpg". Het kan op 2 manieren:
1. Terminal-opdrachten gebruiken:
Open een nieuw terminalvenster en typ de volgende opdracht:
raspistill -o origineel.jpg
Hiermee wordt een stilstaand beeld gemaakt en opgeslagen in de map "/pi/original.jpg".
2. Elke python-IDE gebruiken (ik gebruik IDLE):
Open een nieuwe schets en schrijf de volgende code:
import cv2
video = cv2. VideoCapture(0) while True: ret, frame = video.read() frame = cv2.flip(frame, -1) # gebruikt om de afbeelding verticaal te spiegelen cv2.imshow('original', frame) cv2. imwrite('original.jpg', frame) key = cv2.waitKey(1) if key == 27: break video.release() cv2.destroyAllWindows()
Laten we eens kijken wat er in deze code is gebeurd. De eerste regel is het importeren van onze openCV-bibliotheek om al zijn functies te gebruiken. de functie VideoCapture(0) begint met het streamen van een live video vanaf de bron die door deze functie wordt bepaald, in dit geval is het 0, wat raspi-camera betekent. als je meerdere camera's hebt, moeten er verschillende nummers worden geplaatst. video.read() leest elk frame dat van de camera komt en slaat het op in een variabele genaamd "frame". flip() functie zal de afbeelding spiegelen ten opzichte van de y-as (verticaal) aangezien ik mijn camera omgekeerd monteer. imshow() geeft onze frames weer met het woord "origineel" en imwrite() slaat onze foto op als original.jpg. waitKey(1) wacht 1 ms totdat een willekeurige toetsenbordknop wordt ingedrukt en geeft de ASCII-code terug. als de escape-knop (esc) wordt ingedrukt, wordt een decimale waarde van 27 geretourneerd en wordt de lus dienovereenkomstig verbroken. video.release() stopt met opnemen en destroyAllWindows() sluit elke afbeelding die wordt geopend door de functie imshow().
Ik raad aan om je foto te testen met de tweede methode om vertrouwd te raken met de openCV-functies. De afbeelding wordt opgeslagen in de map "/pi/original.jpg". De originele foto die mijn camera nam, staat hierboven.
Motoren testen:
Deze stap is essentieel om de draairichting van elke motor te bepalen. Laten we eerst een korte introductie geven over het werkingsprincipe van een motorbestuurder. De afbeelding hierboven toont de pin-out van de motordriver. Enable A, Input 1 en Input 2 zijn gekoppeld aan motor A-besturing. Enable B, Input 3 en Input 4 zijn gekoppeld aan motor B-besturing. De richtingsregeling wordt tot stand gebracht door het gedeelte "Ingang" en de snelheidsregeling wordt tot stand gebracht door het gedeelte "Inschakelen". Om bijvoorbeeld de richting van motor A te regelen, stelt u ingang 1 in op HOOG (3,3 V in dit geval omdat we een raspberry pi gebruiken) en stelt u ingang 2 in op LAAG, de motor zal in een specifieke richting draaien en door de tegenovergestelde waarden in te stellen naar Ingang 1 en Ingang 2, zal de motor in de tegenovergestelde richting draaien. Als ingang 1 = ingang 2 = (HOOG of LAAG), draait de motor niet. Schakel pinnen nemen een Puls Breedte Modulatie (PWM) ingangssignaal van de Raspberry (0 tot 3,3 V) en laten de motoren dienovereenkomstig draaien. Een 100% PWM-signaal betekent bijvoorbeeld dat we werken aan de maximale snelheid en 0% PWM-signaal betekent dat de motor niet draait. De volgende code wordt gebruikt om de richtingen van motoren te bepalen en hun snelheden te testen.
import tijd
importeer RPi. GPIO als GPIO GPIO.setwarnings(False) # Stuurmotorpinnen steering_enable = 22 # Fysieke pin 15 in1 = 17 # Fysieke pin 11 in2 = 27 # Fysieke pin 13 #Throttle Motors-pinnen throttle_enable = 25 # Fysieke pin 22 in3 = 23 # Fysieke pin 16 in4 = 24 # Fysieke pin 18 GPIO.setmode(GPIO. BCM) # Gebruik GPIO-nummering in plaats van fysieke nummering 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) # Stuurmotorbesturing GPIO.output(in1, GPIO. HOOG) GPIO.output(in2, GPIO. LOW) besturing = GPIO. PWM(steering_enable, 1000) # stel de schakelfrequentie in op 1000 Hz steering.stop() # Throttle Motors Control GPIO.output (in3, GPIO. HIGH) GPIO.output(in4, GPIO. LOW) throttle = GPIO. PWM(throttle_enable, 1000) # stel de schakelfrequentie in op 1000 Hz throttle.stop() time.sleep(1) throttle.start(25) # start de motor op 25 % PWM-signaal-> (0.25 * batterijspanning) - driver's loss steering.start(100) # start de motor op 100% PWM-signaal-> (1 * accuspanning) - verlies van de bestuurder time.sleep(3) throttle.stop() steering.stop()
Deze code laat de smoormotoren en de stuurmotor 3 seconden draaien en stopt ze dan. De (driver's loss) kan worden bepaald met behulp van een voltmeter. We weten bijvoorbeeld dat een 100% PWM-signaal de volledige batterijspanning op de motorterminal moet geven. Maar door PWM in te stellen op 100%, ontdekte ik dat de driver een daling van 3 V veroorzaakt en dat de motor 9 V krijgt in plaats van 12 V (precies wat ik nodig heb!). Het verlies is niet lineair, d.w.z. het verlies bij 100% is heel anders dan het verlies bij 25%. Na het uitvoeren van de bovenstaande code waren mijn resultaten als volgt:
Throttling-resultaten: als in3 = HOOG en in4 = LAAG, zullen de smoormotoren met de klok mee (CW) draaien, d.w.z. de auto zal vooruit rijden. Anders zal de auto achteruit rijden.
Besturingsresultaten: als in1 = HOOG en in2 = LAAG, dan draait de stuurmotor maximaal naar links, d.w.z. de auto stuurt naar links. Anders stuurt de auto naar rechts. Na wat experimenten ontdekte ik dat de stuurmotor niet zal draaien als het PWM-signaal niet 100% was (d.w.z. de motor zal ofwel volledig naar rechts of volledig naar links sturen).
Stap 5: Rijstrooklijnen detecteren en koerslijn berekenen
In deze stap wordt het algoritme uitgelegd dat de beweging van de auto regelt. De eerste afbeelding toont het hele proces. De invoer van het systeem is afbeeldingen, de uitvoer is theta (stuurhoek in graden). Merk op dat de verwerking op 1 afbeelding wordt gedaan en op alle frames wordt herhaald.
Camera:
De camera begint met het opnemen van een video met (320 x 240) resolutie. Ik raad aan om de resolutie te verlagen, zodat je een betere framesnelheid (fps) krijgt, omdat fps-daling optreedt na het toepassen van verwerkingstechnieken op elk frame. De onderstaande code is de hoofdlus van het programma en voegt elke stap toe aan deze code.
import cv2
import numpy as np video = cv2. VideoCapture(0) video.set(cv2. CAP_PROP_FRAME_WIDTH, 320) # stel de breedte in op 320 p video.set(cv2. CAP_PROP_FRAME_HEIGHT, 240) # stel de hoogte in op 240 p # The loop 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()
De code hier toont de originele afbeelding die in stap 4 is verkregen en wordt weergegeven in de bovenstaande afbeeldingen.
Converteren naar HSV-kleurruimte:
Nadat u nu video-opnamen hebt gemaakt als frames van de camera, is de volgende stap het converteren van elk frame naar de kleurruimte Tint, Verzadiging en Waarde (HSV). Het belangrijkste voordeel hiervan is dat u kleuren kunt onderscheiden op basis van hun helderheidsniveau. En hier is een goede uitleg van HSV-kleurruimte. Het converteren naar HSV gaat via de volgende functie:
def convert_to_HSV(frame):
hsv = cv2.cvtColor(frame, cv2. COLOR_BGR2HSV) cv2.imshow("HSV", hsv) retourneer hsv
Deze functie wordt aangeroepen vanuit de hoofdlus en retourneert het frame in HSV-kleurruimte. Het door mij verkregen frame in HSV-kleurruimte is hierboven weergegeven.
Detecteer blauwe kleur en randen:
Nadat de afbeelding in HSV-kleurruimte is geconverteerd, is het tijd om alleen de kleur te detecteren waarin we geïnteresseerd zijn (d.w.z. blauwe kleur omdat dit de kleur is van de rijstrooklijnen). Om een blauwe kleur uit een HSV-frame te extraheren, moet een reeks van tint, verzadiging en waarde worden gespecificeerd. zie hier voor een beter idee over HSV-waarden. Na wat experimenten worden de boven- en ondergrenzen van blauwe kleur weergegeven in de onderstaande code. En om de algehele vervorming in elk frame te verminderen, worden randen alleen gedetecteerd met behulp van de canny edge-detector. Meer over canny edge vind je hier. Een vuistregel is om de parameters van de Canny()-functie te selecteren met een verhouding van 1:2 of 1:3.
def detect_edges(frame):
lower_blue = np.array([90, 120, 0], dtype = "uint8") # ondergrens van blauwe kleur upper_blue = np.array([150, 255, 255], dtype="uint8") # bovengrens van blauwe kleur mask = cv2.inRange(hsv, lower_blue, upper_blue) # dit masker filtert alles behalve blauw # detect edge edge = cv2. Cranny(mask, 50, 100) cv2.imshow("edges", edge) return edge
Deze functie wordt ook aangeroepen vanuit de hoofdlus die als parameter het HSV-kleurruimteframe neemt en het edged frame retourneert. Het frame met randen dat ik heb verkregen, is hierboven te vinden.
Selecteer interessegebied (ROI):
Het selecteren van het interessegebied is cruciaal om alleen op 1 regio van het frame te focussen. In dit geval wil ik niet dat de auto veel voorwerpen in de omgeving ziet. Ik wil gewoon dat de auto zich op de rijstrooklijnen concentreert en al het andere negeert. P. S: het coördinatensysteem (x- en y-assen) begint in de linkerbovenhoek. Met andere woorden, het punt (0, 0) begint in de linkerbovenhoek. y-as is de hoogte en x-as is de breedte. De onderstaande code selecteert het interessegebied om alleen op de onderste helft van het frame te focussen.
def region_of_interest(edges):
hoogte, breedte = randen.vorm # extraheer de hoogte en breedte van de randen frame mask = np.zeros_like(edges) # maak een lege matrix met dezelfde afmetingen van het randen frame # focus alleen de onderste helft van het scherm # specificeer de coördinaten van 4 punten (linksonder, linksboven, rechtsboven, rechtsonder) polygoon = np.array(
Deze functie neemt het frame met randen als parameter en tekent een polygoon met 4 vooraf ingestelde punten. Het zal zich alleen concentreren op wat zich binnen de polygoon bevindt en alles daarbuiten negeren. Het frame van mijn interessegebied wordt hierboven weergegeven.
Lijnsegmenten detecteren:
Hough-transformatie wordt gebruikt om lijnsegmenten van een frame met randen te detecteren. Hough-transformatie is een techniek om elke vorm in wiskundige vorm te detecteren. Het kan bijna elk object detecteren, zelfs als het volgens een bepaald aantal stemmen vervormd is. een geweldige referentie voor Hough-transformatie wordt hier getoond. Voor deze toepassing wordt de functie cv2. HoughLinesP() gebruikt om lijnen in elk frame te detecteren. De belangrijke parameters die deze functie nodig heeft zijn:
cv2. HoughLinesP(frame, rho, theta, min_threshold, minLineLength, maxLineGap)
- Frame: is het frame waarin we lijnen willen detecteren.
- rho: Het is de afstandsprecisie in pixels (meestal is dit = 1)
- theta: hoekprecisie in radialen (altijd = np.pi/180 ~ 1 graad)
- min_threshold: minimum aantal stemmen dat het moet krijgen om als een regel te worden beschouwd
- minLineLength: minimale lengte van de lijn in pixels. Elke regel die korter is dan dit nummer wordt niet als een regel beschouwd.
- maxLineGap: maximale afstand in pixels tussen 2 lijnen die als 1 lijn moeten worden behandeld. (Het wordt in mijn geval niet gebruikt omdat de rijstrooklijnen die ik gebruik geen opening hebben).
Deze functie retourneert de eindpunten van een lijn. De volgende functie wordt aangeroepen vanuit mijn hoofdlus om lijnen te detecteren met behulp van Hough-transformatie:
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
Gemiddelde helling en snijpunt (m, b):
herinner eraan dat de vergelijking van de lijn wordt gegeven door y = mx + b. Waar m de helling van de lijn is en b het y-snijpunt is. In dit deel wordt het gemiddelde berekend van hellingen en intercepts van lijnsegmenten die zijn gedetecteerd met Hough-transformatie. Laten we, voordat we dit doen, eens kijken naar de originele framefoto hierboven. De linkerbaan lijkt omhoog te gaan en heeft dus een negatieve helling (herinner je je het startpunt van het coördinatensysteem?). Met andere woorden, de linker rijstrook heeft x1 < x2 en y2 x1 en y2 > y1 wat een positieve helling geeft. Dus alle lijnen met een positieve helling worden beschouwd als rechterbaanpunten. Bij verticale lijnen (x1 = x2) is de helling oneindig. In dit geval zullen we alle verticale regels overslaan om een fout te voorkomen. Om meer nauwkeurigheid aan deze detectie toe te voegen, is elk frame verdeeld in twee gebieden (rechts en links) via 2 grenslijnen. Alle breedtepunten (x-aspunten) die groter zijn dan de rechtergrenslijn, worden geassocieerd met de rechterrijstrookberekening. En als alle breedtepunten kleiner zijn dan de linkergrenslijn, worden ze geassocieerd met de berekening van de linkerrijstrook. De volgende functie neemt het frame onder verwerking en baansegmenten die zijn gedetecteerd met Hough-transformatie en retourneert de gemiddelde helling en het snijpunt van twee baanlijnen.
def average_slope_intercept(frame, line_segments):
lane_lines = if line_segments is None: print("geen lijnsegment gedetecteerd") return lane_lines height, width, _ = frame.shape left_fit = right_fit = grens = left_region_boundary = breedte * (1 - grens) right_region_boundary = breedte * grens voor lijnsegment in lijnsegmenten: voor x1, y1, x2, y2 in lijnsegment: if x1 == x2: print("verticale lijnen overslaan (helling = oneindig)") continue fit = np.polyfit((x1, x2), (y1, y2), 1) helling = (y2 - y1) / (x2 - x1) snijpunt = y1 - (helling * x1) als helling < 0: als x1 < left_region_boundary en x2 right_region_boundary en x2 > right_region_boundary: right_fit. append((helling, snijpunt)) 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 is een 2D-array bestaande uit de coördinaten van de rechter- en linkerrijstrooklijnen # bijvoorbeeld: lan e_lines =
make_points() is een hulpfunctie voor de functie average_slope_intercept() die de begrensde coördinaten van de rijstrooklijnen teruggeeft (van de onderkant naar het midden van het frame).
def make_points(frame, lijn):
hoogte, breedte, _ = frame.shape helling, snijpunt = lijn y1 = hoogte # onderkant van het frame y2 = int(y1 / 2) # maak punten vanaf het midden van het frame naar beneden als helling == 0: helling = 0,1 x1 = int((y1 - snijpunt) / helling) x2 = int((y2 - snijpunt) / helling) return
Om delen door 0 te voorkomen, wordt een voorwaarde gepresenteerd. Als helling = 0, wat betekent y1 = y2 (horizontale lijn), geef de helling dan een waarde in de buurt van 0. Dit heeft geen invloed op de prestaties van het algoritme en voorkomt ook onmogelijke gevallen (delen door 0).
Om de rijstrooklijnen op de frames weer te geven, wordt de volgende functie gebruikt:
def display_lines(frame, lines, line_color=(0, 255, 0), line_width=6): # lijnkleur (B, G, R)
line_image = np.zeros_like(frame) als lijnen niet Geen zijn: voor lijn in lijnen: voor x1, y1, x2, y2 in lijn: 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
cv2.addWeighted() functie neemt de volgende parameters en wordt gebruikt om twee afbeeldingen te combineren, maar met elk een gewicht.
cv2.addWeighted(afbeelding1, alfa, afbeelding2, bèta, gamma)
En berekent het uitvoerbeeld met behulp van de volgende vergelijking:
output = alfa * afbeelding1 + bèta * afbeelding2 + gamma
Meer informatie over de functie cv2.addWeighted() vindt u hier.
Koersregel berekenen en weergeven:
Dit is de laatste stap voordat we snelheden op onze motoren toepassen. De koerslijn is verantwoordelijk om de stuurmotor de richting te geven waarin deze moet draaien en geeft de smoormotoren de snelheid waarmee ze zullen werken. Het berekenen van de koerslijn is pure trigonometrie, tan en atan (tan^-1) trigonometrische functies worden gebruikt. Sommige extreme gevallen zijn wanneer de camera slechts één rijstrooklijn detecteert of wanneer deze geen enkele lijn detecteert. Al deze gevallen worden weergegeven in de volgende functie:
def get_steering_angle(frame, lane_lines):
hoogte, breedte, _ = frame.shape if len(lane_lines) == 2: # als er twee rijstroken worden gedetecteerd _, _, left_x2, _ = lane_lines[0][0] # extraheer left x2 uit array lane_lines _, _, right_x2, _ = lane_lines[1][0] # extraheer rechts x2 uit lane_lines array mid = int(width / 2) x_offset = (left_x2 + right_x2) / 2 - mid y_offset = int(height / 2) elif len(lane_lines) == 1: # als er maar één regel wordt gedetecteerd x1, _, x2, _ = lane_lines[0][0] x_offset = x2 - x1 y_offset = int(height / 2) elif len(lane_lines) == 0: # als er geen lijn wordt gedetecteerd 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 stuurhoek
x_offset in het eerste geval is hoeveel het gemiddelde ((rechts x2 + links x2) / 2) afwijkt van het midden van het scherm. y_offset wordt altijd als hoogte / 2 beschouwd. De laatste afbeelding hierboven toont een voorbeeld van een koerslijn. angle_to_mid_radians is hetzelfde als "theta" getoond in de laatste afbeelding hierboven. Als steering_angle = 90, betekent dit dat de auto een koerslijn heeft loodrecht op de "hoogte / 2"-lijn en dat de auto vooruit rijdt zonder te sturen. Als stuurhoek > 90 moet de auto naar rechts sturen, anders moet hij naar links sturen. Om de koersregel weer te geven, wordt de volgende functie gebruikt:
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 = stuurhoek / 180.0 * math.pi x1 = int(breedte / 2) y1 = hoogte x2 = int(x1 - hoogte / 2 / math.tan (stuurhoek_radiaal)) y2 = int(hoogte / 2) cv2.line(heading_image, (x1, y1), (x2, y2), line_color, line_width) heading_image = cv2.addWeighted(frame, 0.8, heading_image, 1, 1) return header_image
De functie hierboven neemt het frame waarin de koerslijn wordt getekend en de stuurhoek als invoer. Het retourneert het beeld van de koerslijn. Het frame van de koerslijn dat in mijn geval is genomen, wordt weergegeven in de bovenstaande afbeelding.
Alle codes combineren:
De code is nu klaar om te worden geassembleerd. De volgende code toont de hoofdlus van het programma dat elke functie aanroept:
import cv2
importeer numpy als np video = cv2. VideoCapture(0) video.set(cv2. CAP_PROP_FRAME_WIDTH, 320) video.set(cv2. CAP_PROP_FRAME_HEIGHT, 240) while True: ret, frame = video.read() frame = cv2.flip(frame, -1) #Aanroepen van de functies hsv = convert_to_HSV(frame) edge = detect_edges(hsv) roi = region_of_interest(edges) line_segments = detect_line_segments(roi) lane_lines = gemiddeld_helling_intercept(frame, line_segments) lane_lines_image = display_lines(angle_lines) stuurframe = get_steering_angle(frame, lane_lines) heading_image = display_heading_line(lane_lines_image, steering_angle) key = cv2.waitKey(1) if key == 27: break video.release() cv2.destroyAllWindows()
Stap 6: PD-controle toepassen
Nu hebben we onze stuurhoek klaar om naar de motoren te worden gevoerd. Zoals eerder vermeld, als de stuurhoek groter is dan 90, moet de auto naar rechts draaien, anders moet hij naar links. Ik heb een eenvoudige code toegepast die de stuurmotor naar rechts draait als de hoek groter is dan 90 en naar links draait als de stuurhoek kleiner is dan 90 bij een constante smoorsnelheid van (10% PWM) maar ik kreeg veel fouten. De belangrijkste fout die ik kreeg, is dat wanneer de auto een bocht nadert, de stuurmotor direct werkt, maar de smoormotoren vastlopen. Ik probeerde de smoorsnelheid te verhogen tot (20% PWM) in bochten, maar eindigde ermee dat de robot uit de rijstroken kwam. Ik had iets nodig dat de gasklepsnelheid veel verhoogt als de stuurhoek erg groot is en de snelheid een beetje verhoogt als de stuurhoek niet zo groot is en vervolgens de snelheid verlaagt naar een beginwaarde als de auto 90 graden nadert (rechtuit rijdend). De oplossing was om een PD-controller te gebruiken.
PID-controller staat voor Proportional, Integral en Derivative controller. Dit type lineaire controllers wordt veel gebruikt in robotica-toepassingen. De afbeelding hierboven toont de typische PID-feedbackregellus. Het doel van deze controller is om het "instelpunt" op de meest efficiënte manier te bereiken, in tegenstelling tot "aan-uit"-controllers die de installatie onder bepaalde omstandigheden in- of uitschakelen. Sommige trefwoorden moeten bekend zijn:
- Setpoint: is de gewenste waarde die uw systeem moet bereiken.
- Werkelijke waarde: is de werkelijke waarde die door de sensor wordt gedetecteerd.
- Error: is het verschil tussen setpoint en actuele waarde (error = Setpoint - Actuele waarde).
- Gecontroleerde variabele: van zijn naam, de variabele die u wilt controleren.
- Kp: Proportionele constante.
- Ki: Integrale constante.
- Kd: Afgeleide constante.
In het kort werkt de PID-regelsysteemlus als volgt:
- De gebruiker definieert het setpoint dat het systeem nodig heeft om te bereiken.
- De fout wordt berekend (fout = setpoint - actueel).
- P-controller genereert een actie die evenredig is met de waarde van de fout. (fout neemt toe, P actie neemt ook toe)
- I-controller zal de fout in de loop van de tijd integreren, waardoor de steady-state-fout van het systeem wordt geëlimineerd, maar de overschrijding ervan toeneemt.
- D-controller is gewoon de tijdsafgeleide voor de fout. Met andere woorden, het is de helling van de fout. Het voert een actie uit die evenredig is aan de afgeleide van de fout. Deze controller verhoogt de stabiliteit van het systeem.
- De output van de controller is de som van de drie controllers. De uitgang van de controller wordt 0 als de fout 0 wordt.
Een goede uitleg van de PID-controller vindt u hier.
Teruggaand naar de rijbaanauto, mijn gecontroleerde variabele was de snelheid van het smoren (aangezien het sturen slechts twee standen heeft, rechts of links). Voor dit doel wordt een PD-controller gebruikt, omdat D-actie de smoorsnelheid veel verhoogt als de foutverandering erg groot is (dwz grote afwijking) en de auto vertraagt als deze foutverandering 0 nadert. Ik heb de volgende stappen uitgevoerd om een PD te implementeren controleur:
- Stel het setpoint in op 90 graden (ik wil altijd dat de auto rechtdoor rijdt)
- Berekende de afwijkingshoek vanuit het midden
- De afwijking geeft twee informatie: hoe groot de fout is (grootte van afwijking) en welke richting de stuurmotor moet nemen (teken van afwijking). Als de afwijking positief is, moet de auto naar rechts sturen, anders moet hij naar links sturen.
- Aangezien de afwijking negatief of positief is, wordt een "fout"-variabele gedefinieerd en altijd gelijk aan de absolute waarde van de afwijking.
- De fout wordt vermenigvuldigd met een constante Kp.
- De fout ondergaat tijddifferentiatie en wordt vermenigvuldigd met een constante Kd.
- De snelheid van de motor wordt bijgewerkt en de lus begint opnieuw.
De volgende code wordt in de hoofdlus gebruikt om de snelheid van de smoormotoren te regelen:
snelheid = 10 # bedrijfssnelheid in % PWM
#Variabelen die elke lus moeten worden bijgewerkt lastTime = 0 lastError = 0 # PD-constanten Kp = 0,4 Kd = Kp * 0,65 While True: now = time.time() # huidige tijdvariabele dt = now - lastTime afwijking = stuurhoek - 90 # equivalent to angle_to_mid_deg variabele error = abs(deviation) if afwijking -5: # stuur niet als er een 10-graden error range afwijking is = 0 error = 0 GPIO.output(in1, GPIO. LOW) GPIO.output(in2, GPIO. LOW) steering.stop() elif afwijking > 5: # stuur naar rechts als de afwijking positief is GPIO.output(in1, GPIO. LOW) GPIO.output(in2, GPIO. HIGH) steering.start(100) elif afwijking < -5: # stuur naar links als de afwijking negatief is GPIO.output(in1, GPIO. HIGH) GPIO.output(in2, GPIO. LOW) steering.start(100) afgeleide = kd * (error - lastError) / dt proportioneel = kp * fout PD = int(snelheid + afgeleide + proportioneel) spd = abs(PD) als spd> 25: spd = 25 throttle.start(spd) lastError = fout lastTime = time.time()
Als de fout erg groot is (de afwijking van het midden is hoog), zijn de proportionele en afgeleide acties hoog, wat resulteert in een hoge smoorsnelheid. Wanneer de fout 0 nadert (afwijking van het midden is laag), werkt de afgeleide actie omgekeerd (helling is negatief) en wordt de smoorsnelheid laag om de stabiliteit van het systeem te behouden. De volledige code is hieronder bijgevoegd.
Stap 7: Resultaten
De video's hierboven tonen de resultaten die ik heb verkregen. Het heeft wel meer afstemming en verdere aanpassingen nodig. Ik verbond de Raspberry Pi met mijn LCD-scherm omdat de videostreaming via mijn netwerk een hoge latentie had en erg frustrerend was om mee te werken, daarom zijn er draden verbonden met Raspberry Pi in de video. Ik heb foamboards gebruikt om de baan op te tekenen.
Ik wacht op uw aanbevelingen om dit project beter te maken! Ik hoop dat deze instructables goed genoeg was om je wat nieuwe informatie te geven.
Aanbevolen:
Objectdetectie met Dragonboard 410c of 820c met OpenCV en Tensorflow: 4 stappen
Objectdetectie met Dragonboard 410c of 820c met behulp van OpenCV en Tensorflow.: Deze instructie beschrijft hoe OpenCV, Tensorflow en machine learning-frameworks voor Python 3.5 moeten worden geïnstalleerd om de toepassing Objectdetectie uit te voeren
Arduino-robot met afstand, richting en rotatiegraad (oost, west, noord, zuid) spraakgestuurd met behulp van Bluetooth-module en autonome robotbeweging: 6 stappen
Arduino-robot met afstand, richting en rotatiegraad (oost, west, noord, zuid) bestuurd door spraak met behulp van Bluetooth-module en autonome robotbeweging. , Links, Rechts, Oost, West, Noord, Zuid) vereiste afstand in centimeters met spraakopdracht. Robot kan ook autonoom worden verplaatst
Autonome drone: 7 stappen
Autonome drone: in dit project leer je het proces van het bouwen en configureren van een drone, voordat je verder gaat met het onderzoeken van autonome vluchten met behulp van Mission Planner en MATLAB. Houd er rekening mee dat deze instructable alleen als richtlijn bedoeld is. Het gebruik van drones kan heel d
Autonome RC-auto: 7 stappen
Autonome RC-auto: Met de opkomst van zelfrijdende, autonome auto's vandaag, besloot ik de uitdaging aan te gaan om er zelf een te maken. Dit project diende ook als mijn sluitstukproject in mijn Engineering Design and Development en Robotics lessen en ontving een prijs voor
Autonome en op afstand bestuurbare robot: 11 stappen
Autonome en op afstand bestuurbare robot: deze robot is bedoeld om relatief goedkoop en snel te zijn. Dit heb je nodig om aan de slag te gaan: Hardware 1 Raspberry Pi 1 Dual H-Bridge Motor Driver 1 Buck Converter 2 3V-6V DC Motors HC-SR04 Ultrasone SensorOverig Een doos om als chassis te fungeren M