Mục lục:

Ô tô tự động giữ làn đường bằng Raspberry Pi và OpenCV: 7 bước (có hình ảnh)
Ô tô tự động giữ làn đường bằng Raspberry Pi và OpenCV: 7 bước (có hình ảnh)

Video: Ô tô tự động giữ làn đường bằng Raspberry Pi và OpenCV: 7 bước (có hình ảnh)

Video: Ô tô tự động giữ làn đường bằng Raspberry Pi và OpenCV: 7 bước (có hình ảnh)
Video: Nhận diện lane đường cho mô hình xe tự lái - Lightweight Lane Detection for Autonomous Cars 2024, Tháng mười một
Anonim
Ô tô tự động giữ làn đường bằng Raspberry Pi và OpenCV
Ô tô tự động giữ làn đường bằng Raspberry Pi và OpenCV

Trong phần hướng dẫn này, một rô bốt giữ làn đường tự động sẽ được triển khai và sẽ thực hiện các bước sau:

  • Thu thập các bộ phận
  • Cài đặt phần mềm điều kiện tiên quyết
  • Lắp ráp phần cứng
  • Thử nghiệm đầu tiên
  • Phát hiện vạch kẻ làn đường và hiển thị vạch hướng dẫn bằng openCV
  • Triển khai bộ điều khiển PD
  • Kết quả

Bước 1: Thu thập các thành phần

Thu thập các thành phần
Thu thập các thành phần
Thu thập các thành phần
Thu thập các thành phần
Thu thập các thành phần
Thu thập các thành phần
Thu thập các thành phần
Thu thập các thành phần

Các hình ảnh trên cho thấy tất cả các thành phần được sử dụng trong dự án này:

  • Xe RC: Tôi mua được từ một cửa hàng địa phương ở đất nước tôi. Nó được trang bị 3 động cơ (2 để điều tiết và 1 để lái). Nhược điểm chính của chiếc xe này là tay lái bị hạn chế giữa "không lái" và "lái hoàn toàn". Nói cách khác, nó không thể lái theo một góc cụ thể, không giống như những chiếc xe RC lái servo. Bạn có thể tìm thấy bộ phụ kiện xe hơi tương tự được thiết kế đặc biệt cho mâm xôi pi từ đây.
  • Raspberry pi 3 model b +: đây là bộ não của xe sẽ đảm nhiệm rất nhiều công đoạn xử lý. Nó dựa trên bộ vi xử lý lõi tứ 64-bit tốc độ 1,4 GHz. Tôi nhận được của tôi từ đây.
  • Mô-đun máy ảnh Raspberry pi 5 mp: Nó hỗ trợ quay 1080p @ 30 khung hình / giây, 720p @ 60 khung hình / giây và 640x480p 60/90. Nó cũng hỗ trợ giao diện nối tiếp có thể được cắm trực tiếp vào pi raspberry. Nó không phải là lựa chọn tốt nhất cho các ứng dụng xử lý ảnh nhưng nó đủ cho dự án này cũng như nó rất rẻ. Tôi nhận được của tôi từ đây.
  • Trình điều khiển động cơ: Được sử dụng để điều khiển hướng và tốc độ của động cơ DC. Nó hỗ trợ điều khiển 2 động cơ dc trong 1 bo mạch và có thể chịu được 1,5 A.
  • Ngân hàng điện (Tùy chọn): Tôi đã sử dụng một pin dự phòng (được đánh giá ở 5V, 3A) để cấp nguồn riêng cho mâm xôi pi. Một bộ chuyển đổi bước xuống (bộ chuyển đổi buck: dòng đầu ra 3A) nên được sử dụng để cấp nguồn cho pi mâm xôi từ 1 nguồn.
  • Pin LiPo 3s (12 V): Pin Lithium Polymer được biết đến với hiệu suất tuyệt vời trong lĩnh vực chế tạo robot. Nó được sử dụng để cung cấp năng lượng cho trình điều khiển động cơ. Tôi đã mua của tôi từ đây.
  • Dây nhảy từ nam đến nam và nữ đối với nữ.
  • Băng keo hai mặt: Dùng để gắn các thành phần trên xe RC.
  • Dải băng xanh: Đây là một thành phần rất quan trọng của dự án này, nó được sử dụng để làm vạch kẻ giữa hai làn xe ô tô. Bạn có thể chọn bất kỳ màu nào bạn muốn nhưng tôi khuyên bạn nên chọn những màu khác với màu của môi trường xung quanh.
  • Dây buộc và thanh gỗ.
  • Cái vặn vít.

Bước 2: Cài đặt OpenCV trên Raspberry Pi và thiết lập màn hình từ xa

Cài đặt OpenCV trên Raspberry Pi và thiết lập màn hình từ xa
Cài đặt OpenCV trên Raspberry Pi và thiết lập màn hình từ xa

Bước này hơi phiền phức và sẽ mất một chút thời gian.

OpenCV (Open source Computer Vision) là một thư viện phần mềm máy tính và thị giác máy tính mã nguồn mở. Thư viện có hơn 2500 thuật toán được tối ưu hóa. Làm theo hướng dẫn rất đơn giản NÀY để cài đặt openCV trên raspberry pi của bạn cũng như cài đặt hệ điều hành raspberry pi (nếu bạn vẫn chưa cài đặt). Xin lưu ý rằng quá trình xây dựng openCV có thể mất khoảng 1,5 giờ trong một căn phòng được làm mát tốt (vì nhiệt độ của bộ xử lý sẽ rất cao!) Vì vậy hãy uống một chút trà và kiên nhẫn chờ đợi: D.

Đối với màn hình từ xa, hãy làm theo hướng dẫn NÀY để thiết lập quyền truy cập từ xa vào raspberry pi từ thiết bị Windows / Mac của bạn.

Bước 3: Kết nối các bộ phận với nhau

Kết nối các bộ phận với nhau
Kết nối các bộ phận với nhau
Kết nối các bộ phận với nhau
Kết nối các bộ phận với nhau
Kết nối các bộ phận với nhau
Kết nối các bộ phận với nhau

Các hình ảnh trên cho thấy các kết nối giữa raspberry pi, mô-đun máy ảnh và trình điều khiển động cơ. Xin lưu ý rằng các động cơ tôi đã sử dụng hấp thụ 0,35 A ở 9 V mỗi động cơ nên an toàn cho trình điều khiển động cơ để chạy 3 động cơ cùng một lúc. Và vì tôi muốn điều khiển tốc độ của 2 động cơ điều tiết (1 phía sau và 1 phía trước) theo cùng một cách, tôi đã kết nối chúng với cùng một cổng. Tôi đã gắn trình điều khiển động cơ vào bên phải của chiếc xe bằng cách sử dụng băng kép. Đối với mô-đun máy ảnh, tôi đã chèn một dây buộc zip giữa các lỗ vít như hình trên cho thấy. Sau đó, tôi lắp máy ảnh vào một thanh gỗ để có thể điều chỉnh vị trí của máy ảnh theo ý muốn. Cố gắng lắp đặt camera ở giữa xe càng nhiều càng tốt. Tôi khuyên bạn nên đặt máy ảnh cách mặt đất ít nhất 20 cm để trường nhìn phía trước xe sẽ tốt hơn. Sơ đồ Fritzing được đính kèm bên dưới.

Bước 4: Thử nghiệm đầu tiên

Thử nghiệm đầu tiên
Thử nghiệm đầu tiên
Thử nghiệm đầu tiên
Thử nghiệm đầu tiên

Kiểm tra máy ảnh:

Sau khi máy ảnh được cài đặt và thư viện openCV được xây dựng, đã đến lúc kiểm tra hình ảnh đầu tiên của chúng tôi! Chúng tôi sẽ chụp một bức ảnh từ pi cam và lưu nó thành "original.jpg". Nó có thể được thực hiện theo 2 cách:

1. Sử dụng các lệnh đầu cuối:

Mở một cửa sổ dòng lệnh mới và nhập lệnh sau:

raspistill -o original.jpg

Thao tác này sẽ tạo ra một hình ảnh tĩnh và lưu nó trong thư mục "/pi/original.jpg".

2. Sử dụng bất kỳ IDE python nào (tôi sử dụng IDLE):

Mở một bản phác thảo mới và viết mã sau:

nhập cv2

video = cv2. VideoCapture (0) while True: ret, frame = video.read () frame = cv2.flip (frame, -1) # dùng để lật ảnh theo chiều dọc cv2.imshow ('original', frame) cv2. imwrite ('original.jpg', frame) key = cv2.waitKey (1) if key == 27: break video.release () cv2.destroyAllWindows ()

Hãy xem điều gì đã xảy ra trong đoạn mã này. Dòng đầu tiên là nhập thư viện openCV của chúng tôi để sử dụng tất cả các chức năng của nó. chức năng VideoCapture (0) bắt đầu phát video trực tiếp từ nguồn được xác định bởi chức năng này, trong trường hợp này, nó là 0 có nghĩa là camera raspi. nếu bạn có nhiều máy ảnh, nên đặt các số khác nhau. video.read () sẽ đọc từng khung hình đến từ máy ảnh và lưu nó vào một biến gọi là "frame". Hàm flip () sẽ lật hình ảnh theo trục y (theo chiều dọc) vì tôi đang lắp ngược lại máy ảnh của mình. imshow () sẽ hiển thị các khung của chúng ta có từ "original" và imwrite () sẽ lưu ảnh của chúng ta dưới dạng original.jpg. waitKey (1) sẽ đợi trong 1 ms để bất kỳ nút bàn phím nào được nhấn và trả về mã ASCII của nó. nếu nút thoát (esc) được nhấn, giá trị thập phân là 27 được trả về và sẽ phá vỡ vòng lặp tương ứng. video.release () sẽ ngừng ghi và huỷAllWindows () sẽ đóng mọi hình ảnh được mở bởi hàm imshow ().

Tôi khuyên bạn nên kiểm tra ảnh của mình bằng phương pháp thứ hai để làm quen với các chức năng của openCV. Ảnh được lưu trong thư mục "/pi/original.jpg". Ảnh gốc mà máy ảnh của tôi chụp được hiển thị ở trên.

Thử nghiệm Động cơ:

Bước này rất cần thiết để xác định chiều quay của từng động cơ. Đầu tiên xin giới thiệu sơ qua về nguyên lý hoạt động của động cơ điều khiển. Hình ảnh trên cho thấy chốt ra của trình điều khiển động cơ. Kích hoạt A, Đầu vào 1 và Đầu vào 2 được liên kết với điều khiển động cơ A. Kích hoạt B, Đầu vào 3 và Đầu vào 4 được liên kết với điều khiển động cơ B. Kiểm soát hướng được thiết lập bởi phần "Đầu vào" và kiểm soát tốc độ được thiết lập bởi phần "Bật". Ví dụ, để điều khiển hướng của động cơ A, hãy đặt Đầu vào 1 thành CAO (3,3 V trong trường hợp này vì chúng ta đang sử dụng pi mâm xôi) và đặt Đầu vào 2 thành LOW, động cơ sẽ quay theo một hướng cụ thể và bằng cách đặt các giá trị ngược lại đến Đầu vào 1 và Đầu vào 2, động cơ sẽ quay theo hướng ngược lại. Nếu Đầu vào 1 = Đầu vào 2 = (CAO hoặc THẤP), động cơ sẽ không quay. Các chân kích hoạt lấy tín hiệu đầu vào Điều chế độ rộng xung (PWM) từ mâm xôi (0 đến 3,3 V) và chạy động cơ tương ứng. Ví dụ: tín hiệu 100% PWM có nghĩa là chúng ta đang làm việc ở tốc độ tối đa và tín hiệu PWM 0% có nghĩa là động cơ không quay. Đoạn mã sau được sử dụng để xác định hướng của động cơ và kiểm tra tốc độ của chúng.

thời gian nhập khẩu

nhập RPi. GPIO dưới dạng GPIO GPIO.setwarnings (Sai) # Chân động cơ lái lái_enable = 22 # Chốt vật lý 15 in1 = 17 # Chốt vật lý 11 in2 = 27 # Chốt vật lý 13 # Chân động cơ ga ga ga_enable = 25 # Chốt vật lý 22 in3 = 23 # Chân vật lý 16 in4 = 24 # Chân vật lý 18 GPIO.setmode (GPIO. BCM) # Sử dụng cách đánh số GPIO thay vì đánh số vật lý GPIO.setup (in1, GPIO.out) GPIO.setup (in2, GPIO.out) GPIO. thiết lập (in3, GPIO.out) GPIO.setup (in4, GPIO.out) GPIO.setup (ga_enable, GPIO.out) GPIO.setup (Driving_enable, GPIO.out) # Điều khiển động cơ lái GPIO.output (in1, GPIO. CAO) GPIO.output (in2, GPIO. LOW) lái = GPIO. PWM (lái_enable, 1000) # đặt tần số chuyển đổi thành 1000 Hz lái.stop () # Điều khiển động cơ ga GPIO.output (in3, GPIO. HIGH) GPIO.output (in4, GPIO. LOW) ga = GPIO. PWM (ga_enable, 1000) # đặt tần số chuyển đổi thành 1000 Hz ga. % Tín hiệu PWM-> (0,25 * điện áp pin) - trình điều khiển mất lái. bắt đầu (100) # khởi động động cơ ở 100% tín hiệu PWM-> (1 * Điện áp pin) - thời gian mất lái của người lái. ngủ (3) ga.stop () lái.stop ()

Mã này sẽ chạy các mô tơ điều tiết và mô tơ lái trong 3 giây và sau đó sẽ dừng chúng lại. (Mất lái xe) có thể được xác định bằng cách sử dụng một vôn kế. Ví dụ, chúng ta biết rằng tín hiệu PWM 100% sẽ cung cấp cho điện áp đầy của pin tại cực của động cơ. Tuy nhiên, bằng cách đặt PWM thành 100%, tôi thấy rằng trình điều khiển đang gây ra giảm 3 V và động cơ đang nhận 9 V thay vì 12 V (chính xác những gì tôi cần!). Tổn thất không tuyến tính, tức là tổn thất ở mức 100% rất khác với tổn thất ở mức 25%. Sau khi chạy đoạn mã trên, kết quả của tôi như sau:

Kết quả điều chỉnh: nếu in3 = HIGH và in4 = LOW, động cơ điều tiết sẽ có vòng quay Clock-Wise (CW) tức là xe sẽ di chuyển về phía trước. Nếu không, xe sẽ lùi về phía sau.

Kết quả đánh lái: nếu in1 = HIGH và in2 = LOW, động cơ lái sẽ quay sang trái tối đa tức là xe sẽ đánh lái sang trái. Nếu không, xe sẽ bẻ lái sang phải. Sau một số thử nghiệm, tôi nhận thấy rằng động cơ lái sẽ không quay nếu tín hiệu PWM không phải là 100% (tức là động cơ sẽ lái hoàn toàn sang phải hoặc hoàn toàn sang trái).

Bước 5: Phát hiện làn đường và tính toán dòng tiêu đề

Phát hiện đường làn và tính toán đường tiêu đề
Phát hiện đường làn và tính toán đường tiêu đề
Phát hiện đường làn và tính toán đường tiêu đề
Phát hiện đường làn và tính toán đường tiêu đề
Phát hiện đường làn và tính toán đường tiêu đề
Phát hiện đường làn và tính toán đường tiêu đề

Trong bước này, thuật toán sẽ điều khiển chuyển động của ô tô sẽ được giải thích. Hình ảnh đầu tiên cho thấy toàn bộ quá trình. Đầu vào của hệ thống là hình ảnh, đầu ra là theta (góc lái tính bằng độ). Lưu ý rằng, quá trình xử lý được thực hiện trên 1 hình ảnh và sẽ được lặp lại trên tất cả các khung hình.

Máy ảnh:

Máy ảnh sẽ bắt đầu quay video với độ phân giải (320 x 240). Tôi khuyên bạn nên giảm độ phân giải để bạn có thể có được tốc độ khung hình (fps) tốt hơn vì hiện tượng giảm khung hình / giây sẽ xảy ra sau khi áp dụng các kỹ thuật xử lý cho từng khung hình. Đoạn mã dưới đây sẽ là vòng lặp chính của chương trình và sẽ thêm từng bước qua đoạn mã này.

nhập cv2

import numpy as np video = cv2. VideoCapture (0) video.set (cv2. CAP_PROP_FRAME_WIDTH, 320) # đặt chiều rộng thành 320 p video.set (cv2. CAP_PROP_FRAME_HEIGHT, 240) # đặt chiều cao thành 240 p # Vòng lặp trong khi Đúng: 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 ()

Đoạn mã ở đây sẽ hiển thị hình ảnh gốc thu được ở bước 4 và được hiển thị trong các hình ảnh trên.

Chuyển đổi sang Không gian màu HSV:

Bây giờ sau khi quay video dưới dạng khung hình từ máy ảnh, bước tiếp theo là chuyển đổi từng khung hình thành không gian màu Hue, Saturation và Value (HSV). Ưu điểm chính của việc làm như vậy là có thể phân biệt giữa các màu bằng mức độ chói của chúng. Và đây là một lời giải thích tốt về không gian màu HSV. Chuyển đổi sang HSV được thực hiện thông qua chức năng sau:

def convert_to_HSV (khung):

hsv = cv2.cvtColor (frame, cv2. COLOR_BGR2HSV) cv2.imshow ("HSV", hsv) trả về hsv

Hàm này sẽ được gọi từ vòng lặp chính và sẽ trả về khung trong không gian màu HSV. Khung do tôi thu được trong không gian màu HSV được hiển thị ở trên.

Phát hiện màu xanh lam và các cạnh:

Sau khi chuyển đổi hình ảnh thành không gian màu HSV, đã đến lúc chỉ phát hiện màu mà chúng ta quan tâm (tức là màu xanh lam vì nó là màu của các đường làn). Để tách màu xanh lam từ khung HSV, phải chỉ định một dải màu, độ bão hòa và giá trị. tham khảo tại đây để có ý tưởng tốt hơn về các giá trị HSV. Sau một số thử nghiệm, giới hạn trên và giới hạn dưới của màu xanh lam được hiển thị trong đoạn mã bên dưới. Và để giảm sự biến dạng tổng thể trong mỗi khung hình, các cạnh chỉ được phát hiện bằng cách sử dụng máy dò cạnh mờ. Thông tin thêm về cạnh canny được tìm thấy ở đây. Một nguyên tắc chung là chọn các tham số của hàm Canny () với tỷ lệ 1: 2 hoặc 1: 3.

def detector_edges (khung):

Lower_blue = np.array ([90, 120, 0], dtype = "uint8") # giới hạn dưới của màu xanh lam upper_blue = np.array ([150, 255, 255], dtype = "uint8") # giới hạn trên của blue color mask = cv2.inRange (hsv, Lower_blue, upper_blue) # mặt nạ này sẽ lọc ra mọi thứ trừ màu xanh lam # phát hiện các cạnh cạnh = cv2. Canny (mặt nạ, 50, 100) cv2.imshow ("các cạnh", các cạnh) trả về các cạnh

Hàm này cũng sẽ được gọi từ vòng lặp chính lấy tham số là khung không gian màu HSV và trả về khung có viền. Khung viền mà tôi đã lấy được ở trên.

Chọn Khu vực Quan tâm (ROI):

Chọn khu vực quan tâm là rất quan trọng để chỉ tập trung vào 1 khu vực của khung hình. Trong trường hợp này, tôi không muốn chiếc xe nhìn thấy nhiều mục trong môi trường. Tôi chỉ muốn chiếc xe tập trung vào các vạch phân làn và bỏ qua bất cứ điều gì khác. P. S: hệ trục tọa độ (trục x và y) bắt đầu từ góc trên bên trái. Nói cách khác, điểm (0, 0) bắt đầu từ góc trên bên trái. trục y là chiều cao và trục x là chiều rộng. Đoạn mã dưới đây chọn khu vực quan tâm để chỉ tập trung vào nửa dưới của khung.

def region_of_interest (các cạnh):

chiều cao, chiều rộng = các cạnh.shape # trích xuất chiều cao và chiều rộng của mặt nạ khung cạnh 4 điểm (phía dưới bên trái, phía trên bên trái, phía trên bên phải, phía dưới bên phải) polygon = np.array (

Hàm này sẽ lấy khung viền làm tham số và vẽ một đa giác với 4 điểm đặt trước. Nó sẽ chỉ tập trung vào những gì bên trong đa giác và bỏ qua mọi thứ bên ngoài nó. Khung khu vực quan tâm của tôi được hiển thị ở trên.

Phát hiện phân đoạn dòng:

Biến đổi Hough được sử dụng để phát hiện các đoạn thẳng từ khung có viền. Biến đổi Hough là một kỹ thuật để phát hiện bất kỳ hình dạng nào ở dạng toán học. Nó có thể phát hiện gần như bất kỳ vật thể nào ngay cả khi nó bị bóp méo theo một số phiếu bầu. một tài liệu tham khảo tuyệt vời cho phép biến đổi Hough được hiển thị ở đây. Đối với ứng dụng này, hàm cv2. HoughLinesP () được sử dụng để phát hiện các dòng trong mỗi khung hình. Các tham số quan trọng mà hàm này thực hiện là:

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

  • Khung: là khung mà chúng ta muốn phát hiện các dòng trong.
  • rho: Đó là độ chính xác về khoảng cách tính bằng pixel (thường là = 1)
  • theta: độ chính xác góc tính bằng radian (luôn = np.pi / 180 ~ 1 độ)
  • min_threshold: phiếu bầu tối thiểu mà nó phải nhận được để nó được coi là một dòng
  • minLineLength: độ dài tối thiểu của dòng tính bằng pixel. Bất kỳ dòng nào ngắn hơn số này không được coi là một dòng.
  • maxLineGap: khoảng cách tối đa tính bằng pixel giữa 2 dòng được coi là 1 dòng. (Nó không được sử dụng trong trường hợp của tôi vì các vạch làn đường mà tôi đang sử dụng không có bất kỳ khoảng trống nào).

Hàm này trả về các điểm cuối của một dòng. Hàm sau được gọi từ vòng lặp chính của tôi để phát hiện các dòng bằng cách sử dụng biến đổi Hough:

def Discovery_line_searies (cropped_edges):

rho = 1 theta = np.pi / 180 min_threshold = 10 line_searies = cv2. HoughLinesP (crop_edges, rho, theta, min_threshold, np.array (), minLineLength = 5, maxLineGap = 0) return line_segment

Độ dốc trung bình và hệ số chặn (m, b):

nhớ lại rằng phương trình của đường thẳng được cho bởi y = mx + b. Trong đó m là hệ số góc của đường thẳng và b là giao điểm y. Trong phần này, giá trị trung bình của độ dốc và điểm giao nhau của các đoạn thẳng được phát hiện bằng cách sử dụng biến đổi Hough sẽ được tính toán. Trước khi làm như vậy, chúng ta hãy nhìn vào bức ảnh khung ban đầu được hiển thị ở trên. Làn đường bên trái dường như đang đi lên phía trên nên có độ dốc âm (bạn có nhớ điểm bắt đầu của hệ tọa độ không?). Nói cách khác, đường làn bên trái có x1 <x2 và y2 x1 và y2> y1 sẽ cho hệ số góc dương. Vì vậy, tất cả các đường có độ dốc dương đều được coi là các điểm đi đúng làn đường. Trong trường hợp các đường thẳng đứng (x1 = x2), hệ số góc sẽ là vô cùng. Trong trường hợp này, chúng tôi sẽ bỏ qua tất cả các dòng dọc để tránh gặp lỗi. Để tăng thêm độ chính xác cho việc phát hiện này, mỗi khung hình được chia thành hai vùng (bên phải và bên trái) thông qua 2 đường ranh giới. Tất cả các điểm có chiều rộng (điểm trục x) lớn hơn đường ranh giới bên phải, được kết hợp với tính toán làn đường bên phải. Và nếu tất cả các điểm có chiều rộng nhỏ hơn đường ranh giới bên trái, chúng được kết hợp với tính toán làn đường bên trái. Hàm sau lấy khung đang xử lý và các đoạn làn được phát hiện bằng cách sử dụng biến đổi Hough và trả về độ dốc và điểm giao nhau trung bình của hai đường làn.

def average_slope_intercept (frame, line_searies):

lane_lines = if line_seboards is None: print ("không phát hiện thấy đoạn đường thẳng") trả về chiều cao, chiều rộng của lane_lines, _ = frame.shape left_fit = right_fit = border = left_region_boundary = width * (1 - border) right_region_boundary = width * ranh giới cho line_segment trong line_segment: for x1, y1, x2, y2 trong line_segment: if x1 == x2: print ("bỏ qua các đường thẳng đứng (độ dốc = infinity)") continue fit = np.polyfit ((x1, x2), (y1, y2), 1) dốc = (y2 - y1) / (x2 - x1) chặn = y1 - (độ dốc * x1) nếu độ dốc <0: nếu x1 <trái_phần_bằng và x2 phải_vùng_ biên và x2> phải_khuẩn_lục: ngay_mặt. append ((độ dốc, điểm chặn)) left_fit_average = np.average (left_fit, axis = 0) nếu 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 là mảng 2-D bao gồm tọa độ của các đường làn bên phải và bên trái # ví dụ: lan e_lines =

make_points () là một hàm trợ giúp cho hàm average_slope_intercept () sẽ trả về tọa độ giới hạn của các đường làn (từ dưới cùng đến giữa khung).

def make_points (frame, line):

chiều cao, chiều rộng, _ = khung hình. độ dốc, điểm chặn = dòng y1 = chiều cao # đáy khung y2 = int (y1 / 2) # tạo điểm từ giữa khung xuống nếu độ dốc == 0: độ dốc = 0,1 x1 = int ((y1 - mức chặn) / độ dốc) x2 = int ((y2 - điểm chặn) / độ dốc) return

Để ngăn việc chia cho 0, một điều kiện được đưa ra. Nếu độ dốc = 0 có nghĩa là y1 = y2 (đường nằm ngang), hãy đặt giá trị độ dốc gần bằng 0. Điều này sẽ không ảnh hưởng đến hiệu suất của thuật toán cũng như ngăn ngừa trường hợp bất khả thi (chia cho 0).

Để hiển thị đường phân làn trên khung, chức năng sau được sử dụng:

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

line_image = np.zeros_like (frame) if lines not None: for 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) trả về line_image

Hàm cv2.addWeighted () nhận các tham số sau và nó được sử dụng để kết hợp hai hình ảnh nhưng với trọng số của mỗi hình ảnh.

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

Và tính toán hình ảnh đầu ra bằng công thức sau:

output = alpha * image1 + beta * image2 + gamma

Thông tin thêm về hàm cv2.addWeighted () có nguồn gốc tại đây.

Tính toán và hiển thị dòng tiêu đề:

Đây là bước cuối cùng trước khi chúng tôi áp dụng tốc độ cho động cơ của mình. Dòng tiêu đề có nhiệm vụ cung cấp cho động cơ lái hướng mà nó sẽ quay và cung cấp cho động cơ điều tiết tốc độ mà chúng sẽ hoạt động. Tính toán dòng tiêu đề là lượng giác thuần túy, các hàm lượng giác tan và atan (tan ^ -1) được sử dụng. Một số trường hợp cực đoan là khi camera chỉ phát hiện một vạch làn đường hoặc khi nó không phát hiện bất kỳ vạch nào. Tất cả các trường hợp này được hiển thị trong hàm sau:

def get_stuct_angle (frame, lane_lines):

height, width, _ = frame.shape if len (lane_lines) == 2: # nếu phát hiện hai đường làn _, _, left_x2, _ = lane_lines [0] [0] # trích xuất trái x2 từ mảng lane_lines _, _, right_x2, _ = lane_lines [1] [0] # trích phải x2 từ mảng lane_lines mid = int (width / 2) x_offset = (left_x2 + right_x2) / 2 - mid y_offset = int (height / 2) elif len (lane_lines) == 1: # nếu chỉ một dòng được phát hiện x1, _, x2, _ = lane_lines [0] [0] x_offset = x2 - x1 y_offset = int (height / 2) elif len (lane_lines) == 0: # nếu không có dòng nào được phát hiện 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) Driving_angle = angle_to_mid_deg + 90 trả lại lái xe

x_offset trong trường hợp đầu tiên là giá trị trung bình ((phải x2 + trái x2) / 2) khác với giữa màn hình là bao nhiêu. y_offset luôn được coi là chiều cao / 2. Hình ảnh cuối cùng ở trên cho thấy một ví dụ về dòng tiêu đề. angle_to_mid_radians giống như "theta" được hiển thị trong hình cuối cùng ở trên. Nếu Driving_angle = 90, có nghĩa là xe có đường thẳng vuông góc với đường "height / 2" và xe sẽ tiến về phía trước mà không cần đánh lái. Nếu Driving_angle> 90, xe phải đánh lái sang phải, nếu không xe phải đánh lái sang trái. Để hiển thị dòng tiêu đề, hàm sau được sử dụng:

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

header_image = np.zeros_like (frame) height, width, _ = frame.shape Driving_angle_radian = Driving_angle / 180.0 * math.pi x1 = int (width / 2) y1 = height x2 = int (x1 - height / 2 / math.tan (Driving_angle_radian)) y2 = int (height / 2) cv2.line (header_image, (x1, y1), (x2, y2), line_color, line_width) header_image = cv2.addWeighted (frame, 0.8, header_image, 1, 1) return header_image

Hàm trên lấy khung trong đó dòng tiêu đề sẽ được vẽ và góc lái làm đầu vào. Nó trả về hình ảnh của dòng tiêu đề. Khung dòng tiêu đề được chụp trong trường hợp của tôi được hiển thị trong hình trên.

Kết hợp tất cả các mã lại với nhau:

Mã hiện đã sẵn sàng để được lắp ráp. Đoạn mã sau đây cho thấy vòng lặp chính của chương trình gọi từng hàm:

nhập cv2

import numpy dưới dạng np video = cv2. VideoCapture (0) video.set (cv2. CAP_PROP_FRAME_WIDTH, 320) video.set (cv2. CAP_PROP_FRAME_HEIGHT, 240) trong khi True: ret, frame = video.read () frame = cv2.flip (frame, -1) # Gọi các hàm hsv = convert_to_HSV (frame) edge = detector_edges (hsv) roi = region_of_interest (edge) line_searies = detector_line_searies (roi) lane_lines = average_slope_intercept (frame, line_searies) lane_lines_image = display_lines (khung, làn_lines) lái = get_stuct_angle (frame, lane_lines) header_image = display_heading_line (lane_lines_image, Driving_angle) key = cv2.waitKey (1) if key == 27: break video.release () cv2.destroyAllWindows ()

Bước 6: Áp dụng Kiểm soát PD

Áp dụng kiểm soát PD
Áp dụng kiểm soát PD

Bây giờ chúng ta đã có sẵn góc lái để cung cấp cho các động cơ. Như đã đề cập trước đó, nếu góc lái lớn hơn 90, xe nên rẽ phải nếu không thì nên rẽ trái. Tôi đã áp dụng một mã đơn giản là quay động cơ lái sang phải nếu góc trên 90 và quay sang trái nếu góc lái nhỏ hơn 90 ở tốc độ điều chỉnh không đổi là (10% PWM) nhưng tôi đã gặp rất nhiều lỗi. Lỗi chính mà tôi mắc phải là khi xe đến gần khúc cua nào, mô tơ lái tác động trực tiếp nhưng mô tơ điều tiết bị kẹt. Tôi đã cố gắng tăng tốc độ điều tiết lên (20% PWM) ở các ngã rẽ nhưng kết thúc bằng việc rô bốt lao ra khỏi làn đường. Tôi cần một cái gì đó để tăng tốc độ điều chỉnh rất nhiều nếu góc lái rất lớn và tăng tốc độ một chút nếu góc lái không lớn như vậy, sau đó giảm tốc độ về giá trị ban đầu khi xe tiến tới 90 độ (chuyển động thẳng). Giải pháp là sử dụng bộ điều khiển PD.

Bộ điều khiển PID là viết tắt của bộ điều khiển Tỷ lệ, Tích phân và Đạo hàm. Loại bộ điều khiển tuyến tính này được sử dụng rộng rãi trong các ứng dụng chế tạo người máy. Hình ảnh trên cho thấy vòng điều khiển phản hồi PID điển hình. Mục tiêu của bộ điều khiển này là đạt đến "điểm đặt" một cách hiệu quả nhất không giống như bộ điều khiển "bật - tắt" bật hoặc tắt nhà máy theo một số điều kiện. Một số từ khóa nên biết:

  • Điểm đặt: là giá trị mong muốn mà bạn muốn hệ thống của mình đạt được.
  • Giá trị thực: là giá trị thực tế được cảm biến bởi cảm biến.
  • Error: là sự khác biệt giữa điểm đặt và giá trị thực (error = Setpoint - Giá trị thực).
  • Biến được kiểm soát: từ tên của nó, biến bạn muốn kiểm soát.
  • Kp: Hằng số tỷ lệ.
  • Ki: Hằng số tích phân.
  • Kd: Hằng số đạo hàm.

Tóm lại, vòng lặp hệ thống điều khiển PID hoạt động như sau:

  • Người dùng xác định điểm đặt cần thiết để hệ thống đạt tới.
  • Lỗi được tính toán (error = setpoint - thực tế).
  • Bộ điều khiển P tạo ra một hành động tỷ lệ với giá trị của lỗi. (lỗi tăng, hành động P cũng tăng)
  • Bộ điều khiển của tôi sẽ tích hợp lỗi theo thời gian để loại bỏ lỗi trạng thái ổn định của hệ thống nhưng làm tăng độ vọt lố của nó.
  • Bộ điều khiển D chỉ đơn giản là đạo hàm thời gian cho lỗi. Nói cách khác, nó là độ dốc của sai số. Nó thực hiện một hành động tỷ lệ với đạo hàm của lỗi. Bộ điều khiển này làm tăng tính ổn định của hệ thống.
  • Đầu ra của bộ điều khiển sẽ là tổng của ba bộ điều khiển. Đầu ra của bộ điều khiển sẽ trở thành 0 nếu lỗi trở thành 0.

Giải thích tuyệt vời về bộ điều khiển PID có thể được tìm thấy ở đây.

Quay trở lại với chiếc xe giữ làn đường, biến điều khiển của tôi đang điều chỉnh tốc độ (vì tay lái chỉ có hai trạng thái bên phải hoặc bên trái). Một bộ điều khiển PD được sử dụng cho mục đích này vì hành động D làm tăng tốc độ điều tiết rất nhiều nếu sự thay đổi lỗi rất lớn (tức là độ lệch lớn) và làm chậm xe nếu sự thay đổi lỗi này đến gần 0. Tôi đã thực hiện các bước sau để thực hiện một PD bộ điều khiển:

  • Đặt điểm đặt thành 90 độ (Tôi luôn muốn xe chuyển động thẳng)
  • Tính toán góc lệch từ giữa
  • Độ lệch cung cấp hai thông tin: Sai số lớn như thế nào (độ lệch) và hướng động cơ lái (dấu hiệu của độ lệch). Nếu độ lệch là chiều dương, xe nên đánh lái sang phải, nếu không, xe phải bẻ lái sang trái.
  • Vì độ lệch là âm hoặc dương, một biến "lỗi" được xác định và luôn bằng giá trị tuyệt đối của độ lệch.
  • Sai số được nhân với một hằng số Kp.
  • Sai số được phân biệt theo thời gian và được nhân với một hằng số Kd.
  • Tốc độ của động cơ được cập nhật và vòng lặp bắt đầu lại.

Đoạn mã sau được sử dụng trong vòng lặp chính để điều khiển tốc độ của động cơ điều tiết:

speed = 10 # tốc độ hoạt động tính bằng% PWM

# Các biến sẽ được cập nhật mỗi vòng lặp LastTime = 0 lastError = 0 # Hằng số PD Kp = 0,4 Kd = Kp * 0,65 Trong khi True: now = time.time () # biến thời gian hiện tại dt = now - lastTime lệch = lái_angle - tương đương 90 # thành lỗi biến angle_to_mid_deg = abs (độ lệch) nếu độ lệch -5: # không điều khiển nếu có độ lệch phạm vi lỗi 10 độ = 0 lỗi = 0 GPIO.output (in1, GPIO. LOW) GPIO.output (in2, GPIO. LOW) Driving.stop () elif lệch> 5: # lái sang phải nếu độ lệch là dương GPIO.output (in1, GPIO. LOW) GPIO.output (in2, GPIO. HIGH) Driving.start (100) elif lệch < -5: # chỉ đạo sang trái nếu độ lệch là âm GPIO.output (in1, GPIO. HIGH) GPIO.output (in2, GPIO. LOW) drive.start (100) phái sinh = kd * (error - lastError) / dt ratio = kp * lỗi PD = int (tốc độ + đạo hàm + tỷ lệ) spd = abs (PD) nếu spd> 25: spd = 25 ga.start (spd) lastError = lỗi lastTime = time.time ()

Nếu sai số rất lớn (độ lệch từ giữa cao), các hành động tỷ lệ và đạo hàm cao dẫn đến tốc độ điều chỉnh cao. Khi lỗi tiếp cận 0 (độ lệch từ giữa thấp), hành động đạo hàm tác động ngược lại (độ dốc là âm) và tốc độ điều chỉnh sẽ thấp để duy trì sự ổn định của hệ thống. Mã đầy đủ được đính kèm bên dưới.

Bước 7: Kết quả

Các video trên cho thấy kết quả mà tôi thu được. Nó cần điều chỉnh nhiều hơn và điều chỉnh thêm. Tôi đang kết nối raspberry pi với màn hình LCD của mình vì video truyền trực tuyến qua mạng của tôi có độ trễ cao và rất khó chịu khi làm việc với nó, đó là lý do tại sao có dây kết nối với raspberry pi trong video. Tôi đã sử dụng bảng xốp để vẽ đường đi.

Tôi đang chờ nghe các đề xuất của bạn để làm cho dự án này tốt hơn! Vì tôi hy vọng rằng tài liệu hướng dẫn này đủ tốt để cung cấp cho bạn một số thông tin mới.

Đề xuất: