В данной статье мы рассмотрим создание машины на основе платы Raspberry Pi и библиотеки OpenCV, способную самостоятельно (автономно) обнаруживать линию и ехать вдоль нее. Но перед тем как приступать к изучению данного проекта, убедитесь в том, что у вас есть хотя бы базовые знания по использованию библиотеки OpenCV в плате Raspberry Pi.
Задача обнаружения линии относится к задачам сегментации, целью которой является идентификация пикселей, относящихся к классу линии. Для решения этой задачи необходимо использовать методы обработки изображений, которые в данном случае нам предоставляет библиотека OpenCV. Поскольку проект на самом деле очень большой его автор (ссылка на оригинал приведена в конце статьи) решил разделить его на две части. В этой части мы рассмотрим сборку аппаратного обеспечения проекта и сделаем первые движения нашей машиной. В следующей части этого проекта будет рассмотрено непосредственно автономное движение автомобиля вдоль линии.
Также на нашем сайте вы можете посмотреть другие популярные проекты, в которых была использована библиотека OpenCV:
- определение социальной дистанции с помощью Raspberry Pi и OpenCV;
- обнаружение движения на видео с помощью Raspberry Pi и OpenCV;
- определение пола и возраста людей с помощью Raspberry Pi и OpenCV;
- обнаружение масок на лицах людей с помощью Raspberry Pi и OpenCV;
- распознавание эмоций с помощью Raspberry Pi, OpenCV, TensorFlow и Keras.
Последовательность выполнения проекта
Автор проекта разделил весь проект на несколько шагов, которые описаны далее. В данной статье мы рассмотрим первые четыре из этих шагов.
Часть 1:
- Сборка аппаратной части проекта.
- Установка необходимого программного обеспечения.
- Подготовка кода программы для движения автомобиля.
- Первые движения автомобиля.
Часть 2:
- Обнаружение линии с помощью OpenCV.
- Калибровка двигателей машины для следования вдоль линии.
- Окончательное тестирование проекта.
Выполняйте все эти шаги последовательно, не пропуская ни один из них – это залог успешной реализации данного проекта.
Необходимые компоненты
Шасси автомобиля
Автор проекта использовал для этой цели радиоуправляемую модель машины, которую он купил на Amazon. Разумеется, аналогичную модель машины можно подыскать и в интернет магазине Aliexpress, с которым вы наверняка знакомы. Модель машины имеет 3 электродвигателя постоянного тока (два для движения и один для рулевого управления). Но эта машина не может рулить под определенным углом как радиоуправляемая машина под управлением серводвигателя.
Raspberry Pi 4
Автор проекта использовал плату Raspberry Pi 4 (купить на AliExpress) в качестве основного элемента рассматриваемой машины. Можно использовать и плату Raspberry Pi 3 b+, однако в этом случае возможны некоторые задержки в ее работе.
Модуль камеры (Camera Module) для Raspberry Pi
Данный модуль камеры (купить на AliExpress) позволяет записывать видео со скоростью 30 кадров в секунду при разрешении 1080p, 60 кадров в секунду при разрешении 720p и 60/90 кадров в секунду при разрешении 640x480p. Хотя эта камера не является идеальным решением для задач обработки изображений, для наших целей она вполне подойдет – к тому же она относительно недорого стоит.
Крепление для камеры
Для нашего проекта необходимо использовать специальное крепление для камеры чтобы она смотрела на дорогу под необходимым нам (точным) углом. Мы напечатали это крепление с помощью 3D принтера.
Драйвер двигателя
Мы использовали в данном проекте драйвер двигателя L298N (купить на AliExpress) – он позволяет управлять направлением и скоростью вращения двигателей постоянного тока. Один такой модуль драйвера может управлять двумя двигателями постоянного тока и поддерживает ток до 1.5A.
Power Bank
Для подачи питания на плату Raspberry Pi мы использовали power bank. Если у вас его нет, то вы можете использовать батарейку на 5V, либо использовать любой другой источник питания и конвертор питания, обеспечивающий на своем выходе напряжение 5V и ток 2.2A.
Сборка (пак) из двух аккумуляторов 18650
Для подачи питания на модуль драйвера двигателя L298N мы использовали сборку (пак) из двух аккумуляторов 18650, на выходе которого мы получаем напряжение 7.4 Вольта. Данные литий-ионные аккумуляторы форм-фактора 18650 хорошо зарекомендовали себя в различных проектах робототехники для питания драйверов двигателей.
Черная диаграммная бумага и белая лента
Для подготовки трека для нашей автономной машины мы использовали черную диаграммную бумагу и белую ленту. Убедитесь в том, что вы для подготовки трека использовали эти же самые компоненты потому что они будут влиять на алгоритм обнаружения линии, использованный в проекте.
Мы надеемся, что остальные необходимые детали (соединительные провода, болты, гайки и т.д.) для проекта вы сможете подобрать самостоятельно.
Сборка аппаратной части проекта
Первым делом вам необходимо подготовить шасси автомобиля. Поскольку мы для создания проекта использовали модель радиоуправляемой машины, то мы удалили с нее электронную часть, пак батарей и верхнюю часть корпуса как показано на следующем рисунке.
Плату Raspberry Pi 4 мы поместили в корпус Fusion 360. Затем мы закрепили на машине крепление для камеры. Данное крепление было напечатано на 3D принтере.
Далее с помощью проводов мы осуществили необходимые соединения между тремя двигателями, драйвером двигателя и платой Raspberry Pi 4. Двигатели, которые используются для движения машины, подключаются к одной стороне драйвера двигателя, а двигатель, используемый для рулевого управления, подключается к другой стороне драйвера двигателя.
Схема машины с автономным обнаружением линии на Raspberry Pi представлена на следующем рисунке.
Установка OpenCV на Raspberry Pi 4
Перед тем как устанавливать OpenCV и другие необходимые библиотеки обновите операционную систему Raspberry Pi 4 до последней версии. Для этого можно использовать следующую команду:
1 |
sudo apt-get update |
Далее установим программное обеспечение, которое будет необходимо для последующей установки библиотеки OpenCV.
1 2 3 4 5 6 |
sudo apt-get install libhdf5-dev -y sudo apt-get install libhdf5-serial-dev –y sudo apt-get install libatlas-base-dev –y sudo apt-get install libjasper-dev -y sudo apt-get install libqtgui4 –y sudo apt-get install libqt4-test –y |
И, наконец, установим библиотеку OpenCV на плату Raspberry Pi.
1 |
pip3 install opencv-contrib-python==4.1.0.25 |
Подготовка кода программы
Исходные коды всех необходимых для этой части проекта программ вы можете скачать по следующей ссылке с Github, также коды этих программ представлены в конце данной статьи. По приведенной ссылке вы скачаете каталог с именем “Lane Detection”, который содержит файлы “motors.py”, “keyboardmodule.py” и “testdrivemodule.py”. Используя эти файлы программ мы сможем управлять двигателями нашей машины с помощью клавиатуры.
Файл “motors.py”
Этот файл содержит все функции, необходимые для вращения двигателей вперед или назад. В нем мы использовали библиотеку RPi.GPIO чтобы управлять контактами общего назначения (GPIO) платы Raspberry Pi 4.
1 2 |
import RPi.GPIO as GPIO from time import sleep |
Далее приведены коды функций, используемые для движения автомобиля в направлениях “forward” (вперед), “Backward” (назад), “forward left” (вперед влево), “forward right” (вперед вправо), “backward right” (назад вправо), “backward left” (назад влево). Функция stop используется для остановки машины.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
def frontmiddle(): GPIO.output(in3, GPIO.LOW) GPIO.output(in4, GPIO.LOW) def frontright(): p2.ChangeDutyCycle(100) GPIO.output(in3, GPIO.LOW) GPIO.output(in4, GPIO.HIGH) def frontleft(): p2.ChangeDutyCycle(100) GPIO.output(in3, GPIO.HIGH) GPIO.output(in4, GPIO.LOW) def forward(speed=50,time=0): p1.ChangeDutyCycle(speed) GPIO.output(in1, GPIO.HIGH) GPIO.output(in2, GPIO.LOW) frontmiddle() sleep(time) def backward(speed=50,time=0): p1.ChangeDutyCycle(speed) GPIO.output(in1, GPIO.LOW) GPIO.output(in2, GPIO.HIGH) frontmiddle() sleep(time) def stop(time=0): frontmiddle() GPIO.output(in1, GPIO.LOW) GPIO.output(in2, GPIO.LOW) sleep(time) def fright(speed=50,time=0): forward(speed) frontright() sleep(time) def fleft(speed=50,time=0): forward(speed) frontleft() sleep(time) def bright(speed=50,time=0): backward(speed) frontright() sleep(time) def bleft(speed=50,time=0): backward(speed) frontleft() sleep(time) |
Файл “keyboardmodule.py”
Данный файл содержит функцию getKey() чтобы считывать нажатия клавиш с клавиатуры. Для этой цели мы использовали библиотеку pygame.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
def getKey(keyName): ans= False running = True for event in pygame.event.get(): # error is here if event.type == pygame.QUIT: running = False pygame.quit() if running: pygame.display.flip() keyInput = pygame.key.get_pressed() myKey = getattr(pygame,'K_{}'.format(keyName)) if keyInput[myKey]: ans = True pygame.display.update() return ans |
Файл “tesdrivemodule.py”
Нам необходимо подключить (импортировать) файлы motors.py и keyboardmodule.py в файл testdrivemodule.py. Функция km.init() в данном файле используется для инициализации keyboardmodule.py. Далее в бесконечном цикле while мы будем использовать функцию “km.getKey()” для обнаружения нажатия клавиш ‘w’, ‘s’, ‘q’, ‘e’, ’a’ и ’d’. И, в соответствии с нажатиями данных клавиш мы будем использовать функции mot.forward(), mot.backward(), mot.fleft(), mot.fright(), mot.bright() и mot.bleft() для движения машины в соответствующем направлении.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
import motors as mot import keyboardmodule as km km.init() while True: if km.getKey('w'): print('forward') mot.forward(100) elif km.getKey('s'): print('backward') mot.backward(100) elif km.getKey('q'): print('fleft') mot.fleft(100) elif km.getKey('e'): print('fright') mot.fright(100) elif km.getKey('a'): print('bleft') mot.bright(100) elif km.getKey('d'): print('bright') mot.bleft(100) else: mot.stop(0) |
Тестирование работы проекта
Для тестирования работы этой части проекта вам необходимо загрузить коды рассмотренных программ в плату Raspberry Pi 4. Автор проекта использовал программу mobaxterm для доступа к терминалу Raspberry Pi, вы можете использовать для этой цели любую другую удобную вам программу. В терминале вам необходимо запустить на выполнение файл “testdrivemodule.py”. Убедитесь в том, что файлы “motors.py” и “keyboardmodule.py” находятся в одном каталоге с этим файлом.
1 |
python3 testdrivemodule.py |
После запуска этого файла на выполнение вы сможете управлять движениями машины нажимая соответствующие кнопки на клавиатуре. Более подробно работу этой части проекта вы можете посмотреть на видео, приведенном в конце статьи.
Исходные коды программ на Python
motors.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
#MOTOR CLASS import RPi.GPIO as GPIO from time import sleep in1 = 4 in2 = 17 in3 = 18 in4 = 27 en = 22 en2 = 23 temp1 = 1 GPIO.setmode(GPIO.BCM) GPIO.setup(in1, GPIO.OUT) GPIO.setup(in2, GPIO.OUT) GPIO.setup(in3, GPIO.OUT) GPIO.setup(in4, GPIO.OUT) GPIO.setup(en, GPIO.OUT) GPIO.setup(en2, GPIO.OUT) GPIO.output(in1, GPIO.LOW) GPIO.output(in2, GPIO.LOW) GPIO.output(in3, GPIO.LOW) GPIO.output(in4, GPIO.LOW) p1 = GPIO.PWM(en, 1000) p1.start(50) p2 = GPIO.PWM(en2, 4000) p2.start(50) print("r-run s-stop f-forward b-backward l-low m-medium h-high dm-frontmiddle dr-frontright dl-frontleft e-exit") def frontmiddle(): GPIO.output(in3, GPIO.LOW) GPIO.output(in4, GPIO.LOW) def frontright(): p2.ChangeDutyCycle(100) GPIO.output(in3, GPIO.LOW) GPIO.output(in4, GPIO.HIGH) def frontleft(): p2.ChangeDutyCycle(100) GPIO.output(in3, GPIO.HIGH) GPIO.output(in4, GPIO.LOW) def forward(speed=50,time=0): p1.ChangeDutyCycle(speed) GPIO.output(in1, GPIO.HIGH) GPIO.output(in2, GPIO.LOW) frontmiddle() sleep(time) def backward(speed=50,time=0): p1.ChangeDutyCycle(speed) GPIO.output(in1, GPIO.LOW) GPIO.output(in2, GPIO.HIGH) frontmiddle() sleep(time) def stop(time=0): frontmiddle() GPIO.output(in1, GPIO.LOW) GPIO.output(in2, GPIO.LOW) sleep(time) def fright(speed=50,time=0): forward(speed) frontright() sleep(time) def fleft(speed=50,time=0): forward(speed) frontleft() sleep(time) def bright(speed=50,time=0): backward(speed) frontright() sleep(time) def bleft(speed=50,time=0): backward(speed) frontleft() sleep(time) |
keyboardmodule.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
import pygame def init(): pygame.init() win = pygame.display.set_mode((100,100)) def getKey(keyName): ans= False running = True for event in pygame.event.get(): # error is here if event.type == pygame.QUIT: running = False pygame.quit() if running: pygame.display.flip() keyInput = pygame.key.get_pressed() myKey = getattr(pygame,'K_{}'.format(keyName)) if keyInput[myKey]: ans = True pygame.display.update() return ans def main(): if getKey('LEFT'): print('LEFT') if getKey('RIGHT'): print('RIGHT') if getKey('q'): pygame.quit() if __name__ == '__main__': init() while True: main() |
tesdrivemodule.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
import motors as mot import keyboardmodule as km km.init() while True: if km.getKey('w'): print('forward') mot.forward(100) elif km.getKey('s'): print('backward') mot.backward(100) elif km.getKey('q'): print('fleft') mot.fleft(100) elif km.getKey('e'): print('fright') mot.fright(100) elif km.getKey('a'): print('bleft') mot.bright(100) elif km.getKey('d'): print('bright') mot.bleft(100) else: mot.stop(0) |