[Из песочницы] Автономная навигация мобильного робота
Существует огромное количество способов, при помощи которых робот может получать информацию из внешнего мира с целью взаимодействия с ним. Также, в зависимости от задач, поставленных перед ним, различаются и методы обработки этой информации. В данной статье я опишу основные этапы работы, выполненной в рамках школьного проекта, цель которого — систематизировать информацию, посвященную различным методам автономной навигации робота и применить полученные знания в процессе создания робота для соревнований «Кубок РТК».
Введение
На соревнованиях «Кубок РТК» есть блок задач, которые должны быть выполнены без вмешательства оператора. Я считаю, что многие участники несправедливо обходят эти задания стороной, ведь за кажущейся сложностью создания конструкции робота и написания программы, скрываются во многом упрощенные задания из других соревновательных дисциплин, объединенные в одном полигоне. Своим проектом я хочу показать, возможные решения подобных задач, рассмотрев в качестве примера следование по линии.
Для достижения цели проекта были сформулированы следующие промежуточные задачи:
- Анализ регламента соревнований «Кубок РТК»
- Анализ существующих алгоритмов автономного ориентирования мобильного робота
- Создание ПО
Анализ регламента соревнований «Кубок РТК»
На соревнованиях «Кубок РТК» участникам представлен полигон, на котором смоделированы участки различной сложности. Соревнования ставят своей целью стимулировать молодых робототехников на создание устройств, способных работать в экстремальных условиях, преодолевать препятствия, под управлением человека, или же автономно.
Туман:
Буераки:
Участок поля — открытая поверхность, на которой расположение все «большие» испытания (подвесной мост) или испытания, которые невозможно выполнить в условиях ограниченного пространства лабиринта. Также, на многие испытания из зоны поля дополнительно нанесена линия, проехав по которой автономно, робот может получить вдвое больше очков, чем преодолев тот же участок при помощи дистанционного управления.
Башня является завершительной частью всего маршрута. Собрав достаточное количество меток, выдающихся за преодоление особенно сложных испытаний оператору, в башне можно ввести код, который активирует лифт, подымающий робота на верх башни, откуда робот может спрыгнуть, получив большее количество баллов.
Состязания подразделяются на две принципиально отличающиеся друг от друга номинации: «Искатель» и «Экстремал». Это сделано для того, чтобы соревнования проходили между участниками с минимальной разницей в возрасте и опыте разработки робототехнических систем: «Искатель» для младшего уровня, а «Экстремал» — для участников от 14 лет и старше. В номинации «Искатель» оператор может свободно передвигаться по полигону и иметь непосредственный зрительный контакт с машиной, в то время как номинация «Экстремал» предполагает наличие у робота систем видеосвязи и/или компьютерного зрения, так как оператор должен ориентироваться в лабиринте, полагаясь только на встроенные в робота камеру и датчики, находясь при этом за специальной ширмой.
Для квалификации в соревнованиях робот должен либо пройти задание на дистанционное управление манипулятором, или выполнить один из элементов автономности. В рамках реализации проекта была поставлена задача обязательно выполнить задания на автономность, так как они дают наибольшее количество очков при наименьших затратах со стороны оператора. К элементам автономности относятся:
- Движение по линии при помощи датчика освещенности или системы технического зрения на линиях
- Автономный захват маяка при помощи датчика расстояния или систем технического зрения
- Движение по сложной траектории (например, подъем/спуск по лестнице) по линии, с помощи компаса, гироскопа, акселерометра, системы технического зрения или комбинированных методов
Также, баллы за преодоление препятствий удваиваются, если робот проходит их автономно.
В рамках данного проекта будет рассмотрено решение первого из заданий — движения по линии. Наиболее распространенными методами, применяемыми при движении по линии являются датчики освещенности и камера. К плюсам датчиков можно отнести простоту создания программы — многие из них оснащены подстроечным резистором, так что, настроив датчик под фоновое освещение, он будет выдавать 0 или 1 в зависимости от того, находится он на линии или нет. По этой же причине датчики освещенности не требовательны к вычислительной мощности используемого контроллера. Также, из-за этого решение задачи при помощи датчиков освещенности является наименее затратным — стоимость самого простого сенсора составляет 35 рублей, а для относительно стабильной езды по линии достаточно трех датчиков (один установлен на линии, а два — по бокам). Однако, один из главных минусов подобных датчиков — ограничения по установке. Идеально, чтобы датчик был установлен ровно по центру, на небольшом расстоянии от пола, иначе он будет давать некорректные значения. Это не является проблемой на специализированных соревнованиях, где робот должен максимально быстро проехать по трассе, но, в условиях соревнований «Кубок РТК», все вышеизложенные недостатки датчиков могут быть критичными — их установка в первую очередь требует наличия на роботе дополнительных механических частей, поднимающих и опускающих сенсоры, а это требует дополнительного места на роботе, отдельного двигателя, перемещающего датчики, а также является местом потенциальной поломки и увеличивает массу робота.
Камера, в свою очередь, имеет следующие плюсы: обладает практически неограниченным (в сравнении с датчиками) радиусом измерения, т.е. всего один модуль камеры способен одновременно видеть линию, как непосредственно под роботом, так и на достаточном удалении от него, что позволяет, например, оценивать ее кривизну и подбирать пропорциональное управляющее воздействие. При этом, модуль камеры никак не мешает продвижению робота на других частях полигона, не требующих автономности, так как камера закреплена на расстоянии от пола. Основной минус камеры — обработка видео требует наличия на борту робота мощного вычислительного комплекса, а разрабатываемое ПО нуждается в более тонкой отладке, ведь камера получает из окружающего мира на порядок больше информации, нежели три датчика освещенности, при этом камера и компьютер, способный обрабатывать полученную информацию стоят в разы больше трех датчиков и «ардуины».
В данном вопросе лично для меня ответ очевиден — в номинации «экстремал» обязательно наличие у робота курсовой камеры, при помощи которой будет ориентироваться оператор. Если использовать готовые FPV решения, то общая стоимость «датчиков» может получится даже выше, при этом требуя установки дополнительных устройств. Более того, робот с raspberry pi и камерой имеет больший потенциал для развития автономного движения, так как камера может решать большой спектр задач и использоваться не только в движении по линии, при этом не сильно усложняя конструкцию.
Анализ существующих алгоритмов компьютерного зрения
Компьютерное зрение — это теория создания устройств, способных получать изображения объектов реального мира, обрабатывать и использовать полученные данные для решения разного рода прикладных задач без участия человека.
Системы компьютерного зрения состоит из:
- одной или нескольких камер
- вычислительного комплекса
- Программного обеспечения, которое предоставляет инструменты для обработки изображений
- Каналов связи для передачи целевой и телеметрической информации.
Как писалось ранее, существует множество способов идентифицировать интересующие нас объекты. В случае езды по линии, необходимо отделить саму линию от контрастирующего фона (черная линия на белом фоне или белая линия на черном фоне для инверсной линии). Алгоритмы, использующие систему компьютерного зрения можно условно разделить на несколько «шагов» по обработке исходного изображения:
Получение изображений: цифровые изображения получаются напрямую из камеры, из передающегося на устройство видеопотока или в виде отдельных изображений. Значения пикселей обычно соответствуют интенсивности света (цветные или изображения в оттенках серого), но могут быть связаны с различными физическими измерениями, такими как, например, температура из тепловизионной камеры.
Предварительная обработка: перед тем, как методы компьютерного зрения могут быть применены к видеоданным, необходима предварительная обработка, вводимая с целью удовлетворения некоторым условиям, в зависимости от используемого метода. Примерами являются:
- Удаление шума или искажений, вносимых используемым датчиком
- Размытие изображения, используемое с целью избавления от мелких артефактов, возникающих во время работы камеры, элементов декомпрессии, шума, и т.д.
- Улучшение контрастности для того, чтобы нужная информация могла быть обнаружена с большей долей вероятности
- Изменение экспозиции для отсечения теней или пересвеченных элементов
- Масштабирование или обрезка для лучшего различения структур на изображении.
- Перевод изображения в монохромный формат или изменение его разрешения для большего быстродействия системы
Выделение деталей: детали изображения различного уровня сложности выделяются из видеоданных. Типичными примерами таких деталей являются линии, границы, кромки, отдельные точки, области, характерные по какому-либо признаку.
Детектирование: на определённом этапе работы программы отделяется информация, имеющая значение для программы, от остального изображения. Примерами являются:
- Выделение определённого набора интересующих точек по цвету, количеству обособленных пикселей, схожих по какому-то признаку (кривизне фигуры, цвету, яркости и т.д.)
- Сегментация одного или нескольких участков изображения, которые содержат характерный объект.
Высокоуровневая обработка: на этом шаге обилие информации из изображения уменьшается до размера, который можно легко обработать, например, набор определенных пикселей или координаты участка изображения, в котором предположительно находится интересующий нас объект. Примерами являются:
- Фильтрация значений по какому-либо критерию
- Оценка таких параметров, как физические размеры объекта, форма, его расположение в кадре или относительно других характерных объектов
- Классификация
Далее было необходимо выбрать библиотеку, на базе которой будет создаваться программа. Ключевыми факторами в моем выборе стали:
- Поддержка библиотекой интерфейса на языке Python из-за относительной простоты в изучении этого языка новичком, простого синтаксиса, что благотворно сказывается на читаемости программы.
- Портативность, т.е. возможность запуска программы, использующей данную библиотеку на raspberry pi3.
- Распространенность библиотеки, что гарантирует хорошо развитое сообщество из программистов, возможно, уже когда-то сталкивавшихся с проблемами, которые могут возникнуть у вас в процессе работы.
Среди рассмотренных мной вариантов я выделил открытую библиотеку компьютерного зрения OpenCV, так как она поддерживает работу на Python, имеет обширную онлайн-документацию. В интернете есть множество статей и инструкций, описывающих все тонкости работы с данной библиотекой. Есть официальный форум от разработчиков, где любой желающий может задать вопрос о ней. Также, эта библиотека реализована на языках C/C++, что гарантирует быстродействие системы, а ее структура поддерживает различные модули, которые могут быть отключены с целью увеличения производительности.
Разработка программного обеспечения
После установки ОС и первичной настройки Raspberry pi, но перед тем, как начинать создание программы, необходимо установить все необходимые для пакеты. Большинство этих пакетов, в свою очередь, устанавливаются при помощи менеджера пакетов pip (в случае Python 3 — pip3)
$ sudo apt install python3-pip
Далее устанавливаются такие библиотеки, как:
- picamera — библиотека для работы с камерой raspberry pi
- numpy — библиотека для работы с многомерными массивами данных, в качестве которых и представлены изображения
$ sudo pip3 install picamera
$ sudo pip3 install numpy
cmake — Утилита для автоматической сборки программы из исходного кода
cmake-curses-gui — Пакет GUI (графический интерфейс) для cmake
$ sudo apt-get install cmake cmake-curses-gui libgtk2.0-dev
$ sudo apt-get install cmake cmake-curses-gui libgtk2.0-dev
библиотеки для работы с разными форматами изображений и видео и прочее
$ sudo apt-get install libavcodec-dev libavformat-dev libswscale-dev libv4l-dev libx264-dev libxvidcore-dev
$ sudo apt-get install libjpeg-dev libpng12-dev libtiff5-dev libjasper-dev
$ sudo apt-get install gfortran libatlas-base-dev
Для трансляции видеоданных с робота на компьютер будет использован GStreamer — фреймворк, предназначенный для получения, обработки и передачи мультимедиа данных:
$ sudo apt install libgstreamer1.0-0 gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-libav gstreamer1.0-doc gstreamer1.0-tools gstreamer1.0-x gstreamer1.0-alsa gstreamer1.0-gl gstreamer1.0-gtk3 gstreamer1.0-qt5 gstreamer1.0-pulseaudio
Следующим шагом будет установка самой библиотеки openCV из источников, ее конфигурация и сборка. Для этого создается рабочая папка opencv.
$ mkdir opencv
$ cd opencv
Для того, чтобы скачать последние версии библиотеки использован wget –консольная программа для загрузки файлов из сети. На момент создания программы последняя стабильная версия openCV — 4.1.0, поэтому загружаем и распаковываем исходники:
$ wget https://github.com/opencv/opencv/archive/4.1.0.zip -O opencv_source.zip
$ unzip opencv_source.zip
$ wget https://github.com/opencv/opencv_contrib/archive/4.1.0.zip -O opencv_contrib.zip
$ unzip opencv_contrib.zip
После завершения процесса распаковки, исходные архивы можно удалить.
$ rm opencv_source.zip
$ rm opencv_contrib.zip
Создается директория для сборки и конфигурации.
$ cd /home/pi/opencv/opencv-4.1.0
$ mkdir build
$ cd build
Производится настройка параметров сборки, при помощи утилиты cmake. Для этого, все значимые параметры передаются в качестве переменных утилиты, вместе с присваиваемыми значениями:
cmake -D CMAKE_BUILD_TYPE=RELEASE \
-D CMAKE_INSTALL_PREFIX=/usr/local \
-D INSTALL_PYTHON_EXAMPLES=ON \
-D INSTALL_C_EXAMPLES=OFF \
-D BUILD_opencv_python2=OFF \
-D WITH_GSTREAMER=ON \
-D BUILD_EXAMPLES=ON ..
После настройки конфигурации утилита выведет все параметры. Далее необходимо произвести компиляцию библиотеки. Для этого используется консольная команда make –jN, где N — количество ядер, которое будет задействовано в процессе компиляции. Для raspberry pi максимальное число ядер — 4, но узнать это число точно можно прописав в консоли команду nproc.
$ make –j4
Из-за ограниченности ресурсов raspberry, компиляция может идти достаточно долго. В некоторых случаях малина может даже зависнуть, но если потом зайти в папку сборки и заново прописать make, работа продолжится. Если такое произошло, стоит уменьшить количество задействованных ядер, однако, у меня компиляция прошла без проблем. Также, на этом этапе стоит задуматься об активном охлаждении raspberry, ведь даже вместе с ним температура процессора достигала порядка 75 градусов.
Когда компиляция завершилась успешно, библиотеку необходимо установить. Делается это также при помощи утилиты make. Потом сформируем все необходимые связи утилитой ldconfig:
$ sudo make install
$ sudo ldconfig
Проверяем правильность установки, прописав в интерактивном режиме python следующие команды:
import cv2
print(cv2.getBuildInformation())
Свидетельством корректной установки будет являться следующий вывод программы
Следует отметить, что вышеизложенную процедуру компиляции библиотеки необходимо произвести как на роботе, так и на ПК, с которого планируется управлять роботом и на котором будет производится прием трансляции с робота.
Создание схемы распределения видеоданных
Перед тем, как приступить к написанию программного кода необходимо разработать схемы, согласно которым будет функционировать алгоритм. В рассматриваемом случае разработки ПО робота, создаваемого для участия в соревнованиях «Кубок РТК» в номинации «Экстремал», вся программа будет разделена на две части: робот и «пульт», в роли которого будет выступать компьютер с установленной ОС Linux. Одной из важнейших задач здесь является создание примерной схемы того, как видеоданные будут передаваться между различными частями алгоритма. В качестве канала связи между двумя устройствами будет использован Wi-Fi. Пакеты данных, обеспечивающие управление роботом и данные обратной связи будут передаваться от одного устройства к другому при помощи протокола UDP, реализованного в с использованием библиотеки socket. Видеоданные, из-за ограничения по объему UDP пакета будут передаваться при помощи GStreamer. Для удобства отладки будет реализовано два видеопотока:
- основной видеопоток — передает видеоданные напрямую с камеры робота на компьютер для обеспечения минимальной задержки при управлении.
- вспомогательный видеопоток– передает обработанные роботом видеоданные, необходимые для настройки и отладки программы, реализующей компьютерное зрение.
На роботе будут единовременно активны два видеопотока, а компьютер будет выводить нужную картинку в зависимости от включенного режима езды. Робот, в свою очередь в зависимости от включенного или выключенного режима автономности, будет использовать либо управляющие данные, полученные с компьютера, либо сформированные обработчиком изображений.
Дистанционное управление роботом будет осуществляться за счет работы двух параллельных потоков на роботе и на компьютере:
- «пульт» в цикле опрашивает все доступные устройства ввода и формирует управляющий пакет данных, состоящий из самих данных, а также контрольной суммы (на момент внесения финальных правок в статью я отказался от создания контрольных сумм с целью уменьшения задержки, но в исходниках, которые я выложил в конце этот участок кода оставлен) — некоторого значения, рассчитанного по набору данных путем работы некоторого алгоритма, используемого для определения целостности данных при их передаче
- Робот — ожидает подхода данных от компьютера. Распаковывает данные, заново вычисляет контрольную сумму и сравнивает ее с отправленной и рассчитанной на стороне компьютера. Если контрольные суммы совпали, данные передаются основной программе.
Перед разбором алгоритма детектирования линии, предлагаю ознакомиться с особенностями конструкции робота:
Вот — очень похожий на купленный мной кит набор. Состоит он из трех массивных (3 мм в толщину) деталей из гнутого алюминия с нанесенным анодированным покрытием. В комплекте с ним шли коллекторные двигатели с редуктором с одной стороны и датчиками холла с другой, которые впоследствии были заменены на бесколлекторные. С каждой стороны робота по 6 катков, один из них напрямую присоединен к валу мотора. Остальные установлены на два маленьких подшипника с фланцем и прижаты винтом. Пластмассовые гусеницы абсолютно не имеют сцепления. Эту проблему у себя я решил промазав их сверху термоклеем. После обкатки робота на ардуино, было решено делать что-то более серьезное. Для этого в качестве «мозга» робота выбрана rasberry pi 3 b — самая продвинутая на то время модель.
Она, а также шилд для контроля за устройствами, управляющимися посредством ШИМ, помещены в корпус, смоделированный в Solidworks и напечатанный из petg пластика. Корпус оснащен активным охлаждением, что позволило снизить температуру raspberry на порядок в сравнении с обычным пассивным радиатором.
Для обеспечения связи с роботом установлены две промышленные точки доступа ubiquiti bullet M5 hp. Так как круглый корпус (который также требует охлаждения) закрепить на роботе не представлялось возможным, он заменен на напечатанный вариант с добавленной системой активного охлаждения. И на робота, и на «пульт» установлена вот такая антенна.
Манипулятор: в качестве основы для «клешни» выбор пал на следующую модель из thingiverse. Понравилась она своей компактностью, надежной конструкцией, а также фланцем, задуманным под установку на поворотную платформу, которую я с самого начала планировал сделать на своем роботе.
Поворотный блок для клешни, в отличие от самой клешни, разрабатывался с нуля. Состоит он из четырех напечатанных деталей корпуса, шестерни и двух больших подшипников. В качестве двигателя для него используется маленькая серва с вынесенным из корпуса наружу потенциометром, таким образом, что при управлении сервоприводом я могу задавать угол, на который поворачивается сама клешня, а не вал сервопривода. Несмотря на небольшую заявленную мощность, сервопривода вполне хватает для выполнения таких задач, как вращение в клешне литровой бутылки с водой, захваченной не ровно по центру.
из-за ограничения в виде потенциометра (он поворачивается всего где-то на 200 градусов) и платы внутри сервопривода, которая вращала клешню не в полном диапазоне, получилось добиться поворота в 90 градусов в одну сторону и приблизительно 70 в другую (можно увидеть на прикрепленной выше гифке), чего хватает для выполнения всех заданий для проверки возможностей манипулятора, представленных на полигоне соревнований «кубок РТК». Также, на конец манипулятора добавлен инфракрасный лазерный датчик расстояния на базе чипа VL53L0X для создания в дальнейшем алгоритма автоматического захвата маяков, или же других точных работ манипулятором.
Сама «рука» манипулятора собрана из соединительных элементов, шедших в комплекте с сервоприводами, которые я использовал (rds3115). Из их особенностей — крепления, предназначенные для использования в робототехнике, небольшая скорость, но большой создаваемый крутящий момент, вкупе с относительно невысокой стоимостью.
Единственным добавлением к этим элементам является деталь, поворачивающая второй сервопривод на несколько градусов, что позволило уменьшить зону, недоступную для манипулятора:
Из-за того, что центр тяжести у рамы слишком завышен, также пришлось добавить сзади робота дополнительные два колеса без привода, на которые робот может дополнительно опираться. Для большей мобильности эти колеса могут подниматься от земли перпендикулярно ей. В качестве курсовой камеры использован штатный модуль от raspberry, установленный на поворотной платформе под робота, под защитой толстого переднего щитка. Для использования камеры в режиме автономной езды, она может поворачиваться практически перпендикулярно полу.
Сейчас на роботе есть вторая камера, подключающаяся по USB. Расположена она на манипуляторе и предназначена для более точной работы с ним, но также планируется при помощи нее расширить возможности робота по автономной работе, задействовав ее для захвата маяков.
А так выглядит переход робота в режим автономной езды
Создание алгоритма детектирования линии методами библиотеки OpenCV
I.Получение данных
По причине того, что обработчик изображений получает видеоданные не напрямую с камеры, а из основного потока, необходимо перевести их из формата, используемого при трансляции в формат, используемый для обработки изображений, а именно — numpy массив, состоящий из значений красного, зеленого и синего цветов для каждого из пикселей. Для этого необходимы начальные данные — кадр, полученный с модуля камеры raspberry pi.
Самый простой способ получения кадров с камеры c с целью последующей обработки — использование библиотеки picamera. Перед началом работы нужно разрешить доступ к камере через raspi-config --> interfacing options camera --> выбрать yes.
sudo raspi-config
следующий участок кода подключается к камере raspberry и в цикле с заданной частотой получает кадры в виде массива, готового к использованию библиотекой opencv.
from picamera.array import PiRGBArray
from picamera import PiCamera
import cv2
#импортируем все нужные библиотеки
camera = PiCamera()
camera.resolution = (640, 480)
camera.framerate = 30
cap = PiRGBArray(camera, size=(640, 480))
for frame in camera.capture_continuous(cap , format="bgr", use_video_port=True):
new_frame = frame.array
cap.truncate(0)
if False: #здесь должно быть какое-нибудь условие для выхода
break
Также стоит отметить что подобный способ захвата кадров хоть и является самым простым, но имеет серьезный недостаток: он не очень эффективен если необходимо транслировать кадры через GStreamer, так как это требует несколько раз перекодировать видео, что уменьшает быстродействие программы. Намного более быстрым способом получения изображений будет выдавать кадры из видеопотока по требованию обработчика изображений, однако, дальнейшие этапы обработки изображения не будут зависеть от использованного метода.
Пример изображения с курсовой камеры робота без обработки:
II. Предварительная обработка
При езде по линии наиболее просто будет отделить область точек, наиболее контрастирующих с фоновым цветом. Такой метод идеально подходит для соревнований «кубок РТК», ведь там используются черная линя на белом фоне (или белая линия на черном фоне для инверсных участков). Для того, чтобы уменьшить количество информации, нуждающейся в обработке, можно применить к нему алгоритм бинаризации, то есть перевести изображение в монохромный формат, где есть только два типа пикселей — темные и светлые. Перед этим картинку следует перевести в градации серого, а также размыть его для того, чтобы отсечь мелкие дефекты и шум, неизбежно появляющийся в процессе работы камеры. Для размытия картинки применяется фильтр Гаусса.
gray = cv2.cvtColor(self._frame, cv2.COLOR_RGB2GRAY)
blur = cv2.GaussianBlur(gray, (ksize, ksize), 0)
где ksize — размер Гауссова ядра, увеличивая который, можно увеличивать степень размытия.
Пример изображения после перевода в градации серого и размытия:
III. Выделение деталей
После того, как изображение переведено в градации серого, необходимо произвести его бинаризацию по заданному порогу. Это действие позволяет еще сильнее уменьшить объем данных, Данное пороговое значение будет корректироваться перед каждым выездом робота на новом месте, или при изменении условий освещения. В идеале, задачей калибровки является сделать так, чтобы на изображении определился контур линии, но, при этом, на изображении не должно быть других деталей, которые не являются линией:
thresh = cv2.threshold(blur, self._limit, 255, cv2.THRESH_BINARY_INV)[1]
Здесь все пиксели темнее порогового значения (self._limit) заменяются на 0 (черный), светлее — на 255 (белый).
После обработки изображение выглядит следующим образом:
Как можно заметить, программа выделила несколько наиболее темных фрагментов изображения. Однако, откалибровав пороговое значение так, чтобы полностью «поймать» наушники, на экране кроме них появляются и другие белые элементы. Конечно, можно более тонко настраивать порог, да и на соревновательном полигоне камера будет смотреть вниз, не допуская в кадр лишние элементы, но я считаю для себя нужным отделить линию от всего остального.
IV.Детектирование
На бинаризированном изображении я применил алгоритм поиска границ. Он нужен для того, чтобы определить отдельно стоящие пятна и превратить их в удобный массив из значений координат точек, составляющих границу. В случае opencv, как написано в документации, стандартный алгоритм для поиска контуров использует алгоритм Suzuki85 (я не смог найти упоминаний алгоритма с конкретно таким названием нигде, кроме документации opencv, но предположу, что это — алгоритм Сузуки-Абе).
contours = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)[0]
И вот так выглядит полученный на этом этапе кадр:
V. Высокоуровневая обработка
Найдя все контуры в кадре, выбирается контур с наибольшей площадью и принимается за контур линии. Зная координаты всех точек этого контура, находится координата его центра. Для этого используются так называемые «моменты изображения». Момент — это суммарная характеристика контура, рассчитанная суммированием координат всех пикселей контура. Разделяют несколько видов моментов — вплоть до третьего порядка. Для данной задачи нужны только момент нулевого порядка (m00) — количество всех точек, составляющих контур (периметр контура), момент первого порядка (m10), представляющий собой сумму X координат всех точек, и m01 — сумму Y координат всех точек. Делением суммы координат точек по одной из осей на их количество получается среднее арифметическое — примерная координата центра контура. Далее вычисляется отклонение робота от курса: курсу «прямо» соответствует координата точки центра по X близкая к значению ширины кадра, деленной на два. Если координата центра линии близка к центру кадра, то управляющее воздействие минимально, и, соответственно, робот сохраняет свой текущий курс. Если робот отклоняется одну из сторон, то будет вводится пропорциональное отклонению управляющее воздействие, пока он не вернется обратно.
mainContour = max(contours, key = cv2.contourArea)
M = cv2.moments(mainContour)
if M['m00'] != 0:#не получается деление на ноль (т.е. если получен хотя-бы один контур)
cx = int(M['m10']/M['m00'])
cy = int(M['m01']/M['m00'])
Ниже показан схематичный рисунок положения робота относительно линии и кадры, с наложенными на них результатами работы программы: «главный» контур, линии, проходящие через центр контура, а также точка, находящаяся в центре для оценки отклонения. Эти элементы добавляются при помощи следующего кода:
cv2.line(frame, (cx, 0), (cx, self.height), (255, 0, 0), 1) # рисуем перекрестие на контуре
cv2.line(frame, (0, cy), (self.width, cy), (255, 0, 0), 1)
cv2.circle(frame, (self.width//2, self.height//2), 3, (0, 0, 255), -1) #рисуем центральную точку
cv2.drawContours(frame, mainContour, -1, (0, 255, 0), 2, cv2.FILLED) #отображаем контуры на изображении
Для удобства отладки все ранее описанные элементы добавляются на необработанный кадр:
Таким образом, прогнав кадр через алгоритм обработки, мы получили координаты X и Y центра интересующего нас объекта, а также отладочное изображение. Далее схематично показано положение робота относительно линии, а также изображение, прошедшее алгоритм обработки.
Следующим этапом в работе программы является преобразование информации, полученной в предыдущем этапе в значения мощности двух моторов.
Самым простым способом преобразовать разницу между смещением центра цветного пятна относительно центра кадра пропорциональный регулятор (Есть еще релейный регулятор, но, в силу особенностей своей работы, он не очень подходит для езды по линии). Принцип действия подобного алгоритма заключается в том, что регулятор вырабатывает управляющее воздействие на объект пропорционально величине ошибки. Помимо пропорционального регулятора также существуют интегральный, где со временем интегральная составляющая «накапливает» ошибку и дифференциальные, принцип действия которых основывается на применении регулирующего воздействия только при достаточном изменении регулируемой величины. На практике данные простейшие П, И, Д регуляторы комбинируются в регуляторы вида ПИ, ПД, ПИД.
Стоит оговорить, что на своем роботе я пытался «завести» ПИД-регулятор, но его использование не дало никаких серьезных преимуществ перед обычным пропорциональным регулятором. Я допускаю что я мог недостаточно качественно настроить регулятор, но также возможно, что его преимущества нет так явно видны в случае с тяжелым роботом, неспособным физически развить большие