[Из песочницы] Как мы делали робота-футболиста
25 ноября 2012 года в Таллинне проводилось крупнейшее в Балтии соревнование роботов — «Роботекс». Мы решили построить робота в категории профессиональный футбол. Конечно, это будет не Криштиану Роналдо, но вызов интересный. Я опишу детали создания и программирования робота. Имя его — Палмер.Сам футбол проходит на площадке зеленого цвета, на котором расположены 11 оранжевых мячиков для гольфа. Имеются ворота, 15 см высотой и примерно 37 см шириной, желтые у одной стороны и синие с другой. Робот должен искать мячи на поле, захватывать их, выбирать нужные ворота и забивать. На поле находятся два робота. Побеждает тот, кто забьет больше мячей. Технические требования к роботу: цилиндр высотой 35 см и диаметром 35 см. Все просто.После обсуждения деталей и пары ящиков пива пришли к такой конфигурации. На борту будет стоять материнская плата mini-формата с процессором Атом. Плюсы — не требуется охлаждение и отдельное питание, компактность. Минусы соответственно в невысокой производительности. Изображение захватывается камерой от Sony Playstation 3, идеальная камера по цене и качеству. Критичным является частота кадров, так как от нее зависит максимально возможная скорость робота. Допустим, при частоте 30 кадров в секунду и скорости робота 3 метра в секунду он будет проезжать между двумя кадрами 10 см, что явно недостаточно для точности захвата мяча. PS3 выдает до 125 кадров в секунду. Драйвер камеры хорошо вписывается в ядро Linux. Операционная система Ubuntu с загрузкой по USB. Для распознавания образов используется любимая OpenCV.
Помимо камеры для исследования окружающего мира используются ИР-датчики и маяки. Инфракрасные датчики позволяют избегать столкновений с другим роботом или воротами. Питание — литиевый аккумулятор, с понижением питания с 14.8V to 12V для материнской платы. Питание для моторов LiPo аккумуляторы (2×4S 14.8V 2200mAh + 2×2S 7.4V ~2000mAh). Отдельное питание для моторов используется потому, что при старте моторов происходит сильное падение напряжения в цепи, что в свою очередь может сбрасывать микроконтроллер. Да и отдельное питание позволяет убрать высокочастотный шум от моторов, что так же сбивает микроконтроллер. В цепи стоят два типа конденсаторов, одни сглаживают падения напряжения, другие, более быстрые, сглаживают высокочастотный шум. Широкополосный шум возникает в процессе искрения щеток мотора, который, проходя по цепи, сбивает микроконтроллеры. Для решения этой проблемы применяется H-Bridge с оптическими прерывателями. H-Bridge — это реле-мост, схема которого позволяет менять полярность тока на моторах без риска короткого замыкания. Сигналы подходящие к мосту проходят через оптические прерыватели. Оптические прерыватели разрывают сеть, и шум от моторов не попадает в цепь контроллера. Состоят из светодиода и фотоэлемента в одном корпусе, т.е. цепь разомкнута, но сигналы идут. Такие мосты применяются для управления чем угодно. Платы для моста делаем сами.
Для ударов по мячу используется соленоид, который имеет свои блок конденсаторов соединенных параллельно, для создания большого импульса. Для питания соленоида предназначен отдельный аккумулятор.
Далее в комплекте идут электролитические конденсаторы для стабилизации напряжения, и керамические конденсаторы и ферритовые кольца для борьбы с вездесущим шумом, ибо шумит ВСЕ. И остаются такие мелочи как рама, колеса, моторы, болты и прочие винтики. А вот начало.
И через несколько дней.
Механика и электроника собирались в режиме нон-стоп, иногда работали по ночам. Код начал писать параллельно, используя С++ и в качестве среды разработки Qt. Робот — это классический абстрактный автомат, у которого есть набор состояний (мяч найден, движение, поиск ворот, или цель найдена, ракета наведена), причем неважно, военный робот, игрушечный или промышленный. Проблема состоит в том, что состояний должно быть так мало как только возможно, и так много, как необходимо. Если алгоритм делать слишком запутанным, то в какой то момент не будешь понимать, почему робот реагирует именно так. У нас состояний 8: Стартовое, Поиск Мяча, Поиск Ворот, Прицеливание, Избегание препятствий, Заряд конденсаторов соленоида, Удар, Конечное. Переходы между ними образуют граф, который и является алгоритмом.
Больше всего времени уходит на написание и доводку кода, тестирование, калибровку. Что на самом деле и есть самое главное.
Распознавание робота выделенное в отдельные классы, строится так. Каждый кадр проходит обработку, где сначала конвертируются цвета из RGB в HSL, которое более близко к нашему восприятию, и с которым удобнее работать. Для удобства конвертации используется не встроенная функция OpenCV, поскольку конвертация — это вычисления и на них тратится такое дорогое время, а заранее вычисленная таблица цветов (lookup table), где из RGB значение получается смещение в таблице, а после делается выборка цвета по адресу. Выигрываем в скорости, проигрываем в памяти, все как в теории. Далее последовательно ищутся объекты (мячи, ворота) и записываются в список. Объекты ищутся по общему алгоритму, пробегается в цикле каждый пиксель, и если он попадает в диапазон цвета объекта, то на карте ставится бит. В итоге получается битмап-карта с выделенными объектами. Битмап-карта отправляется на вход OpenCV функции, которая выдает обратно список контуров. Далее находим площадь контуров и если площадь контуров больше 40–50% от площади круга (прямоугольника) или в диапазоне 4/Pi, то считаем этот объект распознанным и добавляем его в список. Шары ищутся в некоем диапазоне (маленькие шум, больших не бывает, на потолке тоже смысла шары искать нет). Поскольку камера выдает до 125 кадров в секунду, использовать некоторые встроенные функции OpenCV нельзя, они долгие, но библиотека устроена удобно и через указатели можно пробегать по всей матрице. Осталось сделать проверку того, не выкатился ли мяч за край поля (черным ограничено), поскольку робот не должен выезжать за черную линию. Проверка достаточно проста, чертим линию из центра нижней части изображения до мяча и смотрим, не пересекает ли она черную область. Если пересекает, то контур из списка выкидывается.
Дальше — самое интересное. Робот имеет определенное количество состояний, в которые он переходит в зависимости от условий. Это просто. Алгоритм пишется именно на этом этапе. Самое сложное — это калибровка. Как из полученных объектов и их координат выстроить функцию скорости, направления?
Пробегаем весь выданный список и находим самый большой мяч по площади. Это самый близкий. Теперь, чтобы из координат объекта получить функцию скорости и направления, требуется подумать и покурить матан. Чем выше координаты мяча на изображении и чем он меньше по площади, тем он дальше. Чем более сдвинут по оси X и дальше/ближе, тем более острый, или наоборот тупой угол. Чтобы получить коэффициенты, ставится робот на площадку в один конец по центру, дальше начинается процесс ползания с рулеткой, измерения точек с неким шагом (33 см) и снятием показаний значений объекта в системе распознавания (площадь контура). Наносим значения и пытаемся найти функцию, которая была бы связью одних значений с другими. Точность сильная не нужна, поскольку робот не строит путь раз и навсегда, а корректирует себя 17–18 раз в секунду, столько выдает наша маленькая система. Т.е. надо найти функцию, предел которой стремится к искомым результатам при небольших изменениях аргументов. Далее на колеса подается угол и направление, откуда скорости вычисляются простейшими векторными уравнениями. Рисунок объяснит.
Далее, когда мы умеем двигаться к объектам, алгоритм игры в футбол написать не так сложно. А вот уже близко к соревнованиям. Мой справа.
Результат в действии:
[embedded content]
В субботу вечером мы уже перебрались в спортхол, где проводятся соревнования. Ночью было здорово, свой круг общения, различные клубы и лаборатории с разных стран. Куча народу. Никто не спит. Все друг с другом общаются и интересуются всем, чем можно. Сам я спал 2,5 часа. И все это время шел тюнинг. Как человек, участвующий первый раз, не знал о мелких тонкостях и проблемах, которые встречаются.
Самое главное, что невозможно все просчитать, и упор делается на тестирование. Кто дольше тестирует и доводит, тот и главный. Допустим, повороты лучше делать не всем роботом, а робот должен вращаться вокруг мяча, т.к. при вращении робота мячу добавляется импульс вращения робота, и если на близких дистанциях можно бить с упреждением, и ошибка несущественна, то при ударе с дальних дистанций ошибка огромна, линейная скорость ворот относительно робота большая, и момент стрельбы должен быть еще больше упреждающим. Тут вылезают проблемы. Первая, это то, что ты не знаешь, какая скорость поворота была у робота до этого. То есть если робот едет под углом упреждения прямо на мяч, то он ударит сразу и мимо, поскольку не имел вращения. Или наоборот — подъедет к мячу с поворота в другую сторону, начнет поворачиваться и ударит, но это будет мимо, поскольку из-за инерции его скорость поворота еще не будет расчетной. Т.е. слишком много вариантов, которые надо просчитать. Поворот вокруг оси мяча не добавляет ему импульса вращения, и можно стрелять сразу.
Второе — это проблема с распознаванием. Когда робот крутится, на дальних дистанциях мячи и ворота имеют опять таки большую линейную скорость, и кадр на распознавание идет смазанный. Функция поиска контуров работает по градиентам, но смазанные ворота не имеют четких градиентов, и поэтому вместо одного контура ворот на выходе получаются или десятки контуриков (это ест ресурсы) или ничего не получается (мяч размазывается в маленький оранжевый туманчик). Я имел на входе 17 кадров в секунду, соответственно дальние объекты при вращении для меня были недоступны. Плюс задержка по процессингу. Т.е. когда робот понимает, что вдалеке были ворота, их уже там нет (правда, это я и использовал в свою пользу, когда давал вращение от своих ворот, и задержка как раз была такая, что мой робот поворачивался почти к вражеским воротам). Ультрабук на голове робота решил бы проблемы.
Одно из решений, которое использовала другая наша команда, это резать картинку, т.е. когда мы видим мяч, то лишнее начинаем срезать, центрируем его, а бока срезаем и выкидываем, FPS подскакивает до 60. Та же логика и при распознавании ворот, тем более при поиске, нижняя часть не нужна, ее режем.
У меня была другая камера и со срезами возникли бы проблемы. Хорошее решение с двумя камерами. Одна широкоугольная, смотрит направления и дает обзор. Вторая напротив дает расстояния и видит далеко.
Для решения проблемы поворотов робота я использовал остановку, если через пару секунд во время поворота он ничего не найдет. Остановка дает возможность взглянуть дальше, но под углом 60 градусов, который выдавала наша камера. Дальше снова поиск.
Второй мое упущение — это то, что я не знал, что стартуют роботы с правого угла. Робот должен помнить, в какой стороне ворота противника и куда он должен поворачиваться, иначе неправильный оборот берет много времени. Я определял стороны ворот по цвету, но пока ворота не появились в поле зрения, переменная-множитель направления (плюс или минус один задает сторону поворота) по умолчанию принимала значение -1 против часовой стрелки, а надо было по часовой. И из-за этого в проигрышном матче первые два мяча наш забил на противоходе и потерял время. Итог 6–4, а во втором ничья 5–5, и наш проигрыш. 5 место из 18 команд. Мешал еще высокий центр тяжести, не поставить большую скорость — начинает танцевать. Я прозвал его принтер.
Вообще, здесь очень много нюансов. Что запомнилось — так это интересная тусовка. И ритм. Т.е. скорость и постоянная доработка машин. После каждого матча. С чемпионом мы сыграли 4–3! (тактика защиты, закрыли ворота и били издалека). Первый матч один на один мы провели на соревнованиях! Отсюда невозможность оттестировать стратегию поведения. В течении недели нас преследовали поломки. Плюс у нас камера сбилась (ось) я пытался решить проблемы механики кодом, чего никогда нельзя делать, но времени не было, и только перед последним матчем я все решил. Всего мы сыграли 4 матча.В первом победили робот-кубик.
Во втором выиграли команду Скайпа, у которых был очень интересный робот. Безумно быстрый, маленький, с 8 камерами и FPGA! То есть они сделали то, что вообще не должно было бы работать. Он быстро вылетел, но был очарователен. Вот он.
Были очень интересные решения. Робот, занявший первое место (3 год дорабатывался) имеет обзор 360 градусов. Камера смотрит наверх на конусообразное зеркало. Получается картинка в полярных координатах.
А по поводу все просчитать, есть такая история в анналах «Роботекса», над которой все постоянно смеются.
Как-то лет 5 назад пришли на соревнования два магистра и сказали, что они все все просчитали и построили робота. Они были встречены взрывом хохота. И последующим поражением.
Спасибо за внимание.