Eyeduino — техническое зрение на Ардуино. Часть первая

Некоторое время назад мы уже писали о роботе Eyeduino, участнике конкурса «Hack The Arduino Robot», а сегодня хотим разместить большую подробную статью, повествующую о процессе создания робота, возможных областях применения и великой цели, которую преследовали его создатели, когда решили участвовать в международном робоконкурсе.

1151151551

О том, чтобы попытаться загружать в Ардуино картинки с низким разрешением, мы задумывались ещё некоторое время назад: не видно было принципиальных ограничений, не позволяющих загнать в контроллер Atmega линейки, используемой в Ардуино, картинки порядка 32х32 (назовём их RI — Rough Images, «грубые изображения»), даже если расточительно тратить байт на хранение пиксела. Более того! Мне попадалась статья, где коллега практически без периферии, на АЦП контроллера получал картинку 64х64, да ещё с уровнями серого! Правда, там картинка не сохранялась в память, а гналась в последовательный порт через второй контроллер, по-моему. Я для себя определил некоторое концептуальное ограничение, из-за которого тот способ не подходил нам, а именно: попытаться вписаться в платформу Arduino как в плане железа, так и использования стандартных функций среды разработки.

Есть потенциально ещё вариант — оптическая мышь. Некоторые микросхемы, по крайней мере, с PS/2 интерфейсом, вроде бы поддерживают передачу точек картинки, которую видит камера мыши, но по 1 пикселу и по крайне медленному интерфейсу.

Наконец, третий вариант (или третий и четвёртый, как считать). Это использование Raspberry Pi или чего-то на Android + IOIO или Arduino. Тут барьер оказался больше психологическим: не доводилось раньше работать с Линуксами, поэтому с ходу программировать оказалось проблематичным (в отличие от Arduino). Хотя, думаю, что этот опыт будет полезен и мне, и сыну Алексею, и в следующем сезоне мы постараемся что-нибудь выкатить на «Малине». Правда, трудно представить себе, что это будет столь же простая и безотказная вещь, как Arduino.

Работа с RI — довольно «вкусная» возможность в спортивной робототехнике, например, для классической задачи следования по линии. Правда, только в качестве вспомогательного средства — из-за низкого разрешения и редкого чтения (большинство камер дадут 25 fps, а, скажем, в нашем «Стремительном» цикл чтения с линейки и расчёта скорости происходит раз в 4мс, т.е. на порядок чаще). Зато сколько даст более раннее знание о предстоящих поворотах! В большинстве конструкций для этого используют вынесенную вперёд линейку, а это сразу ухудшает механику (увеличивает момент инерции робота).

Итак, идея была — обрабатывать аналоговый сигнал с камеры. Поскольку памяти картинка должна занимать мало и считываться быстро, наиболее очевидный подход — черно-белая картинка, снимаемая цифровым входом, сигнал на который подаётся с выхода компаратора.

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

Не помню уже, в каком чудесном поиске я нашёл класс микросхем «video sync separator». И недорогой представитель этого семейства LM1881 должен был прекрасно решать наши проблемы: получая на вход видеосигнал, он выдаёт цифровые сигналы и новой строки, и нового кадра, и чётного/нечётного кадра, и цветовой «вспышки» (color burst). Можно было собирать прототип и писать какую-то программу. Что мы и сделали. Прототип на breadbord, Arduino Uno — вперёд!

Суть программы следующая: есть переменная, которая отображает состояние чтения картинки (запрошено/в процессе/выполнено). Есть прерывания по новому кадру и по началу линии. По новому кадру (если считывание запрошено) сбрасываются счётчики текущей строки и линии и состояние переводится в «в процессе». По прерыванию линии проверяем номер линии (нам нужна каждая десятая) и, если подходит, считываем 32 раза выход компаратора. Набрав 32 строки, переводим состояние в «выполнено». В основном цикле программы периодически запрашиваем считывание, ждём «выполнено», потом выводим картинку в текстовом виде (белое — пробел, чёрное ‘#’) в последовательный порт. Экспериментировали с этим хозяйством мы в новогодние каникулы, да так ничего толком не добились: вместо картинки шла какая-то каша.

Второе дыхание тема получила в конкурсе RobotChallenge «Hack the Arduino Robot». Идея была оформлена в виде короткого сочинения и отправлена организаторам.
Дальше были выступления на Geek Picnic: по мини-сумо — провальное, по микро-сумо — победное. С этим результатом ехать на RobotChallenge особого смысла не было.

И вот пришло письмо от организаторов RobotChallenge, что наш проект был отобран среди десяти финалистов и нам готовы прислать робота, чтобы мы стартовали работы. Оказалось, что быстро прислать робота — проблема, пришлось его покупать уже в России. Ладно, это — не самая сложная проблема, которую пришлось решать, а продавец даже любезно сделал скидку ради такого случая!

30ec9a08ac

Ввязавшись в проект, пришлось форсировать получение картинки. Ну не может же быть, что совсем ничего нельзя сделать! Чтобы иметь больше времени на обработку строчки и не прерываться следующей строчкой (я думал, что причина могла быть в этом), мы добавили ещё счётчик: теперь прерывание вызывалось не на каждой строчке, а на каждой десятой. Кроме того, счётчик (похоже, ещё советского дизайна схема К155ИЕ1) имел ещё полезную особенность: двойной вход, связанный логическим отношением «И». Т.е. сигнал с этой микросхемы можно было спокойно включать/отключать, подав на один из входов «1» или «0».

Кроме того, пришлось подумать над минимизацией времени считывания сигнала с компаратора. Приблизительно измерив время выполнения стандартной функции Ардуино digitalRead(), я понял, что она не годится: она выполняется порядка 6мкс, в то время. как нам нужно меньше 2мкс (стандартная строчка идёт чуть более 60мкс, за это время мы надеемся снять 32 точки). Пришлось лезть в реализацию и откидывать то, что можно вычислить и сделать один раз, зная номер PIN, на который заводится выход с компаратора. Отказался даже от цикла, чтобы не тратить время на вычитывание счётчика цикла в регистр, инкремент и снова сохранение в память (подозреваю, в такие команды должен был откомпилиро­ваться цикл for(pixelCount=0;pixelCount<PIXELS_PER_ROW;pixelCount++){…}). Поскольку программной памяти в контроллере на порядок больше, чем оперативной, ради скорости Copy-Paste использовать не стыдно.

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

e09b0e93fb

Ардуино глазами робота

26416d2ac2

Как было получено

 Поднастроив задержки при помощи NOP’ов и покрутив потенциометр, устанавливающий уровень второго входа компаратора (практически, яркость), я получил вполне приемлемую картинку.

Утром пришлось придумывать логотип, чтобы именно его снимок на камеру разместить первым на станице проекта.

Пришла пора портировать прототип на целевую платформу — робота. Для всяких проявлений народного творчества типа нашего на плате предусмотрены площадки для паяния. Внимание! Прежде, чем начать паять, разберитесь, что и куда там подходит, в смысле земли и питания (п’отом заработанный вывод Алексея). Оказалось также, что для того, чтобы подключить схему к прерываниям контроллера, придётся отказаться от чего-то важного: последовательного интерфейса или I²C. Поскольку последовательный интерфейс используется для связи с нижней (моторной) платой и, возможно, с компьютером, жертвой был выбран интерфейс I²C. Жалко, конечно, компас и другие интересные вещи, но проект важнее! Скольких трудов потом это нам стоило, но, возможно, по-другому это не делается вообще.

8d66be7c3b

Логотип проекта

Итак, Алексей распаивет схему, слегка модифицируем программу под новые порты — работает! Причём стабильно, контакт надёжнее, чем на breadboard, а новый потенциометр — вообще красавчик: позволяет тонкую настройку и красиво смотрится на плате.

Ну, хватит только глазеть, пора начинать ходить, поуправляем моторной платой. И тут неожиданно начался кошмар, который вспоминаю с содроганием. Попытка вставить чтение изображения в программу, в которой отправляются команды на моторную плату, приводила к очень печальной вещи: робот уходил в себя, ничего не делал и, самое главное, терял способность взаимодействовать с компьютером! Загрузить что-нибудь даже безобидное уже было невозможно. Проект «Фобос» своими руками!

2d0ca5c46b

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

Ну, в общем, всё оказалось не так плохо, поиск по FAQ свёл интеллектуальную задачу к физическому упражнению: если Вы потеряли Arduino Robot (или другую Ардуину на 32u4) с экрана IDE Arduino и списка USB-устройств, но физически держите в руках, поможет кнопка Reset и многократные попытки загрузить программу. Примерно после 3-4 попыток плата снова возвращается под контроль.

Радость от победы над Фобосом омрачало одно обстоятельство: время шло (а его на проект вообще отпущено невероятно мало) а робот не ехал. Люди! Не позволяйте себе бездумно расточать время, «чтобы не было мучительно больно за бесцельно прожитые годы». Мучительно больно! Лучше сказать невозможно, наверное.

Итак, в первоначальных планах у меня было с ездой по прерывистой линии выступить на открытых соревнованиях Политехнического музея 16.02.2014. К вечеру 15.02.2014 я уверенно освоил Reset, но и только: одновременно считывать видео и ездить робот отказывался. Причина крылась где-то в классе RobotControl: как только я его включал в код, случался Фобос. Класс использовал внутри себя I²C устройства, и, видимо, при инициализации или при дальнейшей жизни пытался поговорить с ними по I²C; поскольку я вешал на соответствующие порты прерывания, похоже было что контроллер уходил в вечную обработку прерываний. Это гипотеза, возможно всё или сложней или, наоборот, банальней.

С этими печальными открытиями я попытался уснуть, утешая себя тем, что завтра всё равно будет с чем поехать на соревнования, но сон, как говорится, не шёл. Шли идеи, в конце концов я продолжил изыскания: решил попытаться написать класс NoI²CRobotControl или просто накопипастить функционал взаимодействия с моторной платой прямо в основной код. Начал с первого, но получилось только второе и к половине 6-го утра у меня был ездящий робот с обработкой видеоизображений. То есть езда и обработка были в одной программе, но обработка мало влияла на езду. Настройки я надеялся дотянуть на соревнованиях, тем более полигон у меня есть только приблизительно похожий на целевой.

6aea6c722d

На соревнованиях — не пошло. Во-первых, пришлось очередной раз убедиться, что чисто пропорциональный регулятор (а именно такой я попытался использовать — вдруг пронесёт) плохо работает для инерционных систем, робот раскачивается и сходит. Во-вторых, к середине дня ушла картинка (как потом выяснили — из-за плохого контакта ушло напряжение на компараторе). Несколько утешили награды в других номинациях и лекция по ROS.

Понадобилось несколько дней (ну не совсем дней, глубоких вечеров) на то, чтобы доделать PD, разобраться с матчастью — и он поехал! И по сплошной линии, и по прерывистой. Причём алгоритм — примитивен, управляющий параметр — эксцентриситет, характеризующий смещение относительно центра — считается как усреднённый центр масс чёрных точек по линиям, с откинутыми краями. «Когда б вы знали, из какого сора растут стихи, не ведая стыда» (А.Ахматова) — цитирую по памяти, прошу простить, если неточно. В общем, робот поехал, поснимали видео для клипа (вспомнили и стали лихорадочно набирать материалы).
Пора было заняться вторым упражнением — охотой на таракана.

e8bf3e6ddb

Собственно алгоритм оказался простым. Точнее, я понял, что сложный (с идентификацией отдельных пятен) сделать не успею, поэтому сделал сильное допущение, что снимать мы будем единственное пятно, и это — таракан. Дальше ищем центр масс пятна (точнее, чёрных точек, мы же сделали сильное допущение) и пытаемся следовать за ним (используя регуляторы в радиальном и тангенциальном направлениях), после чего сигнал на моторы представлял собой суперпозицию этих регуляторов.

Реализация оказалась вполне работоспособной, только камеру мы ещё приподняли: поле зрения оказалось узким, «таракан» быстро покидал его, регулятор зачастую не успевал отрабатывать, а при повышении коэффициентов обратной связи зачастую проскакивал целевое состояние. Дело в том, что мощность моторов весьма нелинейно зависит от задаваемого уровняя сигнала, и при малых уровнях моторы только напрягаются, но не крутят (печальное состояние, надеюсь, дорогой читатель, нам с Вами не придётся попадать в него, по крайней мере, надолго: можно перегореть!).

Итак, преследовать чёрное пятно мы более-менее научились. Точнее, чёрное пятно среднего размера. Был поставлен фильтр, позволяющий мелкие пятна игнорировать, а от крупных — уезжать. Мало ли какой таракан попадётся, если он занимает больше половины поля зрения. может лучше уехать;)

Следующая часть упражнения — активировать сервомашинку, которая будет хлопать «таракана» — оказалась совсем несложной и для меня, и для Алексея. Для Алексея — потому что у него в руках всё работает (когда не горит); для меня — ну, просто подключить серву к роботу реально просто. И выглядело весьма забавно!
В свободное время на Робофесте мы отсняли «охоту на таракана»: уже подходило время монтировать ролик про проект, а материалов было весьма негусто.

Вы никогда не монтировали ролик про свой проект, причём имея очень приблизительное представление и про то, как это делается технически, и про законы жанра, и про другие законы (имею в виду копирайт законодательство: на какую музыку положить ролик и не получить потом иски)? А я монтировал! Уже. И более того, с удовольствием пересматриваю результат. Ну, возможно, это новый вид графомании, для которого ещё предстоит придумать (или уже придумали) какое-то мудрёное название.

Музыкой со мной поделился коллега Николай Сырцев, который играет в группе Disen Gage. Поскольку материалов было немного (как мне казалось), выбрали одну из самых коротких и динамичных композиций и постарались нарастить на неё сюжет. Какой был креатив! И «Прибытие поезда» братьев Люмьер глазами робота! И прибытие робота его же глазами! Точнее, глазом с разрешением 32х32 точки, выводимых символами ‘#’ и ‘ ‘ (пробел) в Serial. И получилось! Впрочем, про графоманию я уже писал.

Дальше — мы выложили ролик организаторам, они выложили его для голосования, и тут начался драйв. То есть его и раньше было достаточно (внимательный читатель наверняка заметил), но тут драйв пошёл иного свойства, как у болельщика на стадионе: очень переживаешь и ничего почти сделать не можешь, разве что инфаркт нажить. Помогли друзья, коллеги, Ассоциация Спортивной Робототехники — было сделано всё, чтобы набрать likes. С помощью какой магии итальянской команде Robopet удалось нас обойти — не знаю, но в итоге в призёры они не попали.

Параллельно пришлось решать вопросы загранпаспорта, австрийской визы, билетов, гостиницы, готовить роботов к другим соревнованиям… Сейчас это всё позади, и это (то, что уже всё позади) для меня до сих пор — повод для радости.

Ну а в Вене снова пришлось поработать: презентовать проект посетителям RobotChallenge. Каким успехом пользовалась охота на таракана! Многих посетителей, правда, больше интересовал вибротаракан, чем робот, который за ним гонялся. А сколько радости детям! Да и нам приходилось общаться: конкуренты не дремали!

Британская команда активно агитировала, французская тоже. Техника в итоге не выдержала. Под конец первого дня (субботы) движения хлопушки стали вялыми, а робот стал вдруг срываться с места и пытаться уехать куда-то. Сейчас-то такое поведение кажется вполне объяснимым: стал заклинивать сервомоторчик, при этом он потреблял достаточно короткозамкнутый ток, напряжение в бортовой сети просаживалось. Видимо, падение было достаточно глубоким, чтобы переставал нормально работать компаратор в цепи захвата видеосигнала. Вместо чёткого пятна — таракана — на вход алгоритма шёл какой-то мрак. Неудивительно, что робот «пугался» большого пятна и панически пытался куда-то уехать, причём со стола.

Сил, чтобы со всем этим разобраться, в субботу у нас уже не осталось, всё, что успели сделать в воскресенье утром — поменять камеру (похоже было, что с ней тоже что-то не то). Комиссия пришла с нами пообщаться, к сожалению, только в воскресенье.

Не знаю уж, поэтому ли нам досталось не первое место или ещё почему: критерии не объявлялись, а правила менялись по ходу, например, куда-то делась номинация community award, которая должна была присутствовать наравне с наградой по оценке жюри. Ну да ладно, первый блин комом, а второе место — тоже очень ничего, волнительно (и во время процедуры награждения, и потом — так и хочется, чтоб все, все знали…) Вот они, медные трубы. Ну а потом — немного погуляли по почти майской Вене — и домой.

Так закончился проект Eyeduino, давший кому что: роботу — примитивное зрение, нам — какие-то навыки и массу адреналина, иностранным коллегам — очередной повод поудивляться на русских.

А Вы, дорогой читатель, поверили, что закончился? Ха! Такие идеи так просто не заканчиваются! Вот что я скажу напоследок: такая область, как компьютерное зрение, нуждается в простой, дешёвой и доступной для понимания и обучения платформе. Eyeduino может стать для CV (computer vision) тем, чем стало Arduino для хобби-робототехники а Basic — для программирования. Да, для этого нужно сделать очень много: разработать shield (есть идеи на несколько вариантов), хорошо бы разработать plugin для Arduino IDE, который можно было бы запускать вместо Serial Monitor, куда получать картинку через Serial, нарабатывать, нарабатывать алгоритмы и методику обучения… Дорогу осилит идущий. Можете рассматривать это как Eyeduino+ manifesto! Но это — уже совсем другая история…

Страница проекта на Facebook 
Материалы проекта на GitHub 

Автор: Эдуард Петренко