Cuprins:

Mașină autonomă de păstrare a benzii de rulare folosind Raspberry Pi și OpenCV: 7 pași (cu imagini)
Mașină autonomă de păstrare a benzii de rulare folosind Raspberry Pi și OpenCV: 7 pași (cu imagini)

Video: Mașină autonomă de păstrare a benzii de rulare folosind Raspberry Pi și OpenCV: 7 pași (cu imagini)

Video: Mașină autonomă de păstrare a benzii de rulare folosind Raspberry Pi și OpenCV: 7 pași (cu imagini)
Video: Cum funcționează PORTBAGAJUL ROBOTIZAT al lui BMW X5 2024, Iulie
Anonim
Mașină autonomă de păstrare a benzii de circulație folosind Raspberry Pi și OpenCV
Mașină autonomă de păstrare a benzii de circulație folosind Raspberry Pi și OpenCV

În acest instructable, un robot autonom de păstrare a benzii va fi implementat și va trece prin următorii pași:

  • Adunarea pieselor
  • Instalarea premiselor software-ului
  • Asamblare hardware
  • Primul test
  • Detectarea liniilor de bandă și afișarea liniei de ghidare folosind openCV
  • Implementarea unui controler PD
  • Rezultate

Pasul 1: Strângerea componentelor

Adunarea componentelor
Adunarea componentelor
Adunarea componentelor
Adunarea componentelor
Adunarea componentelor
Adunarea componentelor
Adunarea componentelor
Adunarea componentelor

Imaginile de mai sus prezintă toate componentele utilizate în acest proiect:

  • Mașină RC: am primit-o pe a mea de la un magazin local din țara mea. Este echipat cu 3 motoare (2 pentru accelerație și 1 pentru direcție). Principalul dezavantaj al acestei mașini este că direcția este limitată între „fără direcție” și „direcție completă”. Cu alte cuvinte, nu poate conduce la un unghi specific, spre deosebire de mașinile RC cu servodirecție. De aici puteți găsi kit auto similar conceput special pentru zmeura pi.
  • Raspberry pi 3 model b +: acesta este creierul mașinii care se va ocupa de multe etape de procesare. Se bazează pe un procesor quad-core pe 64 de biți tactat la 1,4 GHz. Mi-am luat-o pe a mea de aici.
  • Modul de cameră Raspberry pi 5 mp: acceptă înregistrări 1080p @ 30 fps, 720p @ 60 fps și 640x480p 60/90. De asemenea, acceptă interfața serială care poate fi conectată direct la raspberry pi. Nu este cea mai bună opțiune pentru aplicațiile de procesare a imaginilor, dar este suficientă pentru acest proiect, deoarece este foarte ieftină. Mi-am luat-o pe a mea de aici.
  • Motor Driver: Este utilizat pentru a controla direcțiile și turațiile motoarelor de curent continuu. Suportă controlul motoarelor de 2 cc într-o singură placă și poate rezista la 1,5 A.
  • Power Bank (opțional): am folosit o bancă de putere (evaluată la 5V, 3A) pentru a alimenta zmeura pi separat. Pentru a porni zmeura pi de la o sursă, ar trebui să se utilizeze un convertor step down (convertor buck: curent de ieșire 3A).
  • Baterie LiPo de 3 s (12 V): Bateriile cu litiu polimer sunt cunoscute pentru performanțele excelente în domeniul roboticii. Este folosit pentru alimentarea șoferului motorului. Am cumpărat-o pe a mea de aici.
  • Firele jumper de la bărbat la bărbat și de la femeie la femeie.
  • Bandă dublă: Utilizată pentru montarea componentelor pe mașina RC.
  • Bandă albastră: Aceasta este o componentă foarte importantă a acestui proiect, este utilizată pentru realizarea celor două benzi pe care va circula mașina. Puteți alege orice culoare doriți, dar vă recomand să alegeți culori diferite de cele din mediul înconjurător.
  • Cravate cu fermoar și bare de lemn.
  • Șurubelniță.

Pasul 2: Instalarea OpenCV pe Raspberry Pi și configurarea afișajului la distanță

Instalarea OpenCV pe Raspberry Pi și configurarea afișajului la distanță
Instalarea OpenCV pe Raspberry Pi și configurarea afișajului la distanță

Acest pas este puțin enervant și va dura ceva timp.

OpenCV (Open source Computer Vision) este o bibliotecă de software open source de viziune computerizată și învățare automată. Biblioteca are peste 2500 de algoritmi optimizați. Urmați ACEST ghid foarte simplu pentru a instala openCV pe raspberry pi, precum și pentru a instala sistemul de operare raspberry pi (dacă tot nu ați făcut-o). Vă rugăm să rețineți că procesul de construire a openCV poate dura aproximativ 1,5 ore într-o cameră bine răcită (deoarece temperatura procesorului va crește foarte mult!), Așa că luați ceai și așteptați cu răbdare: D.

Pentru afișajul de la distanță, urmați și ACEST ghid pentru a configura accesul de la distanță la raspberry pi de pe dispozitivul dvs. Windows / Mac.

Pasul 3: Conectarea pieselor împreună

Conectarea părților împreună
Conectarea părților împreună
Conectarea părților împreună
Conectarea părților împreună
Conectarea părților împreună
Conectarea părților împreună

Imaginile de mai sus arată conexiunile dintre raspberry pi, modulul camerei și driverul motorului. Vă rugăm să rețineți că motoarele pe care le-am folosit absorb 0,35 A la 9 V fiecare, ceea ce face siguranța conducătorului motorului să ruleze 3 motoare în același timp. Și din moment ce vreau să controlez viteza celor două motoare de strangulare (1 spate și 1 față) exact în același mod, le-am conectat la același port. Am montat șoferul motorului pe partea dreaptă a mașinii folosind bandă dublă. În ceea ce privește modulul camerei, am introdus o cravată cu fermoar între orificiile șuruburilor, așa cum arată imaginea de mai sus. Apoi, potrivesc camera pe o bară de lemn, astfel încât să pot regla poziția camerei după cum vreau. Încercați să instalați camera cât mai mult posibil în mijlocul mașinii. Recomand amplasarea camerei la cel puțin 20 cm deasupra solului, astfel încât câmpul vizual din fața mașinii să se îmbunătățească. Schema Fritzing este atașată mai jos.

Pasul 4: Primul test

Primul test
Primul test
Primul test
Primul test

Testarea camerei:

Odată ce camera este instalată și biblioteca OpenCV este construită, este timpul să testăm prima noastră imagine! Vom face o fotografie din pi cam și o vom salva ca „original.jpg”. Se poate face în 2 moduri:

1. Utilizarea comenzilor terminale:

Deschideți o nouă fereastră de terminal și tastați următoarea comandă:

raspistill -o original.jpg

Aceasta va lua o imagine statică și o va salva în directorul „/pi/original.jpg”.

2. Folosind orice IDE python (eu folosesc IDLE):

Deschideți o schiță nouă și scrieți următorul cod:

import cv2

video = cv2. VideoCapture (0) în timp ce True: ret, frame = video.read () frame = cv2.flip (frame, -1) # folosit pentru a răsturna vertical imaginea cv2.imshow („original”, cadru) cv2. imwrite ('original.jpg', cadru) key = cv2.waitKey (1) if key == 27: break video.release () cv2.destroyAllWindows ()

Să vedem ce s-a întâmplat în acest cod. Prima linie este importarea bibliotecii noastre openCV pentru a-și folosi toate funcțiile. funcția VideoCapture (0) începe să transmită în flux un videoclip live de la sursa determinată de această funcție, în acest caz este 0, ceea ce înseamnă o cameră raspi. dacă aveți mai multe camere, ar trebui plasate numere diferite. video.read () va citi fiecare cadru provine de la cameră și îl va salva într-o variabilă numită „cadru”. funcția flip () va răsuci imaginea în raport cu axa y (vertical), deoarece îmi montez camera invers. imshow () va afișa cadrele noastre conduse de cuvântul „original” și imwrite () ne va salva fotografia ca original.jpg. waitKey (1) va aștepta 1 ms pentru apăsarea oricărui buton de la tastatură și returnează codul ASCII. dacă este apăsat butonul escape (esc), se returnează o valoare zecimală de 27 și va rupe bucla în consecință. video.release () va opri înregistrarea și destroyAllWindows () va închide fiecare imagine deschisă de funcția imshow ().

Vă recomandăm să vă testați fotografia cu a doua metodă pentru a vă familiariza cu funcțiile openCV. Imaginea este salvată în directorul „/pi/original.jpg”. Fotografia originală făcută de camera mea este prezentată mai sus.

Testarea motoarelor:

Acest pas este esențial pentru a determina direcția de rotație a fiecărui motor. În primul rând, să facem o scurtă introducere cu privire la principiul de funcționare al unui șofer de motor. Imaginea de mai sus arată pin-out-ul șoferului motorului. Activarea A, Intrarea 1 și Intrarea 2 sunt asociate cu controlul motorului A. Activarea B, Intrarea 3 și Intrarea 4 sunt asociate cu controlul motorului B. Controlul direcției este stabilit prin partea „Intrare”, iar controlul vitezei este stabilit prin partea „Activare”. Pentru a controla direcția motorului A, de exemplu, setați intrarea 1 la HIGH (3,3 V în acest caz, deoarece utilizăm un pi zmeură) și setați intrarea 2 la LOW, motorul se va roti într-o direcție specifică și setând valorile opuse. la intrarea 1 și intrarea 2, motorul se va roti în direcția opusă. Dacă Intrarea 1 = Intrarea 2 = (HIGH sau LOW), motorul nu se va roti. Pinii de activare iau un semnal de intrare Modularea lățimii pulsului (PWM) de la zmeură (0 la 3,3 V) și pornește motoarele în consecință. De exemplu, un semnal 100% PWM înseamnă că lucrăm la viteza maximă, iar 0% semnal PWM înseamnă că motorul nu se rotește. Următorul cod este utilizat pentru a determina direcțiile motoarelor și a testa viteza acestora.

timpul de import

importați RPi. GPIO ca GPIO GPIO.setwarnings (Fals) # Pinii motorului de direcție steering_enable = 22 # Pinul fizic 15 in1 = 17 # Pinul fizic 11 in2 = 27 # Pinul fizic 13 # Pinii motorului clapetei throttle_enable = 25 # Pinul fizic 22 in3 = 23 # Pin fizic 16 in4 = 24 # Pin fizic 18 GPIO.setmode (GPIO. BCM) # Folosiți numerotarea GPIO în loc de numerotare fizică 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. HIGH) GPIO.output (in2, GPIO. LOW) direcție = GPIO. PWM (steering_enable, 1000) # setați frecvența de comutare la 1000 Hz direcție.stop () # Throttle Motors Control GPIO.output (in3, GPIO. HIGH) GPIO.output (in4, GPIO. LOW) throttle = GPIO. PWM (throttle_enable, 1000) # setează frecvența de comutare la 1000 Hz throttle.stop () time.sleep (1) throttle.start (25) # pornește motorul la 25 % Semnal PWM-> (0,25 * tensiune baterie) - șofer loss steering.start (100) # porneste motorul la semnal 100% PWM-> (1 * Tensiunea bateriei) - timpul de pierdere al șoferului.sleep (3) throttle.stop () steering.stop ()

Acest cod va rula motoarele de control și motorul de direcție timp de 3 secunde și apoi le va opri. (Pierderea șoferului) poate fi determinată folosind un voltmetru. De exemplu, știm că un semnal 100% PWM ar trebui să dea tensiunea completă a bateriei la terminalul motorului. Dar, setând PWM la 100%, am constatat că șoferul provoacă o cădere de 3 V și motorul primește 9 V în loc de 12 V (exact ceea ce am nevoie!). Pierderea nu este liniară, adică pierderea la 100% este foarte diferită de pierderea la 25%. După ce am rulat codul de mai sus, rezultatele mele au fost următoarele:

Rezultate de strangulare: dacă in3 = HIGH și in4 = LOW, motoarele de strangulare vor avea o rotație Clock-Wise (CW), adică mașina se va deplasa înainte. În caz contrar, mașina se va deplasa înapoi.

Rezultatele direcției: dacă in1 = HIGH și in2 = LOW, motorul de direcție se va roti la stânga maximă, adică mașina va direcționa spre stânga. În caz contrar, mașina va merge spre dreapta. După câteva experimente, am constatat că motorul de direcție nu se va întoarce dacă semnalul PWM nu era 100% (adică motorul va direcționa fie complet spre dreapta, fie complet spre stânga).

Pasul 5: detectarea liniilor de bandă și calcularea liniei de direcție

Detectarea liniilor de bandă și calcularea liniei de direcție
Detectarea liniilor de bandă și calcularea liniei de direcție
Detectarea liniilor de bandă și calcularea liniei de direcție
Detectarea liniilor de bandă și calcularea liniei de direcție
Detectarea liniilor de bandă și calcularea liniei de direcție
Detectarea liniilor de bandă și calcularea liniei de direcție

În acest pas, va fi explicat algoritmul care va controla mișcarea mașinii. Prima imagine arată întregul proces. Intrarea sistemului este imagini, ieșirea este theta (unghiul de direcție în grade). Rețineți că procesarea se face pe o singură imagine și se va repeta pe toate cadrele.

Aparat foto:

Camera va începe să înregistreze un videoclip cu rezoluție (320 x 240). Vă recomandăm să reduceți rezoluția, astfel încât să puteți obține o rată mai bună a cadrelor (fps), deoarece scăderea fps va avea loc după aplicarea tehnicilor de procesare la fiecare cadru. Codul de mai jos va fi bucla principală a programului și va adăuga fiecare pas peste acest cod.

import cv2

import numpy ca np video = cv2. VideoCapture (0) video.set (cv2. CAP_PROP_FRAME_WIDTH, 320) # setați lățimea la 320 p video.set (cv2. CAP_PROP_FRAME_HEIGHT, 240) # setați înălțimea la 240 p # Bucla în timp ce Adevărat: 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 ()

Codul de aici va arăta imaginea originală obținută la pasul 4 și este prezentată în imaginile de mai sus.

Convertiți în spațiu color HSV:

Acum, după ce ați înregistrat video ca cadre de pe cameră, următorul pas este de a converti fiecare cadru în nuanță, saturație și valoare (HSV). Principalul avantaj al acestui lucru este să puteți diferenția culorile în funcție de nivelul lor de lumină. Iată o bună explicație a spațiului de culoare HSV. Conversia la HSV se face prin următoarea funcție:

def convert_to_HSV (cadru):

hsv = cv2.cvtColor (cadru, cv2. COLOR_BGR2HSV) cv2.imshow ("HSV", hsv) return hsv

Această funcție va fi apelată din bucla principală și va returna cadrul în spațiul de culoare HSV. Cadrul obținut de mine în spațiul de culoare HSV este prezentat mai sus.

Detectați culoarea albastră și muchiile:

După convertirea imaginii în spațiu color HSV, este timpul să detectăm doar culoarea care ne interesează (adică culoarea albastră, deoarece este culoarea liniilor de bandă). Pentru a extrage culoarea albastră dintr-un cadru HSV, trebuie specificată o gamă de nuanță, saturație și valoare. consultați aici pentru a avea o idee mai bună despre valorile HSV. După unele experimente, limitele superioare și inferioare ale culorii albastre sunt afișate în codul de mai jos. Și pentru a reduce distorsiunea generală a fiecărui cadru, marginile sunt detectate numai folosind un detector de margini puternic. Mai multe despre cany edge se găsesc aici. O regulă generală este de a selecta parametrii funcției Canny () cu un raport de 1: 2 sau 1: 3.

def detect_edges (cadru):

lower_blue = np.array ([90, 120, 0], dtype = "uint8") # limita inferioară de culoare albastră upper_blue = np.array ([150, 255, 255], dtype = "uint8") # limita superioară a albastru mască de culoare = cv2.inRange (hsv, lower_blue, upper_blue) # această mască va filtra totul, cu excepția albastru # detectare margini margini = cv2. Canny (mască, 50, 100) cv2.imshow ("margini", margini) margini returnate

Această funcție va fi apelată și din bucla principală care ia ca parametru cadrul spațiului de culoare HSV și returnează cadrul tăiat. Cadrul tăiat pe care l-am obținut este găsit mai sus.

Selectați regiunea de interes (ROI):

Selectarea regiunii de interes este crucială pentru a vă concentra doar pe o regiune a cadrului. În acest caz, nu vreau ca mașina să vadă o mulțime de obiecte în mediu. Vreau doar ca mașina să se concentreze pe liniile de bandă și să ignore orice altceva. P. S: sistemul de coordonate (axele x și y) începe din colțul din stânga sus. Cu alte cuvinte, punctul (0, 0) începe din colțul din stânga sus. axa y fiind înălțimea și axa x fiind lățimea. Codul de mai jos selectează regiunea de interes pentru a se concentra doar pe jumătatea inferioară a cadrului.

def regiune_de_interes (margini):

înălțime, lățime = margini. formă # extrageți înălțimea și lățimea marginii marginii mască = np.zeros_like (margini) # faceți o matrice goală cu aceleași dimensiuni ale ramei marginilor # focalizați doar jumătatea inferioară a ecranului # specificați coordonatele 4 puncte (stânga jos, stânga sus, dreapta sus, dreapta jos) poligon = np.array (

Această funcție va lua cadrul tivit ca parametru și desenează un poligon cu 4 puncte prestabilite. Se va concentra doar pe ceea ce se află în interiorul poligonului și va ignora tot ceea ce se află în afara acestuia. Cadrul meu de regiune de interes este prezentat mai sus.

Detectați segmentele de linie:

Transformarea Hough este utilizată pentru a detecta segmente de linie dintr-un cadru tăiat. Transformarea Hough este o tehnică de detectare a oricărei forme în formă matematică. Poate detecta aproape orice obiect, chiar dacă este distorsionat în funcție de un anumit număr de voturi. o mare referință pentru transformarea Hough este prezentată aici. Pentru această aplicație, funcția cv2. HoughLinesP () este utilizată pentru a detecta liniile din fiecare cadru. Parametrii importanți pe care îi ia această funcție sunt:

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

  • Cadru: este cadrul în care dorim să detectăm liniile.
  • rho: Este precizia distanței în pixeli (de obicei este = 1)
  • theta: precizie unghiulară în radiani (întotdeauna = np.pi / 180 ~ 1 grad)
  • min_threshold: vot minim pe care ar trebui să-l obțină pentru a fi considerat drept o linie
  • minLineLength: lungimea minimă a liniei în pixeli. Orice linie mai mică decât acest număr nu este considerată o linie.
  • maxLineGap: spațiu maxim în pixeli între 2 linii care trebuie tratat ca 1 linie. (Nu este folosit în cazul meu, deoarece liniile de bandă pe care le folosesc nu au niciun decalaj).

Această funcție returnează punctele finale ale unei linii. Următoarea funcție este apelată din bucla mea principală pentru a detecta liniile folosind transformarea Hough:

def detect_line_segments (cropped_edges):

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

Panta medie și interceptare (m, b):

reamintim că ecuația liniei este dată de y = mx + b. Unde m este panta liniei și b este interceptarea y. În această parte, se va calcula media pantei și a interceptărilor segmentelor de linie detectate folosind transformarea Hough. Înainte de a face acest lucru, să aruncăm o privire asupra fotografiei originale cu cadre prezentată mai sus. Banda din stânga pare să urce în sus, deci are o pantă negativă (vă amintiți punctul de pornire al sistemului de coordonate?). Cu alte cuvinte, linia de bandă stângă are x1 <x2 și y2 x1 și y2> y1 ceea ce va da o pantă pozitivă. Deci, toate liniile cu pantă pozitivă sunt considerate puncte de bandă dreaptă. În cazul liniilor verticale (x1 = x2), panta va fi infinită. În acest caz, vom sări peste toate liniile verticale pentru a preveni apariția unei erori. Pentru a adăuga mai multă precizie acestei detecții, fiecare cadru este împărțit în două regiuni (dreapta și stânga) prin 2 linii de delimitare. Toate punctele de lățime (punctele axei x) mai mari decât linia de limită dreaptă sunt asociate cu calculul benzii din dreapta. Și dacă toate punctele de lățime sunt mai mici decât linia de limită stângă, acestea sunt asociate cu calculul benzii stângi. Următoarea funcție preia cadrul în procesare și segmentele de bandă detectate folosind transformarea Hough și returnează panta medie și interceptarea a două linii de bandă.

def average_slope_intercept (frame, line_segments):

lane_lines = dacă segmentul_linie este None: print („nu s-a detectat niciun segment de linie”) returnează lane_lines înălțime, lățime, _ = frame.shape left_fit = right_fit = boundary = left_region_boundary = width * (1 - border) right_region_boundary = lățime * limită pentru segmentul_linie în segmentele_linie: pentru x1, y1, x2, y2 în segmentul_linie: dacă x1 == x2: print ("sărind liniile verticale (panta = infinit)") continuați să se potrivească = np.polyfit ((x1, x2), (y1, y2), 1) panta = (y2 - y1) / (x2 - x1) interceptare = y1 - (panta * x1) dacă panta <0: dacă x1 <stânga_regiunea_boundary și x2 dreapta_regiunea_boundary și x2> dreapta_regiunea_boundary: dreapta_fit. append ((panta, interceptare)) 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 este un tablou 2-D format din coordonatele liniilor de bandă dreapta și stânga # de exemplu: lan e_lines =

make_points () este o funcție de ajutor pentru funcția average_slope_intercept () care va returna coordonatele delimitate ale liniilor de bandă (de la partea de jos la mijlocul cadrului).

def make_points (cadru, linie):

înălțime, lățime, _ = înclinare a cadrului, interceptare = linie y1 = înălțime # partea inferioară a cadrului y2 = int (y1 / 2) # face puncte din mijlocul cadrului în jos dacă panta == 0: panta = 0,1 x1 = int ((y1 - interceptare) / pantă) x2 = int ((y2 - interceptare) / pantă) return

Pentru a preveni împărțirea la 0, este prezentată o condiție. Dacă panta = 0 ceea ce înseamnă y1 = y2 (linia orizontală), dați pantei o valoare aproape de 0. Acest lucru nu va afecta performanța algoritmului, precum și va preveni cazurile imposibile (împărțirea la 0).

Pentru a afișa liniile de bandă pe cadre, se folosește următoarea funcție:

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

imagine_linie = np.zeros_like (cadru) dacă liniile nu sunt Niciuna: pentru linie în linii: pentru x1, y1, x2, y2 în linie: cv2.line (linie_imagine, (x1, y1), (x2, y2), line_color, lățime_linie) imagine_linie = cv2.add Ponderat (cadru, 0,8, imagine_linie, 1, 1) întoarcere imagine_linie

Funcția cv2.addWeighted () ia următorii parametri și este utilizată pentru a combina două imagini, dar cu fiecare greutate.

cv2.addWeighted (imagine1, alfa, imagine2, beta, gamma)

Și calculează imaginea de ieșire folosind următoarea ecuație:

ieșire = alfa * imagine1 + beta * imagine2 + gamma

Mai multe informații despre funcția cv2.addWeighted () sunt derivate aici.

Calculați și afișați linia de titlu:

Acesta este ultimul pas înainte de a aplica viteze motoarelor noastre. Linia de direcție este responsabilă pentru a da motorului de direcție direcția în care ar trebui să se rotească și să dea motoarelor de accelerație viteza cu care vor funcționa. Calculul liniei de direcție este trigonometrie pură, se utilizează funcții trigonometrice tan și atan (tan ^ -1). Unele cazuri extreme sunt atunci când camera detectează o singură linie de bandă sau când nu detectează nicio linie. Toate aceste cazuri sunt prezentate în următoarea funcție:

def get_steering_angle (frame, lane_lines):

înălțime, lățime, _ = frame.shape if len (lane_lines) == 2: # dacă sunt detectate două linii de bandă _, _, left_x2, _ = lane_lines [0] [0] # extrage stânga x2 din banda_lines line _, _, 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 (height / 2) elif len (lane_lines) == 1: # dacă se detectează o singură linie x1, _, x2, _ = lane_lines [0] [0] x_offset = x2 - x1 y_offset = int (height / 2) elif len (lane_lines) == 0: # dacă nu este detectată nicio linie x_offset = 0 y_offset = int (înălțime / 2) angle_to_mid_radian = math.atan (x_offset / y_offset) angle_to_mid_deg = int (angle_to_mid_radian * 180.0 / math.pi) direction_angle = angle_to_mid_deg + 90 return direction_angle

x_offset în primul caz este cât de mult diferă media ((dreapta x2 + stânga x2) / 2) de mijlocul ecranului. y_offset este întotdeauna considerat înălțime / 2. Ultima imagine de mai sus prezintă un exemplu de linie de direcție. angle_to_mid_radians este același cu "theta" afișat în ultima imagine de mai sus. Dacă direcția_angle = 90, înseamnă că mașina are o linie de direcție perpendiculară pe linia „înălțime / 2” și mașina se va deplasa înainte fără direcție. Dacă direcția_angle> 90, mașina ar trebui să vireze spre dreapta, altfel ar trebui să vireze spre stânga. Pentru a afișa linia de titlu, se utilizează următoarea funcție:

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

head_image = np.zeros_like (frame) înălțime, lățime, _ = frame.shape steering_angle_radian = direction_angle / 180.0 * math.pi x1 = int (width / 2) y1 = height x2 = int (x1 - height / 2 / math.tan (direcție_angle_radian)) y2 = int (înălțime / 2) cv2.line (head_image, (x1, y1), (x2, y2), line_color, line_width) heading_image = cv2.addWeighted (cadru, 0.8, head_image, 1, 1) returnează imaginea_titlu

Funcția de mai sus ia cadrul în care va fi trasată linia de direcție și unghiul de direcție ca intrare. Returnează imaginea liniei de titlu. Rama liniei de titlu luată în cazul meu este afișată în imaginea de mai sus.

Combinând toate codurile împreună:

Codul este acum gata de asamblare. Următorul cod arată bucla principală a programului care apelează fiecare funcție:

import cv2

import numpy ca np video = cv2. VideoCapture (0) video.set (cv2. CAP_PROP_FRAME_WIDTH, 320) video.set (cv2. CAP_PROP_FRAME_HEIGHT, 240) în timp ce True: ret, frame = video.read () frame = cv2.flip (cadru, -1) #Calling the functions hsv = convert_to_HSV (frame) edge = detect_edges (hsv) roi = region_of_interest (margini) line_segments = detect_line_segments (roi) lane_lines = average_slope_intercept (frame, line_segments) lane_lines_image = display_lines (frame, lane_lines) = get_steering_angle (frame, lane_lines) heading_image = display_heading_line (lane_lines_image, direction_angle) key = cv2.waitKey (1) if key == 27: break video.release () cv2.destroyAllWindows ()

Pasul 6: Aplicarea controlului PD

Aplicarea controlului PD
Aplicarea controlului PD

Acum avem unghiul de direcție gata pentru a fi alimentat la motoare. După cum sa menționat mai devreme, dacă unghiul de virare este mai mare de 90, mașina ar trebui să vireze la dreapta, altfel ar trebui să vireze la stânga. Am aplicat un cod simplu care rotește motorul de direcție la dreapta dacă unghiul este peste 90 și îl rotește la stânga dacă unghiul de direcție este mai mic de 90 la o viteză constantă de strangulare de (10% PWM), dar am primit o mulțime de erori. Principala eroare pe care am primit-o este când mașina se apropie de orice viraj, motorul de direcție acționează direct, dar motoarele de accelerație se blochează. Am încercat să măresc viteza de strangulare pentru a fi (20% PWM) la viraje, dar am încheiat cu robotul ieșind din benzi. Am avut nevoie de ceva care mărește foarte mult viteza de strangulare dacă unghiul de virare este foarte mare și crește puțin viteza dacă unghiul de virare nu este atât de mare, apoi scade viteza la o valoare inițială, deoarece mașina se apropie de 90 de grade (deplasându-se drept). Soluția a fost utilizarea unui controler PD.

Controlerul PID înseamnă controler proporțional, integral și derivat. Acest tip de controlere liniare este utilizat pe scară largă în aplicații de robotică. Imaginea de mai sus arată bucla tipică de control al feedback-ului PID. Scopul acestui controler este de a atinge „setpoint-ul” cu cel mai eficient mod, spre deosebire de controlerele „on-off” care pornesc sau opresc instalația în funcție de anumite condiții. Unele cuvinte cheie ar trebui cunoscute:

  • Setpoint: este valoarea dorită pe care doriți să o atingă sistemul dvs.
  • Valoare reală: este valoarea reală detectată de senzor.
  • Eroare: este diferența dintre valoarea de referință și valoarea reală (eroare = Valoarea de referință - Valoarea reală).
  • Variabilă controlată: din numele său, variabila pe care doriți să o controlați.
  • Kp: Constanta proporțională.
  • Ki: Constanta integrala.
  • Kd: Constanta derivată.

Pe scurt, bucla sistemului de control PID funcționează după cum urmează:

  • Utilizatorul definește punctul de referință necesar pentru a ajunge la sistem.
  • Eroarea este calculată (eroare = setpoint - real).
  • Controlerul P generează o acțiune proporțională cu valoarea erorii. (eroarea crește, crește și acțiunea P)
  • Controlerul I va integra eroarea în timp, ceea ce elimină eroarea de stare staționară a sistemului, dar crește depășirea acestuia.
  • Controlerul D este pur și simplu derivatul timpului pentru eroare. Cu alte cuvinte, este panta erorii. Face o acțiune proporțională cu derivata erorii. Acest controler mărește stabilitatea sistemului.
  • Ieșirea controlerului va fi suma celor trei controlere. Ieșirea controlerului va deveni 0 dacă eroarea devine 0.

O explicație excelentă a controlerului PID poate fi găsită aici.

Revenind la mașina de păstrare a benzii, variabila mea controlată a fost viteza de strangulare (deoarece direcția are doar două stări, fie la dreapta, fie la stânga). Un controler PD este utilizat în acest scop, deoarece acțiunea D crește foarte mult viteza de strangulare dacă schimbarea erorii este foarte mare (adică deviație mare) și încetinește mașina dacă această modificare de eroare se apropie de 0. Am făcut următorii pași pentru a implementa un PD controlor:

  • Setați valoarea de referință la 90 de grade (vreau întotdeauna ca mașina să se miște drept)
  • Calculat unghiul de deviere de la mijloc
  • Abaterea oferă două informații: cât de mare este eroarea (magnitudinea abaterii) și ce direcție trebuie să ia motorul de direcție (semn al abaterii). Dacă abaterea este pozitivă, mașina ar trebui să vireze la dreapta, altfel ar trebui să vireze la stânga.
  • Deoarece deviația este fie negativă, fie pozitivă, este definită o variabilă "eroare" și întotdeauna egală cu valoarea absolută a abaterii.
  • Eroarea este înmulțită cu o Kp constantă.
  • Eroarea suferă diferențierea timpului și este înmulțită cu o constantă Kd.
  • Viteza motoarelor este actualizată și bucla începe din nou.

Următorul cod este utilizat în bucla principală pentru a controla viteza motoarelor de strangulare:

viteza = 10 # viteza de funcționare în% PWM

#Variabile care urmează să fie actualizate fiecare buclă lastTime = 0 lastError = 0 # constante PD Kp = 0,4 Kd = Kp * 0,65 În timp ce este adevărat: acum = time.time () # variabilă de timp curent dt = now - lastTime deviation = direction_angle - 90 # equivalent la angle_to_mid_deg variabilă eroare = abs (abatere) dacă deviație -5: # nu direcționați dacă există o abatere a intervalului de eroare de 10 grade = 0 eroare = 0 GPIO.output (in1, GPIO. LOW) GPIO.output (in2, GPIO. LOW) steering.stop () elif deviation> 5: # steer right if deviation is positive GPIO.output (in1, GPIO. LOW) GPIO.output (in2, GPIO. HIGH) steering.start (100) elif deviation < -5: # virează la stânga dacă deviația este negativă GPIO.output (in1, GPIO. HIGH) GPIO.output (in2, GPIO. LOW) steering.start (100) derivative = kd * (error - lastError) / dt proportional = kp * eroare PD = int (viteză + derivată + proporțională) spd = abs (PD) dacă spd> 25: spd = 25 throttle.start (spd) lastError = eroare lastTime = time.time ()

Dacă eroarea este foarte mare (abaterea de la mijloc este mare), acțiunile proporționale și derivate sunt mari, rezultând o viteză mare de strangulare. Când eroarea se apropie de 0 (abaterea de la mijloc este mică), acțiunea derivată acționează invers (panta este negativă) și viteza de strangulare devine scăzută pentru a menține stabilitatea sistemului. Codul complet este atașat mai jos.

Pasul 7: Rezultate

Videoclipurile de mai sus arată rezultatele obținute de mine. Are nevoie de mai multe reglaje și ajustări suplimentare. Conectam raspberry pi la ecranul meu LCD, deoarece fluxul video prin rețeaua mea avea o latență ridicată și era foarte frustrant să lucrez cu acesta, de aceea există fire conectate la raspberry pi în videoclip. Am folosit plăci de spumă pentru a desena pista.

Aștept să aud recomandările dvs. pentru a îmbunătăți acest proiect! Deoarece sper că acest instructable a fost suficient de bun pentru a vă oferi informații noi.

Recomandat: