Satura rādītājs:

Autonomā joslu saturoša automašīna, izmantojot Raspberry Pi un OpenCV: 7 soļi (ar attēliem)
Autonomā joslu saturoša automašīna, izmantojot Raspberry Pi un OpenCV: 7 soļi (ar attēliem)

Video: Autonomā joslu saturoša automašīna, izmantojot Raspberry Pi un OpenCV: 7 soļi (ar attēliem)

Video: Autonomā joslu saturoša automašīna, izmantojot Raspberry Pi un OpenCV: 7 soļi (ar attēliem)
Video: ADAS – VĒJSTIKLA KAMERAS KALIBRĒŠANA 2024, Novembris
Anonim
Autonomā joslu saturoša automašīna, izmantojot Raspberry Pi un OpenCV
Autonomā joslu saturoša automašīna, izmantojot Raspberry Pi un OpenCV

Šajā instrukcijā tiks ieviests autonoms joslas saglabāšanas robots, kas veiks šādas darbības:

  • Daļu apkopošana
  • Programmatūras instalēšanas priekšnoteikumi
  • Aparatūras montāža
  • Pirmais tests
  • Joslu līniju noteikšana un vadošās līnijas parādīšana, izmantojot openCV
  • PD kontroliera ieviešana
  • Rezultāti

1. darbība: sastāvdaļu savākšana

Komponentu vākšana
Komponentu vākšana
Komponentu vākšana
Komponentu vākšana
Komponentu vākšana
Komponentu vākšana
Komponentu vākšana
Komponentu vākšana

Iepriekš redzamajos attēlos ir redzamas visas šajā projektā izmantotās sastāvdaļas:

  • RC automašīna: es saņēmu savu no vietējā veikala manā valstī. Tas ir aprīkots ar 3 motoriem (2 droseļvārsta un 1 stūrēšanai). Šīs automašīnas galvenais trūkums ir tas, ka stūrēšana ir ierobežota starp "bez stūres" un "pilnu stūrēšanu". Citiem vārdiem sakot, atšķirībā no servostūres RC automašīnām tā nevar stūrēt noteiktā leņķī. Šeit varat atrast līdzīgu automašīnas komplektu, kas īpaši izstrādāts aveņu pi.
  • Raspberry pi 3 modelis b+: šīs ir automašīnas smadzenes, kas tiks galā ar daudziem apstrādes posmiem. Tā pamatā ir četrkodolu 64 bitu procesors, kura frekvence ir 1,4 GHz. Savējo es dabūju no šejienes.
  • Raspberry pi 5 mp kameras modulis: atbalsta 1080p @ 30 fps, 720p @ 60 fps un 640x480p 60/90 ierakstīšanu. Tas atbalsta arī seriālo saskarni, kuru var pievienot tieši aveņu pi. Tas nav labākais risinājums attēlu apstrādes lietojumprogrammām, taču šim projektam tas ir pietiekami, kā arī ļoti lēts. Savējo es dabūju no šejienes.
  • Motora draiveris: tiek izmantots, lai kontrolētu līdzstrāvas motoru virzienus un apgriezienus. Tas atbalsta 2 līdzstrāvas motoru vadību vienā plāksnē un var izturēt 1,5 A.
  • Barošanas banka (pēc izvēles): es izmantoju strāvas banku (nominālā pie 5 V, 3 A), lai atsevišķi ieslēgtu aveņu pi. Lai ieslēgtu aveņu pi no 1 avota, jāizmanto pazeminošs pārveidotājs (buck converter: 3A izejas strāva).
  • 3s (12 V) LiPo akumulators: litija polimēru baterijas ir pazīstamas ar izcilu sniegumu robotikas jomā. To izmanto, lai darbinātu motora vadītāju. Es nopirku savu no šejienes.
  • Džempera vadi no tēviņa līdz vīrietim un no sievietes uz sievieti.
  • Divpusēja lente: izmanto detaļu montāžai uz RC automašīnas.
  • Zilā lente: Šī ir ļoti svarīga šī projekta sastāvdaļa, to izmanto, lai izveidotu divas joslu līnijas, starp kurām automašīna brauks. Jūs varat izvēlēties jebkuru vēlamo krāsu, bet es iesaku izvēlēties dažādas krāsas nekā apkārtējā vide.
  • Rāvējslēdzēji un koka stieņi.
  • Skrūvgriezis.

2. darbība: OpenCV instalēšana Raspberry Pi un tālvadības displeja iestatīšana

OpenCV instalēšana Raspberry Pi un attālā displeja iestatīšana
OpenCV instalēšana Raspberry Pi un attālā displeja iestatīšana

Šis solis ir nedaudz kaitinošs un prasīs zināmu laiku.

OpenCV (Open source Computer Vision) ir atvērtā pirmkoda datora redzes un mašīnmācīšanās programmatūras bibliotēka. Bibliotēkā ir vairāk nekā 2500 optimizētu algoritmu. Izpildiet šo ļoti vienkāršo rokasgrāmatu, lai instalētu openCV savā aveņu pi, kā arī instalētu aveņu pi OS (ja jūs to vēl neesat izdarījis). Lūdzu, ņemiet vērā, ka openCV izveides process labi atdzesētā telpā var aizņemt aptuveni 1,5 stundas (jo procesora temperatūra būs ļoti augsta!), Tāpēc izdzeriet tēju un pacietīgi gaidiet: D.

Attālinātajā displejā ievērojiet arī ŠO rokasgrāmatu, lai no Windows/Mac ierīces iestatītu attālo piekļuvi jūsu aveņu pi.

3. darbība: detaļu savienošana kopā

Daļu savienošana kopā
Daļu savienošana kopā
Daļu savienošana kopā
Daļu savienošana kopā
Daļu savienošana kopā
Daļu savienošana kopā

Augšējie attēli parāda savienojumus starp aveņu pi, kameras moduli un motora draiveri. Lūdzu, ņemiet vērā, ka manis izmantotie motori absorbē 0,35 A pie 9 V, kas motora vadītājam ļauj droši darbināt 3 motorus vienlaikus. Un, tā kā es vēlos kontrolēt 2 droseļvārsta ātrumu (1 aizmugurē un 1 priekšā) tieši tādā pašā veidā, es tos savienoju ar to pašu portu. Es uzstādīju motora vadītāju automašīnas labajā pusē, izmantojot dubultu lenti. Kas attiecas uz kameras moduli, es ievietoju rāvējslēdzēju starp skrūvju caurumiem, kā parādīts iepriekš redzamajā attēlā. Pēc tam es ievietoju kameru koka stienī, lai es varētu pielāgot kameras stāvokli pēc vēlēšanās. Mēģiniet pēc iespējas uzstādīt kameru automašīnas vidū. Es iesaku novietot kameru vismaz 20 cm virs zemes, lai redzamības lauks automašīnas priekšā kļūtu labāks. Fritzinga shēma ir pievienota zemāk.

4. solis: pirmais tests

Pirmais tests
Pirmais tests
Pirmais tests
Pirmais tests

Kameras pārbaude:

Kad kamera ir instalēta un openCV bibliotēka ir izveidota, ir pienācis laiks pārbaudīt mūsu pirmo attēlu! Mēs uzņemsim fotoattēlu no pi cam un saglabāsim to kā "original.jpg". To var izdarīt 2 veidos:

1. Izmantojot termināļa komandas:

Atveriet jaunu termināļa logu un ierakstiet šādu komandu:

raspistill -o original.jpg

Tas uzņems nekustīgu attēlu un saglabās to direktorijā "/pi/original.jpg".

2. Izmantojot jebkuru python IDE (es izmantoju IDLE):

Atveriet jaunu skici un uzrakstiet šādu kodu:

importēt cv2

video = cv2. VideoCapture (0), kamēr True: ret, frame = video.read () frame = cv2.flip (frame, -1) # izmanto, lai vertikāli apvērstu attēlu cv2.imshow ('oriģināls', kadrs) cv2. imwrite ('original.jpg', frame) taustiņš = cv2.waitKey (1) if key == 27: break video.release () cv2.destroyAllWindows ()

Apskatīsim, kas notika šajā kodā. Pirmā rinda importē mūsu openCV bibliotēku, lai izmantotu visas tās funkcijas. funkcija VideoCapture (0) sāk straumēt tiešraides video no avota, kas noteikts ar šo funkciju, šajā gadījumā tā ir 0, kas nozīmē raspi kameru. ja jums ir vairākas kameras, jāievieto dažādi numuri. video.read () nolasīs katru kadru, kas nāk no kameras, un saglabās to mainīgajā, ko sauc par "rāmi". funkcija flip () apgriezīs attēlu attiecībā pret y asi (vertikāli), jo es montēju kameru apgriezti. imshow () parādīs mūsu rāmjus ar vārdu "oriģināls", un imwrite () saglabās mūsu fotoattēlu kā oriģinālu.jpg. waitKey (1) gaidīs 1 ms, līdz tiks nospiesta jebkura tastatūras poga, un atgriezīs savu ASCII kodu. ja tiek nospiesta poga Esc (Esc), tiek atgriezta decimālā vērtība 27 un attiecīgi tiks pārtraukta cilpa. video.release () pārtrauks ierakstīšanu un iznīcinās AllWindows () aizvērs visus attēlus, kas atvērti, izmantojot funkciju imshow ().

Es ieteiktu pārbaudīt savu fotoattēlu ar otro metodi, lai iepazītos ar openCV funkcijām. Attēls tiek saglabāts direktorijā "/pi/original.jpg". Sākotnējā fotogrāfija, ko uzņēma mana kamera, ir parādīta iepriekš.

Motoru pārbaude:

Šis solis ir būtisks, lai noteiktu katra motora rotācijas virzienu. Vispirms īsi iepazīstinām ar motora vadītāja darbības principu. Augšējā attēlā parādīta motora vadītāja piespraude. Iespējot A, 1. un 2. ievade ir saistīta ar A motora vadību. Iespējot B, 3. ievade un 4. ievade ir saistītas ar motora B vadību. Virziena vadību nosaka daļa "Ievadi", un ātruma kontroli - daļa "Iespējot". Piemēram, lai kontrolētu motora A virzienu, iestatiet 1. ieeju uz HIGH (šajā gadījumā 3.3 V, jo mēs izmantojam aveņu pi) un iestatiet 2. ievadi uz LOW, motors griežas noteiktā virzienā un iestatot pretējas vērtības uz 1. un 2. ieeju motors griežas pretējā virzienā. Ja 1. ievade = 2. ievade = (HIGH vai LOW), motors negriežas. Iespējošanas tapas ņem aveņu impulsa platuma modulācijas (PWM) ievades signālu (no 0 līdz 3,3 V) un attiecīgi iedarbina motorus. Piemēram, 100% PWM signāls nozīmē, ka mēs strādājam pie maksimālā ātruma, un 0% PWM signāls nozīmē, ka motors negriežas. Šis kods tiek izmantots, lai noteiktu motoru virzienus un pārbaudītu to ātrumu.

importa laiks

importēt RPi. GPIO kā GPIO GPIO.setwarnings (False) # Stūres motora tapas 23 # Fiziskā tapa 16 in4 = 24 # Fiziskā tapa 18 GPIO.setmode (GPIO. BCM) # Fiziskās numerācijas vietā izmantojiet GPIO numerāciju GPIO.setup (in1, GPIO.out) GPIO.setup (in2, GPIO.out) GPIO. iestatīšana (in3, GPIO.out) GPIO.setup (in4, GPIO.out) GPIO.setup (droseļvārsta iespējojams, GPIO.izvēle) GPIO.setup (stūres iespējošana, GPIO.izv.) # Stūres motora vadība GPIO.izvade (in1, GPIO. HIGH) GPIO.output (in2, GPIO. LOW) stūrēšana = GPIO. PWM (stūres ieslēgšana, 1000) # iestatiet pārslēgšanas frekvenci uz 1000 Hz stūres. Stop () # Droseļvārsta motoru vadība GPIO. Izeja (in3, GPIO. HIGH) GPIO.output (in4, GPIO. LOW) droseļvārsts = GPIO. PWM (throttle_enable, 1000) # iestatiet pārslēgšanas frekvenci uz 1000 Hz droseļvārstu. stop () time.sleep (1) droseļvārsts. start (25) # iedarbina motoru pie 25 % PWM signāls-> (0,25 * akumulatora spriegums) - vadītāja stūres zudums.start (100) # iedarbina motoru ar 100% PWM signālu-> (1 * akumulatora spriegums) - vadītāja zaudējuma laiks. miega (3) droseļvārsts. apstāšanās () stūrēšana. apstāšanās ()

Šis kods darbinās droseļvārstu un stūres motoru 3 sekundes un pēc tam tos apturēs. (Vadītāja zaudējumu) var noteikt, izmantojot voltmetru. Piemēram, mēs zinām, ka 100% PWM signālam vajadzētu nodrošināt pilnu akumulatora spriegumu motora spailē. Bet, iestatot PWM uz 100%, es atklāju, ka draiveris izraisa 3 V kritumu un motors saņem 9 V, nevis 12 V (tieši tas, kas man vajadzīgs!). Zaudējumi nav lineāri, t.i., zaudējumi 100% apmērā ļoti atšķiras no zaudējumiem 25% apmērā. Pēc iepriekš minētā koda palaišanas mani rezultāti bija šādi:

Droseļvārsta rezultāti: ja in3 = HIGH un in4 = LOW, droseļvārsta dzinējiem būs Clock-Wise (CW) rotācija, t.i., automašīna virzīsies uz priekšu. Pretējā gadījumā automašīna pārvietosies atpakaļ.

Stūres rezultāti: ja in1 = HIGH un in2 = LOW, stūres motors griezīsies maksimāli pa kreisi, t.i., automašīna stūrēs pa kreisi. Pretējā gadījumā automašīna stūrēs pa labi. Pēc dažiem eksperimentiem es atklāju, ka stūres motors negriezīsies, ja PWM signāls nebūs 100% (t.i., motors virzīsies vai nu pa labi, vai pilnībā pa kreisi).

5. solis: joslu līniju noteikšana un virziena līnijas aprēķināšana

Joslu līniju noteikšana un kursa līnijas aprēķināšana
Joslu līniju noteikšana un kursa līnijas aprēķināšana
Joslu līniju noteikšana un kursa līnijas aprēķināšana
Joslu līniju noteikšana un kursa līnijas aprēķināšana
Joslu līniju noteikšana un kursa virziena aprēķināšana
Joslu līniju noteikšana un kursa virziena aprēķināšana

Šajā solī tiks izskaidrots algoritms, kas kontrolēs automašīnas kustību. Pirmais attēls parāda visu procesu. Sistēmas ievade ir attēli, izeja ir teta (stūres leņķis grādos). Ņemiet vērā, ka apstrāde tiek veikta 1 attēlam un tiks atkārtota visos kadros.

Kamera:

Kamera sāks ierakstīt video ar (320 x 240) izšķirtspēju. Es iesaku samazināt izšķirtspēju, lai jūs varētu iegūt labāku kadru ātrumu (fps), jo kadru nomaiņas ātrums samazināsies pēc apstrādes metožu piemērošanas katram kadram. Zemāk esošais kods būs programmas galvenā cilpa un pievienos katru soli virs šī koda.

importēt cv2

importēt numpy kā np video = cv2. VideoCapture (0) video.set (cv2. CAP_PROP_FRAME_WIDTH, 320) # iestatiet platumu uz 320 p video.set (cv2. CAP_PROP_FRAME_HEIGHT, 240) # iestatiet augstumu uz 240 p # Cilpa True: ret, frame = video.read () frame = cv2.flip (frame, -1) cv2.imshow ("original", frame) key = cv2.waitKey (1) if key == 27: break video.release () cv2.destroyAllWindows ()

Šeit redzamais kods parādīs sākotnējo attēlu, kas iegūts 4. darbībā, un tas ir redzams iepriekš redzamajos attēlos.

Pārvērst HSV krāsu telpā:

Tagad, kad videoieraksts ir uzņemts kā kadrs no kameras, nākamais solis ir pārveidot katru kadru nokrāsu, piesātinājuma un vērtību (HSV) krāsu telpā. Tā galvenā priekšrocība ir spēja atšķirt krāsas pēc to spilgtuma līmeņa. Un šeit ir labs HSV krāsu telpas skaidrojums. Konvertēšana uz HSV tiek veikta, izmantojot šādu funkciju:

def convert_to_HSV (kadrs):

hsv = cv2.cvtColor (kadrs, cv2. COLOR_BGR2HSV) cv2.imshow ("HSV", hsv) atgriezt hsv

Šī funkcija tiks izsaukta no galvenās cilpas un atgriezīs rāmi HSV krāsu telpā. Rāmis, ko es ieguvu HSV krāsu telpā, ir parādīts iepriekš.

Zilās krāsas un malu noteikšana:

Pēc attēla pārveidošanas HSV krāsu telpā ir pienācis laiks noteikt tikai mūs interesējošo krāsu (t.i., zilo krāsu, jo tā ir joslu līniju krāsa). Lai no HSV rāmja iegūtu zilu krāsu, jānorāda nokrāsu, piesātinājuma un vērtību diapazons. skatiet šeit, lai iegūtu labāku priekšstatu par HSV vērtībām. Pēc dažiem eksperimentiem zilās krāsas augšējā un apakšējā robeža ir parādīta zemāk esošajā kodā. Un, lai samazinātu vispārējos izkropļojumus katrā kadrā, malas tiek noteiktas tikai, izmantojot malu detektoru. Vairāk par canny edge var atrast šeit. Īkšķa noteikums ir izvēlēties Canny () funkcijas parametrus ar attiecību 1: 2 vai 1: 3.

def detect_edges (kadrs):

bottom_blue = np.array ([90, 120, 0], dtype = "uint8") # zilās krāsas apakšējā robeža augšējā_zilā = np.masīvs ([150, 255, 255], dtype = "uint8") # zilā krāsa maska = cv2.inRange (hsv, apakšējais_zils, augšējais_zils) # šī maska filtrēs visu, izņemot zilo # noteikt malas malas = cv2. Canny (maska, 50, 100) cv2.imshow ("malas", malas) atgriešanās malas

Šī funkcija tiks izsaukta arī no galvenās cilpas, kas kā parametru ņem HSV krāsu telpas rāmi un atgriež malas rāmi. Grieztais rāmis, ko es ieguvu, ir atrodams iepriekš.

Atlasiet interesējošo reģionu (IA):

Interesējošā reģiona izvēle ir būtiska, lai koncentrētos tikai uz vienu kadra reģionu. Šajā gadījumā es nevēlos, lai automašīna apkārtnē redzētu daudz priekšmetu. Es tikai vēlos, lai automašīna koncentrētos uz joslu līnijām un ignorētu visu citu. P. S: koordinātu sistēma (x un y asis) sākas no augšējā kreisā stūra. Citiem vārdiem sakot, punkts (0, 0) sākas no augšējā kreisā stūra. y ass ir augstums un x ass ir platums. Zemāk esošais kods izvēlas interesējošo reģionu, lai koncentrētos tikai uz kadra apakšējo pusi.

def region_of_interest (malas):

augstums, platums = malas. forma # izvelciet malu augstumu un platumu rāmja maska = np.zeros_like (malas) # izveidojiet tukšu matricu ar tādiem pašiem malu rāmja izmēriem # fokusējiet tikai ekrāna apakšējo pusi # norādiet koordinātas 4 punkti (apakšējais kreisais, augšējais kreisais, augšējais labais, apakšējais labais) daudzstūris = np. Masīvs (

Šī funkcija ņems rāmja rāmi kā parametru un uzzīmē daudzstūri ar 4 iepriekš iestatītiem punktiem. Tas koncentrēsies tikai uz to, kas atrodas daudzstūra iekšienē, un ignorēs visu, kas atrodas ārpus tā. Mans interešu reģiona rāmis ir parādīts iepriekš.

Noteikt līniju segmentus:

Hough transformācija tiek izmantota, lai noteiktu līniju segmentus no rāmja rāmja. Hough transformācija ir metode jebkuras formas noteikšanai matemātiskā formā. Tas var atklāt gandrīz jebkuru objektu, pat ja tas ir izkropļots atbilstoši noteiktam balsu skaitam. šeit ir parādīta lieliska atsauce uz Hough transformāciju. Šai lietojumprogrammai funkcija cv2. HoughLinesP () tiek izmantota, lai noteiktu līnijas katrā kadrā. Šīs funkcijas svarīgie parametri ir:

cv2. HoughLinesP (rāmis, rho, teta, min_threshold, minLineLength, maxLineGap)

  • Rāmis: ir rāmis, kurā vēlamies noteikt līnijas.
  • rho: tā ir attāluma precizitāte pikseļos (parasti tā ir = 1)
  • teta: leņķa precizitāte radiānos (vienmēr = np.pi/180 ~ 1 grāds)
  • min_threshold: minimālais balsojums, kas tam būtu jāsaņem, lai to uzskatītu par līniju
  • minLineLength: minimālais līnijas garums pikseļos. Jebkura līnija, kas ir īsāka par šo skaitli, netiek uzskatīta par līniju.
  • maxLineGap: maksimālā atstarpe pikseļos starp 2 rindām jāuzskata par 1 rindu. (Manā gadījumā tas netiek izmantots, jo manis izmantotajām joslu līnijām nav atstarpes).

Šī funkcija atgriež līnijas galapunktus. No galvenās cilpas tiek izsaukta šāda funkcija, lai noteiktu līnijas, izmantojot Hough transformāciju:

def detect_line_segments (cropped_edges):

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

Vidējais slīpums un pārtveršana (m, b):

atcerieties, ka līnijas vienādojumu dod y = mx + b. Kur m ir līnijas slīpums un b ir y krustojums. Šajā daļā tiks aprēķināts vidējais slīpumu un līniju segmentu krustojums, kas konstatēts, izmantojot Hou transformāciju. Pirms to izdarīt, apskatīsim iepriekš redzamo oriģinālo rāmja fotoattēlu. Šķiet, ka kreisā josla iet uz augšu, tāpēc tai ir negatīvs slīpums (atcerieties koordinātu sistēmas sākuma punktu?). Citiem vārdiem sakot, kreisās joslas līnijai ir x1 <x2 un y2 x1 un y2> y1, kas sniegs pozitīvu slīpumu. Tātad visas līnijas ar pozitīvu slīpumu tiek uzskatītas par labās joslas punktiem. Vertikālu līniju gadījumā (x1 = x2) slīpums būs bezgalība. Šajā gadījumā mēs izlaidīsim visas vertikālās līnijas, lai novērstu kļūdu. Lai šai noteikšanai pievienotu lielāku precizitāti, katrs rāmis ir sadalīts divos reģionos (labajā un kreisajā pusē), izmantojot 2 robežlīnijas. Visi platuma punkti (x ass punkti), kas ir lielāki par labo robežas līniju, ir saistīti ar labās joslas aprēķinu. Un, ja visi platuma punkti ir mazāki par kreiso robežas līniju, tie ir saistīti ar kreisās joslas aprēķinu. Šī funkcija rāmi apstrādā un joslas segmentus nosaka, izmantojot Hough transformāciju, un atgriež divu joslu līniju vidējo slīpumu un pārtveršanu.

def vidēji_slope_intercept (kadrs, līnijas_segmenti):

lane_lines = ja line_segments nav: drukāt ("nav atrasts līnijas segments") atgriezt joslu_līnijas augstumu, platumu, _ = frame.shape left_fit = right_fit = robeža = left_region_boundary = platums * (1 - robeža) right_region_boundary = platums * robeža line_segmentam line_segments: x1, y1, x2, y2 line_segment: ja x1 == x2: drukāt ("izlaižot vertikālas līnijas (slīpums = bezgalība)") turpināt fit = np.polyfit ((x1, x2), (y1, y2), 1) slīpums = (y2 - y1) / (x2 - x1) pārtveršana = y1 - (slīpums * x1), ja slīpums <0: ja x1 <kreisais_reģionu_ robeža un x2 labais_reģionu_ robeža un x2> labais_reģionu_ robeža: labais_fikss. pievienot ((slīpums, pārtvert)) 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, ass = 0)) ja len (right_fit)> 0: lane_lines.append (make_points (frame, right_fit_average)) # lane_lines ir divdimensiju masīvs, kas sastāv no labās un kreisās joslas līniju koordinātām # piemēram: lan e_lines =

make_points () ir palīgfunkcija vidēji_slope_intercept () funkcijai, kas atdos joslu līniju ierobežotās koordinātas (no apakšas līdz rāmja vidum).

def make_points (rāmis, līnija):

augstums, platums, _ = rāmis. formas slīpums, pārtveršana = līnija y1 = augstums # rāmja apakšdaļa y2 = int (y1 / 2) # veido punktus no rāmja vidus uz leju, ja slīpums == 0: slīpums = 0,1 x1 = int ((y1 - pārtvert) / slīpums) x2 = int ((y2 - pārtvert) / slīpums) atgriezties

Lai novērstu dalīšanu ar 0, tiek parādīts nosacījums. Ja slīpums = 0, kas nozīmē y1 = y2 (horizontāla līnija), piešķiriet slīpumam vērtību, kas ir tuvu 0. Tas neietekmēs algoritma darbību, kā arī novērsīs neiespējamo gadījumu (dalot ar 0).

Lai parādītu joslu līnijas uz rāmjiem, tiek izmantota šāda funkcija:

def display_lines (rāmis, līnijas, line_color = (0, 255, 0), line_width = 6): # līnijas krāsa (B, G, R)

line_image = np.zeros_like (kadrs), ja līnijas nav line_width) line_image = cv2.addWeighted (frame, 0.8, line_image, 1, 1) return line_image

Funkcija cv2.addWeighted () izmanto šādus parametrus, un to izmanto, lai apvienotu divus attēlus, bet katram piešķirot svaru.

cv2.addWeighted (attēls1, alfa, attēls2, beta, gamma)

Un aprēķina izvades attēlu, izmantojot šādu vienādojumu:

izvade = alfa * attēls1 + beta * attēls2 + gamma

Plašāka informācija par funkciju cv2.addWeighted () ir iegūta šeit.

Aprēķināt un parādīt virsraksta līniju:

Šis ir pēdējais solis, pirms mēs pielietojam ātrumus saviem motoriem. Virziena līnija ir atbildīga par stūres motora virzienu, kurā tam jāgriežas, un droseļvārsta motoru darbības ātrumu. Virsraksta aprēķināšana ir tīra trigonometrija, tiek izmantotas iedeguma un atāna (tan^-1) trigonometriskās funkcijas. Daži ārkārtēji gadījumi ir gadījumi, kad kamera nosaka tikai vienu joslas līniju vai kad tā neatklāj nevienu līniju. Visi šie gadījumi ir parādīti šādā funkcijā:

def get_steering_angle (rāmis, joslu līnijas):

augstums, platums, _ = rāmis.shape if len (lane_lines) == 2: # ja tiek atklātas divas joslu līnijas _, _, left_x2, _ = lane_lines [0] [0] # ekstrakts pa kreisi x2 no lane_lines masīva _, _, pa labi_x2, _ = joslu_līnijas [1] [0] # izvilkt labo x2 no joslas_līnijas masīva vidus = int (platums / 2) x_ nobīde = (pa kreisi_x2 + labais_x2) / 2 - vidējais y_ nobīde = int (augstums / 2) elif len (lane_lines) == 1: # ja tiek konstatēta tikai viena līnija x1, _, x2, _ = joslu līnijas [0] [0] x_ nobīde = x2 - x1 y_ nobīde = int (augstums / 2) elif len (lane_lines) == 0: # ja līnija netiek konstatēta

x_ nobīde pirmajā gadījumā ir tas, cik daudz vidējais ((labais x2 + kreisais x2) / 2) atšķiras no ekrāna vidus. y_ nobīde vienmēr tiek uzskatīta par augstumu / 2. Pēdējā attēlā redzams virsraksta līnijas piemērs. Angle_to_mid_radians ir tāds pats kā "teta", kas parādīta pēdējā attēlā iepriekš. Ja stūres stūris = 90, tas nozīmē, ka automašīnai ir virziena līnija, kas ir perpendikulāra līnijai "augstums / 2", un automašīna brauks uz priekšu bez stūres. Ja stūres stūris> 90, automašīnai jāstūrē pa labi, pretējā gadījumā - pa kreisi. Lai parādītu virsraksta rindiņu, tiek izmantota šāda funkcija:

def display_heading_line (rāmis, stūres stūris, line_color = (0, 0, 255), line_width = 5)

head_image = np.zeros_like (frame) augstums, platums, _ = frame.shape stūres_stūris_radian = stūres stūris / 180,0 * math.pi x1 = int (platums / 2) y1 = augstums x2 = int (x1 - augstums / 2 / math.tan (stūres_stūra_radiāns)) y2 = int (augstums / 2) cv2.line (heading_image, (x1, y1), (x2, y2), line_color, line_width) head_image = cv2.addWeighted (frame, 0.8, title_image, 1, 1) atgriezt virsraksta_attēlu

Iepriekš minētā funkcija ņem vērā rāmi, kurā tiks novilkta virziena līnija, un stūres leņķi. Tas atgriež virsraksta līnijas attēlu. Virsraksta līnijas rāmis, kas ņemts manā gadījumā, ir parādīts attēlā iepriekš.

Visu kodu apvienošana:

Kods tagad ir gatavs apkopošanai. Šis kods parāda programmas galveno cilpu, kas izsauc katru funkciju:

importēt cv2

importēt numpy kā np video = cv2. VideoCapture (0) video.set (cv2. CAP_PROP_FRAME_WIDTH, 320) video.set (cv2. CAP_PROP_FRAME_HEIGHT, 240), bet True: ret, frame = video.read () frame = cv2.flip (Kadrs, -1) #Funkciju izsaukšana = get_steering_angle (frame, lane_lines) head_image = display_heading_line (lane_lines_image, stūres_stūris) taustiņš = cv2.waitKey (1) if key == 27: break video.release () cv2.destroyAllWindows ()

6. darbība: PD kontroles pielietošana

PD kontroles pielietošana
PD kontroles pielietošana

Tagad stūres leņķis ir gatavs, lai to varētu ievadīt motoros. Kā minēts iepriekš, ja stūres leņķis ir lielāks par 90, automašīnai jāgriežas pa labi, pretējā gadījumā tai jāgriežas pa kreisi. Es izmantoju vienkāršu kodu, kas pagriež stūres motoru pa labi, ja leņķis ir virs 90, un pagriež to pa kreisi, ja stūres leņķis ir mazāks par 90 ar nemainīgu droseles ātrumu (10% PWM), bet es saņēmu daudz kļūdu. Galvenā kļūda, ko es saņēmu, ir tad, kad automašīna tuvojas jebkuram pagriezienam, stūres motors darbojas tieši, bet droseļvārsta dzinēji iestrēgst. Es mēģināju palielināt droseļvārsta ātrumu (20% PWM) pagriezienos, bet beidzās ar robota izkāpšanu no joslām. Man vajadzēja kaut ko, kas ļoti palielina droseļvārsta ātrumu, ja stūres leņķis ir ļoti liels, un nedaudz palielina ātrumu, ja stūrēšanas leņķis nav tik liels, tad samazina ātrumu līdz sākotnējai vērtībai, automašīnai tuvojoties 90 grādiem (virzoties taisni). Risinājums bija izmantot PD kontrolieri.

PID kontrolieris apzīmē proporcionālo, integrālo un atvasināto regulatoru. Šāda veida lineārie kontrolieri tiek plaši izmantoti robotikas lietojumos. Iepriekš redzamajā attēlā redzama tipiska PID atgriezeniskās saites kontroles cilpa. Šī regulatora mērķis ir visefektīvākajā veidā sasniegt "iestatīto vērtību", atšķirībā no "ieslēgt -izslēgt" kontrolieriem, kas saskaņā ar dažiem nosacījumiem ieslēdz vai izslēdz iekārtu. Ir jāzina daži atslēgvārdi:

  • Iestatītā vērtība: ir vēlamā vērtība, kuru vēlaties sasniegt savā sistēmā.
  • Faktiskā vērtība: ir faktiskā vērtība, ko nosaka sensors.
  • Kļūda: ir starpība starp uzdoto vērtību un faktisko vērtību (kļūda = uzdotā vērtība - faktiskā vērtība).
  • Kontrolējamais mainīgais: no tā nosaukuma mainīgais, kuru vēlaties kontrolēt.
  • Kp: proporcionāla konstante.
  • Ki: neatņemama konstante.
  • Kd: atvasinājuma konstante.

Īsi sakot, PID vadības sistēmas cilpa darbojas šādi:

  • Lietotājs nosaka iestatīto vērtību, kas nepieciešama, lai sistēma sasniegtu.
  • Kļūda tiek aprēķināta (kļūda = uzdotā vērtība - faktiskā).
  • P kontrolieris ģenerē darbību, kas ir proporcionāla kļūdas vērtībai. (palielinās kļūda, palielinās arī P darbība)
  • I kontrolieris laika gaitā integrēs kļūdu, kas novērš sistēmas līdzsvara stāvokļa kļūdu, bet palielina tās pārsniegumu.
  • D kontrolieris ir vienkārši kļūdas laika atvasinājums. Citiem vārdiem sakot, tas ir kļūdas slīpums. Tā veic darbību, kas ir proporcionāla kļūdas atvasinājumam. Šis kontrolieris palielina sistēmas stabilitāti.
  • Kontroliera izeja būs trīs kontrolieru summa. Regulatora izeja kļūs par 0, ja kļūda kļūs par 0.

Lielisku PID regulatora skaidrojumu var atrast šeit.

Atgriežoties pie joslas saglabāšanas automašīnas, mans kontrolētais mainīgais bija droseles ātrums (jo stūrēšanai ir tikai divi stāvokļi - pa labi vai pa kreisi). Šim nolūkam tiek izmantots PD kontrolieris, jo D darbība ievērojami palielina droseļvārsta ātrumu, ja kļūdas izmaiņas ir ļoti lielas (ti, liela novirze), un palēnina automašīnas darbību, ja šīs kļūdas izmaiņas tuvojas 0. Es īstenoju šādas darbības, lai ieviestu PD kontrolieris:

  • Iestatiet iestatīto vērtību uz 90 grādiem (es vienmēr gribu, lai automašīna pārvietojas taisni)
  • Aprēķināts novirzes leņķis no vidus
  • Novirze sniedz divas ziņas: cik liela ir kļūda (novirzes lielums) un kādā virzienā jāvirzās stūres motors (novirzes pazīme). Ja novirze ir pozitīva, automašīnai jāstūrē pa labi, pretējā gadījumā - pa kreisi.
  • Tā kā novirze ir negatīva vai pozitīva, tiek definēts "kļūdas" mainīgais un vienmēr vienāds ar novirzes absolūto vērtību.
  • Kļūda tiek reizināta ar konstantu Kp.
  • Kļūda tiek diferencēta laikā, un tā tiek reizināta ar nemainīgu Kd.
  • Motoru ātrums tiek atjaunināts, un cikls sākas no jauna.

Lai kontrolētu droseļvārsta apgriezienu skaitu, galvenajā cilpā tiek izmantots šāds kods:

ātrums = 10 # darba ātrums % PWM

# Mainīgie, kas jāatjaunina katru cilpu lastTime = 0 lastError = 0 # PD konstantes Kp = 0.4 Kd = Kp * 0.65 Kaut gan True: now = time.time () # pašreizējā laika mainīgais dt = now - lastTime deviation = stūres_stūris - 90 # ekvivalents līdz leņķim_milid_deguma mainīgā kļūda = abs (novirze), ja novirze -5: # nevirziet, ja ir 10 grādu kļūdu diapazona novirze = 0 kļūda = 0 GPIO.izvade (in1, GPIO. LOW) GPIO.output (in2, GPIO. ZEMA) stūres. Apstāšanās () elifa novirze> 5: # pagrieziet pa labi, ja novirze ir pozitīva -5: # stūrēt pa kreisi, ja novirze ir negatīva GPIO.output (in1, GPIO. HIGH) GPIO.output (in2, GPIO. LOW) * kļūda PD = int (ātrums + atvasinājums + proporcionāls) spd = abs (PD), ja spd> 25: spd = 25 droseļvārsts.start (spd) lastError = kļūda lastTime = time.time ()

Ja kļūda ir ļoti liela (novirze no vidus ir liela), proporcionālās un atvasinātās darbības ir augstas, kā rezultātā rodas liels droseļvārsta ātrums. Kad kļūda tuvojas 0 (novirze no vidus ir zema), atvasinātā darbība darbojas pretēji (slīpums ir negatīvs), un droseles ātrums kļūst zems, lai saglabātu sistēmas stabilitāti. Pilns kods ir pievienots zemāk.

7. darbība: rezultāti

Iepriekš redzamajos videoklipos ir redzami iegūtie rezultāti. Tam ir nepieciešama lielāka regulēšana un papildu pielāgojumi. Es pieslēdzu aveņu pi savam LCD displeja ekrānam, jo video straumēšanai tīklā bija liels latentums un ar to bija ļoti apgrūtinoši strādāt, tāpēc videoklipā ir pievienoti vadi, kas savienoti ar aveņu pi. Es izmantoju putu dēļus, lai zīmētu sliežu ceļu.

Es gaidu jūsu ieteikumus, lai uzlabotu šo projektu! Es ceru, ka šīs instrukcijas bija pietiekami labas, lai sniegtu jums jaunu informāciju.

Ieteicams: