Indholdsfortegnelse:

Autonom Lane-Keeping Car ved hjælp af Raspberry Pi og OpenCV: 7 trin (med billeder)
Autonom Lane-Keeping Car ved hjælp af Raspberry Pi og OpenCV: 7 trin (med billeder)

Video: Autonom Lane-Keeping Car ved hjælp af Raspberry Pi og OpenCV: 7 trin (med billeder)

Video: Autonom Lane-Keeping Car ved hjælp af Raspberry Pi og OpenCV: 7 trin (med billeder)
Video: CAMPING on MOUNTAIN - Elevated TENT - RAIN 2024, November
Anonim
Autonom kørebane ved hjælp af Raspberry Pi og OpenCV
Autonom kørebane ved hjælp af Raspberry Pi og OpenCV

I denne instruktør vil en autonom baneholderrobot blive implementeret og vil gennemgå følgende trin:

  • Samling af Dele
  • Forudsætninger for installation af software
  • Hardware samling
  • Første test
  • Registrering af banelinjer og visning af styrelinjen ved hjælp af openCV
  • Implementering af en PD -controller
  • Resultater

Trin 1: Indsamling af komponenter

Indsamling af komponenter
Indsamling af komponenter
Indsamling af komponenter
Indsamling af komponenter
Indsamling af komponenter
Indsamling af komponenter
Indsamling af komponenter
Indsamling af komponenter

Billederne ovenfor viser alle de komponenter, der bruges i dette projekt:

  • RC bil: Jeg fik min fra en lokal butik i mit land. Den er udstyret med 3 motorer (2 til gasspjæld og 1 til styring). Den største ulempe ved denne bil er, at styringen er begrænset mellem "ingen styring" og "fuld styring". Med andre ord kan den ikke styre i en bestemt vinkel, i modsætning til servostyrede RC-biler. Du kan finde lignende bilsæt designet specielt til hindbær pi herfra.
  • Raspberry pi 3 model b+: dette er bilens hjerne, der vil klare mange forarbejdningstrin. Den er baseret på en quad-core 64-bit processor, der er klokket til 1,4 GHz. Jeg fik min herfra.
  • Raspberry pi 5 mp kameramodul: Det understøtter 1080p @ 30 fps, 720p @ 60 fps og 640x480p 60/90 optagelse. Det understøtter også seriel grænseflade, som kan sættes direkte i hindbær pi. Det er ikke den bedste løsning til billedbehandlingsprogrammer, men det er tilstrækkeligt til dette projekt, såvel som det er meget billigt. Jeg fik min herfra.
  • Motordriver: bruges til at styre retninger og hastigheder på DC -motorerne. Det understøtter styring af 2 jævnstrømsmotorer i 1 bord og kan modstå 1,5 A.
  • Power Bank (valgfri): Jeg brugte en powerbank (vurderet til 5V, 3A) til at tænde for hindbærpi separat. En nedtrapningsomformer (bukkonverter: 3A udgangsstrøm) bør bruges for at tænde for hindbærpien fra 1 kilde.
  • 3s (12 V) LiPo -batteri: Litiumpolymerbatterier er kendt for deres fremragende ydeevne inden for robotteknologi. Det bruges til at drive motorføreren. Jeg købte min herfra.
  • Mand til mand og hun til hun.
  • Dobbeltsidet tape: Bruges til at montere komponenterne på RC -bilen.
  • Blå tape: Dette er en meget vigtig komponent i dette projekt, det bruges til at lave de to vognbanelinjer, som bilen vil køre mellem. Du kan vælge hvilken som helst farve, du vil have, men jeg anbefaler at vælge andre farver end dem i miljøet omkring.
  • Lynlåsbånd og træstænger.
  • Skruetrækker.

Trin 2: Installation af OpenCV på Raspberry Pi og opsætning af fjerndisplay

Installation af OpenCV på Raspberry Pi og opsætning af fjernskærm
Installation af OpenCV på Raspberry Pi og opsætning af fjernskærm

Dette trin er lidt irriterende og vil tage noget tid.

OpenCV (Open source Computer Vision) er et open source -computervision og maskinlæringssoftwarebibliotek. Biblioteket har over 2500 optimerede algoritmer. Følg DENNE meget ligetil vejledning for at installere openCV på din hindbær pi samt installere hindbær pi OS (hvis du stadig ikke gjorde det). Bemærk, at processen med at bygge openCV kan tage omkring 1,5 time i et afkølet rum (da processorens temperatur vil blive meget høj!) Så tag lidt te og vent tålmodigt: D.

For fjernskærmen skal du også følge DENNE vejledning til opsætning af fjernadgang til din hindbær pi fra din Windows/Mac -enhed.

Trin 3: Tilslutning af dele sammen

Tilslutning af dele sammen
Tilslutning af dele sammen
Tilslutning af dele sammen
Tilslutning af dele sammen
Tilslutning af dele sammen
Tilslutning af dele sammen

Billederne ovenfor viser forbindelserne mellem hindbær pi, kameramodul og motor driver. Bemærk, at de motorer, jeg brugte, absorberer 0,35 A ved 9 V hver, hvilket gør det sikkert for motorføreren at køre 3 motorer på samme tid. Og da jeg vil styre de 2 gasspjældsmotorers hastighed (1 bag og 1 foran) nøjagtig samme måde, har jeg forbundet dem til den samme port. Jeg monterede motorchaufføren på højre side af bilen ved hjælp af dobbeltbånd. Hvad angår kameramodulet, indsatte jeg en lynlås mellem skruehullerne som billedet ovenfor viser. Derefter monterer jeg kameraet på en træstang, så jeg kan justere kameraets position, som jeg vil. Prøv at installere kameraet i midten af bilen så meget som muligt. Jeg anbefaler at placere kameraet mindst 20 cm over jorden, så synsfeltet foran bilen bliver bedre. Fritzing -skematikken er vedhæftet nedenfor.

Trin 4: Første test

Første test
Første test
Første test
Første test

Kameratest:

Når kameraet er installeret, og openCV -biblioteket er bygget, er det tid til at teste vores første billede! Vi tager et foto fra pi cam og gemmer det som "original.jpg". Det kan gøres på 2 måder:

1. Brug af terminalkommandoer:

Åbn et nyt terminalvindue, og skriv følgende kommando:

raspistill -o original.jpg

Dette vil tage et stillbillede og gemme det i mappen "/pi/original.jpg".

2. Brug af enhver python IDE (jeg bruger IDLE):

Åbn en ny skitse og skriv følgende kode:

import cv2

video = cv2. VideoCapture (0) mens True: ret, frame = video.read () frame = cv2.flip (frame, -1) # bruges til at vende billedet lodret cv2.imshow ('original', frame) cv2. imwrite ('original.jpg', frame) key = cv2.waitKey (1) if key == 27: break video.release () cv2.destroyAllWindows ()

Lad os se, hvad der skete i denne kode. Den første linje importerer vores openCV -bibliotek for at bruge alle dets funktioner. VideoCapture (0) -funktionen begynder at streame en livevideo fra kilden bestemt af denne funktion, i dette tilfælde er det 0, hvilket betyder raspi -kamera. hvis du har flere kameraer, skal der placeres forskellige numre. video.read () vil læse hvert billede kommer fra kameraet og gemme det i en variabel kaldet "frame". flip () -funktionen vil vende billedet i forhold til y-aksen (lodret), da jeg monterer mit kamera omvendt. imshow () viser vores rammer ledet af ordet "original" og imwrite () gemmer vores foto som original.jpg. waitKey (1) venter i 1 ms, før en tastaturknap trykkes ned, og returnerer sin ASCII -kode. hvis der trykkes på escape (esc) -knappen, returneres en decimalværdi på 27 og vil bryde sløjfen i overensstemmelse hermed. video.release () stopper optagelsen og destroyAllWindows () lukker hvert billede, der åbnes med funktionen imshow ().

Jeg anbefaler at teste dit foto med den anden metode for at blive fortrolig med openCV -funktioner. Billedet gemmes i biblioteket "/pi/original.jpg". Det originale foto, som mit kamera tog, er vist ovenfor.

Test af motorer:

Dette trin er afgørende for at bestemme rotationsretningen for hver motor. Lad os først få en kort introduktion til arbejdsprincippet for en motordriver. Billedet ovenfor viser motorchaufførens pin-out. Aktiver A, input 1 og input 2 er knyttet til motor A -styring. Aktiver B, input 3 og input 4 er knyttet til motor B -styring. Retningskontrol etableres af "Input" -delen, og hastighedskontrol etableres af "Enable" -del. For at styre motorens retning f.eks., Skal du indstille input 1 til HIGH (3,3 V i dette tilfælde, da vi bruger en hindbærpi) og indstille input 2 til LOW, vil motoren dreje i en bestemt retning og ved at indstille de modsatte værdier til input 1 og input 2, vil motoren dreje i den modsatte retning. Hvis input 1 = input 2 = (HIGH eller LOW), vil motoren ikke dreje. Aktiver stifter tager et Pulse Width Modulation (PWM) indgangssignal fra hindbær (0 til 3,3 V) og kører motorerne i overensstemmelse hermed. For eksempel betyder et 100% PWM -signal, at vi arbejder på den maksimale hastighed, og 0% PWM -signal betyder, at motoren ikke roterer. Følgende kode bruges til at bestemme motorers retninger og teste deres hastigheder.

importtid

import RPi. GPIO som GPIO GPIO.setwarnings (False) # Styremotorpinde styring_aktiveret = 22 # Fysisk stift 15 in1 = 17 # Fysisk stift 11 in2 = 27 # Fysisk stift 13 #Trottle Motors Pins gashåndtag = 25 # Fysisk stift 22 in3 = 23 # Fysisk Pin 16 in4 = 24 # Fysisk Pin 18 GPIO.setmode (GPIO. BCM) # Brug GPIO -nummerering i stedet for fysisk nummerering 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) # Styremotorstyring GPIO.output (in1, GPIO. HIGH) GPIO.output (in2, GPIO. LOW) styring = GPIO. PWM (styring_aktiveret, 1000) # indstil koblingsfrekvensen til 1000 Hz styring. Stop () # Gasmotorstyring GPIO.output (in3, GPIO. HIGH) GPIO.output (in4, GPIO. LOW) throttle = GPIO. PWM (throttle_enable, 1000) # sæt koblingsfrekvensen til 1000 Hz throttle.stop () time.sleep (1) throttle.start (25) # starter motoren ved 25 % PWM signal-> (0,25 * batterispænding) - førers tabstyring. start (100) # starter motoren ved 100% PWM -signal-> (1 * batterispænding) - førerens tabtid. søvn (3) gashåndtag. stop () styring. stop ()

Denne kode kører gasspjældsmotorer og styremotor i 3 sekunder og stopper dem derefter. (Førerens tab) kan bestemmes ved hjælp af et voltmeter. For eksempel ved vi, at et 100% PWM -signal skal give det fulde batteris spænding ved motorens terminal. Men ved at indstille PWM til 100%fandt jeg ud af, at driveren forårsager et fald på 3 V, og motoren får 9 V i stedet for 12 V (præcis hvad jeg har brug for!). Tabet er ikke lineært, dvs. tabet ved 100% er meget forskelligt fra tabet ved 25%. Efter at have kørt ovenstående kode var mine resultater som følger:

Drosselresultater: hvis in3 = HIGH og in4 = LOW, vil throttling-motorerne have en Clock-Wise (CW) rotation, dvs. bilen vil bevæge sig fremad. Ellers kører bilen baglæns.

Styringsresultater: hvis in1 = HIGH og in2 = LOW, drejer styremotoren maksimalt til venstre, dvs. bilen vil styre til venstre. Ellers vil bilen styre til højre. Efter nogle forsøg fandt jeg ud af, at styremotoren ikke vil dreje, hvis PWM -signalet ikke var 100% (dvs. motoren styrer enten helt til højre eller helt til venstre).

Trin 5: Registrering af banelinjer og beregning af kurslinje

Registrering af banelinjer og beregning af kurslinje
Registrering af banelinjer og beregning af kurslinje
Registrering af banelinjer og beregning af kurslinje
Registrering af banelinjer og beregning af kurslinje
Registrering af banelinjer og beregning af kurslinje
Registrering af banelinjer og beregning af kurslinje

I dette trin forklares algoritmen, der styrer bilens bevægelse. Det første billede viser hele processen. Systemets input er billeder, output er theta (styrevinkel i grader). Bemærk, at behandlingen udføres på 1 billede og vil blive gentaget på alle rammer.

Kamera:

Kameraet begynder at optage en video med (320 x 240) opløsning. Jeg anbefaler at sænke opløsningen, så du kan få bedre billedhastighed (fps), da fps -fald vil forekomme efter anvendelse af behandlingsteknikker på hver ramme. Koden herunder er programmets hovedsløjfe og tilføjer hvert trin over denne kode.

import cv2

import numpy som np video = cv2. VideoCapture (0) video.set (cv2. CAP_PROP_FRAME_WIDTH, 320) # indstil bredden til 320 p video.set (cv2. CAP_PROP_FRAME_HEIGHT, 240) # indstil højden til 240 p # Løkken, mens loop Sandt: 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 ()

Koden her viser det originale billede opnået i trin 4 og er vist på billederne ovenfor.

Konverter til HSV Color Space:

Nu efter at have taget videooptagelse som rammer fra kameraet, er det næste trin at konvertere hvert billede til farvetone Hue, Saturation og Value (HSV). Den største fordel ved at gøre det er at kunne skelne mellem farver ved deres lysstyrkeniveau. Og her er en god forklaring på HSV -farverum. Konvertering til HSV udføres via følgende funktion:

def convert_to_HSV (ramme):

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

Denne funktion kaldes fra hovedsløjfen og returnerer rammen i HSV -farverum. Den ramme, jeg fik i HSV -farverum, er vist ovenfor.

Find blå farve og kanter:

Efter konvertering af billedet til HSV -farverum er det tid til kun at registrere den farve, vi er interesseret i (dvs. blå farve, da det er farven på banelinjerne). For at udtrække blå farve fra en HSV -ramme skal der angives et område af nuance, mætning og værdi. henvis her for at få en bedre ide om HSV -værdier. Efter nogle forsøg er de øvre og nedre grænser for blå farve vist i nedenstående kode. Og for at reducere den overordnede forvrængning i hver ramme, kanter detekteres kun ved hjælp af kanyle -detektor. Mere om canny edge findes her. En tommelfingerregel er at vælge parametrene for Canny () -funktionen med et forhold på 1: 2 eller 1: 3.

def detect_edges (frame):

lavere_blå = np.array ([90, 120, 0], dtype = "uint8") # nedre grænse for blå farve upper_blue = np.array ([150, 255, 255], dtype = "uint8") # øvre grænse for blå farve maske = cv2.inRange (hsv, lavere_blå, øvre_blå) # denne maske filtrerer alt udover blå # opdager kanter kanter = cv2. Canny (maske, 50, 100) cv2.imshow ("kanter", kanter) tilbagekanter

Denne funktion vil også blive kaldt fra hovedsløjfen, der som parameter tager HSV -farverumrammen og returnerer den kantede ramme. Den kantede ramme, jeg fik, findes ovenfor.

Vælg interesseområde (ROI):

Valg af interesseområde er afgørende for kun at fokusere på 1 område af rammen. I dette tilfælde ønsker jeg ikke, at bilen skal se mange ting i miljøet. Jeg vil bare have, at bilen fokuserer på vognbanelinjerne og ignorerer alt andet. PS: koordinatsystemet (x og y akser) starter fra øverste venstre hjørne. Med andre ord starter punktet (0, 0) fra øverste venstre hjørne. y-aksen er højden og x-aksen er bredden. Koden herunder vælger et område af interesse, der kun fokuserer på den nederste halvdel af rammen.

def region_for_interesse (kanter):

højde, bredde = kanter. form # ekstraher højden og bredden af kanterne rammemaske = np. nuller_lignende (kanter) # lav en tom matrix med samme dimensioner af kanterammen # fokusér kun den nederste halvdel af skærmen # angiv koordinaterne for 4 punkter (nederst til venstre, øverst til venstre, øverst til højre, nederst til højre) polygon = np.array (

Denne funktion tager den kantede ramme som parameter og tegner en polygon med 4 forudindstillede punkter. Det vil kun fokusere på, hvad der er inde i polygonen og ignorere alt uden for det. Min ramme for interesse er vist ovenfor.

Find linjesegmenter:

Hough transform bruges til at detektere linjesegmenter fra en kantet ramme. Hough transform er en teknik til at opdage enhver form i matematisk form. Det kan opdage næsten ethvert objekt, selvom det er forvrænget i henhold til et vist antal stemmer. en stor reference til Hough -transformation er vist her. Til denne applikation bruges funktionen cv2. HoughLinesP () til at registrere linjer i hver ramme. De vigtige parametre, som denne funktion tager, er:

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

  • Ramme: er den ramme, vi ønsker at registrere linjer i.
  • rho: Det er afstandspræcisionen i pixels (normalt er den = 1)
  • theta: kantet præcision i radianer (altid = np.pi/180 ~ 1 grad)
  • min_tærskel: minimumstemme, den skal have for at blive betragtet som en linje
  • minLineLength: mindste linjelængde i pixels. Enhver linje, der er kortere end dette tal, betragtes ikke som en linje.
  • maxLineGap: maksimal afstand i pixels mellem 2 linjer, der skal behandles som 1 linje. (Det bruges ikke i mit tilfælde, da de banelinjer, jeg bruger, ikke har noget hul).

Denne funktion returnerer slutpunkterne for en linje. Følgende funktion kaldes fra min hovedsløjfe til at detektere linjer ved hjælp af Hough transform:

def detect_line_segments (beskåret_kanter):

rho = 1 theta = np.pi / 180 min_tærskel = 10 line_segments = cv2. HoughLinesP (beskåret_kanter, rho, theta, min_tærskel, np.array (), minLineLength = 5, maxLineGap = 0) returlinjesegmenter

Gennemsnitlig hældning og skæringspunkt (m, b):

husk, at ligningens ligning er givet med y = mx + b. Hvor m er linjens hældning og b er y-skæringen. I denne del beregnes gennemsnittet af skråninger og aflytninger af linjesegmenter, der registreres ved hjælp af Hough -transformation. Inden vi gør det, lad os tage et kig på det originale rammefoto vist ovenfor. Den venstre vognbane ser ud til at gå opad, så den har en negativ hældning (kan du huske startpunktet for koordinatsystemet?). Med andre ord har den venstre vognbanelinje x1 <x2 og y2 x1 og y2> y1, hvilket vil give en positiv hældning. Så alle linjer med positiv hældning betragtes som højre vognbanepunkter. I tilfælde af lodrette linjer (x1 = x2) vil hældningen være uendelig. I dette tilfælde springer vi alle lodrette linjer over for at undgå at få en fejl. For at tilføje denne nøjagtighed mere nøjagtighed er hver ramme opdelt i to områder (højre og venstre) gennem 2 grænselinjer. Alle breddepunkter (x-aksepunkter) større end højre grænselinje er forbundet med beregning af højre bane. Og hvis alle breddepunkter er mindre end den venstre grænselinje, er de forbundet med beregning af venstre spor. Den følgende funktion tager rammen under behandling og lanesegmenter opdaget ved hjælp af Hough -transformering og returnerer den gennemsnitlige hældning og aflytning af to bane linjer.

def average_slope_intercept (frame, line_segments):

lane_lines = hvis line_segments er Ingen: print ("ingen linjesegment registreret") returner Lane_lines højde, bredde, _ = frame.shape left_fit = right_fit = boundary = left_region_boundary = bredde * (1 - grænse) right_region_boundary = bredde * grænse for line_segment i line_segments: for x1, y1, x2, y2 i line_segment: hvis x1 == x2: print ("springer lodrette linjer (hældning = uendeligt)") fortsæt fit = np.polyfit ((x1, x2), (y1, y2), 1) hældning = (y2 - y1) / (x2 - x1) skæringspunkt = y1 - (hældning * x1) hvis hældning <0: hvis x1 <venstre_region_grænse og x2 højre_region_grænse og x2> højre_region_grænse: højre_fit. tilføj ((hældning, skæringspunkt)) 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) hvis len (right_fit)> 0: lane_lines.append (make_points (frame, right_fit_average)) # lane_lines er et 2-D-array, der består af koordinaterne for højre og venstre banelinje # f.eks.: lan e_lines =

make_points () er en hjælperfunktion til funktionen average_slope_intercept (), der returnerer de afgrænsede koordinater for banelinjerne (fra bunden til midten af rammen).

def make_points (ramme, linje):

højde, bredde, _ = ramme. formhældning, skæringspunkt = linje y1 = højde # bunden af rammen y2 = int (y1 / 2) # lav punkter fra midten af rammen og ned, hvis hældning == 0: hældning = 0,1 x1 = int ((y1 - skæringspunkt) / hældning) x2 = int ((y2 - skæringspunkt) / hældning) retur

For at forhindre dividering med 0 præsenteres en betingelse. Hvis hældning = 0, hvilket betyder y1 = y2 (vandret linje), skal du give hældningen en værdi nær 0. Dette påvirker ikke algoritmens ydeevne, såvel som det forhindrer umuligt tilfælde (divideret med 0).

Følgende funktion bruges til at vise banelinierne på rammerne:

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

line_image = np.zeros_like (frame) hvis linjer ikke er None: for line in lines: for x1, y1, x2, y2 in line: 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 () -funktionen tager følgende parametre, og den bruges til at kombinere to billeder, men med at give hver en vægt.

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

Og beregner outputbilledet ved hjælp af følgende ligning:

output = alfa * image1 + beta * image2 + gamma

Flere oplysninger om funktionen cv2.addWeighted () stammer her.

Beregn og vis overskriftslinje:

Dette er det sidste trin, før vi anvender hastigheder på vores motorer. Kørelinjen er ansvarlig for at give styremotoren den retning, den skal rotere i, og give gasmotorerne den hastighed, hvormed de vil arbejde. Beregning af kurslinje er ren trigonometri, tan og atan (tan^-1) trigonometriske funktioner bruges. Nogle ekstreme tilfælde er, når kameraet kun registrerer en sporbane, eller når det ikke registrerer nogen linje. Alle disse tilfælde er vist i følgende funktion:

def get_steering_angle (frame, lane_lines):

højde, bredde, _ = ramme.form hvis len (bane_linjer) == 2: # hvis to banelinjer opdages _, _, venstre_x2, _ = bane_linjer [0] [0] # ekstrakt tilbage x2 fra lane_lines array _, _, right_x2, _ = lane_lines [1] [0] # udtræk højre x2 fra lane_lines array mid = int (bredde / 2) x_offset = (venstre_x2 + højre_x2) / 2 - midt y_offset = int (højde / 2) elif len (lane_lines) == 1: # hvis der kun registreres en linje x1, _, x2, _ = lane_lines [0] [0] x_offset = x2 - x1 y_offset = int (højde / 2) elif len (lane_lines) == 0: # hvis der ikke opdages nogen linje x_offset = 0 y_offset = int (højde / 2) vinkel_til_mid_radian = matematik.atan (x_forskydning / y_forskydning) vinkel_til_mid_deg = int (vinkel_til_midt_radian * 180,0 / math.pi) styring_vinkel = vinkel_til_mid_deg + 90 returstyringsvinkel

x_offset i det første tilfælde er, hvor meget gennemsnittet ((højre x2 + venstre x2) / 2) adskiller sig fra midten af skærmen. y_offset anses altid for at være højde / 2. Det sidste billede ovenfor viser et eksempel på overskriftslinje. angle_to_mid_radians er det samme som "theta" vist på det sidste billede ovenfor. Hvis styringsvinkel = 90 betyder det, at bilen har en kurs i retning vinkelret på linjen "højde / 2", og bilen bevæger sig fremad uden at styre. Hvis styring_vinkel> 90, skal bilen styre til højre, ellers skal den styre til venstre. Følgende funktion bruges til at vise overskriftslinjen:

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

heading_image = np.zeros_like (frame) højde, bredde, _ = frame.shape steering_angle_radian = styring_angle / 180,0 * math.pi x1 = int (bredde / 2) y1 = højde x2 = int (x1 - højde / 2 / matematik. tan) (steering_angle_radian)) y2 = int (højde / 2) cv2.line (heading_image, (x1, y1), (x2, y2), line_color, line_width) heading_image = cv2.addWeighted (frame, 0.8, heading_image, 1, 1) returner overskrift_billede

Funktionen ovenfor tager rammen, hvor kurslinjen vil blive tegnet, og styrevinklen som input. Det returnerer billedet af overskriftslinjen. Overskriftslinjens ramme taget i mit tilfælde er vist på billedet ovenfor.

Kombinerer alle koder sammen:

Koden er nu klar til at blive samlet. Følgende kode viser programmets hovedsløjfe, der kalder hver funktion:

import cv2

importer numpy som np video = cv2. VideoCapture (0) video.set (cv2. CAP_PROP_FRAME_WIDTH, 320) video.set (cv2. CAP_PROP_FRAME_HEIGHT, 240) mens True: ret, frame = video.read () frame = cv2.flip (frame, -1) #Calling the features hsv = convert_to_HSV (frame) edge = detect_edges (hsv) roi = region_of_interest (kanter) line_segments = detect_line_segments (roi) lane_lines = average_slope_intercept (frame, line_segments) lane_lines_image = display_lines (frame_lines) = 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 ()

Trin 6: Anvendelse af PD -kontrol

Anvendelse af PD -kontrol
Anvendelse af PD -kontrol

Nu har vi vores styrevinkel klar til at blive fodret til motorerne. Som nævnt tidligere, hvis styrevinklen er større end 90, skal bilen dreje til højre, ellers skal den dreje til venstre. Jeg anvendte en simpel kode, der drejer styremotoren til højre, hvis vinklen er over 90 og drejer den til venstre, hvis styrevinklen er mindre end 90 ved en konstant gashastighed på (10% PWM), men jeg fik mange fejl. Den største fejl, jeg fik, er, når bilen nærmer sig en hvilken som helst sving, styremotoren virker direkte, men spjældmotorerne sidder fast. Jeg forsøgte at øge gashastigheden til at være (20% PWM) i sving, men endte med at robotten kom ud af banerne. Jeg havde brug for noget, der øger gashåndtaget meget, hvis styrevinklen er meget stor og øger hastigheden en smule, hvis styrevinklen ikke er så stor, sænker derefter hastigheden til en startværdi, når bilen nærmer sig 90 grader (kører lige). Løsningen var at bruge en PD -controller.

PID -controller står for Proportional, Integral og Derivative controller. Denne type lineære controllere bruges meget i robotikapplikationer. Billedet ovenfor viser den typiske PID -feedback -kontrolsløjfe. Målet med denne controller er at nå "setpunktet" med den mest effektive måde i modsætning til "on - off" controllere, der tænder eller slukker anlægget i henhold til nogle betingelser. Nogle søgeord bør være kendt:

  • Setpunkt: er den ønskede værdi, du ønsker, at dit system skal nå.
  • Faktisk værdi: er den faktiske værdi, der registreres af sensoren.
  • Fejl: er forskellen mellem setpunkt og faktisk værdi (fejl = Setpunkt - Faktisk værdi).
  • Kontrolleret variabel: fra dens navn, den variabel, du ønsker at styre.
  • Kp: Proportionel konstant.
  • Ki: Integral konstant.
  • Kd: Afledt konstant.

Kort fortalt fungerer PID -kontrolsystemsløjfen som følger:

  • Brugeren definerer det setpunkt, der er nødvendigt for at systemet kan nå.
  • Fejlen beregnes (fejl = setpunkt - faktisk).
  • P -controller genererer en handling, der er proportional med fejlens værdi. (fejl stiger, P -handling stiger også)
  • I controller vil integrere fejlen over tid, hvilket eliminerer systemets steady state fejl, men øger dens overskridelse.
  • D -controller er simpelthen tidsafledt for fejlen. Med andre ord er det fejlens hældning. Det udfører en handling, der er proportional med afledningen af fejlen. Denne controller øger systemets stabilitet.
  • Output fra controlleren vil være summen af de tre controllere. Controllerens output bliver 0, hvis fejlen bliver 0.

En god forklaring på PID -controller kan findes her.

Når jeg vender tilbage til vognbanebilen, var min kontrollerede variabel gashastighed (da styringen kun har to tilstande enten til højre eller venstre). En PD -controller bruges til dette formål, da D -handling øger gashastigheden meget, hvis fejlændringen er meget stor (dvs. stor afvigelse) og bremser bilen, hvis denne fejlændring nærmer sig 0. Jeg gjorde følgende trin for at implementere en PD controller:

  • Indstil setpunktet til 90 grader (jeg vil altid have, at bilen kører lige)
  • Beregnede afvigelsesvinklen fra midten
  • Afvigelsen giver to oplysninger: Hvor stor fejlen er (afvigelsens størrelse) og hvilken retning styremotoren skal tage (tegn på afvigelse). Hvis afvigelsen er positiv, skal bilen styre til højre, ellers skal den styre til venstre.
  • Da afvigelsen enten er negativ eller positiv, defineres en "fejl" -variabel og altid lig med den absolutte værdi af afvigelsen.
  • Fejlen ganges med en konstant Kp.
  • Fejlen undergår tidsdifferentiering og ganges med en konstant Kd.
  • Motors hastighed opdateres, og sløjfen starter igen.

Følgende kode bruges i hovedsløjfen til at kontrollere gasmotorernes hastighed:

hastighed = 10 # driftshastighed i % PWM

#Variabler, der skal opdateres hver sløjfe lastTime = 0 lastError = 0 # PD -konstanter Kp = 0,4 Kd = Kp * 0,65 Mens True: nu = time.time () # nuværende tidsvariabel dt = nu - sidsteTidsafvigelse = styring_vinkel - 90 # ækvivalent til vinkel_til_mid_deg variabel fejl = abs (afvigelse) hvis afvigelse -5: # ikke styre, hvis der er en 10 -graders fejlområdeafvigelse = 0 fejl = 0 GPIO.output (in1, GPIO. LOW) GPIO.output (in2, GPIO. LOW) steering.stop () elif -afvigelse> 5: # styre til højre, hvis afvigelsen er positiv GPIO.output (in1, GPIO. LOW) GPIO.output (in2, GPIO. HIGH) steering.start (100) elif -afvigelse < -5: # styre til venstre, hvis afvigelsen er negativ GPIO.output (in1, GPIO. HIGH) GPIO.output (in2, GPIO. LOW) styring.start (100) derivat = kd * (fejl - lastError) / dt proportional = kp * fejl PD = int (hastighed + derivat + proportional) spd = abs (PD) hvis spd> 25: spd = 25 gashåndtag. start (spd) lastError = fejl lastTime = time.time ()

Hvis fejlen er meget stor (afvigelsen fra midten er høj), er proportionelle og afledte handlinger høje, hvilket resulterer i høj gasreguleringshastighed. Når fejl nærmer sig 0 (afvigelse fra midten er lav), virker den afledte handling omvendt (hældningen er negativ), og gashastigheden bliver lav for at opretholde systemets stabilitet. Den fulde kode er vedhæftet herunder.

Trin 7: Resultater

Videoer ovenfor viser de resultater, jeg opnåede. Det kræver mere tuning og yderligere justeringer. Jeg tilsluttede hindbær pi til min LCD -skærm, fordi videostreamingen over mit netværk havde høj latenstid og var meget frustrerende at arbejde med, derfor er der ledninger forbundet til hindbær pi i videoen. Jeg brugte skumplader til at tegne banen på.

Jeg venter på at høre dine anbefalinger for at gøre dette projekt bedre! Da jeg håber, at denne instruks var god nok til at give dig nogle nye oplysninger.

Anbefalede: