Talaan ng mga Nilalaman:

Autonomous Lane-Keeping Car Gamit ang Raspberry Pi at OpenCV: 7 Hakbang (na may Mga Larawan)
Autonomous Lane-Keeping Car Gamit ang Raspberry Pi at OpenCV: 7 Hakbang (na may Mga Larawan)

Video: Autonomous Lane-Keeping Car Gamit ang Raspberry Pi at OpenCV: 7 Hakbang (na may Mga Larawan)

Video: Autonomous Lane-Keeping Car Gamit ang Raspberry Pi at OpenCV: 7 Hakbang (na may Mga Larawan)
Video: BAMF ESS Inertial Navigation Systems an essential tool to create the roadmap for Autonomous Vehicle 2024, Nobyembre
Anonim
Autonomous Lane-Keeping Car Gamit ang Raspberry Pi at OpenCV
Autonomous Lane-Keeping Car Gamit ang Raspberry Pi at OpenCV

Sa mga itinuturo na ito, ang isang autonomous lane na pagpapanatili ng lane ay ipapatupad at dadaan sa mga sumusunod na hakbang:

  • Mga bahagi ng pagtitipon
  • Pag-install ng mga kinakailangan sa software
  • Pagpupulong ng hardware
  • Unang Pagsubok
  • Ang pagtuklas ng mga linya ng linya at pagpapakita ng linya ng paggabay gamit ang openCV
  • Pagpapatupad ng isang PD controller
  • Mga Resulta

Hakbang 1: Pagtitipon ng Mga Bahagi

Pagtitipon ng Mga Bahagi
Pagtitipon ng Mga Bahagi
Pagtitipon ng Mga Bahagi
Pagtitipon ng Mga Bahagi
Pagtitipon ng Mga Bahagi
Pagtitipon ng Mga Bahagi
Pagtitipon ng Mga Bahagi
Pagtitipon ng Mga Bahagi

Ipinapakita ng mga imahe sa itaas ang lahat ng mga sangkap na ginamit sa proyektong ito:

  • RC car: Nakuha ko ang minahan mula sa isang lokal na tindahan sa aking bansa. Nilagyan ito ng 3 motor (2 para sa throttling at 1 para sa pagpipiloto). Ang pangunahing kawalan ng kotseng ito ay ang pagpipiloto ay limitado sa pagitan ng "walang pagpipiloto" at "buong pagpipiloto". Sa madaling salita, hindi ito maaaring patnubayan sa isang tukoy na anggulo, hindi tulad ng mga servo-steering RC car. Maaari kang makahanap ng katulad na car kit na espesyal na idinisenyo para sa raspberry pi mula rito.
  • Raspberry pi 3 model b +: ito ang utak ng kotse na hahawak sa maraming yugto ng pagproseso. Ito ay batay sa isang quad core na 64-bit na processor na naka-orasan sa 1.4 GHz. Nakuha ko ang akin dito.
  • Modul ng camera ng Raspberry pi 5 mp: Sinusuportahan nito ang 1080p @ 30 fps, 720p @ 60 fps, at 640x480p 60/90 na pag-record. Sinusuportahan din nito ang serial interface na maaaring mai-plug nang direkta sa raspberry pi. Hindi ito ang pinakamahusay na pagpipilian para sa mga aplikasyon sa pagproseso ng imahe ngunit sapat ito para sa proyektong ito pati na rin ito ay napaka mura. Nakuha ko ang akin dito.
  • Motor Driver: Ginagamit upang makontrol ang mga direksyon at bilis ng mga motor na DC. Sinusuportahan nito ang kontrol ng 2 dc motors sa 1 board at makatiis sa 1.5 A.
  • Power Bank (Opsyonal): Gumamit ako ng isang power bank (na-rate sa 5V, 3A) upang paandarin ang hiwalay na raspberry pi. Ang isang step down converter (buck converter: 3A output kasalukuyang) ay dapat gamitin upang mapalakas ang raspberry pi mula sa 1 mapagkukunan.
  • 3s (12 V) LiPo na baterya: Ang mga baterya ng Lithium Polymer ay kilala para sa kanilang mahusay na pagganap sa larangan ng robotics. Ginagamit ito upang paandarin ang driver ng motor. Bumili ako dito ng akin.
  • Lalake sa lalaki at babae sa mga kable ng jumper ng babae.
  • Double sided tape: Ginamit upang mai-mount ang mga bahagi sa RC car.
  • Blue tape: Ito ay isang napaka-importanteng bahagi ng proyektong ito, ginagamit ito upang gawin ang dalawang linya ng linya kung saan ang kotse ay magmaneho sa pagitan. Maaari kang pumili ng anumang kulay na gusto mo ngunit inirerekumenda kong pumili ng mga kulay na naiiba kaysa sa mga nasa paligid.
  • Mga kurbatang zip at mga kahoy na bar.
  • Screw driver.

Hakbang 2: Pag-install ng OpenCV sa Raspberry Pi at Pag-set up ng Remote Display

Pag-install ng OpenCV sa Raspberry Pi at Pag-set up ng Remote Display
Pag-install ng OpenCV sa Raspberry Pi at Pag-set up ng Remote Display

Ang hakbang na ito ay medyo nakakainis at magtatagal.

Ang OpenCV (Open source Computer Vision) ay isang open source computer vision at library ng software ng pag-aaral ng machine. Ang library ay may higit sa 2500 mga na-optimize na algorithm. Sundin ITO napaka deretsong gabay upang mai-install ang openCV sa iyong raspberry pi pati na rin ang pag-install ng raspberry pi OS (kung hindi mo pa rin ginawa). Mangyaring tandaan na ang proseso ng pagbuo ng openCV ay maaaring tumagal ng halos 1.5 oras sa isang pinalamig na silid (dahil ang temperatura ng processor ay magiging napakataas!) Kaya't magkaroon ng tsaa at matiyagang maghintay: D.

Para sa remote display, sundin din ang gabay na ITO upang ma-setup ang malayuang pag-access sa iyong raspberry pi mula sa iyong Windows / Mac device.

Hakbang 3: Pagkonekta ng Mga Bahaging Magkasama

Pagkonekta ng Mga Bahaging Magkasama
Pagkonekta ng Mga Bahaging Magkasama
Pagkonekta ng Mga Bahaging Magkasama
Pagkonekta ng Mga Bahaging Magkasama
Pagkonekta ng Mga Bahaging Magkasama
Pagkonekta ng Mga Bahaging Magkasama

Ipinapakita ng mga imahe sa itaas ang mga koneksyon sa pagitan ng raspberry pi, module ng kamera at driver ng motor. Mangyaring tandaan na ang mga motor na ginamit ko ay sumisipsip ng 0.35 A sa 9 V bawat isa na ginagawang ligtas para sa driver ng motor na magpatakbo ng 3 motor nang sabay. At dahil gusto kong kontrolin ang bilis ng 2 mga throttling motor (1 likuran at 1 harap) nang eksakto sa parehong paraan, ikinonekta ko ang mga ito sa parehong port. Inilagay ko ang driver ng motor sa kanang bahagi ng kotse gamit ang double tape. Tulad ng para sa module ng camera, nagsingit ako ng isang zip tie sa pagitan ng mga butas ng tornilyo tulad ng ipinapakita ng imahe sa itaas. Pagkatapos, inilagay ko ang camera sa isang kahoy na bar upang maisaayos ko ang posisyon ng camera ayon sa gusto ko. Subukang i-install ang camera sa gitna ng kotse hangga't maaari. Inirerekumenda ko ang paglalagay ng camera ng hindi bababa sa 20 cm sa itaas ng lupa upang ang patlang ng view sa harap ng kotse ay magiging mas mahusay. Ang eskematiko ng Fritzing ay nakakabit sa ibaba.

Hakbang 4: Unang Pagsubok

Unang Pagsubok
Unang Pagsubok
Unang Pagsubok
Unang Pagsubok

Pagsubok sa Camera:

Kapag na-install na ang camera, at nakabukas ang library ng openCV, oras na upang subukan ang aming unang imahe! Kukuha kami ng larawan mula sa pi cam at i-save ito bilang "orihinal.jpg". Maaari itong gawin sa 2 paraan:

1. Paggamit ng Mga Utos sa Terminal:

Magbukas ng isang bagong window ng terminal at i-type ang sumusunod na utos:

raspistill -o orihinal.jpg

Kukuha ito ng isang imahe na tahimik at mai-save ito sa direktoryo ng "/pi/original.jpg".

2. Paggamit ng anumang python IDE (Gumagamit ako ng IDLE):

Magbukas ng isang bagong sketch at isulat ang sumusunod na code:

import cv2

video = cv2. VideoCapture (0) habang Totoo: ret, frame = video.read () frame = cv2.flip (frame, -1) # ginamit upang i-flip ang imahe patayo cv2.imshow ('orihinal', frame) cv2. imwrite ('original.jpg', frame) key = cv2.waitKey (1) kung key == 27: break video.release () cv2.destroyAllWindows ()

Tingnan natin kung ano ang nangyari sa code na ito. Ang unang linya ay ina-import ang aming openCV library upang magamit ang lahat ng mga pag-andar nito. ang pag-andar ng VideoCapture (0) ay nagsisimulang mag-stream ng isang live na video mula sa mapagkukunan na tinutukoy ng pagpapaandar na ito, sa kasong ito ay 0 na nangangahulugang raspi camera. kung mayroon kang maraming mga camera, iba't ibang mga numero ay dapat na mailagay. babasahin ng video.read () ang bawat frame na nagmula sa camera at i-save ito sa isang variable na tinatawag na "frame". I-flip () ng function na i-flip ang imahe na may paggalang sa y-axis (patayo) dahil inversely ko ang aking camera. ipapakita ng imshow () ang aming mga frame na pinamumunuan ng salitang "orihinal" at imwrite () ay i-save ang aming larawan bilang orihinal na.jpg. maghihintay ang waitKey (1) ng 1 ms para sa anumang pindutan ng keyboard na mapindot at ibabalik ang ASCII code nito. kung ang pindutan ng pagtakas (esc) ay pinindot, isang decimal na halaga na 27 ay ibabalik at masisira ang loop nang naaayon. ihihinto ng video.release () ang pagre-record at sirain ang AllWindows () ay isasara ang bawat imahe na binuksan ng imshow () na pagpapaandar.

Inirerekumenda kong subukan ang iyong larawan sa pangalawang pamamaraan upang pamilyar sa mga pagpapaandar ng openCV. Ang imahe ay nai-save sa direktoryo ng "/pi/original.jpg". Ang orihinal na larawang kinunan ng aking camera ay ipinapakita sa itaas.

Mga Motors ng Pagsubok:

Mahalaga ang hakbang na ito upang matukoy ang direksyon ng pag-ikot ng bawat motor. Una, magkaroon tayo ng isang maikling pagpapakilala sa nagtatrabaho prinsipyo ng isang motor driver. Ipinapakita ng imahe sa itaas ang pin-out ng driver ng motor. Paganahin ang A, Input 1 at Input 2 na nauugnay sa motor A control. Paganahin ang B, Input 3 at Input 4 ay nauugnay sa motor B control. Ang kontrol sa direksyon ay itinatag ng bahagi ng "Input" at ang kontrol sa bilis ay itinatag ng "Paganahin" na bahagi. Halimbawa, upang makontrol ang direksyon ng motor A, itakda ang Input 1 sa TAAS (3.3 V sa kasong ito dahil gumagamit kami ng isang raspberry pi) at itakda ang Input 2 sa LOW, ang motor ay umiikot sa isang tukoy na direksyon at sa pamamagitan ng pagtatakda ng mga kabaligtaran na halaga sa Input 1 at Input 2, ang motor ay paikutin sa kabaligtaran. Kung Input 1 = Input 2 = (MATAAS o Mababa), ang motor ay hindi magpapihit. Paganahin ang mga pin kumuha ng isang signal ng pag-input ng Pulse Width Modulation (PWM) mula sa raspberry (0 hanggang 3.3 V) at patakbuhin ang mga motor nang naaayon. Halimbawa, ang isang 100% signal ng PWM ay nangangahulugan na nagtatrabaho kami sa maximum na bilis at 0% signal ng PWM ay nangangahulugang ang motor ay hindi umiikot. Ang sumusunod na code ay ginagamit upang matukoy ang mga direksyon ng motor at subukan ang kanilang bilis.

oras ng pag-import

i-import ang RPi. GPIO bilang GPIO GPIO.setwarnings (Mali) # Steering Motor Pins steering_enable = 22 # Physical Pin 15 in1 = 17 # Physical Pin 11 in2 = 27 # Physical Pin 13 #Throttle Motors Pins throttle_enable = 25 # Physical Pin 22 in3 = 23 # Physical Pin 16 in4 = 24 # Physical Pin 18 GPIO.setmode (GPIO. BCM) # Gumamit ng pagnunumero ng GPIO sa halip na pisikal na pagnumero ng 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) # Steering Motor Control GPIO.output (in1, GPIO. TAAS) GPIO.output (in2, GPIO. LOW) pagpipiloto = GPIO. PWM (steering_enable, 1000) # itakda ang dalas ng paglipat sa 1000 Hz steering.stop () # Throttle Motors Control GPIO.output (in3, GPIO. HIGH) GPIO.output (in4, GPIO. LOW) throttle = GPIO. PWM (throttle_enable, 1000) # itakda ang dalas ng paglipat sa 1000 Hz throttle.stop () time.s Sleep (1) throttle.start (25) # ay nagsisimula ang motor sa 25 % Signal ng PWM-> (0.25 * Boltahe ng baterya) - mga driver pagkawala ng pagpipiloto.start (100) # nagsisimula ang motor sa 100% signal PWM-> (1 * Boltahe ng Baterya) - oras ng pagkawala ng driver. pagtulog (3) throttle.stop () steering.stop ()

Patakbuhin ng code na ito ang mga throttling motor at pagpipiloto ng motor sa loob ng 3 segundo at pagkatapos ay titigilan sila. Ang (pagkawala ng driver) ay maaaring matukoy gamit ang isang voltmeter. Halimbawa, alam namin na ang isang 100% signal na PWM ay dapat magbigay ng boltahe ng buong baterya sa terminal ng motor. Ngunit, sa pamamagitan ng pagtatakda ng PWM sa 100%, nalaman ko na ang driver ay nagdudulot ng isang 3 V drop at ang motor ay nakakakuha ng 9 V sa halip na 12 V (eksakto kung ano ang kailangan ko!). Ang pagkawala ay hindi linear na ibig sabihin ang pagkawala sa 100% ay ibang-iba mula sa pagkawala sa 25%. Matapos patakbuhin ang code sa itaas, ang aking mga resulta ay ang sumusunod:

Mga Resulta ng Throttling: kung in3 = MATAAS at in4 = LOW, ang mga throttling motor ay magkakaroon ng rotasyon ng Clock-Wise (CW) ibig sabihin ang sasakyan ay uusad. Kung hindi man, ang sasakyan ay lilipat ng paatras.

Mga Resulta sa Pagmo Kung hindi man, ang kotse ay pipilitan ng tama. Matapos ang ilang mga eksperimento, nalaman ko na ang manibela na motor ay hindi liliko kung ang signal ng PWM ay hindi 100% (ibig sabihin, ang motor ay pipilitan ng ganap sa kanan o ganap sa kaliwa).

Hakbang 5: Pagtuklas sa Mga Linya ng Lane at Pagkalkula sa Linya ng Pag-heading

Ang pagtuklas ng mga linya ng Lane at Pagkalkula ng Heading Line
Ang pagtuklas ng mga linya ng Lane at Pagkalkula ng Heading Line
Ang pagtuklas ng mga linya ng Lane at Pagkalkula ng Heading Line
Ang pagtuklas ng mga linya ng Lane at Pagkalkula ng Heading Line
Ang pagtuklas ng mga linya ng Lane at Pagkalkula ng Heading Line
Ang pagtuklas ng mga linya ng Lane at Pagkalkula ng Heading Line

Sa hakbang na ito, ipapaliwanag ang algorithm na makokontrol ang paggalaw ng kotse. Ipinapakita ng unang imahe ang buong proseso. Ang pag-input ng system ay mga imahe, ang output ay theta (anggulo ng pagpipiloto sa mga degree). Tandaan na, ang pagproseso ay tapos na sa 1 imahe at mauulit sa lahat ng mga frame.

Camera:

Magsisimula ang camera sa pagrekord ng isang video na may (320 x 240) resolusyon. Inirerekumenda ko ang pagbaba ng resolusyon upang makakakuha ka ng mas mahusay na rate ng frame (fps) dahil ang drop ng fps ay magaganap pagkatapos mag-apply ng mga diskarte sa pagproseso sa bawat frame. Ang code sa ibaba ay magiging pangunahing loop ng programa at idaragdag ang bawat hakbang sa code na ito.

import cv2

import numpy as np video = cv2. VideoCapture (0) video.set (cv2. CAP_PROP_FRAME_WIDTH, 320) # itakda ang lapad sa 320 p video.set (cv2. CAP_PROP_FRAME_HEIGHT, 240) # itakda ang taas sa 240 p # Ang loop habang Totoo: ret, frame = video.read () frame = cv2.flip (frame, -1) cv2.imshow ("orihinal", frame) key = cv2.waitKey (1) kung key == 27: putulin ang video.release () cv2.destroyAllWindows ()

Ipapakita ng code dito ang orihinal na imaheng nakuha sa hakbang 4 at ipinapakita sa mga imahe sa itaas.

I-convert sa HSV Color Space:

Ngayon pagkatapos kumuha ng pagrekord ng video bilang mga frame mula sa camera, ang susunod na hakbang ay upang i-convert ang bawat frame sa puwang ng kulay ng Hue, saturation, at Value (HSV). Ang pangunahing bentahe ng paggawa nito ay upang maiba ang pagkakaiba sa pagitan ng mga kulay sa pamamagitan ng kanilang antas ng ningning. At narito ang isang mahusay na paliwanag sa puwang ng kulay ng HSV. Ang pag-convert sa HSV ay tapos na sa pamamagitan ng sumusunod na pagpapaandar:

def convert_to_HSV (frame):

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

Ang pagpapaandar na ito ay tatawagan mula sa pangunahing loop at ibabalik ang frame sa puwang ng kulay ng HSV. Ang frame na nakuha ko sa puwang ng kulay ng HSV ay ipinapakita sa itaas.

Nakita ang Kulay ng Asul at Mga Mata:

Matapos ma-convert ang imahe sa puwang ng kulay ng HSV, oras na upang makita lamang ang kulay na interesado kami (ibig sabihin, asul na kulay dahil ito ang kulay ng mga linya ng linya). Upang makuha ang asul na kulay mula sa isang frame na HSV, dapat tukuyin ang isang hanay ng kulay, saturation at halaga. sumangguni dito upang magkaroon ng isang mas mahusay na ideya sa mga halaga ng HSV. Pagkatapos ng ilang mga eksperimento, ang itaas at mas mababang mga limitasyon ng asul na kulay ay ipinapakita sa code sa ibaba. At upang mabawasan ang pangkalahatang pagbaluktot sa bawat frame, ang mga gilid ay napansin lamang gamit ang canny edge detector. Dagdag pa tungkol sa canny edge ay matatagpuan dito. Ang isang panuntunan sa hinlalaki ay upang piliin ang mga parameter ng pag-andar ng Canny () na may ratio na 1: 2 o 1: 3.

def detect_edges (frame):

lower_blue = np.array ([90, 120, 0], dtype = "uint8") # mas mababang limitasyon ng asul na kulay sa itaas_blue = np.array ([150, 255, 255], dtype = "uint8") # itaas na limitasyon ng asul na kulay na mask = cv2.inRange (hsv, lower_blue, upper_blue) # ang mask na ito ay i-filter ang lahat ngunit ang asul na # tuklasin ang mga gilid ng gilid = cv2. Canny (mask, 50, 100) cv2.imshow ("edge", edge) ibalik ang mga gilid

Ang pagpapaandar na ito ay tatawagan din mula sa pangunahing loop na tumatagal bilang isang parameter na frame ng puwang ng kulay na HSV at ibabalik ang talim na frame. Ang gilid na frame na nakuha ko ay matatagpuan sa itaas.

Piliin ang Rehiyon ng Interes (ROI):

Ang pagpili ng rehiyon ng interes ay mahalaga na ituon lamang ang 1 rehiyon ng frame. Sa kasong ito, hindi ko nais na makita ng kotse ang maraming mga item sa kapaligiran. Nais ko lamang na ang kotse ay tumuon sa mga linya ng linya at huwag pansinin ang anupaman. P. S: ang coordinate system (x at y axes) ay nagsisimula mula sa kaliwang sulok sa itaas. Sa madaling salita, ang punto (0, 0) ay nagsisimula mula sa kaliwang sulok sa itaas. y-axis na ang taas at x-axis ang lapad. Ang code sa ibaba ay pipili ng rehiyon ng interes na nakatuon lamang sa ibabang kalahati ng frame.

def region_of_interest (mga gilid):

taas, lapad = gilid.shape # kunin ang taas at lapad ng mga gilid ng frame mask = np.zeros_like (mga gilid) # gumawa ng isang walang laman na matrix na may parehong mga sukat ng mga gilid frame # tumutok lamang sa mas mababang kalahati ng screen # tukuyin ang mga coordinate ng 4 na puntos (ibabang kaliwa, kaliwang itaas, kanang itaas, kanang ibaba) polygon = np.array (

Dadalhin ng pagpapaandar na ito ang naka-gilid na frame bilang parameter at kumukuha ng isang polygon na may 4 na mga preset na puntos. Magtutuon lamang ito sa kung ano ang nasa loob ng polygon at hindi papansinin ang lahat sa labas nito. Ang aking rehiyon ng frame ng interes ay ipinapakita sa itaas.

Tuklasin ang Mga Segment ng Linya:

Ginagamit ang hough transform upang makita ang mga segment ng linya mula sa isang talim na frame. Ang Hough transform ay isang pamamaraan upang makita ang anumang hugis sa form na matematika. Maaari itong tuklasin ang halos anumang bagay kahit na ito ay baluktot ayon sa ilang bilang ng mga boto. isang mahusay na sanggunian para sa Hough transform ay ipinakita dito. Para sa application na ito, ginagamit ang function na cv2. HoughLinesP () upang makita ang mga linya sa bawat frame. Ang mga mahahalagang parameter na ginagawa ng pagpapaandar na ito ay:

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

  • Frame: ay ang frame na nais nating makita ang mga linya.
  • rho: Ito ang distansya ng katumpakan sa mga pixel (karaniwang ito ay = 1)
  • theta: anggular na katumpakan sa mga radian (laging = np.pi / 180 ~ 1 degree)
  • min_threshold: minimum na boto na dapat makuha para sa ito upang maituring bilang isang linya
  • MinLineLength: minimum na haba ng linya sa mga pixel. Anumang linya na mas maikli kaysa sa bilang na ito ay hindi itinuturing na isang linya.
  • maxLineGap: maximum na puwang ng mga pixel sa pagitan ng 2 mga linya upang gamutin bilang 1 linya. (Hindi ito ginagamit sa aking kaso dahil ang mga linya ng linya na ginagamit ko ay walang anumang puwang).

Ibinabalik ng pagpapaandar na ito ang mga endpoint ng isang linya. Ang sumusunod na pagpapaandar ay tinawag mula sa aking pangunahing loop upang makita ang mga linya gamit ang Hough transform:

def detect_line_segments (cropping_edges):

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

Average na slope at Intercept (m, b):

alalahanin na ang equation ng linya ay ibinigay ng y = mx + b. Kung saan ang slope ng linya at b ay ang y-intercept. Sa bahaging ito, kakalkulahin ang average ng mga slope at intercepts ng mga segment ng linya gamit ang Hough transform. Bago gawin ito, tingnan muna natin ang orihinal na larawan ng frame na ipinakita sa itaas. Ang kaliwang linya ay lilitaw na paitaas kaya't ito ay may negatibong pagdulas (tandaan ang punto ng pagsisimula ng sistema ng coordinate?). Sa madaling salita, ang kaliwang linya ng linya ay may x1 <x2 at y2 x1 at y2> y1 na magbibigay ng positibong slope. Kaya, ang lahat ng mga linya na may positibong slope ay isinasaalang-alang ng tamang point ng lane. Sa kaso ng mga patayong linya (x1 = x2), ang slope ay magiging infinity. Sa kasong ito, lalaktawan namin ang lahat ng mga patayong linya upang maiwasan ang pagkakaroon ng isang error. Upang magdagdag ng higit na kawastuhan sa pagtuklas na ito, ang bawat frame ay nahahati sa dalawang mga rehiyon (kanan at kaliwa) sa pamamagitan ng 2 mga linya ng hangganan. Ang lahat ng mga puntos ng lapad (puntos ng x-axis) na mas malaki kaysa sa kanang linya ng hangganan, ay naiugnay sa pagkalkula ng kanang linya. At kung ang lahat ng mga puntos ng lapad ay mas mababa sa kaliwang linya ng hangganan, nauugnay ang mga ito sa pagkalkula ng kaliwang linya. Ang sumusunod na pag-andar ay tumatagal ng frame sa ilalim ng pagproseso at mga segment ng lane na napansin gamit ang Hough transform at ibabalik ang average na slope at intercept ng dalawang linya ng lane.

def average_slope_intercept (frame, line_segments):

lane_lines = kung ang line_segments ay Wala: i-print ("walang natagpuang linya na linya") ibalik ang taas ng lane_lines, lapad, _ = frame.shape left_fit = right_fit = hangganan = left_region_boundary = lapad * (1 - hangganan) right_region_boundary = lapad * hangganan para sa line_segment sa line_segments: para sa x1, y1, x2, y2 sa line_segment: kung x1 == x2: print ("paglaktaw ng mga patayong linya (slope = infinity)") magpatuloy na magkasya = np.polyfit ((x1, x2), (y1, y2), 1) slope = (y2 - y1) / (x2 - x1) intercept = y1 - (slope * x1) kung slope <0: kung x1 <left_region_boundary at x2 right_region_boundary at x2> right_region_boundary: right_fit. idagdag ((slope, intercept)) left_fit_average = np.average (left_fit, axis = 0) kung len (left_fit)> 0: lane_lines.append (make_points (frame, left_fit_average)) right_fit_average = np.average (right_fit, axis = 0) kung len (kanan_fit)> 0: lane_lines.append (make_points (frame, right_fit_average)) # lane_lines ay isang 2-D array na binubuo ng mga coordinate ng kanan at kaliwang linya ng lane # halimbawa: lan e_lines =

Ang make_points () ay isang function ng helper para sa average_slope_intercept () na pagpapaandar na ibabalik ang mga nakagapos na mga coordinate ng mga linya ng linya (mula sa ilalim hanggang sa gitna ng frame).

def make_points (frame, linya):

taas, lapad, _ = frame.shope slope, intercept = line y1 = taas # ilalim ng frame y2 = int (y1 / 2) # gumawa ng mga puntos mula sa gitna ng frame pababa kung slope == 0: slope = 0.1 x1 = int ((y1 - intercept) / slope) x2 = int ((y2 - intercept) / slope) bumalik

Upang maiwasan ang paghati sa 0, ipinakita ang isang kundisyon. Kung slope = 0 na nangangahulugang y1 = y2 (pahalang na linya), bigyan ang slope ng halaga na malapit sa 0. Hindi ito makakaapekto sa pagganap ng algorithm pati na rin mapipigilan ang imposibleng kaso (paghati sa 0).

Upang maipakita ang mga linya ng linya sa mga frame, ginagamit ang sumusunod na pagpapaandar:

def display_lines (frame, linya, line_color = (0, 255, 0), line_width = 6): # kulay ng linya (B, G, R)

line_image = np.zeros_like (frame) kung ang mga linya ay wala: para sa linya sa mga linya: para sa x1, y1, x2, y2 sa linya: 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

Ang function na cv2.addWeighted () ay kumukuha ng mga sumusunod na parameter at ginagamit ito upang pagsamahin ang dalawang mga imahe ngunit sa pagbibigay ng timbang sa bawat isa.

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

At kinakalkula ang imahe ng output gamit ang sumusunod na equation:

output = alpha * image1 + beta * image2 + gamma

Dagdag pang impormasyon tungkol sa pagpapaandar ng cv2.addWeighted () ay nakuha dito.

Kalkulahin at Ipakita ang Linya ng Pag-heading:

Ito ang panghuling hakbang bago mag-apply kami ng mga bilis sa aming mga motor. Ang linya ng heading ay responsable na bigyan ang manibela ng motor ng direksyon kung saan dapat itong paikutin at bigyan ang mga throttling motor ng bilis kung saan sila gagana. Ang pagkalkula ng linya ng heading ay purong trigonometry, ginagamit ang tan at atan (tan ^ -1) na mga trigonometric function. Ang ilang matinding kaso ay kapag ang camera ay nakakita lamang ng isang linya ng linya o kapag hindi ito nakakakita ng anumang linya. Ang lahat ng mga kasong ito ay ipinapakita sa sumusunod na pagpapaandar:

def get_steering_angle (frame, lane_lines):

taas, lapad, _ = frame.shape kung len (lane_lines) == 2: # kung ang dalawang linya ng linya ay nakita _, _, left_x2, _ = lane_lines [0] [0] # i-extract ang natira x2 mula sa 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 (taas / 2) elif len (lane_lines) == 1: # kung isang linya lamang ang napansin x1, _, x2, _ = lane_lines [0] [0] x_offset = x2 - x1 y_offset = int (taas / 2) elif len (lane_lines) == 0: # kung walang linya ang napansin x_offset = 0 y_offset = int (taas / 2) anggulo_to_mid_radian = math.atan (x_offset / y_offset) anggulo_to_mid_deg = int (anggulo_to_mid_radian * 180.0 / math.pi) steering_angle = anggulo_to_mid_deg + 90 bumalik sa pagpipiloto

x_offset sa unang kaso ay kung magkano ang average ((kanan x2 + kaliwa x2) / 2) naiiba mula sa gitna ng screen. Ang y_offset ay palaging kinuha upang maging taas / 2. Ang huling imahe sa itaas ay nagpapakita ng isang halimbawa ng heading line. ang anggulo_to_mid_radians ay kapareho ng "theta" na ipinakita sa huling imahe sa itaas. Kung ang steering_angle = 90, nangangahulugan ito na ang kotse ay may linya ng heading na patayo sa linya na "taas / 2" at ang sasakyan ay uusad nang walang pagpipiloto. Kung steering_angle> 90, ang kotse ay dapat na umiwas pakanan kung hindi man ay dapat itong patnubayan pakaliwa. Upang maipakita ang linya ng heading, ginagamit ang sumusunod na pagpapaandar:

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

heading_image = np.zeros_like (frame) taas, lapad, _ = frame.shape steering_angle_radian = steering_angle / 180.0 * math.pi x1 = int (lapad / 2) y1 = taas x2 = int (x1 - taas / 2 / math.tan (steering_angle_radian)) y2 = int (taas / 2) cv2.line (heading_image, (x1, y1), (x2, y2), line_color, line_width) heading_image = cv2.addWeighted (frame, 0.8, heading_image, 1, 1) bumalik heading_image

Ang pag-andar sa itaas ay tumatagal ng frame kung saan iginuhit ang linya ng heading at angulo ng pagpipiloto bilang pag-input. Ibinabalik nito ang imahe ng linya ng heading. Ang frame ng linya ng heading na kinuha sa aking kaso ay ipinapakita sa imahe sa itaas.

Pagsasama-sama ng Lahat ng Code:

Ang code ngayon ay handa nang tipunin. Ipinapakita ng sumusunod na code ang pangunahing loop ng program na tumatawag sa bawat pagpapaandar:

import cv2

import numpy as np video = cv2. VideoCapture (0) video.set (cv2. CAP_PROP_FRAME_WIDTH, 320) video.set (cv2. CAP_PROP_FRAME_HEIGHT, 240) habang True: ret, frame = video.read () frame = cv2.flip (frame, -1) #Calling the functions hsv = convert_to_HSV (frame) edge = detect_edges (hsv) roi = region_of_interest (edge) line_segments = detect_line_segments (roi) lane_lines = average_slope_intercept (frame, line_segments) lane_lines_image = display_lines (frame) = get_steering_angle (frame, lane_lines) heading_image = display_heading_line (lane_lines_image, steering_angle) key = cv2.waitKey (1) kung key == 27: putulin ang video.release () cv2.destroyAllWindows ()

Hakbang 6: Paglalapat ng PD Control

Paglalapat ng PD Control
Paglalapat ng PD Control

Ngayon ay handa na ang aming direksyon ng pagpipiloto upang pakainin ang mga motor. Tulad ng nabanggit kanina, kung ang anggulo ng pagpipiloto ay higit sa 90, ang kotse ay dapat na kumanan pakanan kung hindi man ay dapat itong lumiko sa kaliwa. Nag-apply ako ng isang simpleng code na lumiliko pakanan ng manibela kung ang anggulo ay nasa itaas ng 90 at iikot ito sa kaliwa kung ang pagpipiloto ay mas mababa sa 90 sa isang pare-pareho ang bilis ng pag-throttling ng (10% PWM) ngunit marami akong mga error na nakuha. Ang pangunahing error na nakuha ko ay kapag ang kotse ay lumapit sa anumang pagliko, ang manibela ng motor ay direktang kumikilos ngunit ang mga throttling motor ay ma-jam. Sinubukan kong dagdagan ang bilis ng throttling upang maging (20% PWM) sa pagliko ngunit natapos sa paglabas ng robot sa mga daanan. Kailangan ko ng isang bagay na madaragdagan ang bilis ng pag-throttling kung ang anggulo ng pagpipiloto ay napakalaki at pinapataas ang bilis kung ang anggulo ng pagpipiloto ay hindi gaanong kalaki at binabawasan ang bilis sa isang paunang halaga habang papalapit ang kotse sa 90 degree (tuwid na gumagalaw). Ang solusyon ay ang paggamit ng isang PD controller.

Ang taga-kontrol ng PID ay nangangahulugang Proportional, Integral at Derivative controller. Ang ganitong uri ng mga linear Controller ay malawakang ginagamit sa mga application ng robotics. Ipinapakita ng imahe sa itaas ang tipikal na loop ng kontrol sa feedback ng PID. Ang layunin ng tagakontrol na ito ay upang maabot ang "setpoint" na may pinaka mahusay na paraan hindi katulad ng "on - off" na mga Controller na buksan o i-off ang halaman ayon sa ilang mga kundisyon. Ang ilang mga keyword ay dapat na kilala:

  • Setpoint: ay ang ninanais na halaga na nais mong maabot ng iyong system.
  • Tunay na halaga: ay ang aktwal na halagang naramdaman ng sensor.
  • Error: ay ang pagkakaiba sa pagitan ng setpoint at aktwal na halaga (error = Setpoint - Aktwal na halaga).
  • Kinokontrol na variable: mula sa pangalan nito, ang variable na nais mong kontrolin.
  • Kp: Patuloy na proporsyonal.
  • Ki: Integral pare-pareho.
  • Kd: Patuloy na nagmula.

Sa maikli, gumagana ang loop ng control system ng PID tulad ng sumusunod:

  • Tinutukoy ng gumagamit ang setpoint na kinakailangan upang maabot ng system.
  • Ang error ay kinakalkula (error = setpoint - aktwal).
  • Ang P controller ay bumubuo ng isang proporsyonal na pagkilos sa halaga ng error. (tumataas ang error, tumataas din ang aksyon ng P)
  • Isasama ko ang controller ang error sa paglipas ng panahon na nag-aalis ng error na matatag na estado ng system ngunit pinapataas ang overshoot nito.
  • Ang D controller ay simpleng hango ng oras para sa error. Sa madaling salita, ito ay ang slope ng error. Gumagawa ito ng proporsyonal na pagkilos sa hango ng error. Ang tagakontrol na ito ay nagdaragdag ng katatagan ng system.
  • Ang output ng controller ay magiging kabuuan ng tatlong mga Controller. Ang output ng tagakontrol ay magiging 0 kung ang 0 ay naging error.

Ang isang mahusay na paliwanag ng PID controller ay matatagpuan dito.

Bumabalik sa lane na nag-iingat ng kotse, ang aking kinokontrol na variable ay ang bilis ng pag-throttle (dahil ang pagpipiloto ay may dalawang estado alinman sa kanan o kaliwa). Ginagamit ang isang PD controller para sa hangaring ito dahil ang pagkilos ng D ay nagdaragdag ng bilis ng pag-throttling kung ang pagbabago ng error ay napakalaki (ibig sabihin, malaking paglihis) at pinapabagal ang kotse kung ang pagbabago ng error na ito ay papalapit sa 0. Ginawa ko ang mga sumusunod na hakbang upang magpatupad ng isang PD tagakontrol:

  • Itakda ang setpoint sa 90 degree (lagi kong nais na ang kotse ay kumilos nang diretso)
  • Kinakalkula ang anggulo ng paglihis mula sa gitna
  • Ang paglihis ay nagbibigay ng dalawang impormasyon: Kung gaano kalaki ang error (magnitude ng paglihis) at kung anong direksyon ang kukunin ng manibela na motor (tanda ng paglihis). Kung positibo ang paglihis, ang kotse ay dapat na patnubayan pakanan kung hindi man dapat itong patnubayan sa kaliwa.
  • Dahil ang paglihis ay alinman sa negatibo o positibo, ang isang variable na "error" ay tinukoy at laging katumbas ng ganap na halaga ng paglihis.
  • Ang error ay pinarami ng isang pare-pareho na Kp.
  • Ang error ay sumasailalim ng pagkakaiba-iba ng oras at pinarami ng isang pare-pareho na Kd.
  • Ang bilis ng motor ay na-update at nagsisimula muli ang loop.

Ang sumusunod na code ay ginagamit sa pangunahing loop upang makontrol ang bilis ng mga throttling motor:

bilis = 10 # bilis ng pagpapatakbo sa% PWM

#Variables to be updated each loop lastTime = 0 lastError = 0 # PD Constants Kp = 0.4 Kd = Kp * 0.65 While True: now = time.time () # kasalukuyang time variable dt = now - lastTime deviation = steering_angle - 90 # katumbas sa anggulo_to_mid_deg variable error = abs (paglihis) kung paglihis -5: # huwag patnubayan kung mayroong 10-degree na saklaw na paglihis ng error = 0 error = 0 GPIO.output (in1, GPIO. LOW) GPIO.output (in2, GPIO. LOW) steering.stop () elif deviation> 5: # patnubayan kanan kung ang paglihis ay positibo GPIO.output (in1, GPIO. LOW) GPIO.output (in2, GPIO. HIGH) pagpipiloto.start (100) elif deviation < -5: # steer left kung ang deviation ay negatibong GPIO.output (in1, GPIO. HIGH) GPIO.output (in2, GPIO. LOW) steering.start (100) derivative = kd * (error - lastError) / dt proportional = kp * error PD = int (speed + derivative + proportional) spd = abs (PD) kung spd> 25: spd = 25 throttle.start (spd) lastError = error lastTime = time.time ()

Kung ang error ay napakalaki (ang paglihis mula sa gitna ay mataas), ang proporsyonal at hinalaw na mga aksyon ay mataas na nagreresulta sa mataas na bilis ng pag-throttling. Kapag lumapit ang error sa 0 (ang paglihis mula sa gitna ay mababa), ang pagkilos na nagmula ay gumagalaw nang pabalik-balik (ang slope ay negatibo) at ang bilis ng throttling ay makakakuha ng mababa upang mapanatili ang katatagan ng system. Ang buong code ay nakakabit sa ibaba.

Hakbang 7: Mga Resulta

Ipinapakita ng mga video sa itaas ang mga resulta na nakuha ko. Kailangan nito ng higit pang pag-tune at karagdagang mga pagsasaayos. Kinokonekta ko ang raspberry pi sa aking LCD display screen dahil ang video na dumadaloy sa aking network ay may mataas na latency at napakasimang gumana, iyon ang dahilan kung bakit may mga wire na konektado sa raspberry pi sa video. Gumamit ako ng mga foam board upang iguhit ang track.

Naghihintay ako na pakinggan ang iyong mga rekomendasyon upang mapagbuti ang proyektong ito! Tulad ng pag-asa ko na ang mga itinuturo na ito ay sapat na sapat upang mabigyan ka ng ilang bagong impormasyon.

Inirerekumendang: