Innholdsfortegnelse:
- Trinn 1: Samle komponenter
- Trinn 2: Installere OpenCV på Raspberry Pi og konfigurere ekstern skjerm
- Trinn 3: Koble deler sammen
- Trinn 4: Første test
- Trinn 5: Oppdagelse av banelinjer og beregning av kurslinje
- Trinn 6: Påføre PD Control
- Trinn 7: Resultater
Video: Autonom bil som holder bilen ved hjelp av Raspberry Pi og OpenCV: 7 trinn (med bilder)
2024 Forfatter: John Day | [email protected]. Sist endret: 2024-01-30 11:22
I denne instruksen vil en autonom kjørefeltrobot bli implementert og vil gå gjennom følgende trinn:
- Samle deler
- Installere forutsetninger for programvare
- Maskinvare montering
- Første test
- Oppdage kjørefeltlinjer og vise styrelinjen ved hjelp av openCV
- Implementere en PD -kontroller
- Resultater
Trinn 1: Samle komponenter
Bildene ovenfor viser alle komponentene som brukes i dette prosjektet:
- RC -bil: Jeg fikk min fra en lokal butikk i landet mitt. Den er utstyrt med 3 motorer (2 for struping og 1 for styring). Den største ulempen med denne bilen er at styringen er begrenset mellom "ingen styring" og "full styring". Med andre ord, den kan ikke styre i en bestemt vinkel, i motsetning til servostyrende RC-biler. Du kan finne lignende bilsett designet spesielt for bringebærpi herfra.
- Raspberry pi 3 modell b+: dette er hjernen i bilen som vil håndtere mange behandlingsstadier. Den er basert på en firekjerners 64-biters prosessor klokket til 1,4 GHz. Jeg har min herfra.
- Raspberry pi 5 mp kameramodul: Den støtter 1080p @ 30 fps, 720p @ 60 fps og 640x480p 60/90 opptak. Den støtter også serielt grensesnitt som kan kobles direkte til bringebær pi. Det er ikke det beste alternativet for bildebehandlingsprogrammer, men det er tilstrekkelig for dette prosjektet, så vel som det er veldig billig. Jeg har min herfra.
- Motordriver: Brukes til å kontrollere retninger og hastigheter for likestrømsmotorene. Den støtter kontroll av 2 likestrømsmotorer i 1 brett og tåler 1,5 A.
- Power Bank (valgfritt): Jeg brukte en powerbank (vurdert til 5V, 3A) for å slå på bringebær -pi separat. En nedtrappingskonverter (bukkonverter: 3A utgangsstrøm) bør brukes for å drive bringebær -pi fra én kilde.
- 3s (12 V) LiPo -batteri: Litiumpolymerbatterier er kjent for sin utmerkede ytelse innen robotteknologi. Den brukes til å drive motorføreren. Jeg kjøpte min herfra.
- Mann til mann og hun til hun.
- Dobbeltsidig tape: Brukes til å montere komponentene på RC -bilen.
- Blå tape: Dette er en veldig viktig komponent i dette prosjektet, det brukes til å lage de to banelinjene som bilen skal kjøre mellom. Du kan velge hvilken som helst farge du vil ha, men jeg anbefaler å velge andre farger enn de i miljøet rundt.
- Glidelås og trebjelker.
- Skrutrekker.
Trinn 2: Installere OpenCV på Raspberry Pi og konfigurere ekstern skjerm
Dette trinnet er litt irriterende og vil ta litt tid.
OpenCV (Open source Computer Vision) er et åpen kildekode datavisjon og maskinlæringsprogramvarebibliotek. Biblioteket har over 2500 optimaliserte algoritmer. Følg DENNE veldig enkle guiden for å installere openCV på din bringebær pi samt installere bringebær pi OS (hvis du fortsatt ikke gjorde det). Vær oppmerksom på at prosessen med å bygge openCV kan ta rundt 1,5 timer i et godt avkjølt rom (siden prosessortemperaturen vil bli veldig høy!) Så ta litt te og vent tålmodig: D.
For ekstern skjerm følger du også DENNE veiledningen for å konfigurere ekstern tilgang til din bringebær pi fra Windows/Mac -enheten.
Trinn 3: Koble deler sammen
Bildene ovenfor viser sammenhengen mellom bringebær pi, kameramodul og motordriver. Vær oppmerksom på at motorene jeg brukte absorberer 0,35 A ved 9 V hver, noe som gjør det trygt for motorføreren å kjøre 3 motorer samtidig. Og siden jeg vil kontrollere de to gassmotorens hastighet (1 bak og 1 foran) nøyaktig samme måte, koblet jeg dem til samme port. Jeg monterte motorføreren på høyre side av bilen ved hjelp av dobbeltbånd. Når det gjelder kameramodulen, satte jeg inn en glidelås mellom skruehullene som bildet ovenfor viser. Deretter monterer jeg kameraet på en trebjelke, slik at jeg kan justere kameraets posisjon som jeg vil. Prøv å installere kameraet i midten av bilen så mye som mulig. Jeg anbefaler å plassere kameraet minst 20 cm over bakken, slik at synsfeltet foran bilen blir bedre. Fritzing -skjemaet er vedlagt nedenfor.
Trinn 4: Første test
Kameratesting:
Når kameraet er installert og openCV -biblioteket er bygget, er det på tide å teste vårt første bilde! Vi tar et bilde fra pi cam og lagrer det som "original.jpg". Det kan gjøres på 2 måter:
1. Bruke terminalkommandoer:
Åpne et nytt terminalvindu og skriv inn følgende kommando:
raspistill -o original.jpg
Dette tar et stillbilde og lagrer det i katalogen "/pi/original.jpg".
2. Bruke hvilken som helst python IDE (jeg bruker IDLE):
Åpne en ny skisse og skriv følgende kode:
importer cv2
video = cv2. VideoCapture (0) mens True: ret, frame = video.read () frame = cv2.flip (frame, -1) # brukes til å snu bildet vertikalt cv2.imshow ('original', frame) cv2. imwrite ('original.jpg', frame) key = cv2.waitKey (1) if key == 27: break video.release () cv2.destroyAllWindows ()
La oss se hva som skjedde i denne koden. Den første linjen importerer openCV -biblioteket vårt for å bruke alle funksjonene. VideoCapture (0) -funksjonen begynner å streame en live video fra kilden bestemt av denne funksjonen, i dette tilfellet er det 0 som betyr raspi -kamera. Hvis du har flere kameraer, bør forskjellige tall plasseres. video.read () vil lese hver ramme som kommer fra kameraet og lagre den i en variabel som kalles "frame". flip () -funksjonen vil snu bildet i forhold til y-aksen (vertikalt) siden jeg monterer kameraet mitt omvendt. imshow () vil vise bildene våre ledet av ordet "original" og imwrite () vil lagre bildet vårt som original.jpg. waitKey (1) venter i 1 ms på at tastaturet skal trykkes og returnerer ASCII -koden. Hvis du trykker på escape (esc) -knappen, returneres en desimalverdi på 27 og vil bryte sløyfen tilsvarende. video.release () vil stoppe opptaket og destroyAllWindows () vil lukke hvert bilde som åpnes av imshow () -funksjonen.
Jeg anbefaler å teste bildet ditt med den andre metoden for å bli kjent med openCV -funksjoner. Bildet lagres i katalogen "/pi/original.jpg". Det originale bildet kameraet mitt tok er vist ovenfor.
Testing av motorer:
Dette trinnet er avgjørende for å bestemme rotasjonsretningen til hver motor. La oss først ha en kort introduksjon om arbeidsprinsippet til en motorfører. Bildet ovenfor viser motor-driverens pin-out. Aktiver A, inngang 1 og inngang 2 er knyttet til motor A -kontroll. Aktiver B, inngang 3 og inngang 4 er knyttet til motor B -kontroll. Retningskontroll etableres med "Input" -delen og hastighetskontroll etableres med "Enable" -del. For å kontrollere retningen til motor A for eksempel, sett Input 1 til HIGH (3,3 V i dette tilfellet siden vi bruker en bringebær pi) og sett inngang 2 til LOW, vil motoren spinne i en bestemt retning og ved å sette de motsatte verdiene til inngang 1 og inngang 2, vil motoren spinne i motsatt retning. Hvis inngang 1 = inngang 2 = (HØY eller LAV), vil motoren ikke slå. Aktiver pinner ta et pulsbreddemodulerings (PWM) inngangssignal fra bringebæret (0 til 3,3 V) og kjør motorene deretter. For eksempel betyr et 100% PWM -signal at vi jobber med maksimal hastighet og 0% PWM -signal betyr at motoren ikke roterer. Følgende kode brukes til å bestemme motorens retninger og teste hastighetene deres.
importtid
importer RPi. GPIO som GPIO GPIO.setwarnings (False) # Styremotorpinner steering_enable = 22 # Fysisk pinne 15 in1 = 17 # Fysisk pinne 11 in2 = 27 # Fysisk pinne 13 #Trottle Motors Pins throttle_enable = 25 # Fysisk pinne 22 in3 = 23 # Fysisk pinne 16 in4 = 24 # Fysisk pinne 18 GPIO.setmode (GPIO. BCM) # Bruk 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 (steering_enable, 1000) # sett koblingsfrekvensen til 1000 Hz styring. Stopp () # Throttle Motors Control GPIO.output (in3, GPIO. HIGH) GPIO.output (in4, GPIO. LOW) throttle = GPIO. PWM (throttle_enable, 1000) # sett koblingsfrekvensen til 1000 Hz throttle.stop () time.sleep (1) throttle.start (25) # starter motoren på 25 % PWM signal-> (0,25 * batterispenning) - sjåføren tapstyring. start (100) # starter motoren ved 100% PWM -signal-> (1 * batterispenning) - førerens tapstid. søvn (3) gass. stopp () styring. stopp ()
Denne koden vil kjøre gassmotorer og styremotor i 3 sekunder og deretter stoppe dem. (Førerens tap) kan bestemmes ved hjelp av et voltmeter. For eksempel vet vi at et 100% PWM -signal skal gi hele batteriets spenning på motorens terminal. Men ved å sette PWM til 100%, fant jeg ut at driveren forårsaker et 3 V -fall og motoren får 9 V i stedet for 12 V (akkurat det jeg trenger!). Tapet er ikke lineært, dvs. tapet med 100% er veldig forskjellig fra tapet med 25%. Etter å ha kjørt koden ovenfor, var resultatene mine som følger:
Gassreguleringsresultater: hvis in3 = HIGH og in4 = LOW, vil gassmotorene ha en Clock-Wise (CW) rotasjon, dvs. bilen vil gå fremover. Ellers vil bilen bevege seg bakover.
Styringsresultater: hvis in1 = HIGH og in2 = LOW, vil styremotoren dreie maksimalt til venstre, dvs. bilen vil styre til venstre. Ellers vil bilen styre til høyre. Etter noen eksperimenter fant jeg ut at styremotoren ikke vil snu hvis PWM -signalet ikke var 100% (dvs. motoren vil styre enten helt til høyre eller helt til venstre).
Trinn 5: Oppdagelse av banelinjer og beregning av kurslinje
I dette trinnet vil algoritmen som skal kontrollere bilens bevegelse bli forklart. Det første bildet viser hele prosessen. Inngangen til systemet er bilder, utgangen er theta (styrevinkel i grader). Vær oppmerksom på at behandlingen er utført på 1 bilde og vil gjentas på alle rammer.
Kamera:
Kameraet begynner å ta opp en video med (320 x 240) oppløsning. Jeg anbefaler å senke oppløsningen, slik at du kan få bedre bildefrekvens (fps) siden fps -fall vil oppstå etter bruk av behandlingsteknikker på hver ramme. Koden nedenfor vil være programmets hovedløkke og vil legge hvert trinn over denne koden.
importer cv2
import numpy as np video = cv2. VideoCapture (0) video.set (cv2. CAP_PROP_FRAME_WIDTH, 320) # sett bredden til 320 p video.set (cv2. CAP_PROP_FRAME_HEIGHT, 240) # sett høyden til 240 p # Sløyfen mens Sant: 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 bildet som ble oppnådd i trinn 4 og er vist på bildene ovenfor.
Konverter til HSV Color Space:
Etter å ha tatt videoopptak som rammer fra kameraet, er neste trinn å konvertere hver ramme til fargetone for fargetone, metning og verdi (HSV). Den største fordelen med å gjøre det er å kunne skille mellom farger etter lysstyrkenivået. Og her er en god forklaring på HSV -fargerommet. Konvertering til HSV gjøres via følgende funksjon:
def convert_to_HSV (ramme):
hsv = cv2.cvtColor (ramme, cv2. COLOR_BGR2HSV) cv2.imshow ("HSV", hsv) returner hsv
Denne funksjonen vil bli kalt fra hovedsløyfen og returnere rammen i HSV -fargerommet. Rammen som ble oppnådd av meg i HSV -fargerommet er vist ovenfor.
Finn blå farge og kanter:
Etter å ha konvertert bildet til HSV -fargerom, er det på tide å oppdage bare fargen vi er interessert i (dvs. blå farge siden det er fargen på banelinjene). For å trekke ut blå farge fra en HSV -ramme, bør et område av fargetone, metning og verdi spesifiseres. referer her for å få en bedre ide om HSV -verdier. Etter noen eksperimenter vises de øvre og nedre grensene for blå farge i koden nedenfor. Og for å redusere den totale forvrengningen i hver ramme, oppdages kanter bare ved hjelp av en kanydetektor. Mer om canny edge finnes her. En tommelfingerregel er å velge parameterne for Canny () -funksjonen med et forhold på 1: 2 eller 1: 3.
def detect_edges (ramme):
lavere_blå = np.array ([90, 120, 0], dtype = "uint8") # nedre grense for blå farge upper_blue = np.array ([150, 255, 255], dtype = "uint8") # øvre grense for blå farge maske = cv2.inRange (hsv, nedre_blå, øvre_blå) # denne masken vil filtrere bort alt annet enn blå # oppdage kanter kanter = cv2. Canny (maske, 50, 100) cv2.imshow ("kanter", kanter) returkanter
Denne funksjonen vil også bli kalt fra hovedsløyfen som tar som parameter HSV -fargerommet og returnerer den kantede rammen. Den kantede rammen jeg fikk, er funnet ovenfor.
Velg interesseområde (ROI):
Å velge område av interesse er avgjørende for å fokusere bare på en region av rammen. I dette tilfellet vil jeg ikke at bilen skal se mange ting i miljøet. Jeg vil bare at bilen skal fokusere på banelinjene og ignorere alt annet. PS: koordinatsystemet (x og y akser) starter fra øvre venstre hjørne. Med andre ord starter punktet (0, 0) fra øvre venstre hjørne. y-aksen er høyden og x-aksen er bredden. Koden nedenfor velger område av interesse for å fokusere bare på den nedre halvdelen av rammen.
def region_for_interest (kanter):
høyde, bredde = kanter. form # trekk ut høyden og bredden på kantrammen = np. nuller_lignende (kanter) # lag en tom matrise med samme dimensjoner på kantrammen # fokuser bare på nedre halvdel av skjermen # angi koordinatene til 4 punkter (nedre venstre, øvre venstre, øvre høyre, nedre høyre) polygon = np.array (
Denne funksjonen tar den kantede rammen som parameter og tegner en polygon med 4 forhåndsinnstilte punkter. Det vil bare fokusere på det som er inne i polygonen og ignorere alt utenfor det. Min interesseområde er vist ovenfor.
Oppdag linjesegmenter:
Hough -transformasjon brukes til å oppdage linjesegmenter fra en kantet ramme. Hough transform er en teknikk for å oppdage enhver form i matematisk form. Den kan oppdage nesten alle gjenstander, selv om den er forvrengt i henhold til et visst antall stemmer. en flott referanse for Hough transform er vist her. For denne applikasjonen brukes cv2. HoughLinesP () -funksjonen til å oppdage linjer i hver ramme. De viktige parameterne denne funksjonen tar er:
cv2. HoughLinesP (frame, rho, theta, min_threshold, minLineLength, maxLineGap)
- Ramme: er rammen vi vil oppdage linjer i.
- rho: Det er avstandspresisjonen i piksler (vanligvis er den = 1)
- theta: kantet presisjon i radianer (alltid = np.pi/180 ~ 1 grad)
- min_threshold: minimumstemme den bør få for å bli sett på som en linje
- minLineLength: minimum lengde på linjen i piksler. En linje som er kortere enn dette tallet regnes ikke som en linje.
- maxLineGap: maksimal gap i piksler mellom 2 linjer som skal behandles som 1 linje. (Den brukes ikke i mitt tilfelle siden banelinjene jeg bruker ikke har noe gap).
Denne funksjonen returnerer endepunktene til en linje. Følgende funksjon kalles fra hovedløkken min for å oppdage linjer ved hjelp av Hough -transform:
def detect_line_segments (beskåret_kanter):
rho = 1 theta = np.pi / 180 min_threshold = 10 line_segments = cv2. HoughLinesP (beskåret_kanter, rho, theta, min_terskel, np.array (), minLineLength = 5, maxLineGap = 0) returlinjesegmenter
Gjennomsnittlig stigning og skjæringspunkt (m, b):
husk at linjens ligning er gitt av y = mx + b. Hvor m er linjens skråning og b er y-skjæringspunktet. I denne delen vil gjennomsnittet av bakker og avskjæringer av linjesegmenter oppdaget ved bruk av Hough -transformasjon bli beregnet. Før vi gjør det, la oss ta en titt på det originale rammebildet vist ovenfor. Venstre kjørefelt ser ut til å gå oppover, så det har en negativ stigning (husker startpunktet for koordinatsystemet?). Med andre ord har den venstre kjørefeltlinjen x1 <x2 og y2 x1 og y2> y1 som vil gi en positiv stigning. Så alle linjer med positiv stigning regnes som høyre kjørefelt. Ved vertikale linjer (x1 = x2) vil stigningen være uendelig. I dette tilfellet hopper vi over alle vertikale linjer for å forhindre at det blir en feil. For å tilføre denne gjenkjenningen mer nøyaktighet, er hver ramme delt inn i to områder (høyre og venstre) gjennom to grenselinjer. Alle breddepunkter (x-aksepunkter) større enn høyre grenselinje, er knyttet til beregning av høyre felt. Og hvis alle breddepunkter er mindre enn venstre grenselinje, er de assosiert med venstre kjørefeltberegning. Følgende funksjon tar rammen under behandling og lanesegmenter som er oppdaget ved hjelp av Hough -transformasjon og returnerer gjennomsnittlig stigning og skjæringspunkt for to banelinjer.
def average_slope_intercept (ramme, linjesegmenter):
lane_lines = hvis line_segments er Ingen: print ("ingen linjesegment oppdaget") returner lane_lines høyde, bredde, _ = frame.shape left_fit = right_fit = boundary = left_region_boundary = bredde * (1 - grense) right_region_boundary = bredde * grense for line_segment i line_segments: for x1, y1, x2, y2 i line_segment: hvis x1 == x2: print ("hopper over vertikale linjer (skråning = uendelig)") fortsett fit = np.polyfit ((x1, x2), (y1, y2), 1) helling = (y2 - y1) / (x2 - x1) skjæringspunkt = y1 - (skråning * x1) hvis helling <0: hvis x1 <venstre_region_grense og x2 høyre_region_grense og x2> høyre_region_grense: høyre_fit. legg til ((skråning, skjæ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 en 2-D-matrise som består av koordinatene til høyre og venstre feltlinje # for eksempel: lan e_lines =
make_points () er en hjelperfunksjon for funksjonen average_slope_intercept () som returnerer de begrensede koordinatene til banelinjene (fra bunnen til midten av rammen).
def make_points (ramme, linje):
høyde, bredde, _ = ramme. formhelling, skjæringspunkt = linje y1 = høyde # bunn av rammen y2 = int (y1 / 2) # lag poeng fra midten av rammen og ned hvis helning == 0: skråning = 0,1 x1 = int ((y1 - skjæringspunkt) / skråning) x2 = int ((y2 - avskjæring) / skråning) retur
For å unngå å dele med 0, presenteres en betingelse. Hvis helning = 0 som betyr y1 = y2 (horisontal linje), gi skråningen en verdi nær 0. Dette vil ikke påvirke algoritmens ytelse, så vel som det vil forhindre umulig tilfelle (dividere med 0).
Følgende funksjon brukes for å vise banelinjene på rammene:
def display_lines (frame, lines, line_color = (0, 255, 0), line_width = 6): # line color (B, G, R)
line_image = np.zeros_like (ramme) 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 () -funksjonen tar følgende parametere, og den brukes til å kombinere to bilder, men med å gi hver en vekt.
cv2.addWeighted (image1, alfa, image2, beta, gamma)
Og beregner utgangsbildet ved å bruke følgende ligning:
output = alfa * image1 + beta * image2 + gamma
Mer informasjon om cv2.addWeighted () -funksjonen er avledet her.
Beregn og vis overskriftslinje:
Dette er det siste trinnet før vi bruker hastigheter på motorene våre. Retningslinjen er ansvarlig for å gi styremotoren retningen den skal rotere i, og gi gassmotorene hastigheten de vil operere med. Beregning av kurslinje er ren trigonometri, tan og atan (tan^-1) trigonometriske funksjoner brukes. Noen ekstreme tilfeller er når kameraet oppdager bare en kjørefeltlinje eller når det ikke oppdager noen linje. Alle disse tilfellene er vist i følgende funksjon:
def get_steering_angle (frame, lane_lines):
høyde, bredde, _ = ramme.form hvis len (lane_lines) == 2: # hvis to banelinjer oppdages _, _, left_x2, _ = lane_lines [0] [0] # ekstrakt igjen x2 fra lane_lines array _, _, right_x2, _ = lane_lines [1] [0] # extract right x2 from lane_lines array mid = int (width / 2) x_offset = (left_x2 + right_x2) / 2 - mid y_offset = int (høyde / 2) elif len (lane_lines) == 1: # hvis bare en linje oppdages x1, _, x2, _ = lane_lines [0] [0] x_offset = x2 - x1 y_offset = int (høyde / 2) elif len (lane_lines) == 0: # hvis ingen linje blir oppdaget x_offset = 0 y_offset = int (høyde / 2) vinkel_til_mid_radian = matematikk.atan (x_forskyvning / y_forskyvning) vinkel_til_mid_deg = int (vinkel_til_mid_radian * 180,0 / math.pi) styring_vinkel = vinkel_til_mid_deg + 90 returstyringsvinkel
x_offset i det første tilfellet er hvor mye gjennomsnittet ((høyre x2 + venstre x2) / 2) er forskjellig fra midten av skjermen. y_offset regnes alltid som høyde / 2. Det siste bildet ovenfor viser et eksempel på overskriftslinje. angle_to_mid_radians er det samme som "theta" vist i det siste bildet ovenfor. Hvis styringsvinkel = 90 betyr det at bilen har en retningslinje vinkelrett på "høyde / 2" -linjen, og bilen vil bevege seg fremover uten styring. Hvis styring_vinkel> 90, bør bilen styre til høyre ellers bør den styre til venstre. For å vise overskriftslinjen brukes følgende funksjon:
def display_heading_line (frame, steering_angle, line_color = (0, 0, 255), line_width = 5)
heading_image = np.zeros_like (ramme) høyde, bredde, _ = frame.shape steering_angle_radian = steering_angle / 180.0 * math.pi x1 = int (bredde / 2) y1 = høyde x2 = int (x1 - høyde / 2 / matematikk. tan) (steering_angle_radian)) y2 = int (høyde / 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 heading_image
Funksjonen ovenfor tar rammen der kurslinjen skal tegnes og styrevinkelen som input. Det returnerer bildet av overskriftslinjen. Overskriftslinjens ramme tatt i mitt tilfelle er vist på bildet ovenfor.
Å kombinere all kode sammen:
Koden er nå klar til å settes sammen. Følgende kode viser hovedløkken til programmet som kaller hver funksjon:
importer 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 (ramme, -1) #Kall funksjonene 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 ()
Trinn 6: Påføre PD Control
Nå har vi vår styrevinkel klar til å mates til motorene. Som nevnt tidligere, hvis styrevinkelen er større enn 90, bør bilen svinge til høyre ellers skal den svinge til venstre. Jeg brukte en enkel kode som dreier styremotoren til høyre hvis vinkelen er over 90 og svinger den til venstre hvis styrevinkelen er mindre enn 90 ved en konstant gasshastighet på (10% PWM), men jeg fikk mange feil. Hovedfeilen jeg fikk er når bilen nærmer seg en sving, styremotoren virker direkte, men gassmotorene setter seg fast. Jeg prøvde å øke gasshastigheten til å være (20% PWM) i svinger, men endte med at roboten kom ut av banene. Jeg trengte noe som øker gasshastigheten mye hvis styrevinkelen er veldig stor og øker farten litt hvis styrevinkelen ikke er så stor og senker hastigheten til en startverdi når bilen nærmer seg 90 grader (beveger seg rett). Løsningen var å bruke en PD -kontroller.
PID -kontrolleren står for Proportional, Integral and Derivative controller. Denne typen lineære kontrollere er mye brukt i robotteknologi. Bildet ovenfor viser den typiske PID -tilbakekoblingskontrollsløyfen. Målet med denne kontrolleren er å nå "settpunktet" med den mest effektive måten i motsetning til "på - av" -kontrollere som slår på eller av anlegget i henhold til noen forhold. Noen søkeord bør være kjent:
- Settpunkt: er ønsket verdi du vil at systemet skal nå.
- Faktisk verdi: er den faktiske verdien som sensoren registrerer.
- Feil: er forskjellen mellom settpunkt og faktisk verdi (feil = settpunkt - faktisk verdi).
- Kontrollert variabel: fra navnet, variabelen du ønsker å kontrollere.
- Kp: proporsjonal konstant.
- Ki: Integral konstant.
- Kd: Derivat konstant.
Kort sagt, PID -kontrollsystemløkken fungerer som følger:
- Brukeren definerer settpunktet som er nødvendig for at systemet skal nå.
- Feilen beregnes (feil = settpunkt - faktisk).
- P -kontrolleren genererer en handling som er proporsjonal med feilens verdi. (feil øker, P -handling øker også)
- I -kontrolleren vil integrere feilen over tid som eliminerer systemets steady state -feil, men øker overskridelsen.
- D -kontrolleren er ganske enkelt tidsderivatet for feilen. Med andre ord, det er feilens skråning. Det utfører en handling proporsjonal med derivatet av feilen. Denne kontrolleren øker systemets stabilitet.
- Utgangen til kontrolleren vil være summen av de tre kontrollerne. Kontrollerens utgang blir 0 hvis feilen blir 0.
En flott forklaring på PID -kontrolleren finner du her.
Når jeg gikk tilbake til kjørefeltet, var den kontrollerte variabelen min hastighet (siden styringen bare har to stater enten til høyre eller venstre). En PD -kontroller brukes til dette formålet siden D -handling øker gasshastigheten mye hvis feilendringen er veldig stor (dvs. stor avvik) og bremser bilen hvis denne feilendringen nærmer seg 0. Jeg gjorde følgende trinn for å implementere en PD kontroller:
- Sett settpunktet til 90 grader (jeg vil alltid at bilen skal bevege seg rett)
- Beregnet avviksvinkelen fra midten
- Avviket gir to opplysninger: Hvor stor feilen er (avvikets størrelse) og hvilken retning styremotoren må ta (tegn på avvik). Hvis avviket er positivt, bør bilen styre til høyre ellers bør den styre til venstre.
- Siden avviket er enten negativt eller positivt, er en "feil" -variabel definert og alltid lik den absolutte verdien av avviket.
- Feilen multipliseres med en konstant Kp.
- Feilen gjennomgår tidsdifferensiering og multipliseres med en konstant Kd.
- Motors hastighet oppdateres og sløyfen starter igjen.
Følgende kode brukes i hovedsløyfen for å kontrollere gassmotorens hastighet:
hastighet = 10 # driftshastighet i % PWM
#Variabler som skal oppdateres hver sløyfe lastTime = 0 lastError = 0 # PD -konstanter Kp = 0,4 Kd = Kp * 0,65 Mens True: nå = time.time () # nåværende tidsvariabel dt = nå - sisteTidsavvik = styring_vinkel - 90 # ekvivalent til vinkel_til_mid_deg variabel feil = abs (avvik) hvis avvik -5: # ikke styr hvis det er et 10 -graders feilområde avvik = 0 feil = 0 GPIO.output (in1, GPIO. LOW) GPIO.output (in2, GPIO. LOW) steering.stop () elif -avvik> 5: # styre til høyre hvis avviket er positivt GPIO.output (in1, GPIO. LOW) GPIO.output (in2, GPIO. HIGH) steering.start (100) elif -avvik < -5: # styre til venstre hvis avviket er negativt GPIO.output (in1, GPIO. HIGH) GPIO.output (in2, GPIO. LOW) styring.start (100) derivat = kd * (feil - lastError) / dt proporsjonal = kp * feil PD = int (hastighet + derivat + proporsjonal) spd = abs (PD) hvis spd> 25: spd = 25 throttle.start (spd) lastError = error lastTime = time.time ()
Hvis feilen er veldig stor (avviket fra midten er høyt), er proporsjonale og avledede handlinger høye, noe som resulterer i høy gasshastighet. Når feilen nærmer seg 0 (avviket fra midten er lavt), virker den avledede handlingen omvendt (skråningen er negativ) og gasshastigheten blir lav for å opprettholde stabiliteten i systemet. Hele koden er vedlagt nedenfor.
Trinn 7: Resultater
Videoene ovenfor viser resultatene jeg oppnådde. Den trenger mer tuning og ytterligere justeringer. Jeg koblet bringebær -pi til LCD -skjermen min fordi videostrømmingen over nettverket mitt hadde høy latens og var veldig frustrerende å jobbe med, det er derfor det er ledninger koblet til bringebær -pi i videoen. Jeg brukte skumplater til å tegne banen på.
Jeg venter på å høre dine anbefalinger for å gjøre dette prosjektet bedre! Som jeg håper at denne instruksen var god nok til å gi deg ny informasjon.
Anbefalt:
DIY -- Hvordan lage en edderkopprobot som kan kontrolleres ved hjelp av smarttelefon ved hjelp av Arduino Uno: 6 trinn
DIY || Hvordan lage en edderkopprobot som kan kontrolleres ved hjelp av smarttelefon ved hjelp av Arduino Uno: Mens du lager en edderkopprobot, kan du lære så mange ting om robotikk. Som å lage roboter er både underholdende og utfordrende. I denne videoen skal vi vise deg hvordan du lager en Spider -robot, som vi kan bruke ved hjelp av smarttelefonen vår (Androi
Hvordan lage en drone ved hjelp av Arduino UNO - Lag en quadcopter ved hjelp av mikrokontroller: 8 trinn (med bilder)
Hvordan lage en drone ved hjelp av Arduino UNO | Lag en Quadcopter ved hjelp av mikrokontroller: Introduksjon Besøk min Youtube -kanal En Drone er en veldig dyr gadget (produkt) å kjøpe. I dette innlegget skal jeg diskutere hvordan jeg får det billig? Og hvordan kan du lage din egen slik til en billig pris … Vel, i India er alle materialer (motorer, ESCer
FinduCar: en smart bilnøkkel som leder folk til hvor bilen står: 11 trinn (med bilder)
FinduCar: en smart bilnøkkel som guider folk til hvor bilen står: For å løse problemene ovenfor foreslår dette prosjektet å utvikle en smart bilnøkkel som kan lede folk dit de parkerte bilen. Og planen min er å integrere en GPS i bilnøkkelen. Det er ikke nødvendig å bruke smarttelefonappen for å spore
Autonom kontroll av turtallet på motoren ved hjelp av tilbakemeldingssystem fra en IR -basert turteller: 5 trinn (med bilder)
Autonom kontroll av turtallet på motoren ved hjelp av tilbakemeldingssystem fra en IR -basert turteller: Det er alltid behov for å automatisere en prosess, det være seg en enkel/uhyrlig. Jeg fikk ideen om å gjøre dette prosjektet fra en enkel utfordring som jeg møtte mens jeg fant metoder for å vanne/vanne det lille landet vårt. Problemet med ingen strømforsyningsledning
Hvordan lage en autonom basketballball som bruker en IRobot Lag som base: 7 trinn (med bilder)
Slik lager du en autonom basketballball som bruker en IRobot Lag som base: Dette er min oppføring for iRobot Create -utfordringen. Den vanskeligste delen av hele denne prosessen for meg var å bestemme hva roboten skulle gjøre. Jeg ønsket å demonstrere de kule funksjonene i Create, samtidig som jeg la til litt robo -teft. Alle mine