Tartalomjegyzék:

Autonóm sávtartó autó a Raspberry Pi és az OpenCV használatával: 7 lépés (képekkel)
Autonóm sávtartó autó a Raspberry Pi és az OpenCV használatával: 7 lépés (képekkel)

Videó: Autonóm sávtartó autó a Raspberry Pi és az OpenCV használatával: 7 lépés (képekkel)

Videó: Autonóm sávtartó autó a Raspberry Pi és az OpenCV használatával: 7 lépés (képekkel)
Videó: Lesson 28: Car-6 SunFounder self Driving Arduino car using | Robojax 2024, Július
Anonim
Autonóm sávtartó autó a Raspberry Pi és az OpenCV használatával
Autonóm sávtartó autó a Raspberry Pi és az OpenCV használatával

Ebben az utasításban egy autonóm sávtartó robot kerül bevezetésre, amely a következő lépéseken megy keresztül:

  • Alkatrészek összegyűjtése
  • A szoftver telepítésének előfeltételei
  • Hardver összeszerelés
  • Első teszt
  • Sávvonalak észlelése és a vezetővonal megjelenítése az openCV használatával
  • PD vezérlő megvalósítása
  • Eredmények

1. lépés: Összetevők összegyűjtése

Összetevők összegyűjtése
Összetevők összegyűjtése
Összetevők összegyűjtése
Összetevők összegyűjtése
Összetevők összegyűjtése
Összetevők összegyűjtése
Összetevők összegyűjtése
Összetevők összegyűjtése

A fenti képek a projektben használt összes összetevőt mutatják:

  • RC autó: Az enyémet hazám helyi boltjából szereztem be. 3 motorral van felszerelve (2 a fojtószelephez és 1 a kormányzáshoz). Ennek az autónak a fő hátránya, hogy a kormányzás korlátozott a "nincs kormányzás" és a "teljes kormányzás" között. Más szavakkal, nem tud meghatározott szögben kormányozni, ellentétben a szervokormányos RC autóval. Itt találhat hasonló autós készletet, amelyet kifejezetten a málna pi -hez terveztek.
  • Raspberry pi 3 b+modell: ez az autó agya, amely sok feldolgozási lépést kezel. Ez egy négymagos 64 bites processzoron alapul, 1,4 GHz-en. Innen kaptam az enyémet.
  • Raspberry pi 5 mp kamera modul: Támogatja az 1080p @ 30 fps, 720p @ 60 fps és 640x480p 60/90 felvételt. Támogatja a soros interfészt is, amely közvetlenül a málna pi -hez csatlakoztatható. Ez nem a legjobb megoldás képfeldolgozó alkalmazásokhoz, de elegendő ehhez a projekthez, valamint nagyon olcsó. Innen kaptam az enyémet.
  • Motorhajtó: Az egyenáramú motorok irányának és sebességének szabályozására szolgál. Támogatja a 2 egyenáramú motor vezérlését 1 kártyán, és ellenáll 1,5 A -nak.
  • Tápegység (opcionális): A málna pi külön tápellátásához tápegységet használtam (5V, 3A). A málna pi 1 forrásból történő áramellátásához lépcsőzetes átalakítót (bak konverter: 3A kimeneti áram) kell használni.
  • 3s (12 V) LiPo akkumulátor: A lítium -polimer akkumulátorok kiváló teljesítményükről ismertek a robotika területén. A motor meghajtására szolgál. Az enyémet innen vettem.
  • Férfi -férfi és női -női jumper vezetékek.
  • Kétoldalas szalag: az alkatrészek RC autóra szerelésére szolgál.
  • Kék szalag: Ez egy nagyon fontos eleme ennek a projektnek, abból a két sávból készülnek, amelyek között az autó haladni fog. Bármilyen színt választhat, de azt javaslom, hogy válasszon más színeket, mint a környezetben.
  • Cipzárak és fa rudak.
  • Csavarhúzó.

2. lépés: Az OpenCV telepítése a Raspberry Pi -re és a távoli kijelző beállítása

Az OpenCV telepítése a Raspberry Pi -re és a távoli kijelző beállítása
Az OpenCV telepítése a Raspberry Pi -re és a távoli kijelző beállítása

Ez a lépés kissé idegesítő, és eltart egy ideig.

Az OpenCV (Open source Computer Vision) egy nyílt forráskódú számítógépes látás- és gépi tanulási szoftverkönyvtár. A könyvtár több mint 2500 optimalizált algoritmust tartalmaz. Kövesse EZT a nagyon egyszerű útmutatót az openCV telepítéséhez a málna pi -re, valamint a raspberry pi operációs rendszer telepítéséhez (ha még nem tette meg). Kérjük, vegye figyelembe, hogy az openCV felépítése körülbelül 1,5 órát vehet igénybe egy jól hűtött helyiségben (mivel a processzor hőmérséklete nagyon magas lesz!), Ezért igyon egy teát és várjon türelmesen: D.

A távoli megjelenítéshez kövesse ezt az útmutatót is, hogy beállítsa a Windows/Mac -eszközéről a raspberry pi távoli elérését.

3. lépés: Az alkatrészek összekapcsolása

Az alkatrészek összekapcsolása
Az alkatrészek összekapcsolása
Az alkatrészek összekapcsolása
Az alkatrészek összekapcsolása
Az alkatrészek összekapcsolása
Az alkatrészek összekapcsolása

A fenti képek a Raspberry pi, a kameramodul és a motorvezérlő közötti kapcsolatokat mutatják be. Kérjük, vegye figyelembe, hogy az általam használt motorok mindegyike 0,35 A -t vesz fel 9 V -on, ami biztonságossá teszi a motorvezető számára, hogy egyszerre 3 motort futtasson. És mivel a két fojtómotor sebességét (1 hátul és 1 elöl) pontosan ugyanúgy akarom szabályozni, ezért ugyanahhoz a porthoz kötöttem őket. A motor meghajtót az autó jobb oldalára szereltem dupla szalaggal. Ami a kamera modult illeti, behelyeztem egy cipzárat a csavarlyukak közé, ahogy a fenti kép mutatja. Ezután illesszem a fényképezőgépet egy fatörzsre, hogy tetszés szerint beállíthassam a kamera helyzetét. Próbálja a kamerát az autó közepére telepíteni, amennyire csak lehetséges. Javaslom, hogy a fényképezőgépet legalább 20 cm -rel helyezze el a talaj felett, hogy az autó előtti látómező jobb legyen. A Fritzing vázlata az alábbiakban található.

4. lépés: Első teszt

Első teszt
Első teszt
Első teszt
Első teszt

Kamera tesztelés:

Miután telepítette a kamerát és felépítette az openCV könyvtárat, itt az ideje, hogy teszteljük első képünket! Fényképet készítünk a pi cam -ról, és elmentjük "original.jpg" néven. Ezt kétféleképpen lehet megtenni:

1. A terminálparancsok használata:

Nyisson meg egy új terminál ablakot, és írja be a következő parancsot:

raspistill -o original.jpg

Ez állóképet készít, és a "/pi/original.jpg" könyvtárba menti.

2. Bármilyen python IDE használatával (IDLE -t használok):

Nyisson meg egy új vázlatot, és írja be a következő kódot:

import cv2

video = cv2. VideoCapture (0), míg True: ret, frame = video.read () frame = cv2.flip (frame, -1) # a kép függőleges elforgatására szolgál cv2.imshow ('eredeti', keret) cv2. imwrite ('original.jpg', frame) key = cv2.waitKey (1) if key == 27: break video.release () cv2.destroyAllWindows ()

Lássuk, mi történt ebben a kódban. Az első sor az openCV könyvtár importálása, hogy minden funkcióját használhassa. a VideoCapture (0) függvény élő videó közvetítését kezdi el a funkció által meghatározott forrásból, ebben az esetben 0, ami raspi kamerát jelent. ha több kamerája van, akkor különböző számokat kell elhelyezni. A video.read () olvassa, hogy minden képkocka a kamerából származik, és elmenti a "frame" nevű változóba. A flip () függvény elfordítja a képet az y tengelyhez képest (függőlegesen), mivel fordítva szerelem fel a kamerát. Az imshow () megjeleníti a kereteinket az "eredeti" szóval, az imwrite () pedig a fényképünket eredeti-j.webp

Javaslom, hogy tesztelje fotóját a második módszerrel, hogy megismerkedjen az openCV funkcióival. A kép az "/pi/original.jpg" könyvtárba kerül mentésre. A fényképezőgépem által készített eredeti fénykép fent látható.

Motorok tesztelése:

Ez a lépés elengedhetetlen az egyes motorok forgásirányának meghatározásához. Először nézzük meg röviden a motorvezető működési elvét. A fenti képen látható a motor meghajtójának kivezetése. Az A engedélyezése, az 1. és a 2. bemenet az A motor vezérléséhez kapcsolódik. A B engedélyezése, a 3. és 4. bemenet a B motor vezérléséhez kapcsolódik. Az irányítást az "Input" rész, a sebességszabályozást az "Enable" rész határozza meg. Például az A motor irányításának szabályozásához állítsa az 1. bemenetet HIGH (ebben az esetben 3,3 V -ra, mivel málna pi -t használunk), és állítsa a 2. bemenetet LOW -ra, a motor meghatározott irányba forog, és az ellenkező értékeket állítja be az 1. és 2. bemenetre a motor az ellenkező irányba forog. Ha 1. bemenet = 2. bemenet = (HIGH vagy LOW), a motor nem forog. Az engedélyező csapok impulzusszélesség -modulációs (PWM) bemeneti jelet vesznek a málnából (0–3,3 V), és ennek megfelelően működtetik a motorokat. Például a 100% PWM jel azt jelenti, hogy a maximális fordulatszámon dolgozunk, a 0% PWM jel pedig azt, hogy a motor nem forog. A következő kódot használjuk a motorok irányának meghatározására és sebességük tesztelésére.

importálási idő

importálja az RPi. GPIO fájlt GPIO GPIO.setwarnings (hamis) 23 # Fizikai pin 16 in4 = 24 # Physical Pin 18 GPIO.setmode (GPIO. BCM) # GPIO számozás használata fizikai számozás helyett GPIO.setup (in1, GPIO.out) GPIO.setup (in2, GPIO.out) GPIO. beállítás (in3, GPIO.out) GPIO.setup (in4, GPIO.out) GPIO.setup (throttle_enable, GPIO.out) GPIO.setup (kormány_enable, GPIO.out) # Steering Motor Control GPIO.output (in1, GPIO). HIGH) GPIO.output (in2, GPIO. LOW) kormányzás = GPIO. PWM (kormányzás engedélyezhető, 1000) # állítsa be a kapcsolási frekvenciát 1000 Hz -es kormányzásra. Stop () # Throttle Motors Control GPIO.output (in3, GPIO. HIGH) GPIO.output (in4, GPIO. LOW) fojtószelep = GPIO. PWM (throttle_enable, 1000) # állítsa a kapcsolási frekvenciát 1000 Hz -es fojtószelepre. stop () time.sleep (1) fojtószelep. start (25) # 25 -nél indítja el a motort % PWM jel-> (0,25 * akkumulátor feszültség) - vezetői veszteségkormányzás.indítás (100) # elindítja a motort 100% -os PWM jelnél-> (1 * akkumulátorfeszültség) - a vezető veszteségi ideje. alvó (3) fojtószelep.leáll () kormányzás

Ez a kód 3 másodpercig működteti a fojtó- és kormánymotorokat, majd leállítja őket. A (vezető vesztesége) voltmérővel határozható meg. Például tudjuk, hogy a 100% -os PWM jelnek meg kell adnia a teljes akkumulátor feszültségét a motor terminálján. De a PWM 100%-ra állításával azt tapasztaltam, hogy a meghajtó 3 V -os csökkenést okoz, és a motor 12 V helyett 9 V -ot kap (pontosan erre van szükségem!). A veszteség nem lineáris, azaz a 100% -os veszteség nagyon különbözik a 25% -os veszteségtől. A fenti kód futtatása után az eredményeim a következők voltak:

Fojtási eredmények: ha in3 = HIGH és in4 = LOW, akkor a fojtószelep-motorok óramutató (CW) forgatással rendelkeznek, azaz az autó előre fog haladni. Ellenkező esetben az autó hátrafelé mozog.

Kormányzási eredmények: ha in1 = HIGH és in2 = LOW, a kormánymotor a maximális bal oldalán forog, azaz az autó balra fog kormányozni. Ellenkező esetben az autó jobbra fog kormányozni. Néhány kísérlet után megállapítottam, hogy a kormánymotor nem forog, ha a PWM jel nem 100% (azaz a motor vagy teljesen jobbra, vagy teljesen balra fog kormányozni).

5. lépés: Sávvonalak észlelése és az irányvonal kiszámítása

Sávvonalak észlelése és irányvonal kiszámítása
Sávvonalak észlelése és irányvonal kiszámítása
Sávvonalak észlelése és irányvonal kiszámítása
Sávvonalak észlelése és irányvonal kiszámítása
Sávvonalak észlelése és irányvonal kiszámítása
Sávvonalak észlelése és irányvonal kiszámítása

Ebben a lépésben ismertetjük az autó mozgását vezérlő algoritmust. Az első kép az egész folyamatot mutatja. A rendszer bemenete képek, a kimenet théta (kormányzási szög fokban). Ne feledje, hogy a feldolgozás 1 képen történik, és minden képkockán megismétlődik.

Kamera:

A fényképezőgép megkezdi a (320 x 240) felbontású videó rögzítését. Azt javaslom, hogy csökkentse a felbontást, hogy jobb képkockasebességet (fps) kapjon, mivel az képkocka / másodperc csökkenés az egyes képkockákra vonatkozó feldolgozási technikák alkalmazása után következik be. Az alábbi kód lesz a program fő ciklusa, és minden lépéshez hozzáadja ezt a kódot.

import cv2

importálja a számjegyeket np video = cv2. VideoCapture (0) video.set (cv2. CAP_PROP_FRAME_WIDTH, 320) # állítsa be a szélességet 320 p videó.set (cv2. CAP_PROP_FRAME_HEIGHT, 240) # állítsa be a magasságot 240 p # A ciklus közben Igaz: 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 ()

Az itt található kód a 4. lépésben kapott eredeti képet mutatja, és a fenti képeken látható.

Konvertálás HSV színtérré:

Most, miután a videofelvételt képkockának vette a fényképezőgépről, a következő lépés az egyes képkockák színárnyalat, telítettség és érték (HSV) színtérré alakítása. Ennek fő előnye, hogy meg tudja különböztetni a színeket fényerősségük alapján. És itt van egy jó magyarázat a HSV színtérre. A HSV -re történő konvertálás a következő funkción keresztül történik:

def convert_to_HSV (keret):

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

Ezt a funkciót a fő hurokból hívják meg, és visszaadja a keretet a HSV színtérben. Az általam HSV színtérben kapott keret a fent látható.

Kék szín és élek észlelése:

Miután a képet HSV színtérré alakította, itt az ideje, hogy csak az általunk érdekelt színt észlelje (azaz a kék színt, mivel ez a sávvonalak színe). A HSV keretből a kék szín kinyeréséhez meg kell adni egy színárnyalatot, telítettséget és értéket. ide kattintva jobb képet kaphat a HSV -értékekről. Néhány kísérlet után a kék szín felső és alsó határa látható az alábbi kódban. És az egyes képkockák általános torzításának csökkentése érdekében az éleket csak az éles érzékelővel érzékeli. További információ a canny edge -ről itt található. Alapszabály, hogy a Canny () függvény paramétereit 1: 2 vagy 1: 3 arányban kell kiválasztani.

def detect_edges (keret):

alsó_kék = np.tömb ([90, 120, 0], dtype = "uint8") # kék szín alsó határa felső_kék = np.tömb ([150, 255, 255], dtype = "uint8") # felső határa kék színű maszk = cv2.inRange (hsv, alsó_kék, felső_kék) # ez a maszk kiszűr mindent, kivéve a kék # észleli az éleket élek = cv2. Canny (maszk, 50, 100) cv2.imshow ("élek", élek) visszatérő élek

Ezt a funkciót a fő hurokból is meghívják, amely paraméterként a HSV színtér keretet veszi fel, és visszaadja a szélezett keretet. A szélezett keret, amit kaptam, fent található.

Érdekes régió (ROI) kiválasztása:

Az érdeklődési körzet kiválasztása kulcsfontosságú ahhoz, hogy csak a keret 1 régiójára összpontosítson. Ebben az esetben nem szeretném, ha az autó sok elemet látna a környezetben. Csak azt akarom, hogy az autó a sávvonalakra összpontosítson, és figyelmen kívül hagyjon bármi mást. P. S.: a koordinátarendszer (x és y tengely) a bal felső sarokból indul. Más szóval, a pont (0, 0) a bal felső sarokból indul. y tengely magassága és x tengely szélessége. Az alábbi kód az érdeklődésre számot tartó régiót választja ki, hogy csak a keret alsó felére fókuszáljon.

def region_of_interest (élek):

magasság, szélesség = élek.shape # kivonja az élek magasságát és szélességét keretmaszk = np.zeros_like (élek) # készítsen üres mátrixot az élek keretének azonos méreteivel # csak a képernyő alsó felére fókuszáljon # adja meg a koordinátákat 4 pont (bal alsó, bal felső, jobb felső, jobb alsó) sokszög = np. Tömb (

Ez a funkció az élű keretet veszi paraméterként, és rajzol egy sokszöget 4 előre beállított ponttal. Csak arra fog összpontosítani, ami a sokszög belsejében van, és figyelmen kívül hagy mindent, ami rajta kívül van. Az érdeklődési régióm kerete fent látható.

Vonalszegmensek észlelése:

A Hough transzformációt a vonalszakaszok észlelésére használják egy élű keretből. A Hough transzformáció olyan technika, amely bármilyen alak matematikai formában történő kimutatására szolgál. Szinte minden objektumot képes észlelni, még akkor is, ha bizonyos számú szavazat szerint eltorzult. A Hough -transzformációra vonatkozó nagy hivatkozás itt látható. Ehhez az alkalmazáshoz a cv2. HoughLinesP () függvény a vonalak észlelésére szolgál minden keretben. A funkció fontos paraméterei a következők:

cv2. HoughLinesP (frame, rho, théta, min_threshold, minLineLength, maxLineGap)

  • Keret: az a keret, amelyben vonalakat szeretnénk észlelni.
  • rho: Ez a távolság pontossága pixelben (általában = 1)
  • théta: szögpontosság radiánban (mindig = np.pi/180 ~ 1 fok)
  • min_threshold: minimális szavazat, amelyet meg kell kapnia ahhoz, hogy vonalnak tekintse
  • minLineLength: a sor minimális hossza pixelben. Az ennél rövidebb sor nem tekinthető vonalnak.
  • maxLineGap: 2 sor közötti maximális pixelköz, amelyet 1 sorként kell kezelni. (Az én esetemben nem használják, mivel az általam használt sávvonalakon nincs rés).

Ez a függvény egy sor végpontjait adja vissza. A következő függvényt hívom meg a fő hurokból a vonalak észleléséhez a Hough transzformáció használatával:

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 line_segments

Átlagos meredekség és metszés (m, b):

emlékezzen arra, hogy az egyenlet egyenletét y = mx + b adja. Ahol m az egyenes meredeksége és b az y-metszéspont. Ebben a részben kiszámítják a Hough transzformációval észlelt vonalszakaszok lejtéseinek és metszéseinek átlagát. Mielőtt ezt megtennénk, nézzük meg a fent látható eredeti keretfotót. Úgy tűnik, hogy a bal oldali sáv felfelé halad, így negatív lejtése van (emlékszik a koordináta -rendszer kezdőpontjára?). Más szóval, a bal oldali sáv x1 <x2 és y2 x1 és y2> y1 pozitív lejtést ad. Tehát minden pozitív lejtésű vonal jobb oldali sávpontnak minősül. Függőleges vonalak (x1 = x2) esetén a meredekség végtelen. Ebben az esetben kihagyjuk az összes függőleges vonalat, hogy elkerüljük a hibát. Annak érdekében, hogy nagyobb pontosságot nyújtson az észleléshez, minden keret két határvonalon keresztül két régióra (jobbra és balra) van felosztva. Minden szélességi pont (x tengely pont) nagyobb, mint a jobb oldali határvonal, a jobb oldali sáv kiszámításához kapcsolódik. És ha minden szélességi pont kisebb, mint a bal oldali határvonal, akkor a bal sáv kiszámításához kapcsolódnak. A következő függvény feldolgozza a keretet és a Hough transzformációval észlelt sávszegmenseket, és visszaadja két sávvonal átlagos meredekségét és metszését.

def átlagos_slope_intercept (frame, line_segments):

lane_lines = ha a line_segments értéke nincs: print ("nincs vonalszakasz észlelve") return lane_lines magasság, szélesség, _ = frame.shape left_fit = right_fit = border = left_region_boundary = width * (1 - border) right_region_boundary = szélesség * határvonal a sor_szegmenséhez a vonalszegmensekben: x1, y1, x2, y2 esetén a vonalszakaszban: ha x1 == x2: nyomtatás ("függőleges vonalak kihagyása (lejtés = végtelen)") folytatás illeszkedés = np.polyfit ((x1, x2), (y1, y2), 1) lejtés = (y2 - y1) / (x2 - x1) metszés = y1 - (lejtő * x1) ha lejtés <0: ha x1 <bal_régió_határ és x2 jobb_régióhatár és x2> jobb_régióhatár: jobb_illesztés. hozzáfűzni ((lejtés, elfogás)) 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)) e_vonalak =

A make_points () az átlagos_slope_intercept () függvény segítő függvénye, amely visszaadja a sávvonalak korlátos koordinátáit (alulról a keret közepéig).

def make_points (keret, vonal):

magasság, szélesség, _ = keret. alakú lejtés, metszés = y1 vonal = magasság # a keret alja int ((y1 - elfog) / lejtő) x2 = int ((y2 - elfog) / lejtő) return

A 0 -val való osztás megakadályozása érdekében feltétel kerül bemutatásra. Ha a meredekség = 0, ami azt jelenti, hogy y1 = y2 (vízszintes vonal), adjon a meredekséghez 0 közeli értéket. Ez nem befolyásolja az algoritmus teljesítményét, és megakadályozza a lehetetlen eseteket (osztás 0 -val).

A sávvonalak megjelenítéséhez a kereteken a következő funkciót kell használni:

def display_lines (frame, lines, line_color = (0, 255, 0), line_width = 6): # vonalszín (B, G, R)

line_image = np.zeros_like (frame), ha a vonalak nincsenek line_width) line_image = cv2.addWeighted (frame, 0,8, line_image, 1, 1) return line_image

A cv2.addWeighted () függvény a következő paramétereket veszi fel, és két kép kombinálására szolgál, de mindegyiknek súlyt ad.

cv2.add Súlyos (kép1, alfa, kép2, béta, gamma)

És kiszámítja a kimeneti képet a következő egyenlet segítségével:

output = alfa * kép1 + béta * kép2 + gamma

További információ a cv2.addWeighted () függvényről itt található.

A címsor kiszámítása és megjelenítése:

Ez az utolsó lépés, mielőtt motorjainkra fordulatszámot alkalmazunk. Az irányvonal felelős a kormánymotor forgásirányának megadásáért és a fojtószelep -motorok működési sebességéért. Az irányvonal kiszámítása tiszta trigonometria, tan és atan (tan^-1) trigonometriai függvényeket használnak. Néhány szélsőséges eset az, amikor a kamera csak egy sávos vonalat érzékel, vagy amikor egyetlen vonalat sem. Mindezek az esetek a következő függvényben láthatók:

def get_steering_angle (keret, sáv_vonalak):

magasság, szélesség, _ = frame.shape if len (lane_lines) == 2: # ha két sávvonalat észlel _, _, left_x2, _ = lane_lines [0] [0] # kivonat bal x2 a lane_lines tömbből _, _, right_x2, _ = lane_lines [1] [0] # jobb oldali x2 kivonat a lane_lines tömbből mid = int (width / 2) x_offset = (left_x2 + right_x2) / 2 - y_offset = int (height / 2) elif len (lane_lines) == 1: # ha csak egy sort észlel x1, _, x2, _ = sáv_vonalak [0] [0] x_offset = x2 - x1 y_offset = int (magasság / 2) elif len (lane_lines) == 0: # ha vonal nem észlelhető

Az x_eltolás az első esetben az, hogy az átlag ((jobb x2 + bal x2) / 2) mennyiben különbözik a képernyő közepétől. y_eltolás mindig magasság / 2. A fenti utolsó képen egy példa látható a címsorra. A szög_to_mid_radiánok értéke megegyezik a fenti utolsó képen látható "thétával". Ha a kormányszög = 90, az azt jelenti, hogy az autónak van egy irányvonala, amely merőleges a "magasság / 2" vonalra, és az autó kormányzás nélkül halad előre. Ha a kormányszög> 90, az autónak jobbra kell kormányoznia, különben balra. A címsor megjelenítéséhez a következő funkciót kell használni:

def display_heading_line (keret, kormányszög, line_color = (0, 0, 255), line_width = 5)

head_image = np.zeros_like (frame) magasság, szélesség, _ = frame.shape kormányzási_szög_radian = kormányzási szög / 180,0 * math.pi x1 = int (szélesség / 2) y1 = magasság x2 = int (x1 - magasság / 2 / math.tan [kormányzási_szög_radián]) y2 = int (magasság / 2) cv2.line (head_image, (x1, y1), (x2, y2), line_color, line_width) head_image = cv2.addWeighted (frame, 0.8, head_image, 1, 1) return head_image

A fenti funkció a keretet, amelybe az irányvonalat rajzolja, és a kormányzási szöget veszi be bemenetként. Visszaadja a címsor képét. Az én esetemben vett címsor keret a fenti képen látható.

Az összes kód kombinálása:

A kód készen áll az összeszerelésre. A következő kód az egyes funkciókat meghívó program fő ciklusát mutatja:

import cv2

importálja a számokat np video = cv2. VideoCapture (0) video.set (cv2. CAP_PROP_FRAME_WIDTH, 320) video.set (cv2. CAP_PROP_FRAME_HEIGHT, 240), míg True: ret, frame = video.read () frame = cv2.flip (frame, -1) #A függvények meghívása = get_steering_angle (frame, lane_lines) head_image = display_heading_line (lane_lines_image, kormány_szög) key = cv2.waitKey (1) if key == 27: break video.release () cv2.destroyAllWindows ()

6. lépés: A PD vezérlés alkalmazása

A PD Control alkalmazása
A PD Control alkalmazása

Most már készen állunk a kormányzási szögünkre, hogy betáplálhassuk a motorokhoz. Mint korábban említettük, ha a kormányzási szög nagyobb, mint 90, az autónak jobbra kell fordulnia, különben balra. Alkalmaztam egy egyszerű kódot, amely jobbra fordítja a kormánymotort, ha a szög 90 felett van, és balra fordul, ha a kormányzási szög 90 -nél kisebb, állandó (10% PWM) fojtószelep mellett, de sok hibát kaptam. A fő hiba az, amikor az autó bármelyik kanyarhoz közeledik, a kormánymotor közvetlenül működik, de a fojtószelep motorok elakadnak. Megpróbáltam növelni a fojtószelep sebességét (20% PWM) a kanyarokban, de azzal értem véget, hogy a robot kiszállt a sávokból. Szükségem volt valamire, ami nagyon megnöveli a gázpedált, ha a kormányzási szög nagyon nagy, és egy kicsit növeli a sebességet, ha a kormányzási szög nem olyan nagy, majd csökkenti a sebességet a kezdeti értékre, amikor az autó 90 fokra közeledik (egyenesen halad). A megoldás egy PD vezérlő használata volt.

A PID szabályozó az arányos, az integrált és a származtatott vezérlőket jelenti. Az ilyen típusú lineáris vezérlőket széles körben használják a robotikai alkalmazásokban. A fenti kép a tipikus PID visszacsatolási szabályozó hurkot mutatja. Ennek a vezérlőnek az a célja, hogy a "leghatékonyabb módon" elérje az "alapjelet", ellentétben az "on -off" vezérlőkkel, amelyek bizonyos feltételeknek megfelelően be- vagy kikapcsolják a berendezést. Néhány kulcsszót ismerni kell:

  • Alapjel: az a kívánt érték, amelyet el szeretne érni a rendszeren.
  • Tényleges érték: az érzékelő által érzékelt tényleges érték.
  • Hiba: a különbség az alapjel és a tényleges érték között (hiba = alapjel - tényleges érték).
  • Vezérelt változó: a neve alapján az a változó, amelyet szabályozni szeretne.
  • Kp: Arányos állandó.
  • Ki: integrálállandó.
  • Kd: Származékos állandó.

Röviden, a PID vezérlőrendszer hurka a következőképpen működik:

  • A felhasználó határozza meg a rendszer eléréséhez szükséges alapértéket.
  • A hiba kiszámításra kerül (hiba = alapjel - tényleges).
  • A P vezérlő a hiba értékével arányos műveletet generál. (a hiba nő, a P művelet is nő)
  • Az I vezérlő idővel integrálja a hibát, ami kiküszöböli a rendszer egyensúlyi állapotának hibáját, de növeli a túllépést.
  • A D vezérlő egyszerűen a hiba időderiváltja. Más szóval, ez a hiba lejtése. A hiba deriváltjával arányos műveletet hajt végre. Ez a vezérlő növeli a rendszer stabilitását.
  • A vezérlő kimenete a három vezérlő összege lesz. A vezérlő kimenete 0 lesz, ha a hiba 0 lesz.

A PID -szabályozó nagyszerű magyarázata itt található.

Visszatérve a sávtartó autóhoz, a szabályozott változóm a gázpedál volt (mivel a kormányzásnak csak két állapota van, jobbra vagy balra). PD vezérlőt használnak erre a célra, mivel a D művelet nagyon megnöveli a fojtószelep sebességét, ha a hiba változása nagyon nagy (azaz nagy eltérés), és lelassítja az autót, ha ez a hibaváltás megközelíti a 0 értéket. A PD végrehajtásához a következő lépéseket tettem vezérlő:

  • Állítsa az alapértéket 90 fokra (mindig azt akarom, hogy az autó egyenesen mozogjon)
  • Kiszámította az eltérési szöget a közepétől
  • Az eltérés két információt ad: Mekkora a hiba (az eltérés nagysága), és milyen irányba kell haladnia a kormánymotornak (az eltérés jele). Ha az eltérés pozitív, az autónak jobbra kell kormányoznia, különben balra.
  • Mivel az eltérés negatív vagy pozitív, "hiba" változó van definiálva, és mindig egyenlő az eltérés abszolút értékével.
  • A hibát megszorozzuk egy állandó Kp -val.
  • A hiba idődifferenciálódik, és megszorozzuk egy állandó Kd -vel.
  • A motorok fordulatszáma frissül, és a ciklus újra elindul.

A fő kódban a következő kódot használják a fojtómotorok fordulatszámának szabályozására:

sebesség = 10 # működési sebesség % PWM -ben

#Változók, amelyeket minden ciklusban frissíteni kell szögig_milliméterig változó hiba = absz (eltérés), ha az eltérés -5: # ne kormányozzon, ha 10 fokos hiba tartománybeli eltérés van = 0 hiba = 0 GPIO.output (in1, GPIO. LOW) GPIO.output (in2, GPIO. LOW) kormányzás.megállás () elif eltérés> 5: # jobbra kormányozni, ha az eltérés pozitív -5: # balra kanyarodik, ha az eltérés negatív * hiba PD = int (sebesség + derivált + arányos) spd = abs (PD), ha spd> 25: spd = 25 fojtószelep.indítás (spd) lastError = hiba lastTime = time.time ()

Ha a hiba nagyon nagy (a középsőtől való eltérés nagy), akkor az arányos és a derivált műveletek magasak, ami nagy fojtási sebességet eredményez. Amikor a hiba megközelíti a 0 -t (a közepétől való eltérés alacsony), a derivált művelet fordítva működik (a meredekség negatív), és a fojtószelep sebessége csökken a rendszer stabilitásának fenntartása érdekében. A teljes kód az alábbiakban található.

7. lépés: Eredmények

A fenti videók az általam elért eredményeket mutatják. Ez további hangolást és további beállításokat igényel. Azért csatlakoztattam a málna pi -t az LCD kijelzőmhöz, mert a hálózatomon keresztül továbbított videó nagy késleltetésű volt, és nagyon frusztráló volt vele dolgozni, ezért vannak vezetékek a málna pi -hez csatlakoztatva a videóban. A pálya rajzolásához hablapokat használtam.

Várom javaslatait a projekt jobbá tétele érdekében! Remélem, hogy ez az oktatóanyag elég jó volt ahhoz, hogy új információkat adjon nektek.

Ajánlott: