Оглавление:

Автономный автомобиль для удержания полосы движения с использованием Raspberry Pi и OpenCV: 7 шагов (с изображениями)
Автономный автомобиль для удержания полосы движения с использованием Raspberry Pi и OpenCV: 7 шагов (с изображениями)

Видео: Автономный автомобиль для удержания полосы движения с использованием Raspberry Pi и OpenCV: 7 шагов (с изображениями)

Видео: Автономный автомобиль для удержания полосы движения с использованием Raspberry Pi и OpenCV: 7 шагов (с изображениями)
Видео: Системы Автопилота на Авто 2024, Ноябрь
Anonim
Автономный автомобиль для удержания полосы движения с использованием Raspberry Pi и OpenCV
Автономный автомобиль для удержания полосы движения с использованием Raspberry Pi и OpenCV

В этих инструкциях будет реализован автономный робот, поддерживающий полосу движения, который будет проходить через следующие этапы:

  • Сбор деталей
  • Установка необходимого программного обеспечения
  • Монтаж оборудования
  • Первый тест
  • Обнаружение полос движения и отображение направляющей линии с помощью openCV
  • Реализация контроллера PD
  • Полученные результаты

Шаг 1: Сбор компонентов

Сбор компонентов
Сбор компонентов
Сбор компонентов
Сбор компонентов
Сбор компонентов
Сбор компонентов
Сбор компонентов
Сбор компонентов

На изображениях выше показаны все компоненты, используемые в этом проекте:

  • Радиоуправляемая машина: я купил свой в местном магазине в моей стране. Он оборудован 3 моторами (2 дросселирования и 1 рулевого управления). Основным недостатком этого автомобиля является то, что рулевое управление ограничено между «без рулевого управления» и «полным рулевым управлением». Другими словами, он не может поворачивать под определенным углом, в отличие от радиоуправляемых машин с сервоуправлением. Вы можете найти аналогичный автомобильный комплект, разработанный специально для Raspberry Pi здесь.
  • Raspberry pi 3 model b +: это мозг автомобиля, который будет обрабатывать множество этапов обработки. Он основан на четырехъядерном 64-битном процессоре с тактовой частотой 1,4 ГГц. Я получил свой отсюда.
  • Модуль камеры Raspberry Pi 5 МП: поддерживает запись 1080p при 30 кадрах в секунду, 720p при 60 кадрах в секунду и 640x480p при 60/90 кадрах. Он также поддерживает последовательный интерфейс, который можно подключить непосредственно к Raspberry Pi. Это не лучший вариант для приложений обработки изображений, но для этого проекта его достаточно, так как он очень дешевый. Я получил свой отсюда.
  • Драйвер двигателя: используется для управления направлением и скоростью двигателей постоянного тока. Он поддерживает управление 2 двигателями постоянного тока на 1 плате и выдерживает напряжение 1,5 А.
  • Power Bank (необязательно): я использовал внешний аккумулятор (на 5 В, 3 А), чтобы отдельно включить raspberry pi. Понижающий преобразователь (понижающий преобразователь: выходной ток 3 А) должен использоваться для питания raspberry Pi от 1 источника.
  • Аккумулятор LiPo 3 с (12 В): Литий-полимерные аккумуляторы известны своей превосходной производительностью в области робототехники. Он используется для питания драйвера двигателя. Я купил свой отсюда.
  • Перемычки между мужчинами и женщинами и женщинами.
  • Двусторонний скотч: используется для крепления компонентов на радиоуправляемой машине.
  • Синяя лента: это очень важный компонент этого проекта, он используется для создания двух полос движения, между которыми будет проезжать машина. Вы можете выбрать любой цвет, который хотите, но я рекомендую выбирать цвета, отличные от цветов окружающей среды.
  • Застежки-молнии и деревянные перекладины.
  • Отвертка.

Шаг 2: Установка OpenCV на Raspberry Pi и настройка удаленного дисплея

Установка OpenCV на Raspberry Pi и настройка удаленного дисплея
Установка OpenCV на Raspberry Pi и настройка удаленного дисплея

Этот шаг немного раздражает и займет некоторое время.

OpenCV (компьютерное зрение с открытым исходным кодом) - это библиотека программного обеспечения для компьютерного зрения и машинного обучения с открытым исходным кодом. В библиотеке более 2500 оптимизированных алгоритмов. Следуйте ЭТОМ очень простому руководству, чтобы установить openCV на свой raspberry pi, а также установить операционную систему raspberry pi (если вы еще этого не сделали). Обратите внимание, что процесс создания openCV может занять около 1,5 часов в хорошо охлаждаемой комнате (поскольку температура процессора будет очень высокой!), Так что выпейте чаю и терпеливо ждите: D.

Для удаленного дисплея также следуйте ЭТОМУ руководству по настройке удаленного доступа к вашему raspberry pi с вашего устройства Windows / Mac.

Шаг 3: Соединение деталей вместе

Соединение частей вместе
Соединение частей вместе
Соединение частей вместе
Соединение частей вместе
Соединение частей вместе
Соединение частей вместе

На изображениях выше показаны соединения между Raspberry Pi, модулем камеры и драйвером двигателя. Обратите внимание, что двигатели, которые я использовал, потребляют 0,35 А при напряжении 9 В каждый, что позволяет водителю двигателя безопасно запускать 3 двигателя одновременно. И поскольку я хочу точно так же контролировать скорость двух дроссельных двигателей (1 задний и 1 передний), я подключил их к одному порту. Я закрепил привод мотора на правой стороне машины с помощью двойного скотча. Что касается модуля камеры, я вставил стяжку между отверстиями для винтов, как показано на изображении выше. Затем я прикрепляю камеру к деревянной перекладине, чтобы можно было отрегулировать положение камеры по своему усмотрению. Постарайтесь установить камеру как можно ближе к центру автомобиля. Я рекомендую разместить камеру на высоте не менее 20 см над землей, чтобы улучшить поле зрения перед автомобилем. Схема Fritzing прилагается ниже.

Шаг 4: Первый тест

Первый тест
Первый тест
Первый тест
Первый тест

Тестирование камеры:

После того, как камера установлена и библиотека openCV создана, самое время протестировать наше первое изображение! Мы возьмем фотографию с камеры Pi и сохраним ее как «original.jpg». Сделать это можно двумя способами:

1. Использование команд терминала:

Откройте новое окно терминала и введите следующую команду:

raspistill -o original.jpg

Это сделает неподвижное изображение и сохранит его в каталоге «/pi/original.jpg».

2. Используя любую IDE python (я использую IDLE):

Откройте новый скетч и напишите следующий код:

импорт cv2

video = cv2. VideoCapture (0) while True: ret, frame = video.read () frame = cv2.flip (frame, -1) # используется для поворота изображения по вертикали cv2.imshow ('original', frame) cv2. imwrite ('original.jpg', frame) key = cv2.waitKey (1) if key == 27: break video.release () cv2.destroyAllWindows ()

Посмотрим, что произошло в этом коде. Первая строка импортирует нашу библиотеку openCV для использования всех ее функций. функция VideoCapture (0) начинает потоковую передачу видео в реальном времени из источника, определенного этой функцией, в данном случае это 0, что означает распи-камеру. если у вас несколько камер, нужно поставить разные номера. video.read () будет читать каждый кадр, поступающий с камеры, и сохранять его в переменной, называемой «frame». Функция flip () перевернет изображение относительно оси Y (вертикально), поскольку я устанавливаю камеру в обратном порядке. imshow () отобразит наши кадры со словом «оригинал», а imwrite () сохранит нашу фотографию как original.jpg. waitKey (1) будет ждать 1 мс нажатия любой кнопки клавиатуры и возвращает ее код ASCII. если нажата кнопка escape (esc), возвращается десятичное значение 27 и соответственно разрывает цикл. video.release () остановит запись, а destroyAllWindows () закроет каждое изображение, открытое функцией imshow ().

Я рекомендую протестировать вашу фотографию вторым методом, чтобы познакомиться с функциями openCV. Изображение сохраняется в каталоге "/pi/original.jpg". Исходное фото, сделанное моей камерой, показано выше.

Тестирование двигателей:

Этот шаг важен для определения направления вращения каждого двигателя. Во-первых, давайте кратко познакомимся с принципом работы драйвера двигателя. На изображении выше показана распиновка драйвера двигателя. Разрешение A, вход 1 и вход 2 связаны с управлением двигателем A. Разрешение B, вход 3 и вход 4 связаны с управлением двигателем B. Управление направлением устанавливается частью «Вход», а регулирование скорости - частью «Разрешить». Чтобы управлять направлением двигателя A, например, установите для входа 1 значение HIGH (3,3 В в данном случае, так как мы используем raspberry pi) и установите для входа 2 значение LOW, двигатель будет вращаться в определенном направлении и при установке противоположных значений. к Входу 1 и Входу 2 двигатель вращается в противоположном направлении. Если Вход 1 = Вход 2 = (ВЫСОКИЙ или НИЗКИЙ), двигатель не вращается. Контакты включения принимают входной сигнал широтно-импульсной модуляции (ШИМ) от малины (от 0 до 3,3 В) и соответственно запускают двигатели. Например, сигнал 100% PWM означает, что мы работаем на максимальной скорости, а сигнал 0% PWM означает, что двигатель не вращается. Следующий код используется для определения направления двигателей и проверки их скорости.

время импорта

import RPi. GPIO as GPIO GPIO.setwarnings (False) # Выводы двигателя рулевого управления cabin_enable = 22 # Физический вывод 15 in1 = 17 # Физический вывод 11 in2 = 27 # Физический вывод 13 # Выводы двигателей дроссельной заслонки throttle_enable = 25 # Физический вывод 22 in3 = 23 # Физический контакт 16 in4 = 24 # Физический контакт 18 GPIO.setmode (GPIO. BCM) # Использовать нумерацию GPIO вместо физической 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 (рулевое управление_enable, GPIO.out) # Управление двигателем рулевого управления GPIO.output (in1, GPIO. HIGH) GPIO.output (in2, GPIO. LOW) рулевое управление = GPIO. PWM (рулевое управление, 1000) # установить частоту переключения на 1000 Гц рулевое управление.stop () # Управление двигателями дроссельной заслонки GPIO.output (in3, GPIO. HIGH) GPIO.output (in4, GPIO. LOW) throttle = GPIO. PWM (throttle_enable, 1000) # установить частоту переключения на 1000 Гц throttle.stop () time.sleep (1) throttle.start (25) # запускает двигатель на 25 % ШИМ-сигнал-> (0,25 * напряжение батареи) - драйвер потеря рулевого управления.старт (100) # запускает двигатель при 100% ШИМ-сигнале-> (1 * Напряжение батареи) - время потери водителя. сон (3) throttle.stop () рулевое.стоп ()

Этот код запустит дроссельные двигатели и двигатель рулевого управления на 3 секунды, а затем остановит их. (Потери водителя) можно определить с помощью вольтметра. Например, мы знаем, что сигнал 100% PWM должен давать полное напряжение аккумулятора на клемме двигателя. Но, установив ШИМ на 100%, я обнаружил, что драйвер вызывает падение на 3 В, а двигатель получает 9 В вместо 12 В (именно то, что мне нужно!). Потери не являются линейными, т.е. потери при 100% сильно отличаются от потерь при 25%. После запуска приведенного выше кода мои результаты были следующими:

Результаты дросселирования: если in3 = HIGH и in4 = LOW, двигатели дросселирования будут вращаться по часовой стрелке (CW), то есть автомобиль будет двигаться вперед. В противном случае машина поедет назад.

Результаты рулевого управления: если in1 = HIGH и in2 = LOW, двигатель рулевого управления повернется в крайнее левое положение, т.е. автомобиль повернется влево. В противном случае машина повернется вправо. После некоторых экспериментов я обнаружил, что двигатель рулевого управления не будет вращаться, если сигнал ШИМ не был 100% (то есть двигатель будет вращаться либо полностью вправо, либо полностью влево).

Шаг 5: Определение полос движения и расчет линии курса

Обнаружение полос движения и расчет линии курса
Обнаружение полос движения и расчет линии курса
Обнаружение полос движения и расчет линии курса
Обнаружение полос движения и расчет линии курса
Обнаружение полос движения и расчет линии курса
Обнаружение полос движения и расчет линии курса

На этом шаге будет объяснен алгоритм, который будет управлять движением автомобиля. Первое изображение показывает весь процесс. На входе системы - изображения, на выходе - тета (угол поворота в градусах). Обратите внимание, что обработка выполняется для 1 изображения и будет повторяться для всех кадров.

Камера:

Камера начнет запись видео с разрешением (320 x 240). Я рекомендую снизить разрешение, чтобы получить лучшую частоту кадров (fps), поскольку падение fps будет происходить после применения техник обработки к каждому кадру. Приведенный ниже код будет основным циклом программы и будет добавлять каждый шаг поверх этого кода.

импорт cv2

import numpy as np video = cv2. VideoCapture (0) video.set (cv2. CAP_PROP_FRAME_WIDTH, 320) # установить ширину 320 p video.set (cv2. CAP_PROP_FRAME_HEIGHT, 240) # установить высоту 240 p # Цикл while Истина: 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 ()

Код здесь покажет исходное изображение, полученное на шаге 4, и показано на изображениях выше.

Преобразовать в цветовое пространство HSV:

Теперь, после получения видеозаписи в виде кадров с камеры, следующим шагом будет преобразование каждого кадра в цветовое пространство оттенка, насыщенности и значения (HSV). Основное преимущество этого - возможность различать цвета по уровню яркости. А вот хорошее объяснение цветового пространства HSV. Преобразование в HSV выполняется с помощью следующей функции:

def convert_to_HSV (кадр):

hsv = cv2.cvtColor (frame, cv2. COLOR_BGR2HSV) cv2.imshow ("HSV", hsv) вернуть hsv

Эта функция будет вызвана из основного цикла и вернет кадр в цветовом пространстве HSV. Выше показан кадр, полученный мной в цветовом пространстве HSV.

Обнаружение синего цвета и краев:

После преобразования изображения в цветовое пространство HSV пришло время определить только интересующий нас цвет (то есть синий цвет, поскольку это цвет линий полос). Чтобы извлечь синий цвет из кадра HSV, необходимо указать диапазон оттенка, насыщенности и значения. обратитесь сюда, чтобы получить лучшее представление о значениях HSV. После некоторых экспериментов верхний и нижний пределы синего цвета показаны в приведенном ниже коде. А чтобы уменьшить общее искажение в каждом кадре, края обнаруживаются только с помощью хитрого детектора краев. Подробнее о canny edge можно прочитать здесь. Практическое правило - выбирать параметры функции Canny () с соотношением 1: 2 или 1: 3.

def detect_edges (кадр):

lower_blue = np.array ([90, 120, 0], dtype = "uint8") # нижний предел синего цвета upper_blue = np.array ([150, 255, 255], dtype = "uint8") # верхний предел blue color mask = cv2.inRange (hsv, lower_blue, upper_blue) # эта маска будет отфильтровывать все, кроме синего # обнаруживать края краев = cv2. Canny (mask, 50, 100) cv2.imshow ("край", края) вернуть края

Эта функция также будет вызываться из основного цикла, который принимает в качестве параметра фрейм цветового пространства HSV и возвращает фрейм с обрезанными краями. Окантованная рамка, которую я получил, находится выше.

Выберите регион интереса (ROI):

Выбор интересующей области имеет решающее значение, чтобы сосредоточиться только на одной области кадра. В этом случае я не хочу, чтобы машина видела много предметов в окружающей среде. Я просто хочу, чтобы машина сосредотачивалась на полосах движения и игнорировала все остальное. P. S: система координат (оси x и y) начинается с верхнего левого угла. Другими словами, точка (0, 0) начинается с верхнего левого угла. Ось Y - высота, а ось X - ширина. Приведенный ниже код выбирает интересующую область, чтобы сфокусироваться только на нижней половине кадра.

def region_of_interest (края):

height, width = edge.shape # извлечь высоту и ширину краев frame mask = np.zeros_like (edge) # создать пустую матрицу с такими же размерами краев frame # сфокусировать только нижнюю половину экрана # указать координаты 4 точки (нижний левый, верхний левый, верхний правый, нижний правый) polygon = np.array (

Эта функция принимает рамку с краями в качестве параметра и рисует многоугольник с 4 заданными точками. Он сосредоточится только на том, что находится внутри многоугольника, и проигнорирует все, что находится за его пределами. Рамка моей области интересов показана выше.

Обнаружение сегментов линии:

Преобразование Хафа используется для обнаружения отрезков линии из кадра с краями. Преобразование Хафа - это метод обнаружения любой формы в математической форме. Он может обнаружить практически любой объект, даже если он искажен по определенному количеству голосов. здесь показан отличный справочник по преобразованию Хафа. В этом приложении функция cv2. HoughLinesP () используется для обнаружения строк в каждом кадре. Важными параметрами, которые принимает эта функция, являются:

cv2. HoughLinesP (кадр, rho, theta, min_threshold, minLineLength, maxLineGap)

  • Кадр: это кадр, в котором мы хотим обнаруживать строки.
  • rho: точность расстояния в пикселях (обычно = 1)
  • theta: угловая точность в радианах (всегда = np.pi / 180 ~ 1 градус)
  • min_threshold: минимальное количество голосов, которое он должен получить, чтобы он считался линией
  • minLineLength: минимальная длина строки в пикселях. Любая строка короче этого числа не считается строкой.
  • maxLineGap: максимальный промежуток в пикселях между 2 строками, который рассматривается как 1 строка. (В моем случае он не используется, поскольку используемые мной полосы движения не имеют зазоров).

Эта функция возвращает конечные точки строки. Следующая функция вызывается из моего основного цикла для обнаружения строк с помощью преобразования Хафа:

def detect_line_segments (cropped_edges):

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

Средний угол наклона и точка пересечения (m, b):

Напомним, что уравнение прямой имеет вид y = mx + b. Где m - наклон линии, а b - точка пересечения с y. В этой части будет вычислено среднее значение наклонов и пересечений отрезков линии, обнаруженных с помощью преобразования Хафа. Прежде чем сделать это, давайте взглянем на исходную фотографию рамки, показанную выше. Левая полоса движется вверх, поэтому имеет отрицательный уклон (помните начальную точку системы координат?). Другими словами, левая полоса движения имеет x1 <x2 и y2 x1 и y2> y1, что даст положительный наклон. Таким образом, все линии с положительным уклоном считаются точками правой полосы. В случае вертикальных линий (x1 = x2) наклон будет бесконечным. В этом случае мы пропустим все вертикальные линии, чтобы не получить ошибку. Чтобы повысить точность этого обнаружения, каждый кадр разделен на две области (правую и левую) с помощью 2 граничных линий. Все точки ширины (точки оси x), превышающие правую ограничивающую линию, связаны с вычислением правой полосы. И если все точки ширины меньше левой граничной линии, они связаны с расчетом левой полосы. Следующая функция берет обрабатываемый кадр и сегменты полосы движения, обнаруженные с помощью преобразования Хафа, и возвращает средний уклон и точку пересечения двух линий полосы движения.

def average_slope_intercept (кадр, line_segments):

lane_lines = если line_segments равно None: print («сегмент линии не обнаружен») возвращает lane_lines height, width, _ = frame.shape left_fit = right_fit = Border = left_region_boundary = width * (1 - граница) right_region_boundary = ширина * граница для line_segment в line_segments: для x1, y1, x2, y2 в line_segment: if x1 == x2: print ("пропуск вертикальных линий (наклон = бесконечность)") continue fit = np.polyfit ((x1, x2), (y1, y2), 1) slope = (y2 - y1) / (x2 - x1) intercept = y1 - (slope * x1) if slope <0: если x1 <left_region_boundary и x2 right_region_boundary и x2> right_region_boundary: right_fit. append ((наклон, пересечение)) left_fit_average = np.average (left_fit, axis = 0), если 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 - это двумерный массив, состоящий из координат правой и левой линий полосы # например: lan e_lines =

make_points () - это вспомогательная функция для функции average_slope_intercept (), которая возвращает ограниченные координаты линий полосы движения (от низа до середины кадра).

def make_points (рамка, линия):

height, width, _ = frame.shape slope, intercept = line y1 = height # нижняя часть кадра y2 = int (y1 / 2) # сделать точки от середины кадра вниз, если slope == 0: slope = 0.1 x1 = int ((y1 - перехват) / наклон) x2 = int ((y2 - перехват) / наклон) return

Для предотвращения деления на 0 представлено условие. Если slope = 0, что означает y1 = y2 (горизонтальная линия), присвойте наклону значение, близкое к 0. Это не повлияет на производительность алгоритма, а также предотвратит невозможный случай (деление на 0).

Для отображения полос движения на кадрах используется следующая функция:

def display_lines (frame, lines, line_color = (0, 255, 0), line_width = 6): # цвет линии (B, G, R)

line_image = np.zeros_like (frame), если строк не None: для строки в строках: для x1, y1, x2, y2 в строке: 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 () принимает следующие параметры и используется для объединения двух изображений, но с присвоением каждому из них веса.

cv2.addWeighted (изображение1, альфа, изображение2, бета, гамма)

И вычисляет выходное изображение, используя следующее уравнение:

вывод = альфа * изображение1 + бета * изображение2 + гамма

Более подробная информация о функции cv2.addWeighted () находится здесь.

Вычислить и отобразить строку заголовка:

Это последний шаг перед тем, как мы применим скорости к нашим двигателям. Линия курса отвечает за то, чтобы дать мотору рулевого управления направление, в котором он должен вращаться, и дать моторам дросселирования скорость, с которой они будут работать. Вычисление строки заголовка является чистой тригонометрией, используются тригонометрические функции tan и atan (tan ^ -1). В некоторых крайних случаях камера обнаруживает только одну полосу движения или не обнаруживает никаких линий. Все эти случаи показаны в следующей функции:

def get_steering_angle (кадр, lane_lines):

height, width, _ = frame.shape if len (lane_lines) == 2: # если обнаружены две полосы движения _, _, left_x2, _ = lane_lines [0] [0] # извлечь левый x2 из массива lane_lines _, _, right_x2, _ = lane_lines [1] [0] # извлечь правый x2 из массива lane_lines mid = int (width / 2) x_offset = (left_x2 + right_x2) / 2 - mid y_offset = int (height / 2) elif len (lane_lines) == 1: # если обнаружена только одна линия x1, _, x2, _ = lane_lines [0] [0] x_offset = x2 - x1 y_offset = int (height / 2) elif len (lane_lines) == 0: # если линия не обнаружена 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) рулевой_угол = angle_to_mid_deg + 90 return_ рулевого_угла

x_offset в первом случае - насколько среднее ((правое x2 + левое x2) / 2) отличается от середины экрана. y_offset всегда принимается равным height / 2. На последнем изображении выше показан пример строки заголовка. angle_to_mid_radians - это то же самое, что и "theta", показанное на последнем изображении выше. Если рулевое_угл = 90, это означает, что у автомобиля есть линия курса, перпендикулярная линии «высота / 2», и автомобиль будет двигаться вперед без рулевого управления. Если угол поворота> 90, автомобиль должен поворачивать вправо, в противном случае - влево. Для отображения строки заголовка используется следующая функция:

def display_heading_line (кадр, угол поворота, цвет_линии = (0, 0, 255), ширина_линии = 5)

heading_image = np.zeros_like (frame) height, width, _ = frame.shape рулевой_угол_радиан = рулевой_угол / 180.0 * math.pi x1 = int (width / 2) y1 = height x2 = int (x1 - height / 2 / math.tan (рулевой_угол_радиан)) 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

Вышеупомянутая функция принимает в качестве входных данных фрейм, на котором будет нарисована линия курса, и угол поворота. Возвращает изображение строки заголовка. Кадр линии заголовка, взятый в моем случае, показан на изображении выше.

Объединение всего кода вместе:

Теперь код готов к сборке. В следующем коде показан основной цикл программы, вызывающей каждую функцию:

импорт cv2

импортировать numpy как np video = cv2. VideoCapture (0) video.set (cv2. CAP_PROP_FRAME_WIDTH, 320) video.set (cv2. CAP_PROP_FRAME_HEIGHT, 240), а True: ret, frame = video.read () frame = cv2.flip (frame, -1) # Вызов функций hsv = convert_to_HSV (frame) edge = detect_edges (hsv) roi = region_of_interest (край) line_segments = detect_line_segments (roi) lane_lines = average_slope_intercept (frame, line_segments) lane_lines_image = display_lines (frame_lines) frame_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 ()

Шаг 6: Применение управления PD

Применение управления PD
Применение управления PD

Теперь у нас есть угол поворота, готовый для подачи на двигатели. Как упоминалось ранее, если угол поворота рулевого колеса больше 90, автомобиль должен повернуть направо, в противном случае - налево. Я применил простой код, который поворачивает двигатель рулевого управления вправо, если угол превышает 90, и поворачивает его влево, если угол поворота меньше 90 при постоянной скорости дросселирования (10% PWM), но у меня было много ошибок. Основная ошибка, которую я получил, - когда машина приближается к любому повороту, рулевой двигатель работает напрямую, но дроссельные двигатели заклинивают. Я попытался увеличить скорость троттлинга до 20% ШИМ на поворотах, но закончился тем, что робот вылетел за пределы полосы движения. Мне нужно что-то, что сильно увеличивает скорость дросселирования, если угол поворота очень большой, и немного увеличивает скорость, если угол поворота не такой большой, а затем снижает скорость до начального значения, когда автомобиль приближается к 90 градусам (движется прямо). Решением было использовать контроллер PD.

ПИД-регулятор означает пропорциональный, интегральный и производный регулятор. Этот тип линейных контроллеров широко используется в робототехнике. На изображении выше показан типичный контур ПИД-регулирования с обратной связью. Цель этого контроллера - достичь «уставки» наиболее эффективным способом, в отличие от контроллеров «включения-выключения», которые включают или выключают установку в соответствии с некоторыми условиями. Следует знать некоторые ключевые слова:

  • Уставка: желаемое значение, которого должна достичь ваша система.
  • Фактическое значение: это фактическое значение, измеренное датчиком.
  • Ошибка: разница между заданным и фактическим значением (ошибка = заданное значение - фактическое значение).
  • Управляемая переменная: по ее имени переменная, которой вы хотите управлять.
  • Kp: постоянная пропорциональности.
  • Ki: Интегральная постоянная.
  • Kd: производная константа.

Короче говоря, контур системы ПИД-регулирования работает следующим образом:

  • Пользователь определяет уставку, необходимую для достижения системой.
  • Ошибка вычисляется (ошибка = заданное значение - фактическое).
  • Контроллер P генерирует действие, пропорциональное значению ошибки. (ошибка увеличивается, действие P также увеличивается)
  • Контроллер I со временем интегрирует ошибку, что устраняет ошибку установившегося состояния системы, но увеличивает ее выброс.
  • Регулятор D - это просто производная ошибки по времени. Другими словами, это крутизна ошибки. Он выполняет действие, пропорциональное производной ошибки. Этот контроллер увеличивает стабильность системы.
  • Выход контроллера будет суммой трех контроллеров. Выход контроллера станет 0, если ошибка станет 0.

Здесь можно найти отличное объяснение ПИД-регулятора.

Возвращаясь к автомобилю, удерживающему полосу движения, моей контролируемой переменной была скорость дросселирования (поскольку рулевое управление имеет только два состояния - правое или левое). Для этой цели используется контроллер PD, поскольку действие D значительно увеличивает скорость дросселирования, если изменение ошибки очень велико (т.е. большое отклонение), и замедляет автомобиль, если это изменение ошибки приближается к нулю. Я выполнил следующие шаги для реализации PD. контроллер:

  • Установите уставку на 90 градусов (я всегда хочу, чтобы машина двигалась прямо)
  • Рассчитал угол отклонения от середины
  • Отклонение дает две информации: насколько велика ошибка (величина отклонения) и в каком направлении должен двигаться двигатель рулевого управления (признак отклонения). Если отклонение положительное, автомобиль должен поворачивать вправо, в противном случае - влево.
  • Поскольку отклонение может быть либо отрицательным, либо положительным, определяется переменная «ошибка», которая всегда равна абсолютному значению отклонения.
  • Ошибка умножается на постоянную Kp.
  • Ошибка дифференцируется по времени и умножается на константу Kd.
  • Скорость двигателей обновляется, и цикл запускается снова.

Следующий код используется в основном цикле для управления скоростью дроссельных двигателей:

speed = 10 # рабочая скорость в% PWM

# Переменные, которые будут обновляться в каждом цикле lastTime = 0 lastError = 0 # Константы PD Kp = 0,4 Kd = Kp * 0,65 While True: now = time.time () # переменная текущего времени dt = now - lastTime deviation = рулевое_угл - 90 # эквивалент to angle_to_mid_deg variable error = abs (deviation) if deviation -5: # не рулить, если есть 10-градусное отклонение диапазона ошибки = 0 error = 0 GPIO.output (in1, GPIO. LOW) GPIO.output (in2, GPIO. LOW) wheel.stop () elif deviation> 5: # повернуть вправо, если отклонение положительное GPIO.output (in1, GPIO. LOW) GPIO.output (in2, GPIO. HIGH) cabin.start (100) elif deviation < -5: # повернуть влево, если отклонение отрицательное GPIO.output (in1, GPIO. HIGH) GPIO.output (in2, GPIO. LOW) cabin.start (100) производная = kd * (error - lastError) / dt пропорционально = kp * ошибка PD = int (скорость + производная + пропорциональная) spd = abs (PD), если spd> 25: spd = 25 throttle.start (spd) lastError = error lastTime = time.time ()

Если ошибка очень велика (отклонение от среднего значения велико), пропорциональные и производные действия велики, что приводит к высокой скорости дросселирования. Когда ошибка приближается к 0 (отклонение от среднего низкое), действие производной действует в обратном направлении (наклон отрицательный), и скорость дросселирования становится низкой для поддержания стабильности системы. Полный код прилагается ниже.

Шаг 7: Результаты

На видео выше показаны полученные мной результаты. Это действительно требует дополнительной настройки и дополнительных настроек. Я подключал raspberry pi к своему ЖК-экрану, потому что потоковое видео по моей сети имело высокую задержку и было очень неприятно работать, поэтому в видео есть провода, подключенные к raspberry pi. Я использовал пенопласт, чтобы нарисовать дорожку.

Жду ваших рекомендаций по улучшению этого проекта! Я надеюсь, что эти инструкции были достаточно хороши, чтобы дать вам некоторую новую информацию.

Рекомендуемые: