Daftar Isi:

Autonomous Lane-Keeping Car Menggunakan Raspberry Pi dan OpenCV: 7 Langkah (dengan Gambar)
Autonomous Lane-Keeping Car Menggunakan Raspberry Pi dan OpenCV: 7 Langkah (dengan Gambar)

Video: Autonomous Lane-Keeping Car Menggunakan Raspberry Pi dan OpenCV: 7 Langkah (dengan Gambar)

Video: Autonomous Lane-Keeping Car Menggunakan Raspberry Pi dan OpenCV: 7 Langkah (dengan Gambar)
Video: OpenCV Raspberry Pi Self Driving Car using Neural Networks - Part1/3 2024, November
Anonim
Autonomous Lane-Keeping Car Menggunakan Raspberry Pi dan OpenCV
Autonomous Lane-Keeping Car Menggunakan Raspberry Pi dan OpenCV

Dalam instruksi ini, robot penjaga jalur otonom akan diimplementasikan dan akan melewati langkah-langkah berikut:

  • Mengumpulkan Bagian
  • Menginstal prasyarat perangkat lunak
  • Perakitan perangkat keras
  • Tes Pertama
  • Mendeteksi garis jalur dan menampilkan garis pemandu menggunakan openCV
  • Menerapkan pengontrol PD
  • Hasil

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 proyek ini:

  • Mobil RC: Saya mendapatkan milik saya dari toko lokal di negara saya. Dilengkapi dengan 3 motor (2 untuk pelambatan dan 1 untuk kemudi). Kerugian utama dari mobil ini adalah bahwa kemudi terbatas antara "no steering" dan "full steering". Dengan kata lain, tidak dapat mengarahkan pada sudut tertentu, tidak seperti mobil RC servo-steering. Anda dapat menemukan car kit serupa yang dirancang khusus untuk raspberry pi dari sini.
  • Raspberry pi 3 model b+: ini adalah otak mobil yang akan menangani banyak tahapan pemrosesan. Ini didasarkan pada prosesor quad core 64-bit dengan clock 1,4 GHz. Saya mendapatkan milik saya dari sini.
  • Modul kamera Raspberry pi 5 mp: Mendukung perekaman 1080p @ 30 fps, 720p @ 60 fps, dan 640x480p 60/90. Ini juga mendukung antarmuka serial yang dapat dicolokkan langsung ke raspberry pi. Ini bukan pilihan terbaik untuk aplikasi pemrosesan gambar tetapi cukup untuk proyek ini dan juga sangat murah. Saya mendapatkan milik saya dari sini.
  • Driver Motor: Digunakan untuk mengontrol arah dan kecepatan motor DC. Ini mendukung kontrol 2 motor dc dalam 1 papan dan dapat menahan 1,5 A.
  • Bank Daya (Opsional): Saya menggunakan bank daya (diberi peringkat 5V, 3A) untuk menyalakan raspberry pi secara terpisah. Konverter step down (konverter buck: arus keluaran 3A) harus digunakan untuk menyalakan raspberry pi dari 1 sumber.
  • Baterai LiPo 3s (12 V): Baterai Lithium Polymer dikenal karena kinerjanya yang sangat baik di bidang robotika. Ini digunakan untuk memberi daya pada pengemudi motor. Saya membeli milik saya dari sini.
  • Kabel jumper pria ke pria dan wanita ke wanita.
  • Pita sisi ganda: Digunakan untuk memasang komponen pada mobil RC.
  • Pita biru: Ini adalah komponen yang sangat penting dari proyek ini, digunakan untuk membuat dua jalur di mana mobil akan berkendara di antaranya. Anda dapat memilih warna apa pun yang Anda inginkan tetapi saya sarankan memilih warna yang berbeda dari yang ada di lingkungan sekitar.
  • Zip dasi dan batang kayu.
  • Obeng.

Langkah 2: Menginstal OpenCV di Raspberry Pi dan Mengatur Tampilan Jarak Jauh

Menginstal OpenCV di Raspberry Pi dan Mengatur Tampilan Jarak Jauh
Menginstal OpenCV di Raspberry Pi dan Mengatur Tampilan Jarak Jauh

Langkah ini sedikit mengganggu dan akan memakan waktu.

OpenCV (Open source Computer Vision) adalah open source computer vision dan perpustakaan perangkat lunak pembelajaran mesin. Perpustakaan memiliki lebih dari 2500 algoritma yang dioptimalkan. Ikuti panduan yang sangat mudah INI untuk menginstal openCV pada raspberry pi Anda serta menginstal raspberry pi OS (jika Anda masih belum melakukannya). Harap dicatat bahwa proses pembuatan openCV mungkin memakan waktu sekitar 1,5 jam di ruangan yang didinginkan dengan baik (karena suhu prosesor akan menjadi sangat tinggi!) jadi minumlah teh dan tunggu dengan sabar:D.

Untuk tampilan jarak jauh, ikuti juga panduan INI untuk mengatur akses jarak jauh ke raspberry pi Anda dari perangkat Windows/Mac Anda.

Langkah 3: Menghubungkan Bagian Bersama

Menghubungkan Bagian Bersama
Menghubungkan Bagian Bersama
Menghubungkan Bagian Bersama
Menghubungkan Bagian Bersama
Menghubungkan Bagian Bersama
Menghubungkan Bagian Bersama

Gambar di atas menunjukkan hubungan antara raspberry pi, modul kamera dan driver motor. Harap dicatat bahwa motor yang saya gunakan menyerap 0,35 A pada masing-masing 9 V yang membuatnya aman bagi pengemudi motor untuk menjalankan 3 motor secara bersamaan. Dan karena saya ingin mengontrol kecepatan 2 motor pelambatan (1 belakang dan 1 depan) dengan cara yang persis sama, saya menghubungkannya ke port yang sama. Saya memasang driver motor di sisi kanan mobil menggunakan double tape. Sedangkan untuk modul kamera, saya memasukkan zip tie di antara lubang sekrup seperti yang ditunjukkan gambar di atas. Kemudian, saya memasangkan kamera ke batang kayu sehingga saya bisa mengatur posisi kamera sesuai keinginan. Cobalah untuk memasang kamera di tengah mobil sebanyak mungkin. Saya sarankan untuk menempatkan kamera minimal 20 cm di atas permukaan tanah agar bidang pandang di depan mobil menjadi lebih baik. Skema Fritzing terlampir di bawah ini.

Langkah 4: Tes Pertama

Tes Pertama
Tes Pertama
Tes Pertama
Tes Pertama

Pengujian Kamera:

Setelah kamera terpasang, dan perpustakaan openCV dibuat, saatnya untuk menguji gambar pertama kita! Kami akan mengambil foto dari pi cam dan menyimpannya sebagai "original.jpg". Dapat dilakukan dengan 2 cara:

1. Menggunakan Perintah Terminal:

Buka jendela terminal baru dan ketik perintah berikut:

raspistill -o original.jpg

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

2. Menggunakan python IDE (saya menggunakan IDLE):

Buka sketsa baru dan tulis kode berikut:

impor cv2

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

Mari kita lihat apa yang terjadi dalam kode ini. Baris pertama mengimpor perpustakaan openCV kami untuk menggunakan semua fungsinya. fungsi VideoCapture(0) memulai streaming video langsung dari sumber yang ditentukan oleh fungsi ini, dalam hal ini adalah 0 yang berarti kamera raspi. jika Anda memiliki beberapa kamera, nomor yang berbeda harus ditempatkan. video.read() akan membaca setiap frame yang berasal dari kamera dan menyimpannya dalam sebuah variabel yang disebut "frame". flip() fungsi akan membalik gambar sehubungan dengan sumbu y (vertikal) karena saya memasang kamera saya secara terbalik. imshow() akan menampilkan frame kita yang dipimpin oleh kata "original" dan imwrite() akan menyimpan foto kita sebagai original.jpg. waitKey(1) akan menunggu selama 1 md untuk menekan tombol keyboard dan mengembalikan kode ASCII-nya. jika tombol escape (esc) ditekan, nilai desimal 27 dikembalikan dan akan memutus loop yang sesuai. video.release() akan berhenti merekam dan destroyAllWindows() akan menutup setiap gambar yang dibuka oleh fungsi imshow().

Saya sarankan untuk menguji foto Anda dengan metode kedua untuk membiasakan diri dengan fungsi openCV. Gambar disimpan di direktori "/pi/original.jpg". Foto asli yang diambil kamera saya ditunjukkan di atas.

Pengujian Motor:

Langkah ini penting untuk menentukan arah putaran setiap motor. Pertama, mari kita kenalan singkat tentang prinsip kerja driver motor. Gambar di atas menunjukkan pin-out driver motor. Aktifkan A, Input 1 dan Input 2 dikaitkan dengan kontrol motor A. Aktifkan B, Input 3 dan Input 4 dikaitkan dengan kontrol motor B. Kontrol arah ditetapkan oleh bagian "Input" dan kontrol kecepatan ditetapkan oleh bagian "Aktifkan". Untuk mengontrol arah motor A misalnya, atur Input 1 ke HIGH (3,3 V dalam hal ini karena kita menggunakan raspberry pi) dan atur Input 2 ke LOW, motor akan berputar ke arah tertentu dan dengan menyetel nilai yang berlawanan ke Input 1 dan Input 2, motor akan berputar ke arah yang berlawanan. Jika Input 1 = Input 2 = (TINGGI atau RENDAH), motor tidak akan berputar. Aktifkan pin, ambil sinyal input Pulse Width Modulation (PWM) dari raspberry (0 hingga 3,3 V) dan jalankan motor yang sesuai. Misalnya sinyal PWM 100% berarti kita bekerja pada kecepatan maksimum dan sinyal PWM 0% berarti motor tidak berputar. Kode berikut digunakan untuk menentukan arah motor dan menguji kecepatannya.

waktu impor

import RPi. GPIO as GPIO GPIO.setwarnings(False) # Pin Motor Kemudi steering_enable = 22 # Pin Fisik 15 in1 = 17 # Pin Fisik 11 in2 = 27 # Pin Fisik 13 # Pin Throttle Motors throttle_enable = 25 # Pin Fisik 22 in3 = 23 # Pin Fisik 16 in4 = 24 # Pin Fisik 18 GPIO.setmode(GPIO. BCM) # Gunakan penomoran GPIO daripada penomoran fisik 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) # Kontrol Motor Kemudi GPIO.output(in1, GPIO. HIGH) GPIO.output(in2, GPIO. LOW) steering = GPIO. PWM(steering_enable, 1000) # atur frekuensi switching ke 1000 Hz steering.stop() # Kontrol Motor Throttle GPIO.output(in3, GPIO. HIGH) GPIO.output(in4, GPIO. LOW) throttle = GPIO. PWM(throttle_enable, 1000) # atur frekuensi switching ke 1000 Hz throttle.stop() time.sleep(1) throttle.start(25) # memulai motor pada 25 % sinyal PWM-> (0,25 * Tegangan baterai) - driver loss steering.start(100) # menghidupkan motor pada sinyal PWM 100%-> (1 * Tegangan Baterai) - waktu kehilangan pengemudi.sleep(3) throttle.stop() steering.stop()

Kode ini akan menjalankan motor pelambatan dan motor kemudi selama 3 detik dan kemudian akan menghentikannya. (Kerugian pengemudi) dapat ditentukan dengan menggunakan voltmeter. Sebagai contoh, kita tahu bahwa sinyal PWM 100% harus memberikan tegangan baterai penuh pada terminal motor. Tetapi, dengan menyetel PWM ke 100%, saya menemukan bahwa driver menyebabkan penurunan 3 V dan motor menjadi 9 V, bukan 12 V (persis seperti yang saya butuhkan!). Kerugiannya tidak linier yaitu kerugian pada 100% sangat berbeda dengan kerugian pada 25%. Setelah menjalankan kode di atas, hasil saya adalah sebagai berikut:

Hasil Throttling: jika in3 = HIGH dan in4 = LOW, motor throttling akan memiliki putaran Clock-Wise (CW) yaitu mobil akan bergerak maju. Jika tidak, mobil akan bergerak mundur.

Hasil Kemudi: jika in1 = TINGGI dan in2 = RENDAH, motor kemudi akan berbelok ke kiri maksimal yaitu mobil akan membelok ke kiri. Jika tidak, mobil akan berbelok ke kanan. Setelah beberapa percobaan, saya menemukan bahwa motor kemudi tidak akan berputar jika sinyal PWM tidak 100% (yaitu motor akan mengarahkan sepenuhnya ke kanan atau sepenuhnya ke kiri).

Langkah 5: Mendeteksi Garis Jalur dan Menghitung Garis Pos

Mendeteksi Garis Jalur dan Menghitung Garis Pos
Mendeteksi Garis Jalur dan Menghitung Garis Pos
Mendeteksi Garis Jalur dan Menghitung Garis Pos
Mendeteksi Garis Jalur dan Menghitung Garis Pos
Mendeteksi Garis Jalur dan Menghitung Garis Pos
Mendeteksi Garis Jalur dan Menghitung Garis Pos

Pada langkah ini akan dijelaskan algoritma yang akan mengontrol pergerakan mobil. Gambar pertama menunjukkan seluruh proses. Input dari sistem adalah gambar, outputnya adalah theta (sudut kemudi dalam derajat). Perhatikan bahwa, pemrosesan dilakukan pada 1 gambar dan akan diulang pada semua frame.

Kamera:

Kamera akan mulai merekam video dengan resolusi (320 x 240). Saya sarankan untuk menurunkan resolusi sehingga Anda bisa mendapatkan frame rate (fps) yang lebih baik karena penurunan fps akan terjadi setelah menerapkan teknik pemrosesan pada setiap frame. Kode di bawah ini akan menjadi loop utama program dan akan menambahkan setiap langkah di atas kode ini.

impor cv2

import numpy as np video = cv2. VideoCapture(0) video.set(cv2. CAP_PROP_FRAME_WIDTH, 320) # atur lebar ke 320 p video.set(cv2. CAP_PROP_FRAME_HEIGHT, 240) # atur tinggi ke 240 p # Perulangan while Benar: 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()

Kode di sini akan menunjukkan gambar asli yang diperoleh pada langkah 4 dan ditunjukkan pada gambar di atas.

Konversikan ke Ruang Warna HSV:

Sekarang setelah mengambil rekaman video sebagai bingkai dari kamera, langkah selanjutnya adalah mengubah setiap bingkai menjadi ruang warna Hue, Saturation, dan Value (HSV). Keuntungan utama melakukannya adalah dapat membedakan warna berdasarkan tingkat pencahayaannya. Dan inilah penjelasan yang bagus tentang ruang warna HSV. Konversi ke HSV dilakukan melalui fungsi berikut:

def convert_to_HSV(bingkai):

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

Fungsi ini akan dipanggil dari loop utama dan akan mengembalikan frame dalam ruang warna HSV. Bingkai yang saya peroleh dalam ruang warna HSV ditunjukkan di atas.

Deteksi Warna dan Tepi Biru:

Setelah mengubah gambar menjadi ruang warna HSV, saatnya untuk mendeteksi hanya warna yang kita minati (yaitu warna biru karena itu adalah warna garis jalur). Untuk mengekstrak warna biru dari bingkai HSV, rentang rona, saturasi, dan nilai harus ditentukan. lihat di sini untuk mendapatkan ide yang lebih baik tentang nilai HSV. Setelah beberapa percobaan, batas atas dan bawah warna biru ditunjukkan pada kode di bawah ini. Dan untuk mengurangi distorsi keseluruhan pada setiap frame, tepi dideteksi hanya menggunakan detektor tepi cerdik. Lebih lanjut tentang tepi cerdik ditemukan di sini. Aturan praktisnya adalah memilih parameter fungsi Canny() dengan rasio 1:2 atau 1:3.

def detect_edges(bingkai):

lower_blue = np.array([90, 120, 0], dtype = "uint8") # batas bawah warna biru upper_blue = np.array([150, 255, 255], dtype="uint8") # batas atas topeng warna biru = cv2.inRange(hsv, lower_blue, upper_blue) # topeng ini akan menyaring semuanya kecuali biru # mendeteksi tepi tepi = cv2. Canny(mask, 50, 100) cv2.imshow("tepi", tepi) mengembalikan tepi

Fungsi ini juga akan dipanggil dari loop utama yang mengambil sebagai parameter bingkai ruang warna HSV dan mengembalikan bingkai bermata. Bingkai bermata yang saya dapatkan ditemukan di atas.

Pilih Wilayah Minat (ROI):

Memilih wilayah yang diinginkan sangat penting untuk fokus hanya pada 1 wilayah bingkai. Dalam hal ini, saya tidak ingin mobil melihat banyak barang di lingkungan. Saya hanya ingin mobil fokus pada garis lajur dan mengabaikan hal lain. P. S: sistem koordinat (sumbu x dan y) dimulai dari sudut kiri atas. Dengan kata lain, titik (0, 0) dimulai dari sudut kiri atas. sumbu y sebagai tinggi dan sumbu x sebagai lebar. Kode di bawah ini memilih wilayah yang diinginkan untuk fokus hanya pada bagian bawah bingkai.

def region_of_interest(tepi):

tinggi, lebar = tepi. bentuk # ekstrak tinggi dan lebar tepi bingkai topeng = np.zeros_like(tepi) # buat matriks kosong dengan dimensi bingkai tepi yang sama # hanya fokuskan setengah bagian bawah layar # tentukan koordinat 4 titik (kiri bawah, kiri atas, kanan atas, kanan bawah) poligon = np.array(

Fungsi ini akan mengambil bingkai bermata sebagai parameter dan menggambar poligon dengan 4 titik preset. Itu hanya akan fokus pada apa yang ada di dalam poligon dan mengabaikan semua yang ada di luarnya. Kerangka wilayah minat saya ditunjukkan di atas.

Deteksi Segmen Garis:

Transformasi Hough digunakan untuk mendeteksi segmen garis dari bingkai bermata. Transformasi Hough adalah teknik untuk mendeteksi bentuk apapun dalam bentuk matematika. Itu dapat mendeteksi hampir semua objek bahkan jika itu terdistorsi menurut beberapa jumlah suara. referensi yang bagus untuk transformasi Hough ditampilkan di sini. Untuk aplikasi ini, fungsi cv2. HoughLinesP() digunakan untuk mendeteksi garis pada setiap frame. Parameter penting yang diambil fungsi ini adalah:

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

  • Frame: adalah frame yang ingin kita deteksi garisnya.
  • rho: Ini adalah presisi jarak dalam piksel (biasanya = 1)
  • theta: presisi sudut dalam radian (selalu = np.pi/180 ~ 1 derajat)
  • min_threshold: suara minimum yang harus didapatkan agar dianggap sebagai garis
  • minLineLength: panjang garis minimum dalam piksel. Garis apa pun yang lebih pendek dari angka ini tidak dianggap sebagai garis.
  • maxLineGap: celah maksimum dalam piksel antara 2 baris untuk diperlakukan sebagai 1 baris. (Ini tidak digunakan dalam kasus saya karena jalur jalur yang saya gunakan tidak memiliki celah).

Fungsi ini mengembalikan titik akhir dari sebuah garis. Fungsi berikut dipanggil dari loop utama saya untuk mendeteksi garis menggunakan transformasi 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) mengembalikan line_segments

Kemiringan rata-rata dan Intercept (m, b):

ingat bahwa persamaan garis diberikan oleh y = mx + b. Dimana m adalah kemiringan garis dan b adalah perpotongan y. Pada bagian ini, rata-rata kemiringan dan perpotongan segmen garis yang terdeteksi menggunakan transformasi Hough akan dihitung. Sebelum melakukannya, mari kita lihat foto bingkai asli yang ditunjukkan di atas. Jalur kiri tampak menanjak sehingga memiliki kemiringan negatif (ingat titik awal sistem koordinat?). Dengan kata lain, jalur kiri memiliki x1 < x2 dan y2 x1 dan y2 > y1 yang akan memberikan kemiringan positif. Jadi, semua garis dengan kemiringan positif dianggap sebagai titik jalur kanan. Dalam kasus garis vertikal (x1 = x2), kemiringan akan tak terhingga. Dalam hal ini, kami akan melewati semua garis vertikal untuk mencegah kesalahan. Untuk menambah akurasi deteksi ini, setiap frame dibagi menjadi dua wilayah (kanan dan kiri) melalui 2 garis batas. Semua titik lebar (titik sumbu x) yang lebih besar dari garis batas kanan, diasosiasikan dengan perhitungan lajur kanan. Dan jika semua titik lebar lebih kecil dari garis batas kiri, maka diasosiasikan dengan perhitungan lajur kiri. Fungsi berikut mengambil frame yang sedang diproses dan segmen jalur yang dideteksi menggunakan transformasi Hough dan mengembalikan kemiringan rata-rata dan intersep dari dua jalur jalur.

def average_slope_intercept(bingkai, segmen_baris):

lane_lines = jika line_segments adalah None: print("tidak ada segmen garis yang terdeteksi") kembalikan lane_lines tinggi, lebar, _ = frame.shape left_fit = right_fit = boundary = left_region_boundary = lebar * (1 - batas) right_region_boundary = lebar * batas untuk segmen_baris di segmen_baris: untuk x1, y1, x2, y2 di segmen_baris: jika x1 == x2: print("melewati garis vertikal (kemiringan = tak terhingga)") continue fit = np.polyfit((x1, x2), (y1, y2), 1) kemiringan = (y2 - y1) / (x2 - x1) intersep = y1 - (kemiringan * x1) jika kemiringan < 0: jika x1 < batas_daerah_kiri dan x2 batas_daerah_kanan dan x2 > batas_daerah_kanan: fit_kanan. append((slope, intercept)) 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 adalah larik 2D yang terdiri dari koordinat garis jalur kanan dan kiri # contoh: lan e_lines =

make_points() adalah fungsi pembantu untuk fungsi average_slope_intercept() yang akan mengembalikan koordinat batas garis jalur (dari bawah ke tengah frame).

def make_points(bingkai, garis):

tinggi, lebar, _ = kemiringan bingkai.bentuk, intersep = garis y1 = tinggi # bagian bawah bingkai y2 = int(y1 / 2) # buat titik dari tengah bingkai ke bawah jika kemiringan == 0: kemiringan = 0,1 x1 = int((y1 - intersep) / kemiringan) x2 = int((y2 - intersep) / kemiringan) kembali

Untuk mencegah pembagian dengan 0, suatu kondisi disajikan. Jika kemiringan = 0 yang berarti y1 = y2 (garis horizontal), berikan nilai kemiringan mendekati 0. Ini tidak akan mempengaruhi kinerja algoritma serta akan mencegah kasus yang tidak mungkin (dibagi dengan 0).

Untuk menampilkan garis lajur pada bingkai, fungsi berikut digunakan:

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

line_image = np.zeros_like(frame) jika garis bukan None: untuk line in lines: for x1, y1, x2, y2 in line: cv2.line(line_image, (x1, y1), (x2, y2), line_color, line_width) line_image = cv2.addWeighted(frame, 0.8, line_image, 1, 1) kembalikan line_image

fungsi cv2.addWeighted() mengambil parameter berikut dan digunakan untuk menggabungkan dua gambar tetapi dengan memberi bobot masing-masing.

cv2.addWeighted(gambar1, alfa, gambar2, beta, gamma)

Dan menghitung output gambar menggunakan persamaan berikut:

keluaran = alfa * gambar1 + beta * gambar2 + gamma

Informasi lebih lanjut tentang fungsi cv2.addWeighted() diperoleh di sini.

Hitung dan Tampilkan Garis Tajuk:

Ini adalah langkah terakhir sebelum kita menerapkan kecepatan pada motor kita. Garis heading bertanggung jawab untuk mengarahkan motor kemudi ke arah mana ia harus berputar dan memberikan kecepatan pada motor pelambatan di mana mereka akan beroperasi. Menghitung heading line adalah trigonometri murni, fungsi trigonometri tan dan atan (tan^-1) digunakan. Beberapa kasus ekstrem adalah saat kamera hanya mendeteksi satu jalur jalur atau saat tidak mendeteksi jalur apa pun. Semua kasus ini ditampilkan dalam fungsi berikut:

def get_steering_angle(bingkai, jalur_garis):

tinggi, lebar, _ = frame.shape if len(lane_lines) == 2: # jika dua garis jalur terdeteksi _, _, left_x2, _ = lane_lines[0][0] # ekstrak x2 kiri dari array lane_lines _, _, right_x2, _ = lane_lines[1][0] # ekstrak kanan x2 dari array lane_lines mid = int(width / 2) x_offset = (left_x2 + right_x2) / 2 - mid y_offset = int(height / 2) elif len(lane_lines) == 1: # jika hanya satu baris yang terdeteksi x1, _, x2, _ = lane_lines[0][0] x_offset = x2 - x1 y_offset = int(height / 2) elif len(lane_lines) == 0: # jika tidak ada garis yang terdeteksi 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 return steering_angle

x_offset dalam kasus pertama adalah seberapa besar perbedaan rata-rata ((x2 kanan + kiri x2) / 2) dari tengah layar. y_offset selalu dianggap tinggi / 2. Gambar terakhir di atas menunjukkan contoh baris heading. angle_to_mid_radians sama dengan "theta" yang ditunjukkan pada gambar terakhir di atas. Jika steering_angle = 90, berarti mobil memiliki heading line yang tegak lurus dengan garis “height/2” dan mobil akan bergerak maju tanpa steering. Jika steering_angle > 90, mobil harus berbelok ke kanan jika tidak maka harus membelok ke kiri. Untuk menampilkan baris judul, fungsi berikut digunakan:

def display_heading_line(frame, 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(width / 2) y1 = tinggi x2 = int(x1 - tinggi / 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) kembali heading_image

Fungsi di atas mengambil frame di mana garis heading akan digambar dan sudut kemudi sebagai input. Ini mengembalikan gambar dari garis judul. Bingkai garis judul yang diambil dalam kasus saya ditunjukkan pada gambar di atas.

Menggabungkan Semua Kode Bersama:

Kode sekarang siap untuk dirakit. Kode berikut menunjukkan loop utama dari program yang memanggil setiap fungsi:

impor cv2

impor 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(frame, -1) #Memanggil fungsi hsv = convert_to_HSV(frame) edge = detect_edges(hsv) roi = region_of_interest(edges) line_segments = detect_line_segments(roi) lane_lines = average_slope_intercept(frame, line_segments) steering lane_lines_frame = display_lines = 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: Menerapkan Kontrol PD

Menerapkan Kontrol PD
Menerapkan Kontrol PD

Sekarang kita memiliki sudut kemudi yang siap untuk diumpankan ke motor. Seperti disebutkan sebelumnya, jika sudut kemudi lebih besar dari 90, mobil harus berbelok ke kanan jika tidak maka harus berbelok ke kiri. Saya menerapkan kode sederhana yang membelokkan motor kemudi ke kanan jika sudutnya di atas 90 dan berbelok ke kiri jika sudut kemudi kurang dari 90 pada kecepatan pelambatan konstan (10% PWM) tetapi saya mendapatkan banyak kesalahan. Kesalahan utama yang saya dapatkan adalah ketika mobil mendekati belokan mana pun, motor kemudi langsung bekerja tetapi motor pelambatan macet. Saya mencoba meningkatkan kecepatan pelambatan menjadi (20% PWM) secara bergantian tetapi berakhir dengan robot keluar dari jalur. Saya membutuhkan sesuatu yang meningkatkan kecepatan pelambatan banyak jika sudut kemudi sangat besar dan meningkatkan kecepatan sedikit jika sudut kemudi tidak terlalu besar kemudian menurunkan kecepatan ke nilai awal saat mobil mendekati 90 derajat (bergerak lurus). Solusinya adalah dengan menggunakan pengontrol PD.

Kontroler PID adalah singkatan dari kontroler Proporsional, Integral dan Derivatif. Jenis pengontrol linier ini banyak digunakan dalam aplikasi robotika. Gambar di atas menunjukkan loop kontrol umpan balik PID yang khas. Tujuan dari pengontrol ini adalah untuk mencapai "setpoint" dengan cara yang paling efisien tidak seperti pengontrol "on - off" yang menghidupkan atau mematikan pembangkit sesuai dengan beberapa kondisi. Beberapa kata kunci yang harus diketahui:

  • Setpoint: adalah nilai yang diinginkan yang ingin dicapai oleh sistem Anda.
  • Nilai aktual: adalah nilai aktual yang dirasakan oleh sensor.
  • Error: adalah perbedaan antara setpoint dan nilai aktual (error = Setpoint - Nilai aktual).
  • Variabel terkontrol: dari namanya, variabel yang ingin Anda kendalikan.
  • Kp: Konstanta proporsional.
  • Ki: Konstanta integral.
  • Kd: Konstanta turunan.

Singkatnya, loop sistem kontrol PID bekerja sebagai berikut:

  • Pengguna menentukan setpoint yang diperlukan untuk dicapai sistem.
  • Kesalahan dihitung (kesalahan = setpoint - aktual).
  • Kontroler P menghasilkan aksi yang sebanding dengan nilai error. (kesalahan meningkat, aksi P juga meningkat)
  • Pengontrol I akan mengintegrasikan kesalahan dari waktu ke waktu yang menghilangkan kesalahan kondisi mapan sistem tetapi meningkatkan overshootnya.
  • Kontroler D hanyalah turunan waktu untuk kesalahan. Dengan kata lain, itu adalah kemiringan kesalahan. Itu melakukan tindakan yang sebanding dengan turunan dari kesalahan. Kontroler ini meningkatkan stabilitas sistem.
  • Output dari pengontrol akan menjadi jumlah dari tiga pengontrol. Output controller akan menjadi 0 jika error menjadi 0.

Penjelasan bagus tentang pengontrol PID dapat ditemukan di sini.

Kembali ke mobil penjaga lajur, variabel yang saya kendalikan adalah kecepatan pelambatan (karena kemudi hanya memiliki dua status, kanan atau kiri). Kontroler PD digunakan untuk tujuan ini karena aksi D meningkatkan kecepatan pelambatan banyak jika perubahan kesalahan sangat besar (yaitu penyimpangan besar) dan memperlambat mobil jika perubahan kesalahan ini mendekati 0. Saya melakukan langkah-langkah berikut untuk menerapkan PD pengontrol:

  • Atur setpoint ke 90 derajat (saya selalu ingin mobil bergerak lurus)
  • Menghitung sudut deviasi dari tengah
  • Penyimpangan memberikan dua informasi: Seberapa besar kesalahan (besarnya penyimpangan) dan arah mana yang harus diambil oleh motor kemudi (tanda penyimpangan). Jika deviasi positif, mobil harus berbelok ke kanan jika tidak maka harus berbelok ke kiri.
  • Karena deviasi bisa negatif atau positif, variabel "kesalahan" didefinisikan dan selalu sama dengan nilai absolut deviasi.
  • Kesalahan dikalikan dengan konstanta Kp.
  • Kesalahan mengalami diferensiasi waktu dan dikalikan dengan konstanta Kd.
  • Kecepatan motor diperbarui dan putaran dimulai lagi.

Kode berikut digunakan dalam loop utama untuk mengontrol kecepatan motor pelambatan:

kecepatan = 10 # kecepatan operasi dalam % PWM

#Variables yang akan diupdate setiap loop lastTime = 0 lastError = 0 # Konstanta PD Kp = 0.4 Kd = Kp * 0.65 While True: now = time.time() # current time variable dt = now - lastTime deviasi = steering_angle - 90 # ekivalen ke angle_to_mid_deg variabel error = abs(deviation) jika deviasi -5: # jangan mengarahkan jika ada error 10 derajat range deviasi = 0 error = 0 GPIO.output(in1, GPIO. LOW) GPIO.output(in2, GPIO. LOW) steering.stop() deviasi elif > 5: # belok kanan jika deviasi positif GPIO.output(in1, GPIO. LOW) GPIO.output(in2, GPIO. HIGH) steering.start(100) deviasi elif < -5: # belok kiri jika simpangan negatif GPIO.output(in1, GPIO. HIGH) GPIO.output(in2, GPIO. LOW) steering.start(100) derivative = kd * (error - lastError) / dt proporsional = kp * error PD = int(kecepatan + turunan + proporsional) spd = abs(PD) jika spd> 25: spd = 25 throttle.start(spd) lastError = error lastTime = time.time()

Jika kesalahan sangat besar (deviasi dari tengah tinggi), tindakan proporsional dan turunannya tinggi menghasilkan kecepatan pelambatan yang tinggi. Ketika kesalahan mendekati 0 (deviasi dari tengah rendah), tindakan turunan bertindak terbalik (kemiringan negatif) dan kecepatan pelambatan menjadi rendah untuk menjaga stabilitas sistem. Kode lengkap terlampir di bawah ini.

Langkah 7: Hasil

Video di atas menunjukkan hasil yang saya peroleh. Itu memang membutuhkan lebih banyak penyetelan dan penyesuaian lebih lanjut. Saya menghubungkan raspberry pi ke layar tampilan LCD saya karena streaming video melalui jaringan saya memiliki latensi tinggi dan sangat membuat frustrasi untuk bekerja, itulah sebabnya ada kabel yang terhubung ke raspberry pi dalam video. Saya menggunakan papan busa untuk menggambar trek.

Saya menunggu untuk mendengar rekomendasi Anda untuk membuat proyek ini lebih baik! Karena saya berharap bahwa instruksi ini cukup baik untuk memberi Anda beberapa informasi baru.

Direkomendasikan: