Turinys:

Autonominis juostos laikantis automobilis naudojant „Raspberry Pi“ir „OpenCV“: 7 žingsniai (su nuotraukomis)
Autonominis juostos laikantis automobilis naudojant „Raspberry Pi“ir „OpenCV“: 7 žingsniai (su nuotraukomis)

Video: Autonominis juostos laikantis automobilis naudojant „Raspberry Pi“ir „OpenCV“: 7 žingsniai (su nuotraukomis)

Video: Autonominis juostos laikantis automobilis naudojant „Raspberry Pi“ir „OpenCV“: 7 žingsniai (su nuotraukomis)
Video: Part 5 - Babbitt Audiobook by Sinclair Lewis (Chs 23-28) 2024, Lapkritis
Anonim
Autonominis juostos laikantis automobilis naudojant „Raspberry Pi“ir „OpenCV“
Autonominis juostos laikantis automobilis naudojant „Raspberry Pi“ir „OpenCV“

Šioje instrukcijoje bus įdiegtas autonominis juostos laikymo robotas, kuris atliks šiuos veiksmus:

  • Dalių surinkimas
  • Būtinos programinės įrangos diegimo sąlygos
  • Techninės įrangos surinkimas
  • Pirmasis bandymas
  • Eismo juostų linijų aptikimas ir orientacinės linijos rodymas naudojant „openCV“
  • Įdiegus PD valdiklį
  • Rezultatai

1 žingsnis: Surinkite komponentus

Surinkimo komponentai
Surinkimo komponentai
Surinkimo komponentai
Surinkimo komponentai
Surinkimo komponentai
Surinkimo komponentai
Surinkimo komponentai
Surinkimo komponentai

Aukščiau esančiuose paveikslėliuose rodomi visi šiame projekte naudojami komponentai:

  • RC automobilis: Aš gavau savo vietinėje parduotuvėje savo šalyje. Jame yra 3 varikliai (2 droseliui ir 1 vairui). Pagrindinis šio automobilio trūkumas yra tai, kad vairavimas yra ribojamas tarp „jokio vairavimo“ir „viso vairavimo“. Kitaip tariant, jis negali vairuoti tam tikru kampu, skirtingai nei servo vairo RC automobiliai. Čia galite rasti panašų automobilių rinkinį, specialiai sukurtą aviečių pi.
  • „Raspberry pi 3“modelis b+: tai yra automobilio smegenys, kurios atliks daugybę apdorojimo etapų. Jis pagrįstas keturių branduolių 64 bitų procesoriumi, kurio dažnis yra 1,4 GHz. Aš gavau savo iš čia.
  • „Raspberry pi 5 MP“kameros modulis: palaiko 1080p @ 30 fps, 720p @ 60 fps ir 640x480p 60/90 įrašymą. Jis taip pat palaiko serijinę sąsają, kurią galima prijungti tiesiai prie aviečių pi. Tai nėra geriausias pasirinkimas vaizdo apdorojimo programoms, tačiau šiam projektui to pakanka ir jis yra labai pigus. Aš gavau savo iš čia.
  • Variklio tvarkyklė: naudojama nuolatinės srovės variklių kryptims ir greičiams valdyti. Jis palaiko 2 nuolatinės srovės variklių valdymą vienoje plokštėje ir gali atlaikyti 1,5 A.
  • „Power Bank“(neprivaloma): „Aviečių pi“įjungimui naudoju maitinimo bloką (5 V, 3 A). Norint įjungti aviečių pi iš 1 šaltinio, reikia naudoti pakopinį keitiklį („buck converter“: 3A išėjimo srovė).
  • 3s (12 V) „LiPo“baterija: ličio polimerų baterijos yra žinomos dėl puikių našumų robotikos srityje. Jis naudojamas variklio vairuotojui maitinti. Aš nusipirkau savo iš čia.
  • Džemperio laidai nuo patinų iki patelių ir nuo moterų iki moterų.
  • Dvipusė juosta: naudojama montuoti komponentus ant RC automobilio.
  • Mėlyna juosta: tai yra labai svarbi šio projekto sudedamoji dalis, ji naudojama dviejų eismo juostų linijoms, tarp kurių automobilis važiuos. Galite pasirinkti bet kokią norimą spalvą, bet aš rekomenduoju rinktis kitokias spalvas nei aplinkinė aplinka.
  • Užsegami užtrauktukai ir mediniai strypai.
  • Atsuktuvas.

2 veiksmas: „OpenCV“diegimas „Raspberry Pi“ir nuotolinio ekrano nustatymas

„OpenCV“diegimas „Raspberry Pi“ir nuotolinio ekrano nustatymas
„OpenCV“diegimas „Raspberry Pi“ir nuotolinio ekrano nustatymas

Šis žingsnis šiek tiek erzina ir užtruks šiek tiek laiko.

„OpenCV“(atviro kodo kompiuterinė vizija) yra atvirojo kodo kompiuterinės vizijos ir mašininio mokymosi programinės įrangos biblioteka. Bibliotekoje yra daugiau nei 2500 optimizuotų algoritmų. Vykdykite šį labai paprastą vadovą, kad įdiegtumėte „openCV“į savo aviečių pi, taip pat įdiegtumėte „raspberry pi“OS (jei vis dar to nepadarėte). Atminkite, kad „openCV“kūrimo procesas gerai atvėsusioje patalpoje gali užtrukti apie 1,5 valandos (nes procesoriaus temperatūra bus labai aukšta!), Todėl išgerkite arbatos ir kantriai palaukite: D.

Norėdami nustatyti nuotolinį ekraną, taip pat vadovaukitės šiuo vadovu, kad nustatytumėte nuotolinę prieigą prie savo aviečių pi iš „Windows“/„Mac“įrenginio.

3 žingsnis: dalių sujungimas

Dalių sujungimas kartu
Dalių sujungimas kartu
Dalių sujungimas kartu
Dalių sujungimas kartu
Dalių sujungimas kartu
Dalių sujungimas kartu

Aukščiau esančiuose paveikslėliuose parodytas ryšys tarp „Raspberry pi“, fotoaparato modulio ir variklio tvarkyklės. Atkreipkite dėmesį, kad mano naudojami varikliai sugeria 0,35 A esant 9 V įtampai, todėl variklio vairuotojas gali saugiai paleisti 3 variklius vienu metu. Ir kadangi aš noriu lygiai taip pat valdyti 2 droselinių variklių greitį (1 galinis ir 1 priekinis), prijungiau juos prie to paties prievado. Variklio vairuotoją pritvirtinau dešinėje automobilio pusėje, naudodami dvigubą juostą. Kalbant apie fotoaparato modulį, aš įdėjau užtrauktuką tarp varžtų skylių, kaip parodyta aukščiau esančiame paveikslėlyje. Tada pritaikau fotoaparatą prie medinės juostos, kad galėčiau koreguoti kameros padėtį, kaip noriu. Pabandykite kiek įmanoma sumontuoti fotoaparatą automobilio viduryje. Rekomenduoju fotoaparatą pastatyti bent 20 cm virš žemės, kad matymo laukas priešais automobilį pagerėtų. Fritzingo schema pridedama žemiau.

4 žingsnis: pirmasis bandymas

Pirmasis bandymas
Pirmasis bandymas
Pirmasis bandymas
Pirmasis bandymas

Fotoaparato testavimas:

Įdiegę fotoaparatą ir sukūrę „openCV“biblioteką, laikas išbandyti pirmąjį mūsų vaizdą! Mes padarysime nuotrauką iš pi cam ir išsaugosime ją kaip „original.jpg“. Tai galima padaryti 2 būdais:

1. Naudojant terminalo komandas:

Atidarykite naują terminalo langą ir įveskite šią komandą:

raspistill -o original.jpg

Tai padarys nejudantį vaizdą ir išsaugos jį kataloge „/pi/original.jpg“.

2. Naudojant bet kokį python IDE (aš naudoju IDLE):

Atidarykite naują eskizą ir parašykite šį kodą:

importuoti cv2

video = cv2. VideoCapture (0), o tiesa: ret, frame = video.read () frame = cv2.flip (frame, -1) # naudojamas vertikaliai apversti vaizdą cv2.imshow ('originalus', kadras) cv2. imwrite ('original.jpg', frame) raktas = cv2.waitKey (1) if key == 27: break video.release () cv2.destroyAllWindows ()

Pažiūrėkime, kas atsitiko šiame kodekse. Pirma eilutė importuoja mūsų „openCV“biblioteką, kad galėtų naudotis visomis jos funkcijomis. „VideoCapture“(0) funkcija pradeda transliuoti tiesioginį vaizdo įrašą iš šios funkcijos nustatyto šaltinio, šiuo atveju tai yra 0, o tai reiškia „raspi“kamerą. jei turite kelias kameras, reikia įdėti skirtingus numerius. video.read () perskaitys kiekvieną kadrą iš fotoaparato ir išsaugos jį kintamajame, vadinamame „kadras“. funkcija flip () apvers vaizdą y ašies atžvilgiu (vertikaliai), nes fotoaparatą montuoju atvirkščiai. „imshow“() parodys mūsų rėmelius su žodžiu „originalus“, o „imwrite“() išsaugos mūsų nuotrauką kaip originalią.jpg. waitKey (1) palauks 1 ms, kol bus paspaustas bet kuris klaviatūros mygtukas, ir grąžins ASCII kodą. jei paspaudžiamas pabėgimo (esc) mygtukas, grąžinama dešimtainė reikšmė 27 ir atitinkamai nutraukia kilpą. „video.release“() sustabdys įrašymą ir sunaikins „AllWindows“() uždarys kiekvieną vaizdą, atidarytą naudojant „imshow“() funkciją.

Rekomenduoju išbandyti savo nuotrauką antruoju metodu, kad susipažintumėte su „openCV“funkcijomis. Vaizdas išsaugomas kataloge „/pi/original.jpg“. Originali mano fotoaparato padaryta nuotrauka parodyta aukščiau.

Variklių testavimas:

Šis žingsnis yra būtinas norint nustatyti kiekvieno variklio sukimosi kryptį. Pirma, trumpai supažindinkime su variklio vairuotojo darbo principu. Aukščiau esančiame paveikslėlyje parodyta variklio vairuotojo jungtis. Įgalinti A, 1 ir 2 įėjimai yra susiję su A variklio valdymu. Įgalinti B, įvestis 3 ir įvestis 4 yra susieta su variklio B valdymu. Krypties valdymą nustato įvesties dalis, o greičio valdymą - įjungimo dalis. Pavyzdžiui, norėdami valdyti A variklio kryptį, nustatykite 1 įvestį į HIGH (šiuo atveju 3,3 V, nes mes naudojame aviečių pi) ir nustatykite 2 įvestį į LOW, variklis suksis tam tikra kryptimi ir nustatys priešingas vertes į 1 ir 2 įėjimus variklis suksis priešinga kryptimi. Jei 1 įvestis = 2 įvestis = (HIGH arba LOW), variklis nesisuka. Įgalinimo kaiščiai paima impulsinio pločio moduliacijos (PWM) įvesties signalą iš aviečių (nuo 0 iki 3,3 V) ir atitinkamai paleidžia variklius. Pavyzdžiui, 100% PWM signalas reiškia, kad dirbame ties maksimaliu greičiu, o 0% PWM signalas reiškia, kad variklis nesisuka. Šis kodas naudojamas variklių kryptims nustatyti ir jų greičiams išbandyti.

importo laikas

importuokite RPi. GPIO kaip GPIO GPIO.setwarnings (Netiesa) # Vairavimo variklio kaiščiai 23 # Fizinis kaištis 16 colių4 = 24 # Fizinis kaištis 18 GPIO.setmode (GPIO. BCM) # Naudokite GPIO numeraciją, o ne fizinę GPIO.setup (in1, GPIO.out) GPIO.setup (in2, GPIO.out) GPIO. sąranka (in3, GPIO.out) GPIO.setup (in4, GPIO.out) GPIO.setup (throttle_enable, GPIO.out) GPIO.setup (vairavimo įjungimas, GPIO.out) # Vairavimo variklio valdymas GPIO.output (in1, GPIO). AUKŠTAS) GPIO išėjimas (in2, GPIO. LOW) vairavimas = GPIO. PWM (vairavimas įjungiamas, 1000) # nustatykite perjungimo dažnį į 1000 Hz vairavimo. Stop () # Droselio variklių valdymas GPIO. Išėjimas (in3, GPIO. HIGH) GPIO. output (in4, GPIO. LOW) droselis = GPIO. PWM (throttle_enable, 1000) # nustatykite perjungimo dažnį į 1000 Hz droselinę sklendę. stop () time.sleep (1) droselis. start (25) # paleidžia variklį esant 25 % PWM signalas-> (0,25 * akumuliatoriaus įtampa) - vairuotojo vairavimo nuostoliai. paleidimas (100) # paleidžia variklį esant 100% PWM signalui -> (1 * akumuliatoriaus įtampa) - vairuotojo praradimo laikas. miego režimas (3) droselis. stabdymas () vairavimas

Šis kodas veiks droselinius variklius ir vairo variklį 3 sekundes, o tada juos sustabdys. (Vairuotojo nuostolius) galima nustatyti naudojant voltmetrą. Pavyzdžiui, mes žinome, kad 100% PWM signalas turėtų suteikti visą akumuliatoriaus įtampą variklio gnybte. Tačiau nustačius PWM 100%, aš sužinojau, kad vairuotojas sukelia 3 V kritimą, o variklis gauna 9 V vietoj 12 V (būtent tai, ko man reikia!). Nuostolis nėra tiesinis, ty 100% nuostolis labai skiriasi nuo nuostolio esant 25%. Paleidus aukščiau pateiktą kodą, mano rezultatai buvo tokie:

Akceleratoriaus rezultatai: jei in3 = AUKŠTAS ir in4 = ŽEMAS, droseliniai varikliai pasuks laikrodžio režimu (CW), ty automobilis judės į priekį. Priešingu atveju automobilis judės atgal.

Vairavimo rezultatai: jei in1 = AUKŠTAS ir in2 = ŽEMAS, vairavimo variklis suksis maksimaliai į kairę, ty automobilis pasuks į kairę. Priešingu atveju automobilis pasuks į dešinę. Po kai kurių eksperimentų sužinojau, kad vairo variklis nesisuks, jei PWM signalas nebus 100% (t. Y. Variklis visiškai pasuks į dešinę arba visiškai į kairę).

5 žingsnis: juostų linijų aptikimas ir kurso linijos apskaičiavimas

Juostos linijų aptikimas ir kurso apskaičiavimas
Juostos linijų aptikimas ir kurso apskaičiavimas
Juostos linijų aptikimas ir kurso apskaičiavimas
Juostos linijų aptikimas ir kurso apskaičiavimas
Juostos linijų aptikimas ir kurso apskaičiavimas
Juostos linijų aptikimas ir kurso apskaičiavimas

Šiame žingsnyje bus paaiškintas algoritmas, kuris valdys automobilio judėjimą. Pirmasis vaizdas parodo visą procesą. Sistemos įvestis yra vaizdai, o išvestis - teta (vairavimo kampas laipsniais). Atminkite, kad apdorojimas atliekamas 1 vaizdui ir bus kartojamas visuose kadruose.

Fotoaparatas:

Fotoaparatas pradės įrašyti (320 x 240) raiškos vaizdo įrašą. Rekomenduoju sumažinti skiriamąją gebą, kad gautumėte geresnį kadrų dažnį (fps), nes pritaikius apdorojimo būdus kiekvienam kadrui, sumažės kadrų per sekundę. Žemiau pateiktas kodas bus pagrindinė programos kilpa ir pridės kiekvieną šio kodo žingsnį.

importuoti cv2

importuoti numpy kaip np video = cv2. VideoCapture (0) video.set (cv2. CAP_PROP_FRAME_WIDTH, 320) # nustatyti plotį į 320 p video.set (cv2. CAP_PROP_FRAME_HEIGHT, 240) # nustatyti aukštį iki 240 p # Ciklas Tiesa: 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 ()

Čia pateiktas kodas parodys originalų vaizdą, gautą atliekant 4 veiksmą, ir parodytas aukščiau esančiuose paveikslėliuose.

Konvertuoti į HSV spalvų erdvę:

Dabar, kai vaizdo įrašymas buvo padarytas kaip fotoaparato kadrai, kitas žingsnis yra paversti kiekvieną kadrą į atspalvių, sodrumo ir vertės (HSV) spalvų erdvę. Pagrindinis privalumas yra gebėjimas atskirti spalvas pagal jų ryškumą. Ir čia yra geras HSV spalvų erdvės paaiškinimas. Konvertavimas į HSV atliekamas naudojant šią funkciją:

def convert_to_HSV (kadras):

hsv = cv2.cvtColor (kadras, cv2. COLOR_BGR2HSV) cv2.imshow ("HSV", hsv) grąžinti hsv

Ši funkcija bus iškviesta iš pagrindinės kilpos ir grąžins kadrą HSV spalvų erdvėje. Rėmelis, kurį gavau HSV spalvų erdvėje, parodytas aukščiau.

Aptikti mėlyną spalvą ir kraštus:

Konvertavus vaizdą į HSV spalvų erdvę, laikas aptikti tik mus dominančią spalvą (t. Y. Mėlyną spalvą, nes tai yra juostų linijų spalva). Norint išgauti mėlyną spalvą iš HSV rėmo, reikia nurodyti atspalvio, sodrumo ir vertės diapazoną. žr. čia, kad geriau suprastumėte HSV vertes. Po kai kurių eksperimentų viršutinė ir apatinė mėlynos spalvos ribos rodomos žemiau esančiame kode. O siekiant sumažinti bendrą kiekvieno rėmo iškraipymą, kraštai aptinkami tik naudojant įbrėžto krašto detektorių. Daugiau apie „canny edge“rasite čia. Nykščio taisyklė - pasirinkti Canny () funkcijos parametrus santykiu 1: 2 arba 1: 3.

defect_edges (kadras):

apatinė_ mėlyna = np.masyvas ([90, 120, 0], dtype = "uint8") # apatinė mėlynos spalvos riba viršutinė_ mėlyna = np.masyvas ([150, 255, 255], dtype = "uint8") # viršutinė mėlynos spalvos kaukė = cv2.inRange (hsv, apatinė_ mėlyna, viršutinė_ mėlyna) # ši kaukė filtruos viską, išskyrus mėlyną

Ši funkcija taip pat bus iškviesta iš pagrindinės kilpos, kuri kaip parametrą priima HSV spalvų erdvės rėmelį ir grąžina briaunotą rėmelį. Kraštinis rėmas, kurį aš gavau, yra aukščiau.

Pasirinkite dominantį regioną (IG):

Norint sutelkti dėmesį tik į vieną kadro sritį, labai svarbu pasirinkti dominančią sritį. Šiuo atveju nenoriu, kad automobilis aplinkoje matytų daug daiktų. Aš tiesiog noriu, kad automobilis sutelktų dėmesį į juostų linijas ir nekreiptų dėmesio į visa kita. P. S: koordinačių sistema (x ir y ašys) prasideda nuo viršutinio kairiojo kampo. Kitaip tariant, taškas (0, 0) prasideda nuo viršutinio kairiojo kampo. y ašis yra aukštis, o x ašis-plotis. Žemiau pateiktas kodas pasirenka dominančią sritį, kad sutelktų dėmesį tik į apatinę kadro pusę.

def regiono_interest (kraštai):

aukštis, plotis = kraštai.shape # išgauti kraštų rėmo kaukės aukštį ir plotį = np.zeros_like (kraštai) # sudaryti tuščią matricą su tais pačiais kraštų rėmo matmenimis # fokusuoti tik apatinėje ekrano pusėje # nurodyti koordinates 4 taškai (apatinis kairysis, viršutinis kairysis, viršutinis dešinysis, apatinis dešinysis) daugiakampis = np. Masyvas (

Ši funkcija paims briaunotą rėmelį kaip parametrą ir nubrėžs daugiakampį su 4 iš anksto nustatytais taškais. Jis sutelks dėmesį tik į tai, kas yra daugiakampio viduje, ir ignoruos viską, kas yra už jo ribų. Mano interesų srities rėmas parodytas aukščiau.

Aptikti linijos segmentus:

Hough transformacija naudojama aptikti linijos segmentus iš briaunoto rėmo. Hough transformacija yra metodas, leidžiantis aptikti bet kokią matematinės formos formą. Jis gali aptikti beveik bet kokį objektą, net jei jis iškraipomas pagal tam tikrą balsų skaičių. čia pateikiama puiki nuoroda į Hough transformaciją. Šiai programai cv2. HoughLinesP () funkcija naudojama aptikti kiekvieno kadro linijas. Svarbūs šios funkcijos parametrai:

cv2. HoughLinesP (kadras, rho, teta, min_threshold, minLineLength, maxLineGap)

  • Rėmas: tai rėmas, kuriame norime aptikti linijas.
  • rho: Tai atstumo tikslumas taškais (paprastai jis yra = 1)
  • teta: kampinis tikslumas radianais (visada = np.pi/180 ~ 1 laipsnis)
  • min_threshold: minimalus balsas, kurį jis turėtų gauti, kad jis būtų laikomas linija
  • minLineLength: minimalus eilutės ilgis taškais. Bet kuri eilutė, trumpesnė už šį skaičių, nelaikoma eilute.
  • maxLineGap: didžiausias pikselių tarpas tarp 2 eilučių turi būti laikomas 1 eilute. (Mano atveju jis nenaudojamas, nes mano naudojamos juostos linijos neturi jokio tarpo).

Ši funkcija grąžina tiesės galinius taškus. Ši funkcija iškviečiama iš mano pagrindinės kilpos, norint aptikti linijas naudojant Hough transformaciją:

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

Vidutinis nuolydis ir perėjimas (m, b):

prisiminkime, kad tiesės lygtis pateikiama y = mx + b. Kur m yra tiesės nuolydis, o b-y pjūvis. Šioje dalyje bus apskaičiuotas linijų atkarpų nuolydžių ir pjūvių vidurkis, aptiktas naudojant Hough transformaciją. Prieš tai darydami, pažvelkime į aukščiau pateiktą originalią rėmo nuotrauką. Atrodo, kad kairioji juosta kyla aukštyn, todėl jos nuolydis yra neigiamas (prisiminkite koordinačių sistemos pradžios tašką?). Kitaip tariant, kairioji juostos linija turi x1 <x2 ir y2 x1 ir y2> y1, kurie suteiks teigiamą nuolydį. Taigi visos linijos, turinčios teigiamą nuolydį, laikomos dešinės juostos taškais. Vertikalių linijų atveju (x1 = x2) nuolydis bus begalybė. Tokiu atveju praleisime visas vertikalias linijas, kad išvengtume klaidos. Kad šis aptikimas būtų tikslesnis, kiekvienas kadras yra padalintas į dvi sritis (dešinę ir kairę) per 2 ribines linijas. Visi pločio taškai (x ašies taškai), didesni už dešinę ribinę liniją, yra susieti su dešinės juostos skaičiavimu. Ir jei visi pločio taškai yra mažesni už kairę ribos liniją, jie yra susieti su kairiosios juostos skaičiavimu. Ši funkcija apdoroja kadrą ir nustato juostos segmentus, naudojant Hough transformaciją, ir grąžina vidutinį dviejų juostų linijų nuolydį ir perėmimą.

def medium_slope_intercept (kadras, eilutės segmentai):

lane_lines = jei line_segments nėra: print („linijos segmentas neaptiktas“) grąžina „lane_lines“aukštį, plotį, _ = frame.shape left_fit = right_fit = border = left_region_boundary = width * (1 - border) right_region_boundary = plotis * riba eilutės_segmentui eilutėje, (y1, y2), 1) nuolydis = (y2 - y1) / (x2 - x1) perimti = y1 - (nuolydis * x1), jei nuolydis <0: jei x1 <kairysis_regiono_iribas ir x2 dešinysis_regiono_lenktis ir x2> dešinysis_regiono_lenktis: teisingas_pritaikymas. pridėti ((nuolydis, perimti)) 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)) if len (right_fit)> 0: lane_lines.append (make_points (frame, right_fit_average)) # lane_lines yra 2-D masyvas, sudarytas iš dešinės ir kairės juostos linijų koordinačių # pvz: lan e_lines =

„make_points“() yra pagalbinė funkcija „átlagos_slope_intercept ()“, kuri grąžins ribotas eismo juostos koordinates (nuo apačios iki kadro vidurio).

def make_points (rėmas, linija):

aukštis, plotis, _ = rėmas. formos nuolydis, perėmimas = tiesė y1 = aukštis # rėmo apačia y2 = int (y1 / 2) # padarykite taškus nuo rėmo vidurio žemyn, jei nuolydis == 0: nuolydis = 0,1 x1 = int ((y1 - perimti) / nuolydis) x2 = int ((y2 - perimti) / nuolydis) return

Siekiant išvengti dalijimosi iš 0, pateikiama sąlyga. Jei nuolydis = 0, o tai reiškia, kad y1 = y2 (horizontali linija), nurodykite nuolydį, kuris yra artimas 0. Tai neturės įtakos algoritmo veikimui, taip pat užkirs kelią neįmanomiems atvejams (dalijant iš 0).

Kad rėmeliuose būtų rodomos juostos linijos, naudojama ši funkcija:

def display_lines (rėmas, linijos, linijos_ spalva = (0, 255, 0), linijos plotis = 6): # eilutės spalva (B, G, R)

line_image = np.zeros_like (kadras), jei linijos nėra line_width) line_image = cv2.addWeighted (frame, 0.8, line_image, 1, 1) return line_image

Funkcija cv2.addWeighted () naudoja šiuos parametrus ir naudojama dviem vaizdais sujungti, tačiau kiekvienam suteikiant svorį.

cv2.add Svoris (vaizdas1, alfa, vaizdas2, beta, gama)

Ir apskaičiuoja išvesties vaizdą naudodami šią lygtį:

išvestis = alfa * vaizdas1 + beta * vaizdas2 + gama

Daugiau informacijos apie funkciją cv2.addWeighted () rasite čia.

Apskaičiuokite ir rodykite antraštės eilutę:

Tai yra paskutinis žingsnis prieš pradedant taikyti greičius savo varikliams. Krypties linija yra atsakinga už tai, kad vairo varikliui būtų suteikta kryptis, kuria jis turėtų suktis, ir droselinių variklių greitis, kuriuo jie veiks. Antraštės eilutės apskaičiavimas yra gryna trigonometrija, naudojamos įdegio ir atano (tan^-1) trigonometrinės funkcijos. Kai kurie kraštutiniai atvejai yra tada, kai fotoaparatas aptinka tik vieną juostos liniją arba kai neaptinka jokios linijos. Visi šie atvejai rodomi šioje funkcijoje:

def get_steering_angle (kadras, eismo juostos):

aukštis, plotis, _ = kadras.shape if len (lane_lines) == 2: # jei aptinkamos dvi juostos linijos _, _, left_x2, _ = lane_lines [0] [0] # ištraukta kairė x2 iš lane_lines masyvo _, _, right_x2, _ = lane_lines [1] [0] # ištraukite dešinę x2 iš lane_lines masyvo viduryje = int (plotis / 2) x_offset = (left_x2 + right_x2) / 2 - y_offset = int (aukštis / 2) elif len (lane_lines) == 1: #, jei aptinkama tik viena linija x1, _, x2, _ = juostų linijos [0] [0] x_offset = x2 - x1 y_offset = int (aukštis / 2) elif len (lane_lines) == 0: # jei linija neaptinkama

x_kompensacija pirmuoju atveju yra tai, kiek vidurkis ((dešinė x2 + kairė x2) / 2) skiriasi nuo ekrano vidurio. y_ nuolydis visada laikomas aukščiu / 2. Paskutiniame aukščiau esančiame paveikslėlyje parodytas antraštės eilutės pavyzdys. kampas į vidurį_radianai yra tas pats, kas „teta“, parodyta paskutiniame aukščiau esančiame paveikslėlyje. Jei vairavimo kampas = 90, tai reiškia, kad automobilio posūkio linija yra statmena „aukščio / 2“linijai ir automobilis važiuos į priekį be vairo. Jei vairavimo kampas> 90, automobilis turėtų pasukti į dešinę, kitaip - į kairę. Norėdami rodyti antraštės eilutę, naudojama ši funkcija:

def display_heading_line (rėmas, vairavimo kampas, line_color = (0, 0, 255), line_width = 5)

antraštė_paveikslėlis = np.zeros_like (rėmas) aukštis, plotis, _ = frame.shape stūmimo_angle_radianas = vairavimo kampas / 180,0 * math.pi x1 = int (plotis / 2) y1 = aukštis x2 = int (x1 - aukštis / 2 / math.tan (vairavimo kampo_radianas)) y2 = int (aukštis / 2) cv2.line (antraštės_paveikslėlis, (x1, y1), (x2, y2), eilutės_spalva, linijos plotis) head_image = cv2.addWeighted (frame, 0.8, head_image, 1, 1) grąžinti antraštę_paveikslėlis

Aukščiau pateikta funkcija ima rėmelį, kuriame bus nubrėžta kurso linija, ir vairavimo kampą. Tai grąžina antraštės eilutės vaizdą. Antraštės linijos rėmas, paimtas mano atveju, parodytas aukščiau esančiame paveikslėlyje.

Sujungus visus kodus kartu:

Kodas dabar paruoštas surinkti. Šis kodas rodo pagrindinę programos kilpą, iškviečiančią kiekvieną funkciją:

importuoti cv2

importuoti numpy kaip np video = cv2. VideoCapture (0) video.set (cv2. CAP_PROP_FRAME_WIDTH, 320) video.set (cv2. CAP_PROP_FRAME_HEIGHT, 240), o tiesa: ret, frame = video.read () frame = cv2.flip (kadras, -1) #Funkcijų iškvietimas = get_steering_angle (frame, lane_lines) head_image = display_heading_line (lane_lines_image,vairo_angle) key = cv2.waitKey (1) if key == 27: break video.release () cv2.destroyAllWindows ()

6 žingsnis: PD valdymo taikymas

Taikant PD valdymą
Taikant PD valdymą

Dabar mūsų vairavimo kampas yra paruoštas tiekti varikliams. Kaip minėta anksčiau, jei vairavimo kampas yra didesnis nei 90, automobilis turėtų pasukti į dešinę, kitaip - į kairę. Taikiau paprastą kodą, kuris pasuka vairo variklį į dešinę, jei kampas yra didesnis nei 90, ir pasuka jį į kairę, jei vairavimo kampas yra mažesnis nei 90, esant pastoviam droselio greičiui (10% PWM), bet gavau daug klaidų. Pagrindinė klaida, kurią gavau, yra tai, kad automobilis artėja prie bet kurio posūkio, vairo variklis veikia tiesiogiai, tačiau droseliniai varikliai užstringa. Bandžiau padidinti droselio greitį (20% PWM) posūkiuose, bet baigėsi tuo, kad robotas išlipo iš juostų. Man reikėjo kažko, kas labai padidina droselio greitį, jei vairavimo kampas yra labai didelis, ir šiek tiek padidina greitį, jei vairavimo kampas nėra toks didelis, o tada sumažina greitį iki pradinės vertės, kai automobilis artėja prie 90 laipsnių (juda tiesiai). Sprendimas buvo naudoti PD valdiklį.

PID valdiklis reiškia proporcinį, integralų ir išvestinį valdiklius. Šio tipo linijiniai valdikliai plačiai naudojami robotikos programose. Aukščiau esančiame paveikslėlyje parodyta tipinė PID grįžtamojo ryšio valdymo kilpa. Šio valdiklio tikslas yra pasiekti „nustatytąją vertę“efektyviausiu būdu, skirtingai nuo „įjungimo“valdiklių, kurie tam tikromis sąlygomis įjungia arba išjungia įrenginį. Reikėtų žinoti kai kuriuos raktinius žodžius:

  • Nustatytoji vertė: yra norima reikšmė, kurią norite pasiekti savo sistemoje.
  • Faktinė vertė: yra faktinė jutiklio aptikta vertė.
  • Klaida: yra skirtumas tarp nustatytos vertės ir faktinės vertės (klaida = nustatytoji vertė - faktinė vertė).
  • Kontroliuojamas kintamasis: iš jo pavadinimo kintamasis, kurį norite valdyti.
  • Kp: proporcinga konstanta.
  • Ki: integrali konstanta.
  • Kd: išvestinė konstanta.

Trumpai tariant, PID valdymo sistemos kilpa veikia taip:

  • Vartotojas nustato nustatytą vertę, kurios reikia sistemai pasiekti.
  • Klaida apskaičiuojama (klaida = nustatytoji vertė - faktinė).
  • P valdiklis sukuria veiksmą, proporcingą klaidos vertei. (padaugėja klaidų, padidėja ir P veiksmas)
  • I valdiklis laikui bėgant integruoja klaidą, kuri pašalina sistemos pastovios būsenos klaidą, tačiau padidina jos viršijimą.
  • D valdiklis yra tiesiog klaidos laiko išvestinė. Kitaip tariant, tai yra klaidos nuolydis. Jis atlieka veiksmą, proporcingą klaidos išvestinei priemonei. Šis valdiklis padidina sistemos stabilumą.
  • Valdiklio išvestis bus trijų valdiklių suma. Jei klaida tampa 0, valdiklio išvestis taps 0.

Čia rasite puikų PID valdiklio paaiškinimą.

Grįžtant prie juostos laikančio automobilio, mano valdomas kintamasis buvo droselio greitis (nes vairavimas turi tik dvi būsenas - dešinę arba kairę). Šiam tikslui naudojamas PD valdiklis, nes D veiksmas labai padidina droselio greitį, jei klaidos pasikeitimas yra labai didelis (ty didelis nuokrypis), ir sulėtina automobilį, jei šis klaidos pakeitimas artėja prie 0. Aš įgyvendinau šiuos veiksmus, kad įdiegčiau PD valdiklis:

  • Nustatykite nustatytą vertę į 90 laipsnių (aš visada noriu, kad automobilis judėtų tiesiai)
  • Apskaičiuotas nuokrypio kampas nuo vidurio
  • Nukrypimas suteikia dvi informacijos: kokia didelė klaida (nuokrypio dydis) ir kokia kryptimi turi pasukti vairo variklis (nuokrypio požymis). Jei nuokrypis teigiamas, automobilis turėtų vairuoti į dešinę, kitaip - į kairę.
  • Kadangi nuokrypis yra neigiamas arba teigiamas, „klaidos“kintamasis yra apibrėžtas ir visada lygus absoliučiai nuokrypio vertei.
  • Klaida padauginama iš pastovaus Kp.
  • Klaida diferencijuojama pagal laiką ir padauginama iš pastovaus Kd.
  • Variklių greitis atnaujinamas ir ciklas prasideda iš naujo.

Pagrindinis kodas naudojamas droselinių variklių greičiui valdyti:

greitis = 10 # darbinis greitis % PWM

#Kintamieji, kurie turi būti atnaujinami kiekvieną ciklą kintamoji_kampas_minia_deg kintama paklaida = abs (nuokrypis), jei nuokrypis -5: # nevairuokite, jei yra 10 laipsnių paklaidos diapazono nuokrypis = 0 paklaida = 0 GPIO.output (in1, GPIO. LOW) GPIO.output (in2, GPIO. LOW) vairas.stop () elifo nuokrypis> 5: # nukreipkite į dešinę, jei nuokrypis yra teigiamas GPIO. išvestis (in1, GPIO. LOW) GPIO. išvestis (in2, GPIO. HIGH) vairavimo.start (100) elifo nuokrypis < -5: # nukreipti į kairę, jei nuokrypis yra neigiamas GPIO.output (in1, GPIO. HIGH) GPIO.putput (in2, GPIO. LOW) * klaida PD = int (greitis + išvestinė + proporcinga) spd = abs (PD), jei spd> 25: spd = 25 droselis.start (spd) lastError = klaida lastTime = time.time ()

Jei klaida yra labai didelė (nuokrypis nuo vidurio yra didelis), proporcingi ir išvestiniai veiksmai yra dideli, todėl atsiranda didelis droselio greitis. Kai klaida artėja prie 0 (nuokrypis nuo vidurio yra mažas), išvestinis veiksmas veikia atvirkščiai (nuolydis yra neigiamas), o droselio greitis tampa mažas, kad išlaikytų sistemos stabilumą. Visas kodas pridedamas žemiau.

7 žingsnis: Rezultatai

Aukščiau esantys vaizdo įrašai rodo mano gautus rezultatus. Tam reikia daugiau derinimo ir tolesnių pakeitimų. „Raspberry pi“prijungiau prie savo LCD ekrano, nes vaizdo įrašų srautai per mano tinklą turėjo didelį delsą ir buvo labai varginantys dirbti, todėl vaizdo įraše yra laidų, prijungtų prie aviečių pi. Trasai nubrėžti naudojau putplasčio lentas.

Laukiu jūsų patarimų, kaip pagerinti šį projektą! Tikiuosi, kad šios instrukcijos buvo pakankamai geros, kad suteiktų jums naujos informacijos.

Rekomenduojamas: