Isi kandungan:

Kereta Penjaga Lorong Otonomi Menggunakan Raspberry Pi dan OpenCV: 7 Langkah (dengan Gambar)
Kereta Penjaga Lorong Otonomi Menggunakan Raspberry Pi dan OpenCV: 7 Langkah (dengan Gambar)

Video: Kereta Penjaga Lorong Otonomi Menggunakan Raspberry Pi dan OpenCV: 7 Langkah (dengan Gambar)

Video: Kereta Penjaga Lorong Otonomi Menggunakan Raspberry Pi dan OpenCV: 7 Langkah (dengan Gambar)
Video: Pertarungan di bulan! #BoBoiBoyS3 | Episod 09 2024, Julai
Anonim
Kereta Penjaga Lorong Autonomi Menggunakan Raspberry Pi dan OpenCV
Kereta Penjaga Lorong Autonomi Menggunakan Raspberry Pi dan OpenCV

Dalam instruksional ini, robot pemeliharaan lorong autonomi akan dilaksanakan dan akan melalui langkah-langkah berikut:

  • Mengumpulkan Bahagian
  • Memasang prasyarat perisian
  • Pemasangan perkakasan
  • Ujian Pertama
  • Mengesan garis lorong dan memaparkan garis panduan menggunakan openCV
  • Melaksanakan pengawal PD
  • Keputusan

Langkah 1: Mengumpulkan Komponen

Mengumpulkan Komponen
Mengumpulkan Komponen
Mengumpulkan Komponen
Mengumpulkan Komponen
Mengumpulkan Komponen
Mengumpulkan Komponen
Mengumpulkan Komponen
Mengumpulkan Komponen

Gambar di atas menunjukkan semua komponen yang digunakan dalam projek ini:

  • Kereta RC: Saya mendapat milik saya dari sebuah kedai tempatan di negara saya. Ia dilengkapi dengan 3 motor (2 untuk pendikit dan 1 untuk stereng). Kelemahan utama kereta ini ialah kemudi terhad antara "tanpa stereng" dan "stereng penuh". Dengan kata lain, ia tidak dapat memandu pada sudut tertentu, tidak seperti kereta RC servo-steering. Anda boleh mendapatkan car kit serupa yang direka khas untuk raspberry pi dari sini.
  • Raspberry pi 3 model b +: ini adalah otak kereta yang akan menangani banyak peringkat pemprosesan. Ia berdasarkan pemproses 64-bit quad core yang dicatat pada 1.4 GHz. Saya mendapat milik saya dari sini.
  • Modul kamera Raspberry pi 5 mp: Ia menyokong 1080p @ 30 fps, 720p @ 60 fps, dan rakaman 640x480p 60/90. Ia juga menyokong antara muka bersiri yang boleh dipasang terus ke raspberry pi. Ini bukan pilihan terbaik untuk aplikasi pemprosesan gambar tetapi memadai untuk projek ini dan juga sangat murah. Saya mendapat milik saya dari sini.
  • Pemandu Motor: Digunakan untuk mengawal arah dan kelajuan motor DC. Ia menyokong kawalan motor 2 dc dalam 1 papan dan dapat menahan 1.5 A.
  • Power Bank (Pilihan): Saya menggunakan power bank (dinilai pada 5V, 3A) untuk menghidupkan raspberry pi secara berasingan. Penukar langkah ke bawah (penukar buck: arus keluaran 3A) harus digunakan untuk menghidupkan pi raspberry dari 1 sumber.
  • Baterai LiPo 3s (12 V): Baterai Lithium Polymer terkenal dengan prestasi cemerlang dalam bidang robotik. Ia digunakan untuk memberi kuasa kepada pemandu motor. Saya membeli tambang dari sini.
  • Kabel jumper lelaki hingga lelaki dan perempuan.
  • Pita dua sisi: Digunakan untuk memasang komponen pada kereta RC.
  • Pita biru: Ini adalah komponen yang sangat penting dalam projek ini, ia digunakan untuk membuat dua garis lorong di mana kereta akan bergerak di antara. Anda boleh memilih warna yang anda mahukan tetapi saya cadangkan memilih warna yang berbeza daripada warna di sekitar.
  • Ikatan zip dan batang kayu.
  • Pemacu skru.

Langkah 2: Memasang OpenCV pada Raspberry Pi dan Menyiapkan Paparan Jauh

Memasang OpenCV pada Raspberry Pi dan Menyiapkan Paparan Jauh
Memasang OpenCV pada Raspberry Pi dan Menyiapkan Paparan Jauh

Langkah ini agak menjengkelkan dan akan memakan masa.

OpenCV (Open source Computer Vision) adalah perpustakaan perisian sumber terbuka dan pembelajaran komputer. Perpustakaan mempunyai lebih dari 2500 algoritma yang dioptimumkan. Ikuti panduan INI yang sangat mudah untuk memasang openCV pada raspberry pi anda dan juga memasang OS raspberry pi (jika anda masih tidak). Harap maklum bahawa proses membina openCV mungkin mengambil masa sekitar 1.5 jam di bilik yang disejukkan dengan baik (kerana suhu prosesor akan sangat tinggi!) Oleh itu, minum teh dan tunggu dengan sabar: D.

Untuk paparan jauh, ikuti juga panduan INI untuk menyediakan akses jarak jauh ke pi raspberry anda dari peranti Windows / Mac anda.

Langkah 3: Menyambungkan Bahagian Bersama

Menghubungkan Bahagian Bersama
Menghubungkan Bahagian Bersama
Menghubungkan Bahagian Bersama
Menghubungkan Bahagian Bersama
Menghubungkan Bahagian Bersama
Menghubungkan Bahagian Bersama

Gambar di atas menunjukkan hubungan antara raspberry pi, modul kamera dan pemandu motor. Harap maklum bahawa motor yang saya gunakan menyerap 0,35 A pada 9 V setiap satu yang menjadikannya selamat bagi pemandu motor untuk menjalankan 3 motor pada masa yang sama. Oleh kerana saya mahu mengawal kelajuan 2 motor pendikit (1 belakang dan 1 depan) dengan cara yang sama, saya menghubungkannya ke port yang sama. Saya memasang pemandu motor di sebelah kanan kereta menggunakan double tape. Bagi modul kamera, saya memasukkan tali leher di antara lubang skru seperti yang ditunjukkan oleh gambar di atas. Kemudian, saya pasangkan kamera ke batang kayu supaya saya dapat menyesuaikan kedudukan kamera seperti yang saya mahukan. Cuba pasangkan kamera di bahagian tengah kereta sebanyak mungkin. Saya cadangkan meletakkan kamera sekurang-kurangnya 20 cm di atas tanah supaya bidang pandangan di depan kereta menjadi lebih baik. Skema Fritzing dilampirkan di bawah.

Langkah 4: Ujian Pertama

Ujian Pertama
Ujian Pertama
Ujian Pertama
Ujian Pertama

Ujian Kamera:

Setelah kamera dipasang, dan perpustakaan openCV dibina, sudah tiba masanya untuk menguji gambar pertama kami! Kami akan mengambil gambar dari pi cam dan menyimpannya sebagai "original.jpg". Ia boleh dilakukan dengan 2 cara:

1. Menggunakan Perintah Terminal:

Buka tetingkap terminal baru dan ketik arahan berikut:

raspistill -o original.jpg

Ini akan mengambil gambar pegun dan menyimpannya di direktori "/pi/original.jpg".

2. Menggunakan python IDE (saya menggunakan IDLE):

Buka lakaran baru dan tulis kod berikut:

import cv2

video = cv2. VideoCapture (0) sementara True: ret, frame = video.read () frame = cv2.flip (frame, -1) # digunakan untuk membalikkan gambar secara menegak cv2.imshow ('original', frame) cv2. imwrite ('original.jpg', frame) key = cv2.waitKey (1) if key == 27: rehat video.release () cv2.destroyAllWindows ()

Mari lihat apa yang berlaku dalam kod ini. Baris pertama mengimport perpustakaan openCV kami untuk menggunakan semua fungsinya. fungsi VideoCapture (0) mula mengalirkan video langsung dari sumber yang ditentukan oleh fungsi ini, dalam kes ini adalah 0 yang bermaksud kamera raspi. jika anda mempunyai banyak kamera, nombor yang berbeza harus diletakkan. video.read () akan membaca setiap bingkai berasal dari kamera dan menyimpannya dalam pemboleh ubah yang disebut "bingkai". fungsi flip () akan membalikkan gambar berkenaan dengan paksi-y (secara menegak) kerana saya memasang kamera saya secara terbalik. imshow () akan memaparkan bingkai kami yang diketuai oleh perkataan "original" dan imwrite () akan menyimpan foto kami sebagai original.jpg. waitKey (1) akan menunggu 1 ms untuk sebarang butang papan kekunci ditekan dan mengembalikan kod ASCIInya. jika butang melarikan diri (esc) ditekan, nilai perpuluhan 27 dikembalikan dan akan putus gelung dengan sewajarnya. video.release () akan berhenti merakam dan memusnahkanAllWindows () akan menutup setiap gambar yang dibuka oleh fungsi imshow ().

Saya cadangkan menguji foto anda dengan kaedah kedua untuk membiasakan diri dengan fungsi openCV. Gambar disimpan dalam direktori "/pi/original.jpg". Foto asal yang diambil oleh kamera saya ditunjukkan di atas.

Menguji Motor:

Langkah ini penting untuk menentukan arah putaran setiap motor. Pertama, mari kita buat pengenalan ringkas mengenai prinsip kerja pemandu motor. Gambar di atas menunjukkan pin-out pemandu motor. Aktifkan A, Input 1 dan Input 2 dikaitkan dengan kawalan motor A. Aktifkan B, Input 3 dan Input 4 dikaitkan dengan kawalan motor B. Kawalan arah dibuat oleh bahagian "Input" dan kawalan kelajuan dibuat oleh bahagian "Enable". Untuk mengawal arah motor A misalnya, tetapkan Input 1 ke HIGH (3.3 V dalam hal ini kerana kita menggunakan raspberry pi) dan tetapkan Input 2 ke LOW, motor akan berputar ke arah tertentu dan dengan menetapkan nilai yang berlawanan ke Input 1 dan Input 2, motor akan berputar ke arah yang bertentangan. Sekiranya Input 1 = Input 2 = (TINGGI atau RENDAH), motor tidak akan berpusing. Aktifkan pin mengambil isyarat input Pulse Width Modulation (PWM) dari raspberry (0 hingga 3.3 V) dan jalankan motor dengan sewajarnya. Sebagai contoh, isyarat 100% PWM bermaksud kita sedang bekerja pada kelajuan maksimum dan 0% isyarat PWM bermaksud motor tidak berputar. Kod berikut digunakan untuk menentukan arah motor dan menguji kelajuannya.

masa import

import RPi. GPIO sebagai GPIO GPIO.setwarnings (False) # Steering Motor Pin steering_enable = 22 # Pin Fizikal 15 in1 = 17 # Pin Fizikal 11 in2 = 27 # Pin Fizikal 13 # Pin Throttle Motors throttle_enable = 25 # Pin Fizikal 22 in3 = 23 # Pin Fizikal 16 in4 = 24 # Pin Fizikal 18 GPIO.setmode (GPIO. BCM) # Gunakan penomboran GPIO dan bukannya penomboran fizikal GPIO.setup (in1, GPIO.out) GPIO.setup (in2, GPIO.out) GPIO. persediaan (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. TINGGI) GPIO.output (in2, GPIO. LOW) stereng = GPIO. PWM (steering_enable, 1000) # tetapkan frekuensi beralih ke 1000 Hz steering.stop () # Throttle Motors Control GPIO.output (in3, GPIO. HIGH) GPIO.output (in4, GPIO. LOW) throttle = GPIO. PWM (throttle_enable, 1000) # tetapkan frekuensi beralih ke 1000 Hz throttle.stop () time.sleep (1) throttle.start (25) # memulakan motor pada 25 % PWM signal-> (0.25 * voltan bateri) - pemandu loss steering.start (100) # memulakan motor pada isyarat 100% PWM-> (1 * Voltan Bateri) - masa kehilangan pemandu. tidur (3) throttle.stop () steering.stop ()

Kod ini akan menjalankan motor pendikit dan motor stereng selama 3 saat dan kemudian akan menghentikannya. Kehilangan pemandu boleh ditentukan dengan menggunakan voltmeter. Sebagai contoh, kita tahu bahawa isyarat PWM 100% harus memberikan voltan bateri penuh di terminal motor. Tetapi, dengan menetapkan PWM hingga 100%, saya dapati pemandu menyebabkan penurunan 3 V dan motornya mendapat 9 V dan bukannya 12 V (betul-betul apa yang saya perlukan!). Kerugian tidak linear iaitu kerugian pada 100% sangat berbeza dengan kerugian pada 25%. Setelah menjalankan kod di atas, hasil saya adalah seperti berikut:

Hasil Throttling: jika in3 = TINGGI dan in4 = RENDAH, motor pendikit akan mempunyai putaran Clock-Wise (CW) iaitu kereta akan bergerak ke depan. Jika tidak, kereta akan bergerak ke belakang.

Hasil Pemandu: jika in1 = TINGGI dan in2 = RENDAH, motor stereng akan berpusing di sebelah kiri maksimumnya iaitu kereta akan mengarahkan ke kiri. Jika tidak, kereta akan menghala ke kanan. Selepas beberapa eksperimen, saya dapati motor stereng tidak akan berpusing jika isyarat PWM tidak 100% (iaitu motor akan mengarahkan sepenuhnya ke kanan atau sepenuhnya ke kiri).

Langkah 5: Mengesan Garis Lorong dan Mengira Garisan Tajuk

Mengesan Garis Lorong dan Mengira Garisan Tajuk
Mengesan Garis Lorong dan Mengira Garisan Tajuk
Mengesan Garis Lorong dan Mengira Garisan Tajuk
Mengesan Garis Lorong dan Mengira Garisan Tajuk
Mengesan Garis Lorong dan Mengira Garisan Tajuk
Mengesan Garis Lorong dan Mengira Garisan Tajuk

Dalam langkah ini, algoritma yang akan mengawal pergerakan kereta akan dijelaskan. Gambar pertama menunjukkan keseluruhan proses. Input sistem adalah gambar, outputnya adalah theta (sudut kemudi dalam darjah). Perhatikan bahawa, pemprosesan dilakukan pada 1 gambar dan akan diulang pada semua bingkai.

Kamera:

Kamera akan mula merakam video dengan resolusi (320 x 240). Saya cadangkan menurunkan resolusi supaya anda dapat memperoleh kadar bingkai (fps) yang lebih baik kerana penurunan fps akan berlaku setelah menerapkan teknik pemprosesan pada setiap bingkai. Kod di bawah ini akan menjadi gelung utama program dan akan menambahkan setiap langkah mengatasi kod ini.

import cv2

import numpy sebagai np video = cv2. VideoCapture (0) video.set (cv2. CAP_PROP_FRAME_WIDTH, 320) # tetapkan lebar menjadi 320 p video.set (cv2. CAP_PROP_FRAME_HEIGHT, 240) # tetapkan ketinggian ke 240 p # Gelung sambil Betul: ret, frame = video.read () frame = cv2.flip (frame, -1) cv2.imshow ("original", frame) key = cv2.waitKey (1) if key == 27: rehat video.release () cv2.destroyAllWindows ()

Kod di sini akan menunjukkan gambar asal yang diperoleh pada langkah 4 dan ditunjukkan dalam gambar di atas.

Tukar ke Ruang Warna HSV:

Sekarang setelah mengambil rakaman video sebagai bingkai dari kamera, langkah selanjutnya adalah mengubah setiap bingkai menjadi ruang warna Hue, Saturation, dan Value (HSV). Kelebihan utama melakukannya adalah untuk dapat membezakan antara warna dengan tahap cahaya mereka. Dan inilah penjelasan yang baik mengenai ruang warna HSV. Penukaran ke HSV dilakukan melalui fungsi berikut:

def convert_to_HSV (bingkai):

hsv = cv2.cvtWarna (bingkai, cv2. COLOR_BGR2HSV) cv2.imshow ("HSV", hsv) mengembalikan hsv

Fungsi ini akan dipanggil dari gelung utama dan akan mengembalikan bingkai di ruang warna HSV. Bingkai yang saya perolehi dalam ruang warna HSV ditunjukkan di atas.

Kesan Warna dan Tepi Biru:

Setelah menukar gambar menjadi ruang warna HSV, sudah waktunya untuk mengesan hanya warna yang kita minati (iaitu warna biru kerana itu adalah warna garis lorong). Untuk mengekstrak warna biru dari bingkai HSV, rentang warna, tepu dan nilai harus ditentukan. rujuk di sini untuk mempunyai idea yang lebih baik mengenai nilai HSV. Selepas beberapa eksperimen, had atas dan bawah warna biru ditunjukkan dalam kod di bawah. Dan untuk mengurangkan penyimpangan keseluruhan pada setiap bingkai, tepi hanya dapat dikesan menggunakan alat pengesan tepi yang cerdik. Lebih banyak mengenai tepi cerdik terdapat di sini. Satu peraturan adalah memilih parameter fungsi Canny () dengan nisbah 1: 2 atau 1: 3.

def detect_edges (bingkai):

lower_blue = np.array ([90, 120, 0], dtype = "uint8") # had bawah warna biru upper_blue = np.array ([150, 255, 255], dtype = "uint8") # had atas topeng warna biru = cv2.inRange (hsv, lower_blue, upper_blue) # topeng ini akan menyaring segala-galanya tetapi biru # mengesan tepi tepi = cv2. Canny (topeng, 50, 100) cv2.imshow ("tepi", tepi) tepi kembali

Fungsi ini juga akan dipanggil dari gelung utama yang mengambil parameter bingkai ruang warna HSV dan mengembalikan bingkai bermata. Bingkai tepi yang saya perolehi terdapat di atas.

Pilih Kawasan Menarik (ROI):

Memilih kawasan minat sangat penting untuk hanya memfokuskan pada 1 kawasan bingkai. Dalam kes ini, saya tidak mahu kereta melihat banyak barang di persekitaran. Saya hanya mahu kereta itu fokus pada garis lorong dan mengabaikan perkara lain. P. S: sistem koordinat (paksi x dan y) bermula dari sudut kiri atas. Dengan kata lain, titik (0, 0) bermula dari sudut kiri atas. paksi-y menjadi tinggi dan paksi-x menjadi lebar. Kod di bawah ini memilih kawasan minat untuk hanya fokus pada bahagian bawah bingkai.

def region_of_interest (tepi):

tinggi, lebar = tepi. bentuk # ekstrak ketinggian dan lebar topeng bingkai tepi = np.zeros_like (tepi) # buat matriks kosong dengan dimensi bingkai tepi yang sama # hanya fokus separuh bahagian bawah skrin # tentukan koordinat 4 titik (kiri bawah, kiri atas, kanan atas, kanan bawah) poligon = np.array (

Fungsi ini akan mengambil bingkai bermata sebagai parameter dan melukis poligon dengan 4 titik pratetap. Ia hanya akan menumpukan pada apa yang ada di dalam poligon dan mengabaikan semua yang ada di luarnya. Rangka minat kawasan saya ditunjukkan di atas.

Kesan Segmen Garis:

Hough transform digunakan untuk mengesan segmen garis dari kerangka tepi. Hough transform adalah teknik untuk mengesan sebarang bentuk dalam bentuk matematik. Ia dapat mengesan hampir semua objek walaupun diputarbelitkan mengikut sebilangan suara. rujukan hebat untuk transformasi Hough ditunjukkan di sini. Untuk aplikasi ini, fungsi cv2. HoughLinesP () digunakan untuk mengesan garis di setiap bingkai. Parameter penting fungsi ini adalah:

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

  • Frame: adalah bingkai yang ingin kita mengesan garis.
  • rho: Ini adalah ketepatan jarak dalam piksel (biasanya = 1)
  • theta: ketepatan sudut dalam radian (selalu = np.pi / 180 ~ 1 darjah)
  • min_threshold: undi minimum yang harus diambil untuk dianggap sebagai garis
  • minLineLength: panjang garis minimum dalam piksel. Garis yang lebih pendek daripada nombor ini tidak dianggap sebagai garis.
  • maxLineGap: jurang maksimum dalam piksel antara 2 baris untuk dianggap sebagai 1 baris. (Ini tidak digunakan dalam kes saya kerana garis lorong yang saya gunakan tidak mempunyai jurang).

Fungsi ini mengembalikan titik akhir garis. Fungsi berikut dipanggil dari gelung utama saya untuk mengesan garis menggunakan Hough transform:

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) garis_pembahagian baris

Purata cerun dan pintasan (m, b):

ingat bahawa persamaan garis diberikan oleh y = mx + b. Di mana m adalah cerun garis dan b adalah pintasan-y. Di bahagian ini, purata cerun dan pintasan segmen garis yang dikesan menggunakan transformasi Hough akan dikira. Sebelum melakukannya, mari kita lihat foto bingkai asal yang ditunjukkan di atas. Lorong kiri kelihatan naik ke atas sehingga mempunyai cerun negatif (ingat titik permulaan sistem koordinat?). Dengan kata lain, garis lorong kiri mempunyai x1 <x2 dan y2 x1 dan y2> y1 yang akan memberikan cerun positif. Jadi, semua garis dengan cerun positif dianggap sebagai titik lorong kanan. Sekiranya garis menegak (x1 = x2), cerun akan menjadi tak terhingga. Dalam kes ini, kami akan melangkau semua garis menegak untuk mengelakkan berlakunya ralat. Untuk menambahkan lebih tepat pada pengesanan ini, setiap bingkai dibahagikan kepada dua kawasan (kanan dan kiri) melalui 2 garis sempadan. Semua titik lebar (titik paksi-x) lebih besar daripada garis sempadan kanan, dikaitkan dengan pengiraan lorong kanan. Dan jika semua titik lebar kurang dari garis batas kiri, ia dihubungkan dengan pengiraan lorong kiri. Fungsi berikut mengambil kerangka di bawah pemprosesan dan segmen lorong yang dikesan menggunakan transformasi Hough dan mengembalikan cerun dan pintasan rata-rata dua garis lorong.

def rata_slope_intercept (bingkai, garis_segmen):

lane_lines = if line_segments TIADA: cetak ("tidak ada segmen garis yang dikesan") kembali lane_lines tinggi, lebar, _ = frame.shape left_fit = right_fit = sempadan = left_region_boundary = width * (1 - sempadan) right_region_boundary = lebar * sempadan untuk line_segment di line_segments: untuk x1, y1, x2, y2 in line_segment: if x1 == x2: print ("skipping vertical lines (slope = infinity)") terus sesuai = np.polyfit ((x1, x2), (y1, y2), 1) cerun = (y2 - y1) / (x2 - x1) pintasan = y1 - (cerun * x1) jika cerun <0: if x1 <left_region_boundary dan x2 right_region_boundary dan x2> right_region_boundary: right_fit. tambahkan ((cerun, pintasan)) 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)) jika len (right_fit)> 0: lane_lines.append (make_points (frame, right_fit_average)) # lane_lines adalah susunan 2-D yang terdiri daripada koordinat garis lorong kanan dan kiri # contohnya: lan e_lines =

make_points () adalah fungsi pembantu untuk fungsi_slope_intercept rata-rata () yang akan mengembalikan koordinat terikat garis lorong (dari bawah ke tengah bingkai).

def make_points (bingkai, garis):

tinggi, lebar, _ = bingkai.lereng bentuk, pintasan = garis y1 = tinggi # bahagian bawah bingkai y2 = int (y1 / 2) # buat titik dari tengah bingkai ke bawah jika cerun == 0: cerun = 0.1 x1 = int ((y1 - pintasan) / cerun) x2 = int ((y2 - pintasan) / cerun) kembali

Untuk mengelakkan pembahagian dengan 0, syarat ditunjukkan. Sekiranya cerun = 0 yang bermaksud y1 = y2 (garis mendatar), berikan nilai cerun berhampiran 0. Ini tidak akan mempengaruhi prestasi algoritma dan juga akan mengelakkan kes mustahil (membahagi dengan 0).

Untuk memaparkan garis lorong pada bingkai, fungsi berikut digunakan:

def display_lines (bingkai, garis, line_color = (0, 255, 0), line_width = 6): # warna garis (B, G, R)

line_image = np.zeros_like (bingkai) jika garis tidak ada line_width) line_image = cv2.addWeighted (frame, 0.8, line_image, 1, 1) return line_image

Fungsi cv2.addWeighted () mengambil parameter berikut dan ia digunakan untuk menggabungkan dua gambar tetapi dengan memberi masing-masing berat.

cv2.addWeighted (image1, alpha, image2, beta, gamma)

Dan mengira gambar output menggunakan persamaan berikut:

output = alpha * image1 + beta * image2 + gamma

Maklumat lebih lanjut mengenai fungsi cv2.addWeighted () diperoleh di sini.

Hitung dan Paparkan Baris Tajuk:

Ini adalah langkah terakhir sebelum kita menggunakan kelajuan pada motor kita. Garis menuju bertanggung jawab untuk memberikan arah kemudi motor di mana ia harus berputar dan memberi motor pendikit kecepatan di mana mereka akan beroperasi. Garis tajuk pengiraan adalah fungsi trigonometri murni, tan dan atan (tan ^ -1) digunakan. Beberapa kes yang melampau adalah ketika kamera mengesan hanya satu garis lorong atau ketika tidak mengesan garis mana pun. Semua kes ini ditunjukkan dalam fungsi berikut:

def get_steering_angle (bingkai, lorong_ baris):

tinggi, lebar, _ = frame.shape if len (lane_lines) == 2: # jika dua jalur lorong dikesan _, _, left_x2, _ = lane_lines [0] [0] # ekstrak x2 kiri dari lane_lines array _, _, right_x2, _ = lane_lines [1] [0] # ekstrak x2 kanan dari lane_lines array pertengahan = int (lebar / 2) x_offset = (kiri_x2 + kanan_x2) / 2 - pertengahan y_offset = int (tinggi / 2) elif len (lane_lines) == 1: # jika hanya satu baris yang dikesan x1, _, x2, _ = lane_lines [0] [0] x_offset = x2 - x1 y_offset = int (tinggi / 2) elif len (lane_lines) == 0: # jika tidak ada garis yang dikesan

x_offset dalam kes pertama adalah perbezaan purata ((kanan x2 + kiri x2) / 2) dari bahagian tengah skrin. y_offset selalu diambil sebagai tinggi / 2. Gambar terakhir di atas menunjukkan contoh garis arah. angle_to_mid_radians sama dengan "theta" yang ditunjukkan dalam gambar terakhir di atas. Jika steering_angle = 90, ini bermakna kereta itu mempunyai garis arah yang tegak lurus dengan garis "tinggi / 2" dan kereta akan bergerak ke depan tanpa kemudi. Sekiranya steering_angle> 90, kereta harus memandu ke kanan atau sebaliknya ke kiri. Untuk memaparkan baris tajuk, fungsi berikut digunakan:

def display_heading_line (bingkai, steering_angle, line_color = (0, 0, 255), line_width = 5)

heading_image = np.zeros_like (frame) tinggi, lebar, _ = frame.shape steering_angle_radian = steering_angle / 180.0 * math.pi x1 = int (lebar / 2) y1 = tinggi x2 = int (x1 - tinggi / 2 / math.tan (steering_angle_radian)) y2 = int (tinggi / 2) cv2.line (heading_image, (x1, y1), (x2, y2), line_color, line_width) heading_image = cv2.addWeighted (bingkai, 0.8, tajuk_ gambar, 1, 1) kembali tajuk_ gambar

Fungsi di atas mengambil bingkai di mana garis arah akan digambar dan sudut kemudi sebagai input. Ia mengembalikan gambar dari baris tajuk. Rangka garis tajuk yang diambil dalam kes saya ditunjukkan dalam gambar di atas.

Menggabungkan Semua Kod Bersama:

Kodnya kini siap dipasang. Kod berikut menunjukkan gelung utama program yang memanggil setiap fungsi:

import cv2

import numpy sebagai np video = cv2. VideoCapture (0) video.set (cv2. CAP_PROP_FRAME_WIDTH, 320) video.set (cv2. CAP_PROP_FRAME_HEIGHT, 240) sementara True: ret, frame = video.read () frame = cv2.flip (bingkai, -1) #Memanggil fungsi hsv = convert_to_HSV (frame) edge = detect_edges (hsv) roi = region_of_interest (tepi) line_segments = detect_line_segments (roi) lane_lines = average_slope_intercept (frame, line_segments) lane_lines_image = liner_lines_image = get_steering_angle (frame, lane_lines) heading_image = display_heading_line (lane_lines_image, steering_angle) key = cv2.waitKey (1) if key == 27: break video.release () cv2.destroyAllWindows ()

Langkah 6: Menggunakan PD Control

Mengamalkan PD Control
Mengamalkan PD Control

Sekarang kita mempunyai sudut kemudi kita untuk disalurkan ke motor. Seperti disebutkan sebelumnya, jika sudut kemudi lebih besar dari 90, kereta harus membelok ke kanan atau sebaliknya. Saya menggunakan kod mudah yang memutar motor stereng ke kanan jika sudut melebihi 90 dan membelok ke kiri jika sudut stereng kurang dari 90 pada kelajuan pendikit tetap (10% PWM) tetapi saya mendapat banyak kesalahan. Kesalahan utama yang saya dapat ialah ketika kereta menghampiri giliran, motor stereng bertindak secara langsung tetapi motor pendikit tersekat. Saya cuba meningkatkan kelajuan pendikit menjadi (20% PWM) secara bergiliran tetapi berakhir dengan robot keluar dari lorong. Saya memerlukan sesuatu yang meningkatkan kelajuan pendikit jika sudut kemudi sangat besar dan sedikit meningkatkan kelajuan jika sudut stereng tidak begitu besar maka menurunkan kelajuan ke nilai awal ketika kereta menghampiri 90 darjah (bergerak lurus). Penyelesaiannya adalah dengan menggunakan alat kawalan PD.

Pengawal PID bermaksud pengawal Proportional, Integral dan Derivative. Pengawal linear jenis ini banyak digunakan dalam aplikasi robotik. Gambar di atas menunjukkan gelung kawalan maklum balas PID khas. Matlamat pengawal ini adalah untuk mencapai "setpoint" dengan cara yang paling cekap tidak seperti pengawal "on - off" yang menghidupkan atau mematikan kilang mengikut beberapa keadaan. Beberapa kata kunci harus diketahui:

  • Setpoint: adalah nilai yang diinginkan yang ingin dicapai oleh sistem anda.
  • Nilai sebenar: adalah nilai sebenar yang dirasakan oleh sensor.
  • Ralat: adalah perbezaan antara setpoint dan nilai sebenar (ralat = Titik set - Nilai sebenar).
  • Pemboleh ubah terkawal: dari namanya, pemboleh ubah yang ingin anda kendalikan.
  • Kp: Pemalar berkadar.
  • Ki: Pemalar integral.
  • Kd: Pemalar terbitan.

Ringkasnya, gelung sistem kawalan PID berfungsi seperti berikut:

  • Pengguna menentukan setpoint yang diperlukan agar sistem dapat dicapai.
  • Kesalahan dikira (error = setpoint - sebenar).
  • Pengawal P menghasilkan tindakan yang sebanding dengan nilai ralat. (ralat meningkat, tindakan P juga meningkat)
  • Pengawal saya akan mengintegrasikan ralat dari masa ke masa yang menghilangkan ralat keadaan tetap sistem tetapi meningkatkan kelebihannya.
  • Pengawal D hanyalah turunan masa untuk ralat. Dengan kata lain, ini adalah kecerunan kesalahan. Ia melakukan tindakan sebanding dengan turunan kesalahan. Pengawal ini meningkatkan kestabilan sistem.
  • Keluaran pengawal akan menjadi jumlah ketiga pengawal. Keluaran pengawal akan menjadi 0 jika ralat menjadi 0.

Penjelasan hebat mengenai pengawal PID boleh didapati di sini.

Kembali ke kereta menjaga lorong, pemboleh ubah terkawal saya adalah kelajuan pendikit (kerana stereng hanya mempunyai dua keadaan sama ada kanan atau kiri). Pengawal PD digunakan untuk tujuan ini kerana tindakan D meningkatkan kelajuan pendikit jika perubahan ralat sangat besar (iaitu penyimpangan besar) dan melambatkan kereta jika perubahan ralat ini menghampiri 0. Saya melakukan langkah-langkah berikut untuk melaksanakan PD pengawal:

  • Tetapkan setpoint hingga 90 darjah (saya selalu mahu kereta bergerak lurus)
  • Dikira sudut sisihan dari tengah
  • Penyimpangan memberikan dua maklumat: Seberapa besar kesalahan itu (besarnya penyimpangan) dan arah mana yang harus diambil oleh motor kemudi (tanda penyimpangan). Sekiranya penyimpangan positif, kereta harus menghala ke kanan atau sebaliknya.
  • Oleh kerana penyimpangan sama ada negatif atau positif, pemboleh ubah "ralat" ditakrifkan dan selalu sama dengan nilai mutlak penyimpangan.
  • Kesalahan didarabkan dengan Kp tetap.
  • Kesalahan mengalami pembezaan masa dan didarabkan dengan Kd tetap.
  • Kelajuan motor dikemas kini dan gelung bermula semula.

Kod berikut digunakan dalam gelung utama untuk mengawal kelajuan motor pendikit:

kelajuan = 10 # kelajuan operasi dalam% PWM

#Variabel yang akan dikemas kini setiap gelung lastTime = 0 lastError = 0 # pemalar PD Kp = 0.4 Kd = Kp * 0.65 Walaupun Benar: sekarang = time.time () # pemboleh ubah masa semasa dt = sekarang - deviasi LastTime = steering_angle - 90 # setara ke angle_to_mid_deg ralat pemboleh ubah = abs (penyimpangan) jika sisihan -5: # jangan mengarahkan jika terdapat penyimpangan julat ralat 10 darjah = 0 ralat = 0 GPIO.output (in1, GPIO. LOW) GPIO.output (in2, GPIO. LOW) steering.stop () elif deviation> 5: # steer right if the deviation is positive GPIO.output (in1, GPIO. LOW) GPIO.output (in2, GPIO. HIGH) steering.start (100) elif deviation < -5: # kemudi kiri jika penyimpangan negatif GPIO.output (in1, GPIO. HIGH) GPIO.output (in2, GPIO. LOW) steering.start (100) derivatif = kd * (error - lastError) / dt proporsional = kp * ralat PD = int (speed + derivative + proportional) spd = abs (PD) jika spd> 25: spd = 25 throttle.start (spd) lastError = error lastTime = time.time ()

Sekiranya ralat sangat besar (penyimpangan dari tengah tinggi), tindakan berkadar dan terbitannya tinggi sehingga menghasilkan kelajuan pendikit yang tinggi. Apabila ralat menghampiri 0 (penyimpangan dari tengah rendah), tindakan terbitan bertindak terbalik (cerun negatif) dan kelajuan pendikit semakin rendah untuk mengekalkan kestabilan sistem. Kod penuh dilampirkan di bawah.

Langkah 7: Hasil

Video di atas menunjukkan hasil yang saya perolehi. Ia memerlukan lebih banyak penyesuaian dan penyesuaian selanjutnya. Saya menyambungkan raspberry pi ke skrin paparan LCD saya kerana streaming video di rangkaian saya mempunyai latensi tinggi dan sangat mengecewakan untuk bekerja, sebab itulah terdapat kabel yang disambungkan ke raspberry pi dalam video. Saya menggunakan papan busa untuk menarik trek.

Saya menunggu untuk mendengar cadangan anda untuk menjadikan projek ini lebih baik! Oleh kerana saya berharap arahan ini cukup baik untuk memberi anda beberapa maklumat baru.

Disyorkan: