Вводный курс по программированию роботов

Опубликовано: 2022-03-11
Примечание редактора: 16 октября 2018 г. эта статья была переработана для работы с новейшими технологиями.

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

Стремясь хоть немного повлиять на этот вопрос, в прошлом году я прошел курс теории управления автономными роботами, кульминацией которого стало создание симулятора робота на основе Python, который позволил мне практиковать теорию управления на простом, мобильном, программируемом роботе. .

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

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

  • Математика - мы будем использовать некоторые тригонометрические функции и векторы
  • Python — поскольку Python является одним из наиболее популярных базовых языков программирования роботов, — мы будем использовать основные библиотеки и функции Python.

Фрагменты кода, показанные здесь, являются лишь частью всего симулятора, который опирается на классы и интерфейсы, поэтому для непосредственного чтения кода вам может понадобиться некоторый опыт работы с Python и объектно-ориентированным программированием.

Наконец, необязательные темы, которые помогут вам лучше следовать этому руководству, — это знание того, что такое конечный автомат и как работают датчики диапазона и энкодеры.

Проблема программируемого робота: восприятие против реальности и хрупкость контроля

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

На этом рисунке показано взаимодействие между физическим роботом и компьютерными элементами управления при обучении программированию роботов на языке Python.

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

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

Это одна из ключевых причин сложности программирования робототехники. Мы часто видим видеоролики о последнем исследовательском роботе в лаборатории, демонстрирующем фантастические подвиги ловкости, навигации или командной работы, и у нас возникает соблазн спросить: «Почему это не используется в реальном мире?» Что ж, в следующий раз, когда вы увидите такое видео, обратите внимание на то, насколько строго контролируется лабораторная среда. В большинстве случаев эти роботы способны выполнять эти впечатляющие задачи только до тех пор, пока условия окружающей среды остаются в узких рамках их внутренней модели. Таким образом, одним из ключей к развитию робототехники является разработка более сложных, гибких и надежных моделей, и указанное продвижение зависит от ограничений доступных вычислительных ресурсов.

Одним из ключей к развитию робототехники является разработка более сложных, гибких и надежных моделей.

[Примечание: как философы, так и психологи отмечают, что живые существа также страдают от зависимости от собственного внутреннего восприятия того, что говорят им их чувства. Многие достижения в робототехнике связаны с наблюдением за живыми существами и наблюдением за тем, как они реагируют на неожиданные раздражители. Подумай об этом. Какова ваша внутренняя модель мира? Он отличается от муравья и рыбы? (Надеюсь.) Однако, подобно муравью и рыбе, он, вероятно, чрезмерно упрощает некоторые реалии мира. Когда ваши представления о мире неверны, вы рискуете потерять контроль над вещами. Иногда мы называем это «опасностью». Точно так же, как наш маленький робот борется за выживание в неизвестной вселенной, мы все тоже. Это мощное озарение для робототехников.]

Симулятор программируемого робота

Симулятор, который я построил, написан на Python и очень умно назван Sobot Rimulator . Вы можете найти v1.0.0 на GitHub. В нем не так много наворотов, но он создан, чтобы делать одну вещь очень хорошо: обеспечивать точную симуляцию мобильного робота и давать начинающему робототехнику простую основу для практики программирования программного обеспечения для роботов. Хотя всегда лучше иметь настоящего робота для игры, хороший симулятор робота Python гораздо более доступен и является отличным местом для начала.

В реальных роботах программное обеспечение, генерирующее управляющие сигналы («контроллер»), должно работать на очень высокой скорости и выполнять сложные вычисления. Это влияет на выбор того, какие языки программирования роботов лучше всего использовать: обычно для таких сценариев используется C++, но в более простых робототехнических приложениях Python является очень хорошим компромиссом между скоростью выполнения и простотой разработки и тестирования.

Программное обеспечение, которое я написал, имитирует реального исследовательского робота под названием Khepera, но его можно адаптировать к целому ряду мобильных роботов с различными размерами и датчиками. Так как я пытался запрограммировать симулятор как можно ближе к возможностям реального робота, логика управления может быть загружена в реального робота Хепера с минимальным рефакторингом, и он будет выполнять те же функции, что и смоделированный робот. Реализованные специфические функции относятся к Khepera III, но их можно легко адаптировать к новой Khepera IV.

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

В этом руководстве я буду описывать архитектуру программного обеспечения для управления роботами, которое поставляется с версией 1.0.0 Sobot Rimulator , и предоставлять фрагменты из исходного кода Python (с небольшими изменениями для ясности). Тем не менее, я призываю вас погрузиться в источник и возиться. Симулятор был разветвлен и использовался для управления различными мобильными роботами, в том числе Roomba2 от iRobot. Точно так же, пожалуйста, не стесняйтесь разветвлять проект и улучшать его.

Логика управления роботом ограничена этими классами/файлами Python:

  • models/supervisor.py — этот класс отвечает за взаимодействие между симулируемым миром вокруг робота и самим роботом. Он развивает конечный автомат нашего робота и запускает контроллеры для вычисления желаемого поведения.
  • models/supervisor_state_machine.py — этот класс представляет различные состояния, в которых может находиться робот, в зависимости от его интерпретации датчиков.
  • Файлы в каталоге models/controllers — эти классы реализуют различное поведение робота при известном состоянии среды. В частности, конкретный контроллер выбирается в зависимости от конечного автомата.

Цель

Роботам, как и людям, нужна цель в жизни. Задача нашего программного обеспечения, управляющего этим роботом, будет очень простой: он попытается добраться до заданной целевой точки. Обычно это основная функция, которой должен обладать любой мобильный робот, от автономных автомобилей до роботов-пылесосов. Координаты цели запрограммированы в управляющем программном обеспечении до того, как робот будет активирован, но могут быть сгенерированы из дополнительного приложения Python, которое наблюдает за движениями робота. Например, представьте, что вы проезжаете через несколько путевых точек.

Однако, чтобы усложнить ситуацию, окружение робота может быть усеяно препятствиями. Робот НЕ МОЖЕТ столкнуться с препятствием на пути к цели. Поэтому, если робот сталкивается с препятствием, ему придется найти обходной путь, чтобы продолжить путь к цели.

Программируемый робот

Каждый робот обладает разными возможностями и проблемами управления. Давайте познакомимся с нашим моделируемым программируемым роботом.

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

Входы управления: датчики

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

Наш эталонный робот оснащен девятью инфракрасными датчиками — более новая модель имеет восемь инфракрасных и пять ультразвуковых датчиков приближения — расположенных «юбкой» во всех направлениях. Датчиков, обращенных к передней части робота, больше, чем к задней, потому что роботу обычно важнее знать, что находится перед ним, чем то, что находится позади него.

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

Выходы управления: мобильность

Некоторые роботы передвигаются на ногах. Некоторые катятся как мяч. Некоторые даже скользят, как змеи.

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

API

В Sobot Rimulator разделение между «компьютером» робота и (симулируемым) физическим миром воплощено в файле robot_supervisor_interface.py , который определяет весь API для взаимодействия с датчиками и моторами «настоящего робота»:

  • read_proximity_sensors() возвращает массив из девяти значений в собственном формате датчиков.
  • read_wheel_encoders() возвращает массив из двух значений, указывающих общее количество тиков с момента запуска.
  • set_wheel_drive_rates( v_l, v_r ) принимает два значения (в радианах в секунду) и устанавливает левую и правую скорость колес на эти два значения.

Этот интерфейс внутренне использует объект робота, который предоставляет данные от датчиков и возможность перемещать двигатели или колеса. Если вы хотите создать другого робота, вам просто нужно предоставить другой класс робота Python, который можно использовать с тем же интерфейсом, а остальная часть кода (контроллеры, супервизор и симулятор) будет работать из коробки!

Симулятор

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

Файл world.py — это класс Python, представляющий смоделированный мир с роботами и препятствиями внутри. Функция step внутри этого класса заботится об эволюции нашего простого мира:

  • Применение правил физики к движениям робота
  • Учет столкновений с препятствиями
  • Предоставление новых значений для датчиков робота

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

Пошаговая функция выполняется в цикле, так что robot.step_motion() перемещает робота, используя скорость вращения колеса, вычисленную супервизором на предыдущем шаге симуляции.

 # step the simulation through one time interval def step( self ): dt = self.dt # step all the robots for robot in self.robots: # step robot motion robot.step_motion( dt ) # apply physics interactions self.physics.apply_physics() # NOTE: The supervisors must run last to ensure they are observing the "current" world # step all of the supervisors for supervisor in self.supervisors: supervisor.step( dt ) # increment world time self.world_time += dt

Функция apply_physics() внутренне обновляет значения датчиков приближения робота, чтобы супервизор мог оценить окружающую среду на текущем этапе моделирования. Те же принципы применимы и к энкодерам.

Простая модель

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

  • Местность всегда плоская и ровная
  • Препятствия никогда не бывают круглыми
  • Колеса никогда не скользят
  • Ничто никогда не будет толкать робота
  • Датчики никогда не выходят из строя и не дают ложных показаний
  • Колеса всегда крутятся, когда им говорят

Хотя большинство из этих предположений разумны в среде, похожей на дом, могут присутствовать круглые препятствия. Наше программное обеспечение для предотвращения препятствий имеет простую реализацию и следует за границей препятствий, чтобы объехать их. Мы подскажем читателям, как улучшить систему управления нашего робота с дополнительной проверкой, чтобы избежать круговых препятствий.

Цикл управления

Теперь мы перейдем к ядру нашего управляющего программного обеспечения и объясним поведение, которое мы хотим запрограммировать внутри робота. В этот фреймворк можно добавить дополнительные варианты поведения, и вы должны попробовать свои собственные идеи после того, как закончите читать! Программное обеспечение для робототехники, основанное на поведении, было предложено более 20 лет назад, и оно до сих пор является мощным инструментом для мобильной робототехники. Например, в 2007 году набор моделей поведения использовался в DARPA Urban Challenge — первом соревновании для автомобилей с автономным управлением!

Робот — это динамическая система. Состояние робота, показания его датчиков и воздействие его управляющих сигналов постоянно меняются. Управление ходом событий включает в себя следующие три шага:

  1. Подать управляющие сигналы.
  2. Измерьте результаты.
  3. Генерируйте новые управляющие сигналы, рассчитанные на то, чтобы приблизить нас к нашей цели.

Эти шаги повторяются снова и снова, пока мы не достигнем нашей цели. Чем больше раз мы можем делать это в секунду, тем лучше мы будем контролировать систему. Робот Sobot Rimulator повторяет эти шаги 20 раз в секунду (20 Гц), но многие роботы должны делать это тысячи или миллионы раз в секунду, чтобы иметь адекватный контроль. Помните наше предыдущее введение о разных языках программирования роботов для разных систем робототехники и требованиях к скорости.

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

Изящный трюк: упрощение модели

Чтобы управлять роботом, который мы хотим запрограммировать, мы должны послать сигнал на левое колесо, сообщая ему, как быстро поворачивать, и отдельный сигнал на правое колесо, сообщая ему, как быстро поворачивать. Назовем эти сигналы v L и v R . Однако постоянно думать в терминах v L и v R очень обременительно. Вместо того, чтобы спрашивать: «Как быстро мы хотим, чтобы левое колесо вращалось, и как быстро мы хотим, чтобы вращалось правое колесо?» более естественно спросить: «Как быстро мы хотим, чтобы робот двигался вперед, и как быстро мы хотим, чтобы он поворачивал или менял направление?» Назовем эти параметры скоростью v и угловой (вращательной) скоростью ω (читай «омега»). Оказывается, мы можем основывать всю нашу модель на v и ω вместо v L и v R , и только после того, как мы определили, как мы хотим, чтобы наш запрограммированный робот двигался, математически преобразовать эти два значения в нужные нам v L и v R для фактического управления колесами робота. Это известно как одноколесная модель управления.

При программировании робототехники важно понимать разницу между моделями одноколесных и дифференциальных приводов.

Вот код Python, реализующий финальное преобразование в supervisor.py . Обратите внимание, что если ω равно 0, оба колеса будут вращаться с одинаковой скоростью:

 # generate and send the correct commands to the robot def _send_robot_commands( self ): # ... v_l, v_r = self._uni_to_diff( v, omega ) self.robot.set_wheel_drive_rates( v_l, v_r ) def _uni_to_diff( self, v, omega ): # v = translational velocity (m/s) # omega = angular velocity (rad/s) R = self.robot_wheel_radius L = self.robot_wheel_base_length v_l = ( (2.0 * v) - (omega*L) ) / (2.0 * R) v_r = ( (2.0 * v) + (omega*L) ) / (2.0 * R) return v_l, v_r

Оценка состояния: робот, познай себя

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

  • Направление на препятствия
  • Расстояние от препятствий
  • Положение робота
  • Заголовок робота

Первые два свойства определяются показаниями датчика приближения и довольно просты. Функция API read_proximity_sensors() возвращает массив из девяти значений, по одному для каждого датчика. Мы заранее знаем, что седьмое показание, например, соответствует датчику, который указывает на 75 градусов вправо от робота.

Таким образом, если это значение показывает значение, соответствующее расстоянию 0,1 метра, мы знаем, что на расстоянии 0,1 метра находится препятствие, на 75 градусов левее. Если препятствия нет, датчик вернет показания максимальной дальности 0,2 метра. Таким образом, если мы покажем 0,2 метра на седьмом датчике, мы будем считать, что препятствия в этом направлении на самом деле нет.

Из-за того, как работают инфракрасные датчики (измерение инфракрасного отражения), возвращаемые ими числа представляют собой нелинейное преобразование фактического обнаруженного расстояния. Таким образом, функция Python для определения указанного расстояния должна преобразовать эти показания в метры. Это делается в supervisor.py следующим образом:

 # update the distances indicated by the proximity sensors def _update_proximity_sensor_distances( self ): self.proximity_sensor_distances = [ 0.02-( log(readval/3960.0) )/30.0 for readval in self.robot.read_proximity_sensors() ]

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

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

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

Учитывая наш текущий программный симулятор, мы можем позволить себе выполнять расчет одометрии с частотой 20 Гц — на той же частоте, что и контроллеры. Но было бы неплохо иметь отдельный поток Python, работающий быстрее, чтобы улавливать небольшие движения бегущих строк.

Ниже приведена полная функция одометрии в supervisor.py , которая обновляет оценку позы робота. Обратите внимание, что поза робота состоит из координат x и y и направления theta , которое измеряется в радианах от положительной оси X. Положительный x находится на востоке, а положительный y - на севере. Таким образом, заголовок 0 указывает, что робот смотрит прямо на восток. Робот всегда принимает исходную позу (0, 0), 0 .

 # update the estimated position of the robot using it's wheel encoder readings def _update_odometry( self ): R = self.robot_wheel_radius N = float( self.wheel_encoder_ticks_per_revolution ) # read the wheel encoder values ticks_left, ticks_right = self.robot.read_wheel_encoders() # get the difference in ticks since the last iteration d_ticks_left = ticks_left - self.prev_ticks_left d_ticks_right = ticks_right - self.prev_ticks_right # estimate the wheel movements d_left_wheel = 2*pi*R*( d_ticks_left / N ) d_right_wheel = 2*pi*R*( d_ticks_right / N ) d_center = 0.5 * ( d_left_wheel + d_right_wheel ) # calculate the new pose prev_x, prev_y, prev_theta = self.estimated_pose.scalar_unpack() new_x = prev_x + ( d_center * cos( prev_theta ) ) new_y = prev_y + ( d_center * sin( prev_theta ) ) new_theta = prev_theta + ( ( d_right_wheel - d_left_wheel ) / self.robot_wheel_base_length ) # update the pose estimate with the new values self.estimated_pose.scalar_update( new_x, new_y, new_theta ) # save the current tick count for the next iteration self.prev_ticks_left = ticks_left self.prev_ticks_right = ticks_right

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

Связанный: Учебник по физике видеоигр - Обнаружение столкновений для твердых объектов

Методы программирования роботов на Python: поведение на пути к цели

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

Затем это становится простой задачей и может быть легко запрограммировано на Python. Если мы будем идти вперед, глядя на цель, мы ее достигнем. Благодаря нашей одометрии мы знаем, каковы наши текущие координаты и направление. Мы также знаем, каковы координаты цели, потому что они были запрограммированы заранее. Поэтому, используя немного линейной алгебры, мы можем определить вектор от нашего местоположения к цели, как в go_to_goal_controller.py :

 # return a go-to-goal heading vector in the robot's reference frame def calculate_gtg_heading_vector( self ): # get the inverse of the robot's pose robot_inv_pos, robot_inv_theta = self.supervisor.estimated_pose().inverse().vector_unpack() # calculate the goal vector in the robot's reference frame goal = self.supervisor.goal() goal = linalg.rotate_and_translate_vector( goal, robot_inv_theta, robot_inv_pos ) return goal

Обратите внимание, что мы получаем вектор к цели в системе отсчета робота , а НЕ в мировых координатах. Если цель находится на оси X в системе отсчета робота, это означает, что она находится прямо перед роботом. Таким образом, угол этого вектора от оси X представляет собой разницу между нашим курсом и курсом, по которому мы хотим двигаться. Другими словами, это ошибка между нашим текущим состоянием и тем, каким мы хотим видеть наше текущее состояние. Поэтому мы хотим отрегулировать скорость поворота ω так, чтобы угол между нашим курсом и целью изменялся в сторону 0. Мы хотим минимизировать ошибку:

 # calculate the error terms theta_d = atan2( self.gtg_heading_vector[1], self.gtg_heading_vector[0] ) # calculate angular velocity omega = self.kP * theta_d

self.kP в приведенном выше фрагменте реализации контроллера Python — усиление контроля. Это коэффициент, который определяет, насколько быстро мы поворачиваемся пропорционально тому, насколько далеко мы стоим перед целью. Если ошибка в нашем заголовке равна 0 , то скорость поворота также равна 0 . В реальной функции Python внутри файла go_to_goal_controller.py вы увидите более похожие усиления, так как мы использовали ПИД-регулятор вместо простого пропорционального коэффициента.

Теперь, когда у нас есть наша угловая скорость ω , как мы можем определить нашу поступательную скорость v ? Хорошее общее эмпирическое правило, которое вы, вероятно, знаете инстинктивно: если мы не делаем поворот, мы можем двигаться вперед на полной скорости, а затем, чем быстрее мы поворачиваем, тем больше мы должны замедляться. Обычно это помогает нам поддерживать стабильность нашей системы и действовать в рамках нашей модели. Таким образом, v является функцией ω . В go_to_goal_controller.py уравнение выглядит так:

 # calculate translational velocity # velocity is v_max when omega is 0, # drops rapidly to zero as |omega| rises v = self.supervisor.v_max() / ( abs( omega ) + 1 )**0.5

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

Это пример запрограммированной траектории робота.

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

Методы программирования роботов на Python: поведение избегания препятствий

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

Чтобы упростить сценарий, давайте теперь полностью забудем о цели и просто сделаем нашей целью следующее: Когда перед нами не будет препятствий, двигайтесь вперед. При встрече с препятствием отворачиваемся от него до тех пор, пока оно не перестанет быть перед нами.

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

Аккуратный способ сгенерировать желаемый опорный вектор — превратить наши девять показаний близости в векторы и взять взвешенную сумму. Когда препятствия не обнаружены, векторы суммируются симметрично, в результате чего опорный вектор указывает прямо вперед, как это необходимо. Но если датчик, скажем, с правой стороны улавливает препятствие, он внесет в сумму меньший вектор, и результатом будет опорный вектор, сдвинутый влево.

Для обычного робота с другим расположением датчиков можно применить ту же идею, но может потребоваться изменение веса и/или дополнительная осторожность, когда датчики симметричны спереди и сзади робота, поскольку взвешенная сумма может стать равной нулю. .

При правильном программировании робот может избегать этих сложных препятствий.

Вот код, который делает это в avoid_obstacles_controller.py :

 # sensor gains (weights) self.sensor_gains = [ 1.0+( (0.4*abs(p.theta)) / pi ) for p in supervisor.proximity_sensor_placements() ] # ... # return an obstacle avoidance vector in the robot's reference frame # also returns vectors to detected obstacles in the robot's reference frame def calculate_ao_heading_vector( self ): # initialize vector obstacle_vectors = [ [ 0.0, 0.0 ] ] * len( self.proximity_sensor_placements ) ao_heading_vector = [ 0.0, 0.0 ] # get the distances indicated by the robot's sensor readings sensor_distances = self.supervisor.proximity_sensor_distances() # calculate the position of detected obstacles and find an avoidance vector robot_pos, robot_theta = self.supervisor.estimated_pose().vector_unpack() for i in range( len( sensor_distances ) ): # calculate the position of the obstacle sensor_pos, sensor_theta = self.proximity_sensor_placements[i].vector_unpack() vector = [ sensor_distances[i], 0.0 ] vector = linalg.rotate_and_translate_vector( vector, sensor_theta, sensor_pos ) obstacle_vectors[i] = vector # store the obstacle vectors in the robot's reference frame # accumulate the heading vector within the robot's reference frame ao_heading_vector = linalg.add( ao_heading_vector, linalg.scale( vector, self.sensor_gains[i] ) ) return ao_heading_vector, obstacle_vectors

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

Этот робот успешно избегает препятствий в симуляторе роботов Python.

Методы программирования роботов Python: гибридные автоматы (конечный автомат поведения)

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

Решение, которое мы разработаем, будет заключаться в классе машин, имеющих в высшей степени круто звучащее название гибридных автоматов . Гибридный автомат запрограммирован с несколькими различными режимами поведения или режимами, а также с контролирующим конечным автоматом. Конечный автомат надзора переключается из одного режима в другой в дискретное время (когда цели достигнуты или окружающая среда внезапно слишком сильно изменилась), в то время как каждое поведение использует датчики и колеса, чтобы непрерывно реагировать на изменения окружающей среды. Решение было названо гибридным , потому что оно развивается дискретно и непрерывно.

Наш каркас робота Python реализует конечный автомат в файле supervisor_state_machine.py .

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

Однако, как оказалось, эта логика создаст много проблем. Когда эта система сталкивается с препятствием, она будет склонна отворачиваться от него, а затем, как только оно отойдет от него, разворачиваться и снова натыкаться на него. В результате получается бесконечный цикл быстрых переключений, который делает робота бесполезным. In the worst case, the robot may switch between behaviors with every iteration of the control loop—a state known as a Zeno condition .

There are multiple solutions to this problem, and readers that are looking for deeper knowledge should check, for example, the DAMN software architecture.

What we need for our simple simulated robot is an easier solution: One more behavior specialized with the task of getting around an obstacle and reaching the other side.

Python Robot Programming Methods: Follow-Wall Behavior

Here's the idea: When we encounter an obstacle, take the two sensor readings that are closest to the obstacle and use them to estimate the surface of the obstacle. Then, simply set our reference vector to be parallel to this surface. Keep following this wall until A) the obstacle is no longer between us and the goal, and B) we are closer to the goal than we were when we started. Then we can be certain we have navigated the obstacle properly.

With our limited information, we can't say for certain whether it will be faster to go around the obstacle to the left or to the right. To make up our minds, we select the direction that will move us closer to the goal immediately. To figure out which way that is, we need to know the reference vectors of the go-to-goal behavior and the avoid-obstacle behavior, as well as both of the possible follow-wall reference vectors. Here is an illustration of how the final decision is made (in this case, the robot will choose to go left):

Utilizing a few types of behaviors, the programmed robot avoids obstacles and continues onward.

Determining the follow-wall reference vectors turns out to be a bit more involved than either the avoid-obstacle or go-to-goal reference vectors. Take a look at the Python code in follow_wall_controller.py to see how it's done.

Final Control Design

The final control design uses the follow-wall behavior for almost all encounters with obstacles. However, if the robot finds itself in a tight spot, dangerously close to a collision, it will switch to pure avoid-obstacles mode until it is a safer distance away, and then return to follow-wall. Once obstacles have been successfully negotiated, the robot switches to go-to-goal. Here is the final state diagram, which is programmed inside the supervisor_state_machine.py :

This diagram illustrates the switching between robotics programming behaviors to achieve a goal and avoid obstacles.

Here is the robot successfully navigating a crowded environment using this control scheme:

The robot simulator has successfully allowed the robot software to avoid obstacles and achieve its original purpose.

An additional feature of the state machine that you can try to implement is a way to avoid circular obstacles by switching to go-to-goal as soon as possible instead of following the obstacle border until the end (which does not exist for circular objects!)

Tweak, Tweak, Tweak: Trial and Error

The control scheme that comes with Sobot Rimulator is very finely tuned. It took many hours of tweaking one little variable here, and another equation there, to get it to work in a way I was satisfied with. Robotics programming often involves a great deal of plain old trial-and-error. Robots are very complex and there are few shortcuts to getting them to behave optimally in a robot simulator environment…at least, not much short of outright machine learning, but that's a whole other can of worms.

Robotics often involves a great deal of plain old trial-and-error.

I encourage you to play with the control variables in Sobot Rimulator and observe and attempt to interpret the results. Changes to the following all have profound effects on the simulated robot's behavior:

  • The error gain kP in each controller
  • The sensor gains used by the avoid-obstacles controller
  • The calculation of v as a function of ω in each controller
  • The obstacle standoff distance used by the follow-wall controller
  • The switching conditions used by supervisor_state_machine.py
  • Pretty much anything else

When Programmable Robots Fail

We've done a lot of work to get to this point, and this robot seems pretty clever. Yet, if you run Sobot Rimulator through several randomized maps, it won't be long before you find one that this robot can't deal with. Sometimes it drives itself directly into tight corners and collides. Sometimes it just oscillates back and forth endlessly on the wrong side of an obstacle. Occasionally it is legitimately imprisoned with no possible path to the goal. After all of our testing and tweaking, sometimes we must come to the conclusion that the model we are working with just isn't up to the job, and we have to change the design or add functionality.

In the mobile robot universe, our little robot's “brain” is on the simpler end of the spectrum. Many of the failure cases it encounters could be overcome by adding some more advanced software to the mix. More advanced robots make use of techniques such as mapping , to remember where it's been and avoid trying the same things over and over; heuristics , to generate acceptable decisions when there is no perfect decision to be found; and machine learning , to more perfectly tune the various control parameters governing the robot's behavior.

A Sample of What's to Come

Robots are already doing so much for us, and they are only going to be doing more in the future. While even basic robotics programming is a tough field of study requiring great patience, it is also a fascinating and immensely rewarding one.

In this tutorial, we learned how to develop reactive control software for a robot using the high-level programming language Python. But there are many more advanced concepts that can be learned and tested quickly with a Python robot framework similar to the one we prototyped here. I hope you will consider getting involved in the shaping of things to come!


Acknowledgement: I would like to thank Dr. Magnus Egerstedt and Jean-Pierre de la Croix of the Georgia Institute of Technology for teaching me all this stuff, and for their enthusiasm for my work on Sobot Rimulator.

Related: OpenCV Tutorial: Real-time Object Detection Using MSER in iOS