Mündəricat:

Raspberry Pi və OpenCV istifadə edərək muxtar zolaqlı maşın: 7 addım (şəkillərlə)
Raspberry Pi və OpenCV istifadə edərək muxtar zolaqlı maşın: 7 addım (şəkillərlə)

Video: Raspberry Pi və OpenCV istifadə edərək muxtar zolaqlı maşın: 7 addım (şəkillərlə)

Video: Raspberry Pi və OpenCV istifadə edərək muxtar zolaqlı maşın: 7 addım (şəkillərlə)
Video: Supervised Home Assistant. RaspiOS 64-də asan quraşdırma - Argon One M.2 qutusunda Raspberry PI 4B 2024, Dekabr
Anonim
Raspberry Pi və OpenCV istifadə edərək muxtar zolaq saxlayan avtomobil
Raspberry Pi və OpenCV istifadə edərək muxtar zolaq saxlayan avtomobil

Bu təlimatlarda avtonom zolaq saxlayan robot tətbiq ediləcək və aşağıdakı addımlardan keçəcək:

  • Parçaların toplanması
  • Proqram təminatının quraşdırılması
  • Avadanlıq montajı
  • İlk Test
  • OpenCV istifadə edərək zolaq xətlərinin aşkarlanması və istiqamətləndirici xəttin göstərilməsi
  • Bir PD nəzarətçisinin tətbiqi
  • Nəticələr

Addım 1: Komponentlərin toplanması

Komponentlərin toplanması
Komponentlərin toplanması
Komponentlərin toplanması
Komponentlərin toplanması
Komponentlərin toplanması
Komponentlərin toplanması
Komponentlərin toplanması
Komponentlərin toplanması

Yuxarıdakı şəkillər bu layihədə istifadə olunan bütün komponentləri göstərir:

  • RC avtomobili: Ölkəmdəki yerli bir mağazadan mənimki aldım. 3 mühərriklə təchiz olunmuşdur (2 -si sıxma, 1 -i sükan üçün). Bu avtomobilin əsas dezavantajı sükanın "sükansız" və "tam sükan" arasında məhdud olmasıdır. Başqa sözlə, servo sükan RC avtomobillərindən fərqli olaraq müəyyən bir açı ilə idarə edə bilməz. Xüsusi olaraq moruq pi üçün hazırlanmış oxşar avtomobil dəstini buradan tapa bilərsiniz.
  • Raspberry pi 3 model b+: bu, bir çox emal mərhələsini idarə edəcək avtomobilin beynidir. 1.4 GHz tezlikli dörd nüvəli 64 bitlik prosessora əsaslanır. Burdan öz əlimi aldım.
  • Raspberry pi 5 mp kamera modulu: 1080p @ 30 fps, 720p @ 60 fps və 640x480p 60/90 qeydləri dəstəkləyir. Həm də birbaşa moruq pi -yə qoşula bilən serial interfeysini dəstəkləyir. Şəkil işləmə tətbiqləri üçün ən yaxşı seçim deyil, amma bu layihə üçün kifayət qədər çox ucuz olduğu üçün. Burdan öz əlimi aldım.
  • Motor Sürücüsü: DC mühərriklərinin istiqamətlərini və sürətlərini idarə etmək üçün istifadə olunur. 1 lövhədə 2 dc mühərrikin idarə olunmasını dəstəkləyir və 1,5 A -a dözə bilir.
  • Power Bank (İsteğe bağlı): Moruq pi -ni ayrıca gücləndirmək üçün bir güc bankı (5V, 3A dərəcəsi) istifadə etdim. Moruq pi -ni 1 mənbədən işə salmaq üçün aşağıya çevirici (buck çeviricisi: 3A çıxış cərəyanı) istifadə edilməlidir.
  • 3s (12 V) LiPo batareyası: Lityum Polimer batareyaları robototexnika sahəsində əla performansları ilə tanınır. Motor sürücüsünü gücləndirmək üçün istifadə olunur. Burdan özüm aldım.
  • Kişidən kişiyə və dişi qadından tullanan tellər.
  • İki tərəfli lent: Komponentləri RC avtomobilinə quraşdırmaq üçün istifadə olunur.
  • Mavi lent: Bu, bu layihənin çox vacib bir komponentidir, maşının arasına girəcəyi iki zolaqlı xətti düzəltmək üçün istifadə olunur. İstədiyiniz rəng seçə bilərsiniz, amma ətrafdakılardan fərqli rənglər seçməyi məsləhət görürəm.
  • Fermuar bağları və taxta çubuqlar.
  • Vida sürücüsü.

Addım 2: Raspberry Pi -də OpenCV -nin quraşdırılması və Uzaqdan Ekranın Qurulması

Raspberry Pi -də OpenCV qurmaq və Uzaqdan Ekranı qurmaq
Raspberry Pi -də OpenCV qurmaq və Uzaqdan Ekranı qurmaq

Bu addım bir az əsəbidir və bir az vaxt aparacaq.

OpenCV (Açıq Mənbə Kompüter Vizyonu), açıq mənbə kompüter görmə və maşın öyrənmə proqram kitabxanasıdır. Kitabxanada 2500 -dən çox optimallaşdırılmış alqoritm var. OpenCV -ni moruq pi -yə quraşdırmaq, həmçinin moruq pi OS -ni quraşdırmaq üçün BU çox sadə təlimatı izləyin (hələ də etməmisinizsə). Unutmayın ki, açıq CV-nin qurulması yaxşı soyudulmuş otaqda təxminən 1,5 saat çəkə bilər (çünki prosessorun temperaturu çox yüksək olacaq!) Buna görə bir az çay içib səbirlə gözləyin: D.

Uzaqdan ekran üçün, Windows/Mac cihazınızdan moruq pi -yə uzaqdan giriş qurmaq üçün BU təlimatı izləyin.

Addım 3: Parçaları bir -birinə bağlayın

Parçaları Bir -birinə Bağlamaq
Parçaları Bir -birinə Bağlamaq
Parçaları Bir -birinə Bağlamaq
Parçaları Bir -birinə Bağlamaq
Parçaları Bir -birinə Bağlamaq
Parçaları Bir -birinə Bağlamaq

Yuxarıdakı şəkillər, moruq pi, kamera modulu və motor sürücüsü arasındakı əlaqələri göstərir. Diqqət yetirin ki, istifadə etdiyim mühərriklər hər biri 9 V -də 0,35 A -ı udur, bu da motor sürücüsünün eyni anda 3 mühərriklə işləməsini təhlükəsiz edir. Və 2 qısaldıcı motorun sürətini (1 arxa və 1 ön) eyni şəkildə idarə etmək istədiyim üçün onları eyni limana bağladım. Motor sürücüsünü ikiqat lentlə maşının sağ tərəfinə quraşdırdım. Kamera moduluna gəldikdə, yuxarıdakı şəkildən göründüyü kimi vida delikləri arasına fermuar bağladım. Sonra kameranı taxta çubuğa yerləşdirirəm ki, kameranın mövqeyini istədiyim kimi düzəldə bilim. Mümkün qədər kameranı avtomobilin ortasına quraşdırmağa çalışın. Kameranı yerdən ən az 20 sm yuxarıda yerləşdirməyi məsləhət görürəm ki, avtomobilin qarşısındakı görmə daha yaxşı olsun. Fritzing sxemi aşağıda əlavə edilmişdir.

Addım 4: İlk test

İlk Test
İlk Test
İlk Test
İlk Test

Kamera Testi:

Kamera quraşdırıldıqdan və openCV kitabxanası qurulduqdan sonra ilk imicimizi sınamağın vaxtı gəldi! Pi camdan bir şəkil çəkib "original.jpg" olaraq qeyd edəcəyik. 2 yolla edilə bilər:

1. Terminal əmrlərindən istifadə:

Yeni bir terminal pəncərəsi açın və aşağıdakı əmri yazın:

raspistill -o orijinal.jpg

Bu hərəkətsiz bir şəkil çəkəcək və "/pi/original.jpg" qovluğunda saxlayacaq.

2. Hər hansı bir python IDE istifadə edərək (IDLE istifadə edirəm):

Yeni bir eskiz açın və aşağıdakı kodu yazın:

idxal cv2

video = cv2. VideoCapture (0) Doğru olarkən: ret, frame = video.read () frame = cv2.flip (frame, -1) # görüntünü dik olaraq çevirmək üçün istifadə olunur cv2.imshow ('orijinal', çərçivə) cv2. imwrite ('original.jpg', frame) key = cv2.waitKey (1) əgər key == 27: video.release () cv2.destroyAllWindows ()

Bu kodda nə baş verdiyini görək. Birinci xətt, bütün funksiyalarından istifadə etmək üçün openCV kitabxanamızı idxal edir. VideoCapture (0) funksiyası, bu funksiya ilə təyin olunan mənbədən canlı bir video yayımlamağa başlayır, bu halda raspi kamera deməkdir 0 -dır. birdən çox kameranız varsa, fərqli nömrələr qoyulmalıdır. video.read () hər bir çərçivənin kameradan gəldiyini oxuyacaq və "frame" adlı dəyişənə yazacaq. flip () funksiyası, kameranı tərsinə bağladığım üçün görüntünü y oxuna (şaquli) çevirəcək. imshow () "orijinal" sözünün rəhbərlik etdiyi çərçivələrimizi göstərəcək və imwrite () şəklimizi orijinal-j.webp

OpenCV funksiyaları ilə tanış olmaq üçün şəklinizi ikinci üsulla sınamağı məsləhət görürəm. Şəkil "/pi/original.jpg" qovluğunda saxlanılır. Kameramın çəkdiyi orijinal foto yuxarıda göstərilib.

Test motorları:

Hər bir motorun fırlanma istiqamətini təyin etmək üçün bu addım vacibdir. Əvvəlcə bir motor sürücüsünün iş prinsipi haqqında qısa bir məlumat verək. Yuxarıdakı şəkil motor sürücüsünün bağlanmasını göstərir. A -ı aktivləşdirin, Giriş 1 və Giriş 2 motor A idarəetmə ilə əlaqələndirilir. B -ni aktivləşdirin, Giriş 3 və Giriş 4 motor B idarəetmə ilə əlaqələndirilir. İstiqamət nəzarəti "Giriş" hissəsi, sürət nəzarəti "Enable" hissəsi ilə qurulur. Məsələn, A motorunun istiqamətini idarə etmək üçün Giriş 1 -i YÜKSEK (bu vəziyyətdə moruq pi istifadə etdiyimiz üçün 3.3 V) və Giriş 2 -ni LOW olaraq təyin edin, motor müəyyən bir istiqamətdə və əks dəyərləri təyin edərək fırlanacaq. Giriş 1 və Giriş 2, motor əks istiqamətdə fırlanacaq. Giriş 1 = Giriş 2 = (Yüksək və ya Aşağı) olarsa, motor dönməz. Sancaqlar, moruqdan (0 - 3.3 V) bir Pulse Width Modulation (PWM) giriş siqnalı alır və motorları buna uyğun olaraq işə salır. Məsələn, 100% PWM siqnalı maksimum sürətlə işlədiyimizi və 0% PWM siqnalı motorun dönmədiyini bildirir. Aşağıdakı kod mühərriklərin istiqamətlərini təyin etmək və sürətlərini yoxlamaq üçün istifadə olunur.

idxal vaxtı

GPIO GPIO.setwarnings olaraq RPi. GPIO idxal edin (Yanlış) # Sükan Motoru Pimləri sükan idarə edilə bilər = 22 # Fiziki Pin 15 in1 = 17 # Fiziki Pin 11 in2 = 27 # Fiziki Pin 13 # Gaz Motorları Pins qaz tənzimləyicisi_enable = 25 # Fiziki Pin 22 in3 = 23 # Fiziki Pin 16 in4 = 24 # Fiziki Pin 18 GPIO.setmode (GPIO. BCM) # GPIO.setup (in1, GPIO.out) GPIO.setup (in2, GPIO.out) GPIO əvəzinə GPIO nömrələnməsindən istifadə edin. quraşdırma (in3, GPIO.out) GPIO.sistemi (in4, GPIO.out) GPIO.quraşdırma (tənzimləmə_qəbul edilə bilər, GPIO.out) GPIO.sistemi (sükan idarə edilə bilər, GPIO.out) # Sükan Mühərrikinə Nəzarət GPIO.çıxış (in1, GPIO. YÜKSƏK) GPIO.output (in2, GPIO. LOW) sükan = GPIO. PWM (steering_enable, 1000) # keçid tezliyini 1000 Hz -ə təyin edin.çıxış (in4, GPIO. LOW) qaz = GPIO. PWM (throttle_enable, 1000) # keçid tezliyini 1000 Hz -ə ayarla % PWM siqnalı -> (0,25 * batareya gərginliyi) - sürücü üçündür sükan itkisi.start (100) # mühərriki 100% PWM siqnalında işə salır -> (1 * Batareya Gərginliyi) - sürücünün itkisi vaxtı.yuxu (3) qaz.stop () sükan dayandırma ()

Bu kod, sıxma motorlarını və sükan motorunu 3 saniyə işlədəcək və sonra dayandıracaq. (Sürücünün itkisi) bir voltmetr istifadə edərək təyin edilə bilər. Məsələn, bilirik ki, 100% PWM siqnalı motorun terminalında batareyanın tam gərginliyini verməlidir. Ancaq PWM -ni 100%olaraq təyin edərək, sürücünün 3 V düşməsinə səbəb olduğunu və motorun 12 V yerinə 9 V aldığını gördüm (tam ehtiyacım var!). Zərər xətti deyil, yəni 100% -lik zərər 25% -lik zərərdən çox fərqlidir. Yuxarıdakı kodu işlədikdən sonra nəticələrim belə oldu:

Daralma Nəticələri: əgər in3 = YÜK və in4 = DÜŞÜK olarsa, qısaldıcı mühərriklərdə Clock-Wise (CW) fırlanışı olacaq, yəni avtomobil irəliləyəcək. Əks təqdirdə, avtomobil geriyə doğru hərəkət edəcək.

Sükan Nəticələri: in1 = HIGH və in2 = LOW olarsa, sükan mühərriki maksimum sola dönəcək, yəni avtomobil sola dönəcək. Əks təqdirdə, avtomobil sağa dönəcək. Bəzi təcrübələrdən sonra, PWM siqnalı 100% olmadıqda sükan motorunun dönməyəcəyini gördüm (yəni motor ya tam sağa, ya da tam sola dönəcək).

Addım 5: Zolaq Xəttlərinin Algılanması və Başlıq Xəttinin Hesablanması

Zolaq xətlərinin aşkarlanması və başlıq xəttinin hesablanması
Zolaq xətlərinin aşkarlanması və başlıq xəttinin hesablanması
Zolaq xətlərinin aşkarlanması və başlıq xəttinin hesablanması
Zolaq xətlərinin aşkarlanması və başlıq xəttinin hesablanması
Zolaq xətlərinin aşkarlanması və başlıq xəttinin hesablanması
Zolaq xətlərinin aşkarlanması və başlıq xəttinin hesablanması

Bu addımda avtomobilin hərəkətini idarə edəcək alqoritm izah ediləcək. Birinci şəkil bütün prosesi göstərir. Sistemin girişi görüntülərdir, çıxış teta (dərəcə ilə sükan açısı). Qeyd edək ki, işləmə 1 şəkil üzərində aparılır və bütün çərçivələrdə təkrarlanacaq.

Kamera:

Kamera (320 x 240) qətnamə ilə video çəkməyə başlayacaq. Çözünürlüyü aşağı salmağı məsləhət görürəm ki, daha yaxşı kadr sürəti (fps) əldə edə biləsiniz, çünki hər kadrda işləmə texnikası tətbiq edildikdən sonra fps düşmə baş verəcək. Aşağıdakı kod proqramın əsas döngəsi olacaq və hər bir addımı bu kodun üzərinə əlavə edəcək.

idxal cv2

np video = cv2. VideoCapture (0) video.set (cv2. CAP_PROP_FRAME_WIDTH, 320) kimi numpy idxal edin Doğru: ret, frame = video.read () frame = cv2.flip (frame, -1) cv2.imshow ("original", frame) key = cv2.waitKey (1) key == 27: break video.release () cv2.destroyAllWindows ()

Buradakı kod, 4 -cü addımda əldə edilmiş orijinal görüntünü göstərəcək və yuxarıdakı şəkillərdə göstərilmişdir.

HSV Rəng Məkanına çevirin:

İndi video qeydini kameradan çərçivələr olaraq götürdükdən sonra, növbəti addım hər bir çərçivəni Hue, Saturation və Value (HSV) rəng sahəsinə çevirməkdir. Bunun əsas üstünlüyü, parlaqlıq səviyyəsinə görə rəngləri fərqləndirə bilməkdir. Və burada HSV rəng məkanının yaxşı bir izahı var. HSV -ə çevirmək aşağıdakı funksiya vasitəsilə həyata keçirilir:

def convert_to_HSV (çərçivə):

hsv = cv2.cvtColor (çərçivə, cv2. COLOR_BGR2HSV) cv2.imshow ("HSV", hsv) hsv qaytar

Bu funksiya əsas döngədən çağırılacaq və çərçivəni HSV rəng boşluğuna qaytaracaq. HSV rəng məkanında əldə etdiyim çərçivə yuxarıda göstərilmişdir.

Mavi Rəng və Kenarları Algılayın:

Görüntünü HSV rəng sahəsinə çevirdikdən sonra, yalnız maraqlandığımız rəngi (yəni zolaq xətlərinin rəngi olduğu üçün mavi rəng) aşkar etməyin vaxtı gəldi. HSV çərçivəsindən mavi rəng çıxarmaq üçün bir sıra rəng, doyma və dəyər təyin olunmalıdır. HSV dəyərləri haqqında daha yaxşı bir fikir əldə etmək üçün bura baxın. Bəzi təcrübələrdən sonra mavi rəngin yuxarı və aşağı sərhədləri aşağıdakı kodda göstərilir. Və hər bir çərçivədəki ümumi təhrifi azaltmaq üçün kənarları yalnız qanadlı kənar detektoru istifadə edərək aşkarlanır. Canny edge haqqında daha çox məlumatı burada tapa bilərsiniz. Əsas qayda Canny () funksiyasının parametrlərini 1: 2 və ya 1: 3 nisbətində seçməkdir.

def detect_edges (çərçivə):

low_blue = np.array ([90, 120, 0], dtype = "uint8") # mavi rəngin alt həddi üst_göy = np.array ([150, 255, 255], dtype = "uint8") # yuxarı həddi mavi rəngli maska = cv2.inRange (hsv, alt_mavi, yuxarı_mavi) # bu maska mavi rəngdən başqa hər şeyi süzəcək # kənarların kənarlarını aşkar edir = cv2. Canny (maska, 50, 100) cv2.imshow ("kənarları", kənarları) geri dönən kənarları

Bu funksiya, HSV rəngli boşluq çərçivəsini parametr olaraq götürən və kənarlı çərçivəni qaytaran əsas döngədən də çağırılacaq. Əldə etdiyim kənarlı çərçivə yuxarıda tapılmışdır.

Maraq Bölgəsini (ROI) seçin:

Çərçivənin yalnız 1 bölgəsinə diqqət yetirmək üçün maraq bölgəsinin seçilməsi çox vacibdir. Bu vəziyyətdə maşının ətrafda çoxlu əşyalar görməsini istəmirəm. Sadəcə, maşının zolaq xətlərinə diqqət etməsini və başqa bir şeyi görməməsini istəyirəm. P. S: koordinat sistemi (x və y oxları) sol üst küncdən başlayır. Başqa sözlə, (0, 0) nöqtəsi yuxarı sol küncdən başlayır. y oxu hündürlük və x oxu genişlikdir. Aşağıdakı kod yalnız çərçivənin aşağı yarısına diqqət yetirmək üçün maraq bölgəsini seçir.

def region_of_interest (kənarları):

hündürlük, eni = kənarları.şəkil # kənarların hündürlüyünü və genişliyini çıxarın çərçivə maskası = np.zeros_like (kənarları) # kənarların eyni ölçüləri olan boş bir matris düzəldin # yalnız ekranın aşağı yarısına diqqət yetirin # koordinatlarını göstərin 4 nöqtə (aşağı sol, yuxarı sol, yuxarı sağ, aşağı sağ) çoxbucaqlı = np array (

Bu funksiya, kənarlı çərçivəni parametr olaraq götürəcək və 4 əvvəlcədən təyin edilmiş nöqtəsi olan çoxbucaqlı çəkəcək. Yalnız çoxbucağın içindəki şeylərə diqqət yetirəcək və xaricindəki hər şeyi görməməzliyə vuracaq. Mənim maraq dairəm yuxarıda göstərilmişdir.

Xətt Segmentlərini Algılayın:

Hough transforması, kənarlı bir çərçivədən xətt seqmentlərini aşkar etmək üçün istifadə olunur. Hough transform, hər hansı bir formanı riyazi formada aşkar etmək üsuludur. Bəzi səslərə görə təhrif olunsa belə, demək olar ki, hər hansı bir obyekti aşkar edə bilər. Hough çevrilməsi üçün böyük bir istinad burada göstərilmişdir. Bu proqram üçün cv2. HoughLinesP () funksiyası hər çərçivədəki xətləri aşkar etmək üçün istifadə olunur. Bu funksiyanın vacib parametrləri bunlardır:

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

  • Çərçivə: daxilindəki xətləri aşkar etmək istədiyimiz çərçivədir.
  • rho: Bu piksellərlə məsafə dəqiqliyidir (ümumiyyətlə = 1)
  • teta: radianlarda açısal dəqiqlik (həmişə = np.pi/180 ~ 1 dərəcə)
  • min_threshold: bir xətt olaraq qəbul edilməsi üçün alması lazım olan minimum səs
  • minLineLength: Pikseldə minimum xətt uzunluğu. Bu rəqəmdən daha qısa olan hər hansı bir xətt xətt sayılmır.
  • maxLineGap: 1 xətt kimi qəbul edilməli olan 2 xətt arasındakı maksimum piksel boşluğu. (İstifadə etdiyim zolaq xətlərində boşluq olmadığı üçün mənim vəziyyətimdə istifadə edilmir).

Bu funksiya xəttin son nöqtələrini qaytarır. Aşağıdakı funksiya əsas döngəmdən Hough çevrilməsindən istifadə edərək xətləri aşkar etmək üçün çağırılır:

def detect_line_seqmentləri (kəsilmiş_qiymətlər):

rho = 1 teta = np.pi / 180 dəq. eşik = 10 xətt_segmentləri = cv2.

Orta yamac və kəsmə (m, b):

Xatırladaq ki, xəttin tənliyi y = mx + b ilə verilir. Burada m xəttin yamacı, b isə y-kəsişməsidir. Bu hissədə, Hough çevrilməsindən istifadə edərək təsbit edilən xətt seqmentlərinin yamaclarının və kəsişmələrinin ortalaması hesablanacaq. Bunu etməzdən əvvəl, yuxarıda göstərilən orijinal çərçivə şəklinə bir nəzər salaq. Sol zolaq yuxarıya doğru gedir, buna görə mənfi bir yamac var (koordinat sisteminin başlanğıc nöqtəsini xatırlayın?). Başqa sözlə, sol zolaq xətti müsbət yamac verəcək x1 <x2 və y2 x1 və y2> y1 -ə malikdir. Beləliklə, müsbət yamaclı bütün xətlər sağ zolaq nöqtələri hesab olunur. Şaquli xətlər olduqda (x1 = x2), yamac sonsuz olacaq. Bu vəziyyətdə bir səhv almamaq üçün bütün şaquli xətləri atlayacağıq. Bu aşkarlamaya daha dəqiqlik əlavə etmək üçün hər bir çərçivə 2 sərhəd xətti vasitəsilə iki bölgəyə (sağ və sol) bölünür. Sağ sərhəd xəttindən daha böyük olan bütün genişlik nöqtələri (x-ox nöqtələri) sağ zolağın hesablanması ilə əlaqələndirilir. Və bütün genişlik nöqtələri sol sərhəd xəttindən azdırsa, sol zolaqlı hesablama ilə əlaqələndirilir. Aşağıdakı funksiya, çərçivəni Hough çevrilməsindən istifadə edərək işlənmiş və zolaq seqmentləri altına alır və iki şerit xəttinin orta yamacını və kəsişməsini qaytarır.

def orta_yamac_kəsmə (çərçivə, xətt_segmentləri):

lane_lines = line_segments Yoxdursa: print ("heç bir sətir seqmenti aşkarlanmadı") qayıt lane_lines hündürlüyü, eni, _ = frame.shape left_fit = right_fit = border = left_region_boundary = width * (1 - border) right_region_boundary = line * seqmentlərində line_seqment üçün en * sərhəd: x1, y1, x2, y2 üçün line_seqmentdə: əgər x1 == x2: print ("şaquli xətləri atlama (yamac = sonsuzluq)") davam etdirmək fit = np.polyfit ((x1, x2), (y1, y2), 1) yamac = (y2 - y1) / (x2 - x1) kəsmə = y1 - (yamac * x1) yamac <0 olarsa: x1 <sol_bölge_sərhəd və x2 sağ_bölge_sərhəd və x2> sağ_reqion_sərhəd: sağ_fit. əlavə et ((yamac, kəsmə)) left_fit_average = np.overage (left_fit, ox = 0) if len (left_fit)> 0: lane_lines.append (make_points (frame, left_fit_average)) right_fit_average = np.average (right_fit, axis = 0)) əgər len (sağ_fit)> 0: zolaqlı_ xəttlər e_lines =

make_points (), zolaq xətlərinin məhdudlaşdırılmış koordinatlarını (çərçivənin altından ortasına) qaytaracaq, ortalama_slope_intercept () funksiyası üçün köməkçi funksiyadır.

def make_points (çərçivə, xətt):

hündürlük, genişlik, _ = çərçivə.şəkil yamacı, kəsmə = xətt y1 = hündürlük # çərçivənin alt hissəsi y2 = int (y1 / 2) # yamac == 0 olarsa çərçivənin ortasından aşağı nöqtələr düzəldin: yamac = 0.1 x1 = int ((y1 - kəsmə) / yamac) x2 = int ((y2 - kəsmə) / yamac) qayıt

0 -a bölünməmək üçün bir şərt təqdim olunur. Y1 = y2 (üfüqi xətt) mənasını verən yamac = 0 olarsa, yamacın 0 -a yaxın bir dəyər verin. Bu, alqoritmin işinə təsir göstərməyəcəyi kimi qeyri -mümkün halın da (0 -a bölünməsi) qarşısını alacaq.

Çərçivələrdə zolaqlı xətləri göstərmək üçün aşağıdakı funksiyadan istifadə olunur:

def display_lines (çərçivə, xətlər, line_color = (0, 255, 0), line_width = 6): # xətt rəngi (B, G, R)

line_image = np.zeros_like (çərçivə), əgər xətlər yoxdursa: sətirlərarası xətt üçün: x1, y1, x2, y2 üçün satırda: cv2.line (line_image, (x1, y1), (x2, y2), line_color, line_width) line_image = cv2.addWeighted (frame, 0.8, line_image, 1, 1) return line_image

cv2.addWeighted () funksiyası aşağıdakı parametrləri alır və iki görüntünü birləşdirmək üçün istifadə olunur, ancaq hər birinə ağırlıq verir.

cv2.addWeighted (image1, alfa, image2, beta, qamma)

Və aşağıdakı tənliyi istifadə edərək çıxış görüntüsünü hesablayır:

çıxış = alpha * image1 + beta * image2 + gamma

Cv2.addWeighted () funksiyası haqqında daha çox məlumat burada əldə edilmişdir.

Başlıq xəttini hesablayın və göstərin:

Bu, mühərriklərimizə sürət tətbiq etməzdən əvvəl son addımdır. Başlıq xətti sükan motoruna dönmə istiqaməti vermək və boğma motorlarına işlədikləri sürəti verməkdən məsuldur. Başlıq xətti təmiz trigonometriyadır, tan və atan (tan^-1) trigonometrik funksiyalardan istifadə olunur. Bəzi həddindən artıq hallar, kameranın yalnız bir zolaqlı xətt algılaması və ya heç bir xətt algılamamasıdır. Bütün bu hallar aşağıdakı funksiyada göstərilir:

def get_steering_angle (çərçivə, zolaqlı_ xətlər):

hündürlük, en, _ = frame.shape əgər len (zolaq_xəttləri) == 2: # iki zolaqlı xətt aşkar edilərsə _, _, left_x2, _ = lane_lines [0] [0] # x2 lane_lines massivindən x çıxar #, sağ_x2, _ = zolaqlı_ xəttlər [1] [0] # x2 zolaq_xəttləri sırasından çıxarış mid = int (eni / 2) x_offset = (sol_x2 + sağ_x2) / 2 - orta y_offset = int (yüksəklik / 2) elif len (şerit_ xətləri)) == 1: # yalnız bir xətt aşkar edilərsə x1, _, x2, _ = şeritli xətlər [0] [0] x_offset = x2 - x1 y_offset = int (hündürlük / 2) elif len (zolaqlı_ xətlər) == 0: # heç bir xətt aşkar edilmədikdə x_offset = 0 y_offset = int (height / 2) angle_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 qayıtma

Birinci halda x_offset, ortalamanın ((sağ x2 + sol x2) / 2) ekranın ortasından nə qədər fərqlənməsidir. y_offset həmişə hündürlük / 2 olaraq qəbul edilir. Yuxarıdakı son şəkil başlıq nümunəsini göstərir. angle_to_mid_radians, yuxarıdakı son görüntüdə göstərilən "teta" ilə eynidir. Əgər steering_angle = 90 olarsa, bu, avtomobilin "hündürlük / 2" xəttinə dik bir başlıq xəttinin olması və avtomobilin sükan etmədən irəliləməsi deməkdir. Sükan açarı> 90 olarsa, avtomobil sağa, əks halda sola dönməlidir. Başlıq xəttini göstərmək üçün aşağıdakı funksiyadan istifadə olunur:

def display_heading_line (çərçivə, sükan açısı, line_color = (0, 0, 255), xətt genişliyi = 5)

heading_image = np.zeros_like (frame) hündürlük, en, _ = frame.shape steering_angle_radian = sükan_ açısı / 180.0 * math.pi x1 = int (eni / 2) y1 = h2 = int (x1 - yüksəklik / 2 / math.tan) (steering_angle_radian)) y2 = int (height / 2) cv2.line (heading_image, (x1, y1), (x2, y2), line_color, line_width) heading_image = cv2.addWeighted (frame, 0.8, heading_image, 1, 1) heading_image -ə qayıdın

Yuxarıdakı funksiya, başlıq xəttinin çəkiləcəyi çərçivəni və sükan bucağını giriş olaraq götürür. Başlıq xəttinin görüntüsünü qaytarır. Mənim vəziyyətimdə alınan başlıq xətti çərçivəsi yuxarıdakı şəkildə göstərilmişdir.

Bütün kodları birlikdə birləşdirmək:

İndi kod yığılmağa hazırdır. Aşağıdakı kod, hər bir funksiyanı çağıran proqramın əsas döngəsini göstərir:

idxal cv2

np video = cv2. VideoCapture (0) video.set (cv2. CAP_PROP_FRAME_WIDTH, 320) video.set (cv2. CAP_PROP_FRAME_HEIGHT, 240) olaraq numpy idxal edin, True: ret, frame = video.read () frame = cv2.flip (frame, -1) #Funksiyaların çağırılması hsv = convert_to_HSV (çərçivə) kənarları = detect_edges (hsv) roi = region_of_ maraqlar (kənarlar) line_segments = detect_line_segments (roi) lane_lines = orta_yamaqlar_kəsmə (frame, line_segments) lane_lines_cərgə_xəttləri) = get_steering_angle (frame, lane_lines) heading_image = display_heading_line (lane_lines_image, steering_angle) key = cv2.waitKey (1) key == 27: break video.release () cv2.destroyAllWindows ()

Addım 6: PD Nəzarətinin tətbiqi

PD Nəzarəti tətbiq olunur
PD Nəzarəti tətbiq olunur

İndi sükan bucağımızı mühərriklərə verməyə hazırıq. Daha əvvəl də qeyd edildiyi kimi, sükan açısı 90 -dan çox olarsa, avtomobil sağa, əks halda sola dönməlidir. Bucaq 90 -dan yuxarı olduqda sükan motorunu sağa çevirən və sükan bucağı 90% -dən az olduqda daimi sıxılma sürətində (10% PWM) sola çevirən sadə bir kod tətbiq etdim, amma çox səhvlər aldım. Aldığım əsas səhv, avtomobil hər hansı bir döngəyə yaxınlaşdıqda, sükan mühərriki birbaşa hərəkət edir, amma qazma motorları sıxılır. Dönüşlərdə sıxılma sürətini (20% PWM) artırmağa çalışdım, ancaq robotun zolaqdan çıxması ilə sona çatdı. Sükan bucağı çox böyük olarsa, sıxılma sürətini çox artıran və sükan açısı o qədər də böyük olmasa sürəti bir qədər artıran bir şeyə ehtiyacım var idi, sonra avtomobil 90 dərəcəyə yaxınlaşdıqda sürəti ilkin dəyərə endirir (düz hərəkət edir). Həll PD nəzarətçisindən istifadə etmək idi.

PID nəzarətçisi, mütənasib, inteqral və törəmə nəzarətçi deməkdir. Bu tip xətti idarəedicilər robototexnikada geniş istifadə olunur. Yuxarıdakı şəkil tipik PID geribildirim nəzarət döngəsini göstərir. Bu nəzarətçinin məqsədi, müəyyən şərtlərə görə qurğunu açan və ya söndürən "açma" nəzarətçilərindən fərqli olaraq ən təsirli yolla "təyin olunan nöqtəyə" çatmaqdır. Bəzi açar sözlər bilinməlidir:

  • Ayar nöqtəsi: sisteminizin əldə etməsini istədiyiniz dəyərdir.
  • Həqiqi dəyər: sensor tərəfindən algılanan həqiqi dəyərdir.
  • Xəta: müəyyən edilmiş dəyərlə faktiki dəyər arasındakı fərqdir (səhv = Ayar nöqtəsi - Həqiqi dəyər).
  • Nəzarət olunan dəyişən: adından, nəzarət etmək istədiyiniz dəyişən.
  • Kp: mütənasib sabit.
  • Ki: İntegral sabit.
  • Kd: Törəmə sabit.

Bir sözlə, PID idarəetmə sistemi aşağıdakı kimi işləyir:

  • İstifadəçi, sistemin çatması üçün lazım olan müəyyən nöqtəni təyin edir.
  • Səhv hesablanır (səhv = setpoint - faktiki).
  • P nəzarətçisi xətanın dəyərinə mütənasib bir hərəkət yaradır. (səhv artır, P hərəkəti də artır)
  • I nəzarətçi, sistemin sabit vəziyyət səhvini ortadan qaldıran, lakin həddini aşan səhvini zamanla birləşdirəcək.
  • D nəzarətçisi sadəcə səhv üçün vaxt törəməsidir. Başqa sözlə, səhvin yamacıdır. Səhv törəməsi ilə mütənasib bir hərəkət edir. Bu nəzarətçi sistemin sabitliyini artırır.
  • Nəzarətçinin çıxışı üç nəzarətçinin cəmi olacaq. Səhv 0 olarsa nəzarətçinin çıxışı 0 olacaq.

PID nəzarətçisinin böyük bir izahını burada tapa bilərsiniz.

Zolaq saxlayan maşına qayıdanda, idarə olunan dəyişənim sürəti azaldırdı (sükanın sağ və ya solda yalnız iki vəziyyəti olduğu üçün). Səhv dəyişikliyi çox böyükdürsə (yəni böyük sapma) D hərəkəti tənzimləmə sürətini çox artırır və bu səhv dəyişikliyi 0 -a yaxınlaşırsa avtomobili yavaşlatdığından bu məqsədlə bir PD nəzarətçi istifadə olunur. PD tətbiq etmək üçün aşağıdakı addımları atdım. nəzarətçi:

  • İstədiyiniz nöqtəni 90 dərəcəyə qoyun (həmişə maşının düz hərəkət etməsini istəyirəm)
  • Ortadan sapma bucağını hesablayın
  • Sapma iki məlumat verir: Səhv nə qədər böyükdür (sapmanın böyüklüyü) və sükan motorunun hansı istiqamətə getməsi lazımdır (sapma əlaməti). Əgər sapma müsbət olarsa, avtomobil sağa, əksinə sola dönməlidir.
  • Sapma ya mənfi, ya da müsbət olduğu üçün "səhv" dəyişəni təyin olunur və həmişə sapmanın mütləq dəyərinə bərabərdir.
  • Səhv sabit bir Kp ilə vurulur.
  • Səhv zaman fərqliliyinə məruz qalır və sabit bir Kd ilə vurulur.
  • Motorların sürəti yenilənir və döngə yenidən başlayır.

Qısaltma motorlarının sürətini idarə etmək üçün əsas döngədə aşağıdakı kod istifadə olunur:

sürət = 10 # işləmə sürəti % PWM olaraq

# Dəyişənlər hər döngədə yenilənəcək lastTime = 0 lastError = 0 # PD sabitləri Kp = 0.4 Kd = Kp * 0.65 Doğru halda: indi = vaxt. zaman () # cari vaxt dəyişənləri dt = indi - lastTime sapması = sükan_bucağı - 90 # ekvivalenti to angle_to_mid_deg dəyişən xətası = abs (sapma) sapma -5: # 10 dərəcə bir səhv aralığında sapma = 0 səhv = 0 GPIO.output (in1, GPIO. LOW) GPIO.output (in2, GPIO). DÜŞ) sükan vurma.stop () elif sapması> 5: sapma pozitiv olduqda # sağa dön -5: sapma mənfi GPIO.output (in1, GPIO. HIGH) GPIO.output (in2, GPIO. LOW) sükan.start (100) törəmə = kd * (səhv - lastError) / dt mütənasib = kp * səhv PD = int (sürət + törəmə + mütənasib) spd = abs (PD) spd> 25 olduqda: spd = 25 throttle.start (spd) lastError = error lastTime = time.time ()

Səhv çox böyükdürsə (ortadan sapma yüksəkdir), nisbi və törəmə hərəkətlər yüksəkdir və nəticədə yüksək tıxanma sürəti yaranır. Səhv 0 -a yaxınlaşdıqda (ortadan sapma aşağıdır), törəmə hərəkəti əksinə hərəkət edir (meyl mənfi) və sistemin sabitliyini qorumaq üçün tənzimləmə sürəti aşağı düşür. Tam kod aşağıda əlavə olunur.

Addım 7: Nəticələr

Yuxarıdakı videolar əldə etdiyim nəticələri göstərir. Daha çox tənzimləmə və əlavə düzəlişlərə ehtiyac var. Mən moruq pi -ni LCD ekran ekranına bağlayırdım, çünki şəbəkəm üzərindəki video axını yüksək gecikmə müddəti idi və işləmək çox sinir bozucu idi, buna görə videoda moruq pi -yə bağlı tellər var. Yolu çəkmək üçün köpük lövhələrdən istifadə etdim.

Bu layihəni daha yaxşı etmək üçün tövsiyələrinizi eşitməyi gözləyirəm! Ümid edirəm ki, bu təlimatlar sizə yeni məlumatlar vermək üçün kifayət qədər yaxşı idi.

Tövsiyə: