Innehållsförteckning:

Autonom Lane-Keeping Car med Raspberry Pi och OpenCV: 7 steg (med bilder)
Autonom Lane-Keeping Car med Raspberry Pi och OpenCV: 7 steg (med bilder)

Video: Autonom Lane-Keeping Car med Raspberry Pi och OpenCV: 7 steg (med bilder)

Video: Autonom Lane-Keeping Car med Raspberry Pi och OpenCV: 7 steg (med bilder)
Video: How To Run TensorFlow Lite on Raspberry Pi for Object Detection 2024, November
Anonim
Autonom Lane-Keeping Car med Raspberry Pi och OpenCV
Autonom Lane-Keeping Car med Raspberry Pi och OpenCV

I dessa instruktioner kommer en autonom körfältrobot att implementeras och kommer att passera följande steg:

  • Samlar delar
  • Förutsättningar för att installera programvara
  • Hårdvara montering
  • Första testet
  • Upptäcka körfältslinjer och visa styrlinjen med hjälp av openCV
  • Implementering av en PD -controller
  • Resultat

Steg 1: Samla komponenter

Samla komponenter
Samla komponenter
Samla komponenter
Samla komponenter
Samla komponenter
Samla komponenter
Samla komponenter
Samla komponenter

Bilderna ovan visar alla komponenter som används i detta projekt:

  • RC -bil: Jag fick min från en lokal butik i mitt land. Den är utrustad med 3 motorer (2 för strypning och 1 för styrning). Den största nackdelen med denna bil är att styrningen är begränsad mellan "ingen styrning" och "fullstyrning". Med andra ord kan den inte styra i en specifik vinkel, till skillnad från servostyrande RC-bilar. Du kan hitta liknande bilmonteringssats speciellt utformad för hallonpi härifrån.
  • Raspberry pi 3 modell b+: detta är hjärnan i bilen som kommer att hantera många bearbetningssteg. Den är baserad på en fyrkärnig 64-bitars processor klockad till 1,4 GHz. Jag fick min härifrån.
  • Raspberry pi 5 mp kameramodul: Den stöder 1080p @ 30 fps, 720p @ 60 fps och 640x480p 60/90 inspelning. Det stöder också seriellt gränssnitt som kan anslutas direkt till hallon pi. Det är inte det bästa alternativet för bildbehandlingsapplikationer men det är tillräckligt för detta projekt och det är mycket billigt. Jag fick min härifrån.
  • Motorförare: Används för att styra DC -motorernas riktningar och hastigheter. Den stöder styrning av 2 likströmsmotorer i ett kort och tål 1,5 A.
  • Power Bank (tillval): Jag använde en powerbank (klassad till 5V, 3A) för att slå på hallonpi separat. En nedgångskonverterare (buck -omvandlare: 3A utström) bör användas för att driva hallon -pi från 1 källa.
  • 3s (12 V) LiPo -batteri: Litiumpolymerbatterier är kända för sin utmärkta prestanda inom robotikområdet. Den används för att driva motorföraren. Jag köpte min härifrån.
  • Manliga till manliga och kvinnliga till kvinnliga bygeltrådar.
  • Dubbelsidig tejp: Används för att montera komponenterna på RC -bilen.
  • Blå tejp: Detta är en mycket viktig komponent i detta projekt, det används för att göra de två filbanorna som bilen kommer att köra mellan. Du kan välja vilken färg du vill, men jag rekommenderar att du väljer andra färger än de i omgivningen.
  • Dragkedjor och trästänger.
  • Skruvmejsel.

Steg 2: Installera OpenCV på Raspberry Pi och konfigurera fjärrskärm

Installera OpenCV på Raspberry Pi och konfigurera fjärrskärm
Installera OpenCV på Raspberry Pi och konfigurera fjärrskärm

Det här steget är lite irriterande och tar lite tid.

OpenCV (Open source Computer Vision) är ett bibliotek med öppen källkod för datorsyn och maskininlärning. Biblioteket har över 2500 optimerade algoritmer. Följ DEN här mycket enkla guiden för att installera openCV på din hallon pi samt installera raspberry pi OS (om du fortfarande inte gjorde det). Observera att processen med att bygga openCV kan ta cirka 1,5 timmar i ett välkyldt rum (eftersom processortemperaturen blir mycket hög!) Så ta lite te och vänta tålmodigt: D.

För fjärrskärmen följer du också den här guiden för att konfigurera fjärråtkomst till din hallon pi från din Windows/Mac -enhet.

Steg 3: Ansluta delar tillsammans

Koppla ihop delar
Koppla ihop delar
Koppla ihop delar
Koppla ihop delar
Koppla ihop delar
Koppla ihop delar

Bilderna ovan visar kopplingarna mellan hallon pi, kameramodul och motordrivrutin. Observera att motorerna jag använde absorberar 0,35 A vid 9 V vardera vilket gör det säkert för motorföraren att köra 3 motorer samtidigt. Och eftersom jag vill styra de två strypmotorernas hastighet (1 bak och 1 fram) på exakt samma sätt kopplade jag dem till samma port. Jag monterade motorföraren på bilens högra sida med dubbeltejp. När det gäller kameramodulen satte jag in en dragkedja mellan skruvhålen som bilden ovan visar. Sedan monterar jag kameran på en trästång så att jag kan justera kamerans position som jag vill. Försök att installera kameran i mitten av bilen så mycket som möjligt. Jag rekommenderar att du placerar kameran minst 20 cm över marken så att synfältet framför bilen blir bättre. Fritzing -schemat är bifogat nedan.

Steg 4: Första testet

Första testet
Första testet
Första testet
Första testet

Kameratestning:

När kameran är installerad och openCV -biblioteket har byggts är det dags att testa vår första bild! Vi tar ett foto från pi cam och sparar det som "original.jpg". Det kan göras på 2 sätt:

1. Använda terminalkommandon:

Öppna ett nytt terminalfönster och skriv följande kommando:

raspistill -o original.jpg

Detta tar en stillbild och sparar den i katalogen "/pi/original.jpg".

2. Använda valfri python IDE (jag använder IDLE):

Öppna en ny skiss och skriv följande kod:

importera cv2

video = cv2. VideoCapture (0) medan True: ret, frame = video.read () frame = cv2.flip (frame, -1) # används för att vända bilden vertikalt cv2.imshow ('original', frame) cv2. imwrite ('original.jpg', frame) key = cv2.waitKey (1) if key == 27: break video.release () cv2.destroyAllWindows ()

Låt oss se vad som hände i den här koden. Den första raden importerar vårt openCV -bibliotek för att använda alla dess funktioner. VideoCapture (0) -funktionen börjar strömma en livevideo från källan som bestäms av denna funktion, i detta fall är det 0 vilket betyder raspikamera. Om du har flera kameror ska olika nummer placeras. video.read () kommer att läsa varje bildruta kommer från kameran och spara den i en variabel som kallas "ram". flip () -funktionen vänder bilden med avseende på y-axeln (vertikalt) eftersom jag monterar min kamera omvänt. imshow () visar våra ramar med ordet "original" och imwrite () sparar vårt foto som original.jpg. waitKey (1) väntar i 1 ms för att någon tangentbordsknapp ska tryckas in och returnerar sin ASCII -kod. om Escape (esc) -knappen trycks, returneras ett decimalvärde på 27 och bryter slingan i enlighet därmed. video.release () slutar spela in och destroyAllWindows () stänger varje bild som öppnas med funktionen imshow ().

Jag rekommenderar att du testar ditt foto med den andra metoden för att bekanta dig med openCV -funktioner. Bilden sparas i katalogen "/pi/original.jpg". Det ursprungliga fotot som min kamera tog visas ovan.

Testmotorer:

Detta steg är viktigt för att bestämma rotationsriktningen för varje motor. Låt oss först ha en kort introduktion om arbetsprincipen för en motorförare. Bilden ovan visar motorförarens pin-out. Aktivera A, ingång 1 och ingång 2 är kopplade till motor A -styrning. Aktivera B, ingång 3 och ingång 4 är kopplade till motor B -styrning. Riktningskontroll upprättas av "Input" -delen och hastighetskontroll upprättas av "Enable" -del. För att styra motorns riktning till exempel, ställ in ingång 1 till HÖG (3,3 V i det här fallet eftersom vi använder en hallon pi) och ställ in ingång 2 till LÅG, motorn snurrar i en specifik riktning och genom att ställa in motsatta värden till ingång 1 och ingång 2, snurrar motorn i motsatt riktning. Om ingång 1 = ingång 2 = (HÖG eller LÅG), går inte motorn. Aktivera stiften tar en Pulse Width Modulation (PWM) insignal från hallon (0 till 3,3 V) och kör motorerna i enlighet därmed. Till exempel betyder en 100% PWM -signal att vi arbetar med maxhastigheten och 0% PWM -signal betyder att motorn inte roterar. Följande kod används för att bestämma motorernas riktningar och testa deras varvtal.

importtid

importera RPi. GPIO som GPIO GPIO.setvarnings (falskt) # Styrmotorn Pinnar steering_enable = 22 # Physical Pin 15 in1 = 17 # Physical Pin 11 in2 = 27 # Physical Pin 13 #Trottle Motors Pins throttle_enable = 25 # Physical Pin 22 in3 = 23 # Physical Pin 16 in4 = 24 # Physical Pin 18 GPIO.setmode (GPIO. BCM) # Använd GPIO -numrering istället för 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) # Styrmotorstyrning GPIO.output (in1, GPIO. HIGH) GPIO.output (in2, GPIO. LOW) styrning = GPIO. PWM (steering_enable, 1000) # ställ in omkopplingsfrekvensen till 1000 Hz styrning.stopp () # Gasreglage Motorer Kontroll GPIO.output (in3, GPIO. HIGH) GPIO.output (in4, GPIO. LOW) throttle = GPIO. PWM (throttle_enable, 1000) # ställ in omkopplingsfrekvensen till 1000 Hz throttle.stop () time.sleep (1) throttle.start (25) # startar motorn vid 25 % PWM -signal-> (0,25 * batterispänning) - förarens förluststyrning. start (100) # startar motorn vid 100% PWM -signal-> (1 * batterispänning) - förarens förlusttid. sover (3) gasreglage. stopp () styrning. stopp ()

Denna kod kommer att köra strypmotorerna och styrmotorn i 3 sekunder och sedan stoppa dem. (Förarens förlust) kan bestämmas med hjälp av en voltmeter. Till exempel vet vi att en 100% PWM -signal ska ge hela batteriets spänning vid motorns terminal. Men genom att ställa in PWM till 100%fann jag att föraren orsakar ett 3 V -fall och motorn får 9 V istället för 12 V (precis vad jag behöver!). Förlusten är inte linjär, dvs förlusten med 100% skiljer sig mycket från förlusten med 25%. Efter att ha kört ovanstående kod var mina resultat följande:

Strypresultat: om in3 = HIGH och in4 = LOW kommer strypmotorerna att ha en Clock-Wise (CW) -rotation, det vill säga att bilen går framåt. Annars går bilen bakåt.

Styrresultat: om in1 = HIGH och in2 = LOW, vrider styrmotorn maximalt till vänster, dvs bilen styr till vänster. Annars styr bilen åt höger. Efter några experiment fann jag att styrmotorn inte kommer att rotera om PWM -signalen inte var 100% (dvs motorn styr antingen helt åt höger eller helt till vänster).

Steg 5: Upptäcka körfält och beräkna kurslinje

Upptäcka körfältslinjer och beräkna kurslinje
Upptäcka körfältslinjer och beräkna kurslinje
Upptäcka körfältslinjer och beräkna kurslinje
Upptäcka körfältslinjer och beräkna kurslinje
Upptäcka körfältslinjer och beräkna kurslinje
Upptäcka körfältslinjer och beräkna kurslinje

I detta steg förklaras algoritmen som styr bilens rörelse. Den första bilden visar hela processen. Systemets ingång är bilder, utgången är theta (styrvinkel i grader). Observera att bearbetningen sker på 1 bild och kommer att upprepas på alla ramar.

Kamera:

Kameran börjar spela in en video med (320 x 240) upplösning. Jag rekommenderar att sänka upplösningen så att du kan få bättre bildhastighet (fps) eftersom fps -fall kommer att inträffa efter applicering av bearbetningsteknik på varje bildruta. Koden nedan kommer att vara programmets huvudslinga och kommer att lägga till varje steg över denna kod.

importera cv2

importera numpy som np video = cv2. VideoCapture (0) video.set (cv2. CAP_PROP_FRAME_WIDTH, 320) # ställ in bredden till 320 p video.set (cv2. CAP_PROP_FRAME_HEIGHT, 240) # ställ in höjden till 240 p # Slingan medan 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 här visar originalbilden som erhölls i steg 4 och visas på bilderna ovan.

Konvertera till HSV Color Space:

Nu efter att ha tagit videoinspelning som ramar från kameran är nästa steg att konvertera varje bildruta till färgutrymme för nyans, mättnad och värde (HSV). Den största fördelen med att göra det är att kunna skilja mellan färger genom sin ljusstyrka. Och här är en bra förklaring till HSV -färgutrymme. Konvertering till HSV görs med följande funktion:

def convert_to_HSV (ram):

hsv = cv2.cvtColor (ram, cv2. COLOR_BGR2HSV) cv2.imshow ("HSV", hsv) returnerar hsv

Denna funktion kommer att anropas från huvudslingan och returnera ramen i HSV -färgutrymme. Ramen som jag fått i HSV -färgutrymme visas ovan.

Upptäck blå färg och kanter:

Efter att ha konverterat bilden till HSV -färgutrymme är det dags att bara upptäcka den färg vi är intresserade av (dvs blå färg eftersom det är färgen på körfältslinjerna). För att extrahera blå färg från en HSV -ram, bör ett nyansintervall, mättnad och värde anges. hänvisa här för att få en bättre uppfattning om HSV -värden. Efter några experiment visas de övre och nedre gränserna för blå färg i koden nedan. Och för att minska den totala förvrängningen i varje bildruta detekteras kanterna endast med en kanydetektor. Mer om canny edge finns här. En tumregel är att välja parametrarna för Canny () -funktionen med förhållandet 1: 2 eller 1: 3.

def detect_edges (ram):

lower_blue = np.array ([90, 120, 0], dtype = "uint8") # nedre gräns för blå färg upper_blue = np.array ([150, 255, 255], dtype = "uint8") # övre gräns för blå färgmask = cv2.inRange (hsv, nedre_blå, övre_blå) # denna mask filtrerar bort allt utom blått # upptäcker kanter kanter = cv2. Canny (mask, 50, 100) cv2.imshow ("kanter", kanter) returkanter

Denna funktion kommer också att kallas från huvudslingan som tar som parameter HSV -färgutrymmet och returnerar den kantade ramen. Den kantade ramen jag fick hittades ovan.

Välj intresseområde (ROI):

Att välja intresseområde är avgörande för att bara fokusera på en region i ramen. I det här fallet vill jag inte att bilen ska se många föremål i miljön. Jag vill bara att bilen ska fokusera på körfältet och ignorera allt annat. P. S: koordinatsystemet (x- och y -axlar) börjar från det övre vänstra hörnet. Med andra ord börjar punkten (0, 0) från det övre vänstra hörnet. y-axeln är höjden och x-axeln är bredden. Koden nedan väljer region av intresse för att bara fokusera på den nedre halvan av ramen.

def region_of_interest (kanter):

höjd, bredd = kanter. form # extrahera höjden och bredden på kanternas rammask = np.zeros_like (kanter) # gör en tom matris med samma dimensioner av kanteramen # fokusera endast den nedre halvan av skärmen # ange koordinaterna för 4 punkter (nedre vänster, övre vänster, övre högra, nedre högra) polygon = np.array (

Denna funktion tar den kantade ramen som parameter och ritar en polygon med 4 förinställda punkter. Det kommer bara att fokusera på vad som finns inuti polygonen och ignorera allt utanför det. Min ram av intresse visas ovan.

Upptäck linjesegment:

Hough -transform används för att detektera linjesegment från en kantad ram. Hough transform är en teknik för att upptäcka vilken form som helst i matematisk form. Det kan upptäcka nästan alla objekt även om det är förvrängt enligt ett visst antal röster. en bra referens för Hough -transform visas här. För denna applikation används funktionen cv2. HoughLinesP () för att upptäcka linjer i varje ram. De viktiga parametrarna för denna funktion är:

cv2. HoughLinesP (ram, rho, theta, min_tröskel, minLineLängd, maxLineGap)

  • Ram: är den ram vi vill upptäcka linjer i.
  • rho: Det är avståndets precision i pixlar (vanligtvis är det = 1)
  • theta: vinkeln precision i radianer (alltid = np.pi/180 ~ 1 grad)
  • min_tröskel: minsta röst för att den ska betraktas som en rad
  • minLineLength: minsta radlängd i pixlar. Varje rad som är kortare än detta nummer anses inte vara en rad.
  • maxLineGap: maximalt gap i pixlar mellan 2 rader som ska behandlas som 1 rad. (Det används inte i mitt fall eftersom de körfält som jag använder inte har något mellanrum).

Denna funktion returnerar slutpunkterna för en rad. Följande funktion kallas från min huvudslinga för att upptäcka linjer med Hough -transform:

def detect_line_segments (beskurna_kanter):

rho = 1 theta = np.pi / 180 min_tröskel = 10 line_segments = cv2. HoughLinesP (beskurna_kanter, rho, theta, min_tröskel, np.array (), minLineLength = 5, maxLineGap = 0) returlinjesegment

Genomsnittlig lutning och skärning (m, b):

kom ihåg att linjens ekvation ges av y = mx + b. Där m är linjens lutning och b är y-skärningen. I denna del beräknas genomsnittet av sluttningar och avlyssningar av linjesegment som detekteras med hjälp av Hough -transform. Innan vi gör det, låt oss ta en titt på det ursprungliga ramfotot som visas ovan. Det vänstra körfältet verkar gå uppåt så det har en negativ lutning (kommer du ihåg koordinatsystemets startpunkt?). Med andra ord har den vänstra körfältet x1 <x2 och y2 x1 och y2> y1 vilket ger en positiv lutning. Så alla linjer med positiv lutning betraktas som höger körfältpunkter. Vid vertikala linjer (x1 = x2) kommer lutningen att vara oändlig. I det här fallet hoppar vi över alla vertikala linjer för att förhindra att ett fel uppstår. För att lägga till mer noggrannhet för denna detektering är varje ram uppdelad i två regioner (höger och vänster) genom två gränslinjer. Alla breddpunkter (x-axelpunkter) större än höger gränslinje är associerade med högerfältberäkning. Och om alla breddpunkter är mindre än den vänstra gränslinjen, är de associerade med beräkning av vänsterfält. Följande funktion tar ramen under bearbetning och körfältssegment som detekteras med hjälp av Hough -transform och returnerar den genomsnittliga lutningen och avlyssningen för två körfältslinjer.

def average_slope_intercept (ram, rad_segment):

lane_lines = om line_segments är None: print ("inget radsegment detekterat") returnerar lane_lines höjd, bredd, _ = frame.shape left_fit = right_fit = boundary = left_region_boundary = bredd * (1 - gräns) right_region_boundary = bredd * gräns för line_segment i line_segments: för x1, y1, x2, y2 i line_segment: om x1 == x2: print ("hoppa över vertikala linjer (lutning = oändlighet)") fortsätt fit = np.polyfit ((x1, x2), (y1, y2), 1) lutning = (y2 - y1) / (x2 - x1) avlyssning = y1 - (lutning * x1) om lutning <0: om x1 <vänster_region_gräns och x2 höger_region_gräns och x2> höger_region_gräns: höger_passning. lägg till ((lutning, skärning)) 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) om len (right_fit)> 0: lane_lines.append (make_points (frame, right_fit_average)) # lane_lines är en 2-D-array som består av koordinaterna för höger- och vänsterfältlinjer # till exempel: lan e_lines =

make_points () är en hjälpfunktion för funktionen average_slope_intercept () som kommer att returnera de begränsade koordinaterna för körfältslinjerna (från botten till mitten av ramen).

def make_points (ram, rad):

höjd, bredd, _ = ram. form lutning, skärning = linje y1 = höjd # botten av ramen y2 = int (y1 / 2) # gör punkter från mitten av ramen ner om lutning == 0: lutning = 0,1 x1 = int ((y1 - skärning) / lutning) x2 = int ((y2 - skärning) / lutning) retur

För att förhindra att dividera med 0 presenteras ett villkor. Om lutning = 0 vilket betyder y1 = y2 (horisontell linje), ge lutningen ett värde nära 0. Detta påverkar inte algoritmens prestanda så väl som det förhindrar omöjligt fall (dividerat med 0).

För att visa körfältslinjerna på ramarna används följande funktion:

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

line_image = np.zeros_like (ram) om rader inte är None: för rad i rader: för x1, y1, x2, y2 på rad: 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 tar följande parametrar och den används för att kombinera två bilder men med att ge var och en en vikt.

cv2.addWeighted (bild1, alfa, bild2, beta, gamma)

Och beräknar utmatningsbilden med följande ekvation:

output = alfa * image1 + beta * image2 + gamma

Mer information om funktionen cv2.addWeighted () härleds här.

Beräkna och visa rubrikrad:

Detta är det sista steget innan vi applicerar hastigheter på våra motorer. Körlinjen är ansvarig för att ge styrmotorn i vilken riktning den ska rotera och ge strypmotorerna den hastighet som de kommer att arbeta med. Beräkning av kurslinje är ren trigonometri, tan och atan (tan^-1) trigonometriska funktioner används. Vissa extrema fall är när kameran bara detekterar en körfältlinje eller när den inte känner av någon linje. Alla dessa fall visas i följande funktion:

def get_steering_angle (frame, lane_lines):

höjd, bredd, _ = ram.form om len (lane_lines) == 2: # om två körfält linjer detekteras _, _, left_x2, _ = lane_lines [0] [0] # extrakt kvar x2 från lane_lines array _, _, right_x2, _ = lane_lines [1] [0] # extrahera höger x2 från lane_lines array mid = int (bredd / 2) x_offset = (vänster_x2 + höger_x2) / 2 - mitten y_offset = int (höjd / 2) elif len (lane_lines)) == 1: # om bara en rad detekteras x1, _, x2, _ = lane_lines [0] [0] x_offset = x2 - x1 y_offset = int (höjd / 2) elif len (lane_lines) == 0: # om ingen rad detekteras x_offset = 0 y_offset = int (höjd / 2) vinkel_till_mid_radian = matematik.atan (x_förskjutning / y_förskjutning) vinkel_till_med_deg = int (vinkel_till_med_radian * 180,0 / math.pi) styrningsvinkel = vinkel_till_med_deg + 90 returstyrningsvinkel

x_offset i det första fallet är hur mycket genomsnittet ((höger x2 + vänster x2) / 2) skiljer sig från mitten av skärmen. y_offset anses alltid vara höjd / 2. Den sista bilden ovan visar ett exempel på rubrikrad. angle_to_mid_radians är samma som "theta" som visas i den sista bilden ovan. Om steering_angle = 90 betyder det att bilen har en kurslinje vinkelrätt mot "höjd / 2" -linjen och bilen går framåt utan styrning. Om styrkransen> 90 ska bilen styra åt höger annars ska den styra åt vänster. För att visa rubrikraden används följande funktion:

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

heading_image = np.zeros_like (ram) höjd, bredd, _ = frame.shape steering_angle_radian = steering_angle / 180,0 * math.pi x1 = int (bredd / 2) y1 = höjd x2 = int (x1 - höjd / 2 / matematik. tan)) return rubrik_bild

Funktionen ovan tar ramen där kurslinjen kommer att dras på och styrvinkel som ingång. Det returnerar bilden av rubrikraden. Rubriklinjeramen som tas i mitt fall visas i bilden ovan.

Kombinera all kod tillsammans:

Koden är nu klar att monteras. Följande kod visar programmets huvudslinga som kallar varje funktion:

importera cv2

importera numpy som np video = cv2. VideoCapture (0) video.set (cv2. CAP_PROP_FRAME_WIDTH, 320) video.set (cv2. CAP_PROP_FRAME_HEIGHT, 240) medan True: ret, frame = video.read () frame = cv2.flip (ram, -1) #Ring till funktionerna 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 ()

Steg 6: Tillämpa PD -kontroll

Tillämpa PD -kontroll
Tillämpa PD -kontroll

Nu har vi vår styrvinkel redo att matas till motorerna. Som nämnts tidigare, om styrvinkeln är större än 90, ska bilen svänga åt höger annars ska den svänga till vänster. Jag använde en enkel kod som vrider styrmotorn åt höger om vinkeln är över 90 och vrider den åt vänster om styrvinkeln är mindre än 90 vid en konstant strypningshastighet på (10% PWM) men jag fick många fel. Huvudfelet jag fick är när bilen närmar sig någon sväng, styrmotorn verkar direkt men strypmotorerna fastnar. Jag försökte öka gasreglaget för att vara (20% PWM) i svängar men slutade med att roboten tog sig ur banorna. Jag behövde något som ökar stryphastigheten mycket om styrvinkeln är mycket stor och ökar hastigheten lite om styrvinkeln inte är så stor minskar sedan hastigheten till ett initialvärde när bilen närmar sig 90 grader (rör sig rakt). Lösningen var att använda en PD -controller.

PID -controller står för Proportional, Integral and Derivative controller. Denna typ av linjära styrenheter används ofta i robotikapplikationer. Bilden ovan visar den typiska PID -återkopplingsstyrslingan. Målet med denna styrenhet är att nå "börvärdet" med det mest effektiva sättet till skillnad från "på - av" -kontroller som slår på eller av anläggningen enligt vissa förhållanden. Några sökord bör vara kända:

  • Börvärde: är det önskade värdet du vill att ditt system ska nå.
  • Faktiskt värde: är det verkliga värdet som sensorn känner av.
  • Fel: är skillnaden mellan börvärde och verkligt värde (fel = Börvärde - Faktiskt värde).
  • Kontrollerad variabel: från dess namn, variabeln du vill styra.
  • Kp: Proportionell konstant.
  • Ki: Integral konstant.
  • Kd: Derivat konstant.

Kort sagt fungerar PID -styrsystemslingan enligt följande:

  • Användaren definierar börvärdet som systemet behöver nå.
  • Felet beräknas (fel = börvärde - verkligt).
  • P -styrenheten genererar en åtgärd som är proportionell mot felets värde. (fel ökar, P -åtgärd ökar också)
  • I -styrenheten kommer att integrera felet över tiden vilket eliminerar systemets steady state -fel men ökar överskottet.
  • D -styrenhet är helt enkelt tidsderivatet för felet. Med andra ord är det felets lutning. Det gör en åtgärd proportionell mot derivatet av felet. Denna styrenhet ökar systemets stabilitet.
  • Utmatningen från regulatorn kommer att vara summan av de tre styrenheterna. Styrenhetens utgång blir 0 om felet blir 0.

En bra förklaring av PID -regulatorn hittar du här.

När jag gick tillbaka till körfältet, var min kontrollerade variabel stryphastighet (eftersom styrningen bara har två tillstånd antingen höger eller vänster). En PD -controller används för detta ändamål eftersom D -åtgärd ökar strypningshastigheten mycket om feländringen är mycket stor (dvs stor avvikelse) och saktar ner bilen om denna feländring närmar sig 0. Jag gjorde följande steg för att implementera en PD kontroller:

  • Ställ in börvärdet till 90 grader (jag vill alltid att bilen ska röra sig rakt)
  • Beräknat avvikelsevinkeln från mitten
  • Avvikelsen ger två uppgifter: Hur stort felet är (avvikelsens storlek) och vilken riktning styrmotorn måste ta (tecken på avvikelse). Om avvikelsen är positiv bör bilen styra åt höger annars ska den styra åt vänster.
  • Eftersom avvikelsen är antingen negativ eller positiv definieras en "fel" -variabel och är alltid lika med avvikelsens absoluta värde.
  • Felet multipliceras med en konstant Kp.
  • Felet genomgår tidsdifferentiering och multipliceras med en konstant Kd.
  • Motors hastighet uppdateras och slingan startar igen.

Följande kod används i huvudslingan för att styra strypmotorns hastighet:

hastighet = 10 # driftshastighet i % PWM

#Variabler som ska uppdateras varje slinga lastTime = 0 lastError = 0 # PD -konstanter Kp = 0,4 Kd = Kp * 0,65 Medan True: nu = time.time () # aktuell tidsvariabel dt = nu - lastTime -avvikelse = styrningsvinkel - 90 # ekvivalent till angle_to_mid_deg variabelfel = abs (avvikelse) om avvikelse -5: # inte styra om det finns en 10 -graders felintervallavvikelse = 0 fel = 0 GPIO.output (in1, GPIO. LOW) GPIO.output (in2, GPIO. LÅG) steering.stop () elif -avvikelse> 5: # styra åt höger om avvikelsen är positiv GPIO.output (in1, GPIO. LOW) GPIO.output (in2, GPIO. HIGH) steering.start (100) elif deviation < -5: # styr vänster om avvikelsen är negativ GPIO.output (in1, GPIO. HIGH) GPIO.output (in2, GPIO. LOW) styrning.start (100) derivat = kd * (fel - lastError) / dt proportionell = kp * fel PD = int (hastighet + derivat + proportionellt) spd = abs (PD) om spd> 25: spd = 25 gasreglage. start (spd) lastError = fel lastTime = time.time ()

Om felet är mycket stort (avvikelsen från mitten är hög) är proportionella och derivata åtgärder höga vilket resulterar i hög strypningshastighet. När felet närmar sig 0 (avvikelsen från mitten är låg), verkar den härledda åtgärden omvänd (lutningen är negativ) och strypningshastigheten blir låg för att bibehålla systemets stabilitet. Hela koden bifogas nedan.

Steg 7: Resultat

Videorna ovan visar resultaten jag fått. Det behöver mer inställning och ytterligare justeringar. Jag anslöt hallon pi till min LCD -skärm eftersom videoströmningen över mitt nätverk hade hög latens och var mycket frustrerande att arbeta med, det är därför det finns ledningar anslutna till hallon pi i videon. Jag använde skumbrädor för att rita spåret på.

Jag väntar på att höra dina rekommendationer för att göra detta projekt bättre! Eftersom jag hoppas att denna instruktion var tillräckligt bra för att ge dig lite ny information.

Rekommenderad: