Makinë autonome që mban korsi duke përdorur Raspberry Pi dhe OpenCV: 7 hapa (me fotografi)
Makinë autonome që mban korsi duke përdorur Raspberry Pi dhe OpenCV: 7 hapa (me fotografi)
Anonim
Makinë Autonome e Mbajtjes së Korsisë duke Përdorur Raspberry Pi dhe OpenCV
Makinë Autonome e Mbajtjes së Korsisë duke Përdorur Raspberry Pi dhe OpenCV

Në këtë udhëzues, një robot autonom i mbajtjes së korsisë do të zbatohet dhe do të kalojë nëpër hapat e mëposhtëm:

  • Mbledhja e Pjesëve
  • Instalimi i parakushteve të softuerit
  • Asambleja e harduerit
  • Testi i parë
  • Zbulimi i linjave të korsive dhe shfaqja e linjës udhëzuese duke përdorur openCV
  • Zbatimi i një kontrolluesi PD
  • Rezultatet

Hapi 1: Grumbullimi i përbërësve

Mbledhja e Komponentëve
Mbledhja e Komponentëve
Mbledhja e Komponentëve
Mbledhja e Komponentëve
Mbledhja e Komponentëve
Mbledhja e Komponentëve
Mbledhja e Komponentëve
Mbledhja e Komponentëve

Imazhet e mësipërme tregojnë të gjithë përbërësit e përdorur në këtë projekt:

  • Makina RC: Unë e mora nga një dyqan lokal në vendin tim. Shtë i pajisur me 3 motorë (2 për mbytje dhe 1 për drejtimin). Disavantazhi kryesor i kësaj makine është se drejtimi është i kufizuar midis "pa drejtim" dhe "drejtimit të plotë". Me fjalë të tjera, nuk mund të drejtojë në një kënd të veçantë, ndryshe nga makinat RC servo-timon. Ju mund të gjeni një pajisje të ngjashme makinash të krijuar posaçërisht për mjedrën pi nga këtu.
  • Raspberry pi 3 model b+: ky është truri i makinës i cili do të përballojë shumë faza përpunimi. Bazohet në një procesor me katër bërthama 64-bit me një frekuencë prej 1.4 GHz. Unë e mora nga këtu.
  • Moduli i kamerës Raspberry pi 5 mp: Mbështet 1080p @ 30 fps, 720p @ 60 fps dhe regjistrim 640x480p 60/90. Ai gjithashtu mbështet ndërfaqen serike e cila mund të lidhet direkt në mjedër pi. Nuk është opsioni më i mirë për aplikimet e përpunimit të imazhit, por është i mjaftueshëm për këtë projekt, si dhe është shumë i lirë. Unë e mora nga këtu.
  • Shoferi i motorit: Përdoret për të kontrolluar drejtimet dhe shpejtësitë e motorëve DC. Ai mbështet kontrollin e 2 motorëve DC në 1 tabelë dhe mund të përballojë 1.5 A.
  • Power Bank (Opsionale): Kam përdorur një bankë energjie (e vlerësuar në 5V, 3A) për të ndezur veçmas mjedrën pi. Duhet të përdoret një konvertues hap poshtë (konvertuesi i dollarit: rryma e daljes 3A) për të fuqizuar pi mjedrën nga 1 burim.
  • Bateri LiPo 3s (12 V): Bateritë e litium polimerit janë të njohura për performancën e tyre të shkëlqyer në fushën e robotikës. Përdoret për të fuqizuar drejtuesin e motorit. Bleva timen nga këtu.
  • Tela bluzë nga meshkuj në meshkuj dhe femra në femra.
  • Shirit i dyanshëm: Përdoret për të montuar përbërësit në makinën RC.
  • Shirit blu: Ky është një komponent shumë i rëndësishëm i këtij projekti, përdoret për të bërë dy linjat e korsisë në të cilat makina do të lëvizë midis. Ju mund të zgjidhni çdo ngjyrë që dëshironi, por unë rekomandoj të zgjidhni ngjyra të ndryshme nga ato në mjedisin përreth.
  • Lidhëse zip dhe shufra druri.
  • Vidhosës.

Hapi 2: Instalimi i OpenCV në Raspberry Pi dhe vendosja e ekranit në distancë

Instalimi i OpenCV në Raspberry Pi dhe vendosja e ekranit në distancë
Instalimi i OpenCV në Raspberry Pi dhe vendosja e ekranit në distancë

Ky hap është pak i bezdisshëm dhe do të marrë ca kohë.

OpenCV (Open Source Computer Vision) është një bibliotekë e softuerit të vizionit kompjuterik dhe mësimit të makinerisë. Biblioteka ka mbi 2500 algoritme të optimizuara. Ndiqni këtë udhëzues shumë të drejtpërdrejtë për të instaluar openCV në pi tuaj të mjedrës, si dhe instalimin e OS të mjedrës pi (nëse akoma nuk e keni bërë). Ju lutemi vini re se procesi i ndërtimit të openCV mund të zgjasë rreth 1.5 orë në një dhomë të ftohur mirë (pasi temperatura e procesorit do të bëhet shumë e lartë!) Kështu që pini pak çaj dhe prisni me durim: D.

Për ekranin në distancë, gjithashtu ndiqni K guideTO udhëzues për të konfiguruar qasjen në distancë në pajisjen tuaj Windows/Mac në distancë pi.

Hapi 3: Lidhja e pjesëve së bashku

Lidhja e pjesëve së bashku
Lidhja e pjesëve së bashku
Lidhja e pjesëve së bashku
Lidhja e pjesëve së bashku
Lidhja e pjesëve së bashku
Lidhja e pjesëve së bashku

Imazhet e mësipërme tregojnë lidhjet midis mjedrës pi, modulit të kamerës dhe drejtuesit të motorit. Ju lutemi vini re se motorët që kam përdorur thithin 0.35 A në 9 V secili, gjë që e bën të sigurt për drejtuesin e motorit të drejtojë 3 motorë në të njëjtën kohë. Dhe meqenëse unë dua të kontrolloj shpejtësinë e 2 motorëve të mbytjes (1 mbrapa dhe 1 përpara) saktësisht në të njëjtën mënyrë, i lidha me të njëjtin port. Montova shoferin e motorit në anën e djathtë të makinës duke përdorur shirit të dyfishtë. Sa i përket modulit të kamerës, unë futa një kravatë me zinxhir midis vrimave të vidave siç tregon imazhi më sipër. Pastaj, e vendos kamerën në një shufër druri në mënyrë që të mund të rregulloj pozicionin e kamerës ashtu siç dua. Mundohuni të instaloni kamerën në mes të makinës sa më shumë që të jetë e mundur. Unë rekomandoj vendosjen e kamerës të paktën 20 cm mbi tokë në mënyrë që fusha e shikimit para makinës të përmirësohet. Skema Fritzing është bashkangjitur më poshtë.

Hapi 4: Testi i parë

Testi i parë
Testi i parë
Testi i parë
Testi i parë

Testimi i kamerës:

Pasi të jetë instaluar kamera dhe të krijohet biblioteka openCV, është koha për të testuar imazhin tonë të parë! Ne do të bëjmë një fotografi nga pi cam dhe do ta ruajmë si "original.jpg". Mund të bëhet në 2 mënyra:

1. Përdorimi i Komandave të Terminalit:

Hapni një dritare të re të terminalit dhe shkruani komandën e mëposhtme:

raspistill -o origjinal.jpg

Kjo do të marrë një imazh të palëvizshëm dhe do ta ruajë atë në drejtorinë "/pi/original.jpg".

2. Përdorimi i çdo IDE python (Unë përdor IDLE):

Hapni një skicë të re dhe shkruani kodin e mëposhtëm:

import cv2

video = cv2. VideoCapture (0) ndërsa True: ret, frame = video.read () frame = cv2.flip (frame, -1) # përdoret për ta kthyer imazhin vertikalisht cv2.imshow ('origjinal', kornizë) cv2. imwrite ('original.jpg', frame) kyç = cv2.waitKey (1) nëse çelësi == 27: thye video.release () cv2.destroyAllWindows ()

Le të shohim se çfarë ndodhi në këtë kod. Linja e parë është importimi i bibliotekës sonë openCV për të përdorur të gjitha funksionet e saj. funksioni VideoCapture (0) fillon të transmetojë një video të drejtpërdrejtë nga burimi i përcaktuar nga ky funksion, në këtë rast është 0 që do të thotë kamera raspi. nëse keni shumë kamera, duhet të vendosni numra të ndryshëm. video.read () do të lexojë çdo kornizë që vjen nga kamera dhe do ta ruajë atë në një ndryshore të quajtur "frame". Funksioni rrokullisje () do ta rrokullis imazhin në lidhje me boshtin y (vertikalisht) pasi unë jam duke e ngritur kamerën time në anën e kundërt. imshow () do të shfaq kornizat tona të kryesuara nga fjala "origjinale" dhe imwrite () do të ruajë foton tonë si origjinale.jpg. waitKey (1) do të presë për 1 ms që të shtypet çdo buton i tastierës dhe kthen kodin e tij ASCII. nëse shtypet butoni shpëtues (esc), një vlerë dhjetore prej 27 kthehet dhe do të thyejë lakun në përputhje me rrethanat. video.release () do të ndalojë regjistrimin dhe shkatërronAllWindows () do të mbyllë çdo imazh të hapur nga funksioni imshow ().

Unë rekomandoj që të testoni foton tuaj me metodën e dytë për t'u njohur me funksionet e openCV. Imazhi ruhet në drejtorinë "/pi/original.jpg". Fotografia origjinale që bëri kamera ime është treguar më lart.

Motorët e testimit:

Ky hap është thelbësor për të përcaktuar drejtimin e rrotullimit të secilit motor. Së pari, le të bëjmë një hyrje të shkurtër mbi parimin e punës së një shoferi motorik. Imazhi i mësipërm tregon fiksimin e drejtuesit të motorit. Aktivizo A, Hyrja 1 dhe Hyrja 2 lidhen me kontrollin e motorit A. Aktivizo B, Hyrja 3 dhe Hyrja 4 shoqërohen me kontrollin e motorit B. Kontrolli i drejtimit përcaktohet nga pjesa "Input" dhe kontrolli i shpejtësisë përcaktohet nga pjesa "Enable". Për të kontrolluar drejtimin e motorit A për shembull, vendosni Input 1 në HIGH (3.3 V në këtë rast pasi ne po përdorim një mjedër pi) dhe vendosni Input 2 në LOW, motori do të rrotullohet në një drejtim specifik dhe duke vendosur vlerat e kundërta në hyrjen 1 dhe hyrjen 2, motori do të rrotullohet në drejtim të kundërt. Nëse Hyrja 1 = Hyrja 2 = (E LART or ose E ULT), motori nuk do të kthehet. Aktivizoni kunjat për të marrë një sinjal hyrës të Pulse Width Modulation (PWM) nga mjedra (0 deri në 3.3 V) dhe drejtoni motorët në përputhje me rrethanat. Për shembull, një sinjal 100% PWM do të thotë që ne po punojmë me shpejtësinë maksimale dhe sinjali 0% PWM do të thotë që motori nuk po rrotullohet. Kodi i mëposhtëm përdoret për të përcaktuar drejtimet e motorëve dhe për të testuar shpejtësinë e tyre.

koha e importit

importo RPi. GPIO si GPIO GPIO.setwarnings (False) # Motorët e drejtimit të motorit drejtues_enable = 22 # Pin fizik 15 in1 = 17 # Pin fizik 11 in2 = 27 # Pin fizik 13 # Throttle Motors Pins throttle_enable = 25 # Pin fizike 22 in3 = 23 # Pin fizik 16 in4 = 24 # Pin fizik 18 GPIO.setmode (GPIO. BCM) # Përdorni numërimin GPIO në vend të numërimit fizik GPIO.setup (in1, GPIO.out) GPIO.setup (in2, GPIO.out) GPIO. konfigurimi (in3, GPIO.out) GPIO.setup (in4, GPIO.out) GPIO.setup (throttle_enable, GPIO.out) GPIO.setup (steering_enable, GPIO.out) # Kontrolli i motorit drejtues GPIO.output (in1, GPIO E LART) GPIO.output (in2, GPIO. LOW) drejtimi = GPIO. PWM (drejtues_aktivizues, 1000) # vendosni frekuencën e ndërrimit në drejtimin 1000 Hz.stop () # Kontrolli i Motoreve të Throttle GPIO.putput (in3, GPIO. HIGH) GPIO. dalje (in4, GPIO. LOW) mbytëse = GPIO. PWM (mbytëse_aktive, 1000) # vendosni frekuencën e ndërrimit në 1000 Hz mbytëse. ndaloni () kohën. flini (1) mbytjen.filloni (25) # fillon motorin në 25 % Sinjal PWM-> (0.25 * Tensioni i baterisë) - i shoferit humbja e drejtimit.filloni (100) # e ndez motorin në 100% sinjal PWM-> (1 * Tensioni i baterisë) - koha e humbjes së shoferit.

Ky kod do të punojë motorët e mbytjes dhe motorin drejtues për 3 sekonda dhe më pas do t'i ndalojë ato. (Humbja e shoferit) mund të përcaktohet duke përdorur një voltmetër. Për shembull, ne e dimë se një sinjal 100% PWM duhet të japë tensionin e plotë të baterisë në terminalin e motorit. Por, duke vendosur PWM në 100%, zbulova se shoferi po shkakton një rënie prej 3 V dhe motori po merr 9 V në vend të 12 V (pikërisht ajo që më duhet!). Humbja nuk është lineare, domethënë humbja në 100% është shumë e ndryshme nga humbja në 25%. Pas ekzekutimit të kodit të mësipërm, rezultatet e mia ishin si më poshtë:

Rezultatet e mbytjes: nëse in3 = LART dhe in4 = I POSHT, motorët e mbytjes do të kenë një rrotullim Sahati-Urtë (CW) dmth makina do të ecë përpara. Përndryshe, makina do të lëvizë prapa.

Rezultatet e drejtimit: nëse in1 = LART dhe in2 = I POSHT, motori drejtues do të kthehet në maksimumin e tij majtas dmth makina do të drejtohet majtas. Përndryshe, makina do të drejtohet djathtas. Pas disa eksperimenteve, zbulova se motori i drejtimit nuk do të kthehej nëse sinjali PWM nuk ishte 100% (domethënë motori do të drejtohet plotësisht në të djathtë ose plotësisht në të majtë).

Hapi 5: Zbulimi i vijave të korsisë dhe llogaritja e vijës së drejtimit

Zbulimi i vijave të korsisë dhe llogaritja e vijës së drejtimit
Zbulimi i vijave të korsisë dhe llogaritja e vijës së drejtimit
Zbulimi i vijave të korsisë dhe llogaritja e vijës së drejtimit
Zbulimi i vijave të korsisë dhe llogaritja e vijës së drejtimit
Zbulimi i vijave të korsisë dhe llogaritja e vijës së drejtimit
Zbulimi i vijave të korsisë dhe llogaritja e vijës së drejtimit

Në këtë hap, algoritmi që do të kontrollojë lëvizjen e makinës do të shpjegohet. Imazhi i parë tregon të gjithë procesin. Hyrja e sistemit është imazhe, dalja është theta (këndi i drejtimit në gradë). Vini re se, përpunimi bëhet në 1 imazh dhe do të përsëritet në të gjitha kornizat.

Kamera:

Kamera do të fillojë të regjistrojë një video me (320 x 240) rezolucion. Unë rekomandoj uljen e rezolucionit në mënyrë që të keni një normë më të mirë të kornizës (fps) pasi rënia e fps do të ndodhë pas aplikimit të teknikave të përpunimit në secilën kornizë. Kodi më poshtë do të jetë laku kryesor i programit dhe do të shtojë çdo hap mbi këtë kod.

import cv2

importoni numpy si np video = cv2. VideoCapture (0) video.set (cv2. CAP_PROP_FRAME_WIDTH, 320) # vendosni gjerësinë në 320 p video.set (cv2. CAP_PROP_FRAME_HEIGHT, 240) # vendosni lartësinë në 240 p # The loop while E vërtetë: ret, frame = video.read () frame = cv2.flip (frame, -1) cv2.imshow ("origjinal", frame) kyç = cv2.waitKelës (1) nëse çelësi == 27: pushim video. Publikim () cv2.destroyAllWindows ()

Kodi këtu do të tregojë imazhin origjinal të marrë në hapin 4 dhe tregohet në imazhet e mësipërme.

Shndërrohuni në HSV Color Color:

Tani pas marrjes së regjistrimit të videos si korniza nga kamera, hapi tjetër është konvertimi i secilës kornizë në hapësirë me ngjyra Hue, Saturation dhe Value (HSV). Avantazhi kryesor i kësaj është të jesh në gjendje të dallosh ngjyrat sipas nivelit të ndriçimit të tyre. Dhe këtu është një shpjegim i mirë i hapësirës së ngjyrave HSV. Konvertimi në HSV bëhet përmes funksionit të mëposhtëm:

def convert_to_HSV (kornizë):

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

Ky funksion do të thirret nga laku kryesor dhe do të kthejë kornizën në hapësirën e ngjyrave HSV. Korniza e marrë nga unë në hapësirën e ngjyrave HSV është treguar më lart.

Zbuloni ngjyrën blu dhe skajet:

Pas konvertimit të imazhit në hapësirën e ngjyrave HSV, është koha për të zbuluar vetëm ngjyrën për të cilën ne jemi të interesuar (dmth. Ngjyra blu pasi është ngjyra e linjave të korsisë). Për të nxjerrë ngjyrën blu nga një kornizë HSV, duhet të specifikoni një gamë të ngjyrës, ngopjes dhe vlerës. referojuni këtu për të pasur një ide më të mirë mbi vlerat e HSV. Pas disa eksperimenteve, kufijtë e sipërm dhe të poshtëm të ngjyrës blu tregohen në kodin më poshtë. Dhe për të zvogëluar shtrembërimin e përgjithshëm në secilën kornizë, skajet zbulohen vetëm duke përdorur detektor buzë të hollë. Më shumë rreth kanies edge gjendet këtu. Një rregull i përgjithshëm është që të zgjidhni parametrat e funksionit Canny () me një raport 1: 2 ose 1: 3.

def dete_edges (kornizë):

ulët_blue = np.array ([90, 120, 0], dtype = "uint8") # kufiri i poshtëm i ngjyrës blu sipër_blue = np.array ([150, 255, 255], dtype = "uint8") # kufiri i sipërm i maskë me ngjyrë blu = cv2.inRange (hsv, blu_ e ulët, blu_ e sipërme) # kjo maskë do të filtrojë gjithçka, por blu # zbulon skajet e skajeve = cv2. Canny (maskë, 50, 100) cv2.imshow ("skajet", skajet) kthejnë skajet

Ky funksion do të thirret gjithashtu nga laku kryesor i cili merr si parametër kornizën e hapësirës me ngjyra HSV dhe kthen kornizën me tehe. Korniza me tehe që kam marrë gjendet më sipër.

Zgjidhni Rajonin e Interesit (ROI):

Zgjedhja e rajonit të interesit është vendimtare për t'u përqëndruar vetëm në 1 zonë të kornizës. Në këtë rast, nuk dua që makina të shohë shumë sende në mjedis. Unë thjesht dua që makina të përqëndrohet në linjat e korsisë dhe të injorojë çdo gjë tjetër. P. S: sistemi koordinativ (akset x dhe y) fillon nga këndi i sipërm i majtë. Me fjalë të tjera, pika (0, 0) fillon nga këndi i sipërm i majtë. boshti y është lartësia dhe boshti x është gjerësia. Kodi më poshtë zgjedh rajonin me interes për t'u përqëndruar vetëm në gjysmën e poshtme të kornizës.

def rajoni i_interesit (skajet):

lartësia, gjerësia = skajet. forma # nxjerr lartësinë dhe gjerësinë e skajeve korniza maskë = np.zeros_like (skajet) # bëni një matricë të zbrazët me të njëjtat dimensione të kornizës së skajeve # fokusoni vetëm gjysmën e poshtme të ekranit # specifikoni koordinatat e 4 pikë (poshtë majtas, sipër majtas, sipër djathtas, poshtë djathtas) poligon = np. Array (

Ky funksion do të marrë kornizën me tehe si parametër dhe vizaton një poligon me 4 pika të paracaktuara. Ai do të përqëndrohet vetëm në atë që është brenda poligonit dhe do të injorojë gjithçka jashtë tij. Korniza e rajonit tim të interesit është treguar më lart.

Zbuloni segmentet e linjës:

Transformimi i ashpër përdoret për të zbuluar segmentet e linjës nga një kornizë me tehe. Transformimi i ashpër është një teknikë për të zbuluar çdo formë në formë matematikore. Mund të zbulojë pothuajse çdo objekt edhe nëse është i shtrembëruar sipas një numri votash. një referencë e madhe për transformimin Hough është treguar këtu. Për këtë aplikacion, funksioni cv2. HoughLinesP () përdoret për të zbuluar linjat në secilën kornizë. Parametrat e rëndësishëm që merr ky funksion janë:

cv2. HoughLinesP (kornizë, rho, theta, min_threshold, minLineLength, maxLineGap)

  • Frame: është korniza në të cilën duam të zbulojmë linjat.
  • rho: theshtë saktësia e distancës në piksele (zakonisht është = 1)
  • theta: saktësi këndore në radianë (gjithmonë = np.pi/180 ~ 1 shkallë)
  • pragu min: vota minimale që duhet të marrë që ajo të konsiderohet si rresht
  • minLineLength: gjatësia minimale e rreshtit në pixel. Çdo rresht më i shkurtër se ky numër nuk konsiderohet rresht.
  • maxLineGap: hendeku maksimal në piksele midis 2 rreshtave që do të trajtohet si 1 rresht. (Nuk përdoret në rastin tim pasi linjat e korsisë që po përdor nuk kanë ndonjë boshllëk).

Ky funksion kthen pikat përfundimtare të një linje. Funksioni i mëposhtëm thirret nga laku im kryesor për të zbuluar linjat duke përdorur transformimin Hough:

def segmente_të_ zbulimit (kufijtë e prerë):

rho = 1 theta = np.pi / 180 min_thresh = 10 linja_segmente = cv2. HoughLinesP (buzë të shkurtuara, rho, theta, min_threshold, np.array (), minLineLength = 5, maxLineGap = 0) linja_segmente të kthimit

Pjerrësia mesatare dhe përgjimi (m, b):

rikujtojmë se ekuacioni i drejtëzës jepet me y = mx + b. Ku m është pjerrësia e drejtëzës dhe b është ndërprerja y. Në këtë pjesë, do të llogaritet mesatarja e shpateve dhe kapjeve të segmenteve të linjës të zbuluara duke përdorur transformimin Hough. Para se ta bëni këtë, le të hedhim një vështrim në foton origjinale të kornizës të treguar më sipër. Korsia e majtë duket se po shkon lart kështu që ka një pjerrësi negative (mbani mend pikën fillestare të sistemit të koordinatave?). Me fjalë të tjera, vija e majtë e korsisë ka x1 <x2 dhe y2 x1 dhe y2> y1 e cila do të japë një pjerrësi pozitive. Pra, të gjitha linjat me pjerrësi pozitive konsiderohen pika të drejta të korsisë. Në rast të vijave vertikale (x1 = x2), pjerrësia do të jetë pafundësi. Në këtë rast, ne do të kalojmë të gjitha linjat vertikale për të parandaluar marrjen e një gabimi. Për t'i shtuar më shumë saktësi këtij zbulimi, secila kornizë ndahet në dy rajone (djathtas dhe majtas) përmes 2 vijave kufitare. Të gjitha pikat e gjerësisë (pikat e boshtit x) më të mëdha se vija kufitare e djathtë, shoqërohen me llogaritjen e korsisë së djathtë. Dhe nëse të gjitha pikat e gjerësisë janë më pak se vija kufitare e majtë, ato shoqërohen me llogaritjen e korsisë së majtë. Funksioni i mëposhtëm merr kornizën nën përpunim dhe segmentet e korsive të zbuluara duke përdorur transformimin Hough dhe kthen pjerrësinë mesatare dhe përgjimin e dy linjave të korsisë.

def mesatar_slope_intercept (kuadër, segmente të linjës):

linjat e korsisë = nëse segmentet e linjës nuk janë: print ("asnjë segment i linjës nuk është zbuluar") kthejnë lartësinë, gjerësinë e korsisë, _ = kornizën. forma left_fit = right_fit = kufiri = left_region_boundary = gjerësia * (1 - kufiri) right_region_boundary = gjerësia * kufiri për segmentin e linjës në segmentet e linjës: për x1, y1, x2, y2 në segmentin e vijës: nëse x1 == x2: print ("kapërcimi i vijave vertikale (pjerrësia = pafundësi)") vazhdoni përshtatjen = np.polyfit ((x1, x2), (y1, y2), 1) pjerrësia = (y2 - y1) / (x2 - x1) intercept = y1 - (pjerrësia * x1) nëse pjerrësia <0: nëse x1 <kufiri i majtë_gregon dhe x2 djathtas_regioni_kufitar dhe x2> kufiri i_djathtë: kufiri i djathtë: right_fit. shtoj ((pjerrësia, përgjimi)) left_fit_average = np. mesatarja (left_fit, aksi = 0) nëse len (left_fit)> 0: lane_lines.append (make_points (frame, left_fit_average)) right_fit_average = np.average (right_fit, axis = 0) nëse len (right_fit)> 0: lane_lines.append (make_points (frame, right_fit_average)) # lane_lines është një grup 2-D i përbërë nga koordinatat e linjave të korsisë së djathtë dhe të majtë # për shembull: korsi e_lines =

make_points () është një funksion ndihmës për funksionin average_slope_intercept () i cili do të kthejë koordinatat e kufizuara të vijave të korsisë (nga poshtë në mes të kornizës).

def make_points (kuadër, rresht):

lartësia, gjerësia, _ = korniza. pjerrësia e formës, prerja = rreshti y1 = lartësia # fundi i kornizës y2 = int (y1 / 2) # bëjnë pikë nga mesi i kornizës poshtë nëse pjerrësia == 0: pjerrësia = 0.1 x1 = int ((y1 - intercept) / pjerrësia) x2 = int ((y2 - intercept) / shpat) kthimi

Për të parandaluar pjesëtimin me 0, paraqitet një kusht. Nëse pjerrësia = 0 që do të thotë y1 = y2 (vija horizontale), jepni pjerrësisë një vlerë afër 0. Kjo nuk do të ndikojë në performancën e algoritmit si dhe do të parandalojë rastin e pamundur (pjesëtimi me 0).

Për të shfaqur linjat e korsisë në korniza, përdoret funksioni i mëposhtëm:

def linjat e shfaqjes (korniza, linjat, ngjyra_ngjyra = (0, 255, 0), gjerësia_reshta = 6): # ngjyra e rreshtit (B, G, R)

line_image = np.zeros_like (kornizë) nëse rreshtat nuk janë Asnjë: për rreshtin në rreshta: për x1, y1, x2, y2 në linjë: cv2.line (imazhi_linja, (x1, y1), (x2, y2), ngjyra_ngjyra, line_width) line_image = cv2.addWeighted (frame, 0.8, line_image, 1, 1) line_image return

Funksioni cv2.addWeighted () merr parametrat e mëposhtëm dhe përdoret për të kombinuar dy imazhe, por duke i dhënë secilës një peshë.

cv2.addPeshuar (imazhi1, alfa, imazhi2, beta, gama)

Dhe llogarit imazhin dalës duke përdorur ekuacionin e mëposhtëm:

dalje = alfa * imazh1 + beta * imazh2 + gama

Më shumë informacion në lidhje me funksionin cv2.addWeighted () nxirren këtu.

Llogaritni dhe shfaqni vijën e titullit:

Ky është hapi i fundit para se të aplikojmë shpejtësi në motorët tanë. Linja e drejtimit është përgjegjëse për t'i dhënë motorit drejtues drejtimin në të cilin ai duhet të rrotullohet dhe u jep motorëve të mbytjes shpejtësinë me të cilën ata do të veprojnë. Llogaritja e vijës së kreut është trigonometri e pastër, përdoren funksionet trigonometrike tan dhe atan (tan^-1). Disa raste ekstreme janë kur kamera zbulon vetëm një linjë korsie ose kur nuk zbulon asnjë linjë. Të gjitha këto raste tregohen në funksionin e mëposhtëm:

def get_steering_angle (kornizë, linja_kalimi):

lartësia, gjerësia, _ = korniza.formë nëse len (linjat_korsa) == 2: # nëse zbulohen dy linja korsie _, _, left_x2, _ = korsi_lajn [0] [0] # ekstrakt i mbetur x2 nga grupi i linjave të korsisë _, _, right_x2, _ = linjat_korsi [1] [0] # ekstrakt djathtas x2 nga grupi i linjave të korsisë mes = int (gjerësi / 2) x_offset = (majtas_x2 + djathtas_x2) / 2 - mes y_offset = int (lartësi / 2) elif len (linja_ linja) == 1: # nëse zbulohet vetëm një rresht x1, _, x2, _ = linjat e korsisë [0] [0] x_offset = x2 - x1 y_offset = int (lartësia / 2) elif len (linjat_ korsi) == 0: # nëse asnjë rresht nuk zbulohet x_offset = 0 y_offset = int (lartësia / 2) këndi_to_mid_radian = math.atan (x_offset / y_offset) angle_to_mid_deg = int (angle_to_mid_radian * 180.0 / math.pi) steering_angle = angle_to_mid_deg + 90 kthim drejtimi

x_offset në rastin e parë është sa ndryshon mesatarja ((djathtas x2 + majtas x2) / 2) nga mesi i ekranit. y_offset merret gjithmonë si lartësi / 2. Imazhi i fundit më sipër tregon një shembull të vijës së titullit. angle_to_mid_radians është e njëjtë me "theta" të treguar në imazhin e fundit më sipër. Nëse stere_angle = 90, do të thotë që makina ka një linjë drejtimi pingul me vijën "lartësia / 2" dhe makina do të ecë përpara pa drejtuar. Nëse steering_angle> 90, makina duhet të drejtohet në të djathtë, përndryshe duhet të drejtohet majtas. Për të shfaqur vijën e titullit, përdoret funksioni i mëposhtëm:

def_faqja e kreut të ekranit (korniza, drejtuesi, këndi i ngjyrës = ngjyra (0, 0, 255), gjerësia e linjës = 5)

heading_image = np.zeros_like (kuadri) lartësia, gjerësia, _ = korniza. forma e drejtimit] kthehu heading_image

Funksioni i mësipërm merr kornizën në të cilën do të vizatohet vija e titullit dhe këndi i drejtimit si hyrje. Kthen imazhin e vijës së titullit. Korniza e vijës së titullit të marrë në rastin tim tregohet në imazhin e mësipërm.

Kombinimi i të gjithë Kodit së bashku:

Kodi tani është gati për tu montuar. Kodi i mëposhtëm tregon lakin kryesor të programit që thërret çdo funksion:

import cv2

import numpy si np video = cv2. VideoCapture (0) video.set (cv2. CAP_PROP_FRAME_WIDTH, 320) video.set (cv2. CAP_PROP_FRAME_HEIGHT, 240) ndërsa True: ret, frame = video.read () frame = cv2.flip (kuadër, -1) #Thirrja e funksioneve hsv = konverto_ në_HSV (kornizë) skajet = zbulo_shtet (hsv) roi = rajoni i_interestit (skajet) linjat_segmente = zbulojnë_segmentet (linja) rreshtat e korsisë = mesatare_presi_interceptimi (korniza, segmentet_e linjës) korsi_linjat_kornizë = get_steering_angle (frame, lane_lines) heading_image = display_heading_line (lane_lines_image, steering_angle) key = cv2.waitKey (1) nëse çelësi == 27: pusho videon.release () cv2.destroyAllWindows ()

Hapi 6: Aplikimi i Kontrollit PD

Aplikimi i Kontrollit të PD
Aplikimi i Kontrollit të PD

Tani ne kemi këndin tonë drejtues gati për t'u ushqyer me motorët. Siç u përmend më herët, nëse këndi i drejtimit është më i madh se 90, makina duhet të kthehet djathtas përndryshe duhet të kthehet majtas. Unë aplikova një kod të thjeshtë që e kthen motorin drejtues djathtas nëse këndi është mbi 90 dhe e kthen majtas nëse këndi i drejtimit është më i vogël se 90 me një shpejtësi konstante të goditjes prej (10% PWM), por mora shumë gabime. Gabimi kryesor që kam është kur makina i afrohet çdo kthese, motori drejtues vepron drejtpërdrejt, por motorët e mbytjes bllokohen. Unë u përpoqa të rris shpejtësinë e goditjes në (20% PWM) në kthesa, por përfundova me daljen e robotit nga korsitë. Më duhej diçka që rrit shpejtësinë e goditjes shumë nëse këndi i drejtimit është shumë i madh dhe rrit shpejtësinë pak nëse këndi i drejtimit nuk është aq i madh atëherë zvogëlon shpejtësinë në një vlerë fillestare ndërsa makina afrohet 90 gradë (lëviz drejt). Zgjidhja ishte përdorimi i një kontrolluesi PD.

Kontrolluesi PID qëndron për kontrollues proporcional, integral dhe derivativ. Ky lloj kontrollorësh linearë përdoret gjerësisht në aplikimet robotike. Imazhi i mësipërm tregon lakun tipik të kontrollit të reagimit PID. Qëllimi i këtij kontrolluesi është të arrijë "pikën e caktuar" me mënyrën më efikase ndryshe nga kontrolluesit "ndezur - fikur" të cilët ndezin ose fikin fabrikën sipas disa kushteve. Disa fjalë kyçe duhet të dihen:

  • Pika e përcaktimit: është vlera e dëshiruar që dëshironi të arrijë sistemi juaj.
  • Vlera aktuale: është vlera aktuale e ndjerë nga sensori.
  • Gabim: është ndryshimi midis pikës së caktuar dhe vlerës aktuale (gabimi = Pika e caktuar - Vlera aktuale).
  • Ndryshore e kontrolluar: nga emri i saj, ndryshorja që dëshironi të kontrolloni.
  • Kp: Konstanta proporcionale.
  • Ki: Konstanta integrale.
  • Kd: Konstanta derivative.

Me pak fjalë, laku i sistemit të kontrollit PID funksionon si më poshtë:

  • Përdoruesi përcakton pikën e caktuar të nevojshme që sistemi të arrijë.
  • Gabimi llogaritet (gabimi = pika e caktuar - aktuale).
  • Kontrolluesi P gjeneron një veprim proporcional me vlerën e gabimit. (gabimi rritet, veprimi P gjithashtu rritet)
  • Kontrolluesi I do të integrojë gabimin me kalimin e kohës, i cili eliminon gabimin e gjendjes së qëndrueshme të sistemit, por rrit tejkalimin e tij.
  • Kontrolluesi D është thjesht derivat i kohës për gabimin. Me fjalë të tjera, është pjerrësia e gabimit. Ai bën një veprim proporcional me derivatin e gabimit. Ky kontrollues rrit stabilitetin e sistemit.
  • Dalja e kontrolluesit do të jetë shuma e tre kontrolluesve. Dalja e kontrolluesit do të bëhet 0 nëse gabimi bëhet 0.

Një shpjegim i shkëlqyeshëm i kontrolluesit PID mund të gjendet këtu.

Duke u kthyer në makinën e mbajtjes së korsisë, ndryshorja ime e kontrolluar ishte shpejtësia e goditjes (pasi drejtimi ka vetëm dy gjendje ose të djathtë ose të majtë). Një kontrollues PD përdoret për këtë qëllim pasi veprimi D rrit shpejtësinë e goditjes shumë nëse ndryshimi i gabimit është shumë i madh (dmth devijim i madh) dhe ngadalëson makinën nëse ky ndryshim i gabimit afrohet 0. Unë bëra hapat e mëposhtëm për të zbatuar një PD kontrollues:

  • Vendosni pikën e caktuar në 90 gradë (unë gjithmonë dua që makina të lëvizë drejt)
  • Llogaritja e këndit të devijimit nga mesi
  • Devijimi jep dy informacione: Sa i madh është gabimi (madhësia e devijimit) dhe çfarë drejtimi duhet të marrë motori drejtues (shenjë devijimi). Nëse devijimi është pozitiv, makina duhet të drejtojë djathtas përndryshe duhet të drejtojë majtas.
  • Meqenëse devijimi është ose negativ ose pozitiv, një variabël "gabim" përcaktohet dhe gjithmonë i barabartë me vlerën absolute të devijimit.
  • Gabimi shumëzohet me një Kp konstante.
  • Gabimi pëson diferencim kohor dhe shumëzohet me një Kd konstante.
  • Shpejtësia e motorëve azhurnohet dhe lak fillon përsëri.

Kodi i mëposhtëm përdoret në lakin kryesor për të kontrolluar shpejtësinë e motorëve të mbytjes:

shpejtësia = 10 # shpejtësia e funksionimit në % PWM

# Variablat që do të përditësohen çdo lak lastTime = 0 lastError = 0 # Konstantet e PD Kp = 0.4 Kd = Kp * 0.65 Ndërsa e Vërtetë: tani = koha. Koha () # ndryshorja e kohës aktuale dt = tani - lastTime devijimi = drejtimi_ango - 90 # ekuivalent në angle_to_mid_deg gabim i ndryshueshëm = abs (devijim) nëse devijimi -5: # mos drejtoni nëse ka një devijim të intervalit të gabimit të 10 shkallëve = 0 gabim = 0 GPIO. dalje (in1, GPIO. LOW) GPIO.output (in2, GPIO. LOW) steering.stop () devijimi elif> 5: # drejto djathtas nëse devijimi është pozitiv GPIO.output (in1, GPIO. LOW) GPIO.output (in2, GPIO. HIGH) drejtimi.fillo (100) devijimi elif < -5: # drejto majtas nëse devijimi është negativ GPIO.putput (in1, GPIO. HIGH) GPIO.output (in2, GPIO. LOW) steering.start (100) derivativ = kd * (error - lastError) / dt proporcional = kp * gabim PD = int (shpejtësia + derivat + proporcional) spd = abs (PD) nëse spd> 25: spd = 25 mbytje.fillo (spd) lastError = gabim lastTime = kohë. kohë ()

Nëse gabimi është shumë i madh (devijimi nga mesi është i lartë), veprimet proporcionale dhe derivative janë të larta duke rezultuar në shpejtësi të lartë të goditjes. Kur gabimi i afrohet 0 (devijimi nga mesi është i ulët), veprimi derivativ vepron në mënyrë të kundërt (pjerrësia është negative) dhe shpejtësia e goditjes zvogëlohet për të ruajtur stabilitetin e sistemit. Kodi i plotë është bashkangjitur më poshtë.

Hapi 7: Rezultatet

Videot e mësipërme tregojnë rezultatet që kam marrë. Ka nevojë për më shumë rregullim dhe rregullime të mëtejshme. Po lidhesha me mjedrën pi me ekranin tim LCD sepse videoja që transmetohej në rrjetin tim kishte vonesë të madhe dhe ishte shumë zhgënjyese për të punuar, prandaj ka tela të lidhura me mjedrën pi në video. Kam përdorur pllaka shkumë për të tërhequr rrugën.

Unë jam duke pritur të dëgjoj rekomandimet tuaja për ta bërë këtë projekt më të mirë! Shpresoj që ky udhëzues ishte mjaft i mirë për t'ju dhënë disa informacione të reja.