Предыстория
Много времени утекло с того момента, как я стал начинающим «ардуинщиком». После заказа «стартового набора» из поднебесной и поморгал светодиодами, и покрутил сервоприводами, и даже собрал простенькую платформу Гью-Стюарта из кусков упаковочного полиэтилена, скрепок, кнопок и копеечных сервоприводов SG90.
Прошу прощения за качество фото — другого не сохранилось, как и самой платформы — дети сломали.
С этой платформы и началась, может быть даже кому-то интересная история. Управлять платформой с помощью клавиатуры или джойстиком интересно, но управлять ею датчиком положения в пространстве — интереснее вдвойне. Пробуя корректно заставить работать очень известный датчик от TDK — InvenSense MPU-9250, вспомнилась статья уважаемого ObelardO — «Как я делал костюм захвата движений» и затаилась идея в голове повторить этот опыт.
Что из этого получилось, можно увидеть и прочесть под катом.
Перепробовав всевозможные фильтры, свободно доступные в интернете, перерыв github и все общедоступные форумы, я не нашел практически нигде нет корректного кода для Arduino и MPU-9250. Скорее всего
В качестве визуализации вращений датчика я использовал Blender, потому как буквенно-цифровое представление в кватернионах очень тяжело для человеческого понимания положения датчика в пространстве, уверен что в особых представлениях этот мультимедиа-комбайн на Хабре не нуждается.
Подключаем Blender к Arduino
Моей настольной ОС уже давно (наверное с выходом Windows 10) является Ubuntu — на тот момент 14.04, сейчас 18.04. Далее все манипуляции буду приводить для этой ОС.
Установка Blender тривиальна и не требует никакого описания, я использую версию 2.79b, так как там есть модуль Blender Game Engine.
Для установки необходимых сторонних библиотек:
pip3 install “нужная библиотека”
После установки pySerial, мы можем читать из порта данные о кватернионе напрямую в Blender и крутить кубик, подключив его следующим образом:
import mathutils
import serial
qw = 0.0
qx = 0.0
qy = 0.0
qz = 0.0
arduino = serial.Serial()
arduino.baudrate = 115200
arduino.port = '/dev/ttyUSB0'
def Port():
global qw, qx, qy, qz
global arduino
data = arduino.readline().strip()
quat = data.decode('ascii').split(',')
qw, qx, qy, qz = [float(s) for s in quat]
print(qw)
print(qx)
print(qy)
print(qz)
def Cube():
global qw, qx, qy, qz
scene = bge.logic.getCurrentScene()
cont = bge.logic.getCurrentController()
obj = cont.owner
obj['qw'] = qw
obj['qx'] = qx
obj['qy'] = qy
obj['qz'] = qz
quat_cube = mathutils.Quaternion((obj['qw'], obj['qx'], obj['qy'], obj['qz']))
quat_cube.normalize()
obj.localOrientation = quat_cube()
def Close():
global arduino
arduino.close()
def Start():
global arduino
arduino.open()
Arduino отдает нам кватернион в текстовом виде:
Serial.print(q.w, 4);
Serial.print(",");
Serial.print(q.x, 4);
Serial.print(",");
Serial.print(q.y, 4);
Serial.print(",");
Serial.println(q.z, 4);
Кубик на экране никак не хотел вращаться даже отдаленно напоминая реальные движения датчика в пространстве, движения были очень «шумны» и неестественны.
InvenSense Digital Motion Processor (DMP)
В конце концов я добрался до «магического» Digital Motion Processor (DMP).
Попробовав получить данные магнитометра — я получил `
NaN
`.То есть цифровой компас не отдавал свои значения в DMP, соответственно этот сопроцессор на датчике его не обрабатывал, но движения были как «небо и земля», по сравнению с другими реализациями.
Ну где наша не пропадала? Изучив на тот момент всю доступную документацию по датчику, множество вариантов всевозможного кода для него и продолжая изучать проблему сразу бросилось в глаза — корейский код DMP написан для датчика MPU-9150, который снят с производства. Откорректировав некоторые значения (магнитометр в датчике другой и имеет другой адрес), я был очень удивлен и поражен, что все заработало. Более того, я не только смог получать данные о магнитном поле, но и сам кватернион стал более стабильным — в режиме покоя на столе практически отсутствует дрифт вокруг оси Z (максимальная проверка проводилась около 10 часов). Некоторое смещение происходило только когда я за шнурок отчаянно крутил датчик, мне кажется — это связано с ограничением в 2g или глюками в работе магнитометра. При работе DMP, когда ускорение датчика превышает некоторый порог и данные магнитометра перестают коррелировать с остальными параметрами, DMP перестает учитывать магнитометр при вычислении кватерниона и он становится из 9-ти осевого, 6-ти осевым.
Косвенно это упоминание есть в документе «Motion Driver 6.0 — Features User Guide» от InvenSense, где MPL library каждые 5 сек. проверяет корректность данных от магнитометра и в зависимости от этого использует эти данные в расчетах или нет.
Схема
За основу расположения датчиков на человеке мною была принята следующая картинка:
«Биомеханика» Автор: Владимир Иванович Дубровский
Где светлыми квадратами и цифрами обозначены центры масс частей тела человека, а буквой «S» и черным квадратом центр массы тела человека -итого 15 датчиков — для светлых квадратов и центра массы. Для такого количества датчиков очень напрашивается использование двух I2C 8-ми канальных мультиплексоров (подобное было в костюме упоминаемом в самом начале). Ну а что — дешево и сердито. После некоторых экспериментов я решил использовать связку WeMos D1 mini + MPU-9250 + Battery Shield + MQTT протокол по нескольким для меня причинам:
- Исключение проводного подключения — много соединительных проводов, неизбежные путаница, сложность “одевания и снятия” и возможные электромагнитные наводки.
- Перенос части вычислений с ПК — с одной стороны, ресурсов у любого ПК по сравнению с Arduino более чем достаточно, с другой стороны обработка одного кватерниона одним ESP8266 не сильно ресурсоемкая задача.
- При использовании мультиплексоров я не смог бы перенести математику на микроконтроллер — время считывания данных с датчика около 2 мс, при 15 датчиках это бы заняло 30 мс. Я же использую скорость опроса 50 Гц, т.е. хочу получать новые данные каждые 20 мс, т.е. у меня просто не оставалось бы времени на обработку и корректировку полученных данных.
- Использование протокола MQTT позволит сэкономить полосу пропускания (передаем только необходимые значения когда нужно), к тому же данный протокол быстрее стандартного http.
- Древовидная структура каналов, и правила подписки и публикации сообщений, как по мне более подходит для реализации.
- Возможность передачи дополнительных данных по запросу, кроме кватернионов. Т.е. организация двусторонней связи через сообщения (Start — Stop — Calibration — DeepSleep).
- Универсальность для одного модуля — взаимозаменяемость без перепайки.
- Использование обычной беспроводной сети, обновление «по воздуху» прошивок.
Так как человеческое тело для Blender со скелетом “Game engine” я просто сгенерировал в MakeHuman и обозвал его “adam”, а для беспроводной сети я использую отдельную точку доступа с внутренней сетью 192.168.50.0/24, то общая схема датчиков имеет следующий вид:
Из этой картинки понятно — каналы для сообщений будут иметь вид “adam/head" / ”adam/thigh_r" / ”adam/hand_l" и т.п., а для подписки на нужные достаточно подписаться у брокера соединений на канал “adam/#". Думаю пояснения требует только один “root” — это вектор перемещения в пространстве одноименной кости, которая задает местоположение центра модели, но для удобства обработки на стороне ПК я перевожу этот вектор в кватернион с нулевой скалярной частью, все остальные — “настоящие” кватернионы, которыми задаются положения костей в пространстве.
Немного математики
Что такое кватернионы и почему лучше использовать их, а не матрицы или углы Эйлера
Я же опишу некоторые моменты использованию векторов и кватернионов в своем проекте:
- Процесс калибровки запускаемый с ПК — для этого человек должен принять позу “Т”, с ПК запускается команда для всех датчиков, а каждый датчик запоминает свой сопряженный кватернион. При умножении которого на получаемый мы получаем единичный. Делается это потому что датчики калибруются от 20 до 60 секунд, при этом идет девиация по оси Z.
- Отдельно нужно наверно описать вектор перемещения. В процессе калибровки я беру “идеальный” вектор силы тяжести, поворачиваю его на настоящий кватернион, отнимаю вектор акселерометра датчика от полученного “идеального” в координатах настоящего кватерниона. И создаю вектор корректировки суммой “идеального” и разницы.
- При каком либо перемещении в пространстве, я поворачиваю корректировочный вектор на обратный настоящему, нахожу разницу между этим вектором и вектором акселерометра и поворачиваю на настоящий кватернион. Из полученного вектора и рассчитываю новое положение для кости root.
- Проверка разности кватернионов. Эту функцию используем для понимания есть ли изменения в сравнении с предыдущим отправленным значением или нет. Я не нашел
плохо искалнужную функцию, поэтому использовал следующую из английской Википедии — норма разности это расстояние между двумя кватернионами. Т.е. если норма разности менее какого-то значения = кватернионы практически равны. - Трансляция кватернионов в систему координат кости. Тут все относительно просто — берем векторную часть кватерниона и создаем новый кватернион, где перемещаем векторные части как в Blender и/или если необходимо делаем часть с обратным знаком. Для этого нужно понимать как именно будет расположен датчик на теле и эта часть должна быть “твердым телом” с костью в Blender.Например для “adam/head" такая трансляцию будет (w, y, z, x) (расположение кости z — вперед, x — влево, y — вверх, а датчик расположен x, y, z соответственно и на лбу).
- Для того, чтобы вращения вычисляемые от датчика правильно применялись, нужно знать кватернион кости. Я его поместил сразу в прошивку, но ничего не мешает нам передать его в контроллер при отпарвки команды калибровки (как пример). Чтобы применить вращение от датчика на кости нужно обратный кватернион кости умножить на нужный и затем умножить на прямой.
Сборка
Общая схема соединения WeMos D1 Mini + MPU 9250 не содержит ничего необычного и уверен не требует объяснений.
Итак в новый год я заказал себе подарок на сумму аж в $125:
- WeMos D1 Mini — 16 шт.
- WeMos Battery Shield — 16 шт.
- WeMos Prototype Board — 16 шт.
- GY 9250 — 16 шт.
- Lithium Polymer LiPo Rechargeable Battery JST 2.5mm 1200mAh 3.7V — 15 шт. размерами около 50x34x6мм.
Где-то в середине февраля все мои посылки были доставлены, но, к сожалению, это все хозяйство пролежало на полке до начала марта. Паял наверно неделю или даже больше, выделяя свой обеденный перерыв и/или оставаясь после работы, так как дома не имею паяльной станции.
Кстати Китай — такой Китай, на одной платке D1 была плохо припаяна ESP8266, один датчик 9250 имел КЗ между ножками. Процент брака более 6%, но на мою удачу с помощью фена эти проблемы легко решались.
Фото промежуточное без MPU9250.
И вот
При походе в строймаг были куплены:
20-ти метровая бобина черного ремня 30мм. — цена 155 грн.
Пряжки для ремня D30мм черные 2 шт. в количестве 8 уп. — цена 38,50 грн/уп.
И началось:
- отмерить и нарезать ремни.
- установить пряжки на ремни.
- примерить и определить удобство затягивания ремней и соответственно места где будут датчики.
- примотать датчики “синей изолентой”
- каждый подписать, прошить нужной прошивкой и зарядить
Итог
Мне почему-то кажется что это лучше один раз увидеть чем тысячу раз прочесть.
Нужно сделать несколько пояснений:
- Видео снималось со второй попытки.
- Я не дождался нормальной калибровки датчиков, так как после первой попытки прошивал их.
- Ремни — очень неудобный способ крепления, постоянно съезжают, нужно поправлять, невозможно закрепить чтобы корректно переносились движения на кости.
- Я забыл надеть датчики на ступни.
- Честно — было очень лень переснимать, уверен, что для понимания того, что происходит этого достаточно.
- Также в данном видео нет перемещения, Blender как-то странно себя ведет с костями, но со стандартным кубом все получалось более-менее приемлемо.
- Разумеется все это дело буду дорабатывать, есть куда стремиться.
Послесловие
Сейчас в разработке собственная платка, где все компоненты будут расположены на площади 34х50 мм (примерный размер используемых аккумуляторов). Также планирую добавить на нее более удобную кнопку Reset и RGB-светодиод для сигнализации (работа, калибровка, передача данных и т.п.).
От ремней собираюсь отказаться, даже если купить тканевую эластичную ленту и в местах соединений сделать “липучку” думаю все равно будет неудобно — и долго одевать, и снимать.
Есть идея пошить что-то наподобие компрессионного костюма для тренировок с карманами для датчиков, благо моя свояченица — профессиональный модельер. Это еще более увеличит удобство одевания и снятия, так как все датчики находятся в доступных местах их легко вытащить и вложить из/в кармана, появится возможность стирать костюм и надевать другую одежду сверху.
Думаю применить полученный опыт и наработки в VR — было бы неплохо совместить отслеживание движений и получения ускорений от тела, таким образом использовать тело как геймпад. В этом случае думаю нужна адаптация под конкретную платформу — вот кстати очень интересный концепт SilverCordVR
rPman
Совет, попробуйте для калибровки наложить на датчики qr-коды или что по проще, и снимать себя несколькими FullHD/8k-камерами (хотя последнее может быть дорого) и вычислять положение уже по изображениям.
3al
Получится что-то вроде kinect но с обычными камерами?
yalex1442
Недавно в статье об умных комбайнах писали, что для решения их задачи оказалось достаточно только изображения с камер без спутниковой навигации
kharlashkin Автор
Вот честно — не представляю, какие ресурсы нужны, чтобы, например с 3-х 8k камер в режиме реального времени отслеживать маркеры, поворачивать и распознавать их. Что-то вроде 2-х процессорного сервера на Xeon Silver?
Опять же в этом случае нужно «готовить» помещение, для расчета математики на лету.
rPman
Для КАЛИБРОВКИ реального времени не нужно!
К сожалению даже HD камер не хватит никаких процессоров для реалтайма, так как для адекватной отзывчивости игр требуется не больше пары десятков миллисекунд
Теоритически можно что то сделать с gpgpu, если вы сможете получить сырой поток uncompressed от камеры напрямую в видеокарту и там его анализировать, львиное время тут будет тратиться именно на доставку видеопотока от камеры до видеопроцессора.
Я точно видел проекты распознавания ярких точек в реалтайме (30-60fps) на основе дешевых микроконтроллеров и не fullhd камер, т.е. устанавливаем отражатели в целевых точках и светим на них от камеры.
Если вы найдете soc платы с процессором, поддерживающим fpga и прямым доступом к CMOS HD камеры то большие шансы что можно сделать сверхбыстрое и достаточно точное (миллиметры погрешности на расстоянии метры) распознавание координат.
3al
Oculus Rift использует 3 FullHD камеры (без цвета) и всё это вполне работает на 60Гц.
Оптика не заменяет акселерометры, а дополняет их. Оптика — 60Гц, акселерометры — 1000Гц.
При этом собственно oculus sensor можно заставить определяться как видеокамеру (он собственно и есть видеокамера), поэтому вся обработка происходит не в нём.
rPman
Там железо свое анализирует поток с камер, вы знаете что из общедоступного задешево можно самодельщикам найти? и даже в этом случае готовый софт наверное не найдете, а пилить самому не просто.
kharlashkin Автор
Камера с WiiMote есть за недорого. Отслеживает до 4-х точек одновременно с разрешением 1024х768, углы обзора 33х23 градуса.
rPman
его же хрен купишь кроме как с рук
kharlashkin Автор
По ссылке не сходили? Китайцы уже все сделали, готовый модуль и библиотека под Arduino. Правда цена в два раза выше китайского Wiimote в сборе ;)
rPman
зашел
kharlashkin Автор
Это физическое разрешение сенсора, он с аппаратной обработкой и отдает потом разрешение в 1024х768. Этот же сенсор (или аналог) используется в WiiMote.
Кстати у этого производителя PixArt Imaging Inc. — есть сенсоры с физическим разрешением 98х98 пикселей, но отдают картинку разрешением 4096х4096, с 16 отслеживаемых точек одновременно.
rPman
Наверное тут все же речь идет не о картинке а о угловом разрешении координат вычисляемых источников освещения? Потому что картинку тут вообще не нужно передавать.
Потому что когда я слышу о растровом разрешении, я сразу добавляю возможности использования полутонов для вычисления координат. Кстати при этом погрешности в граничных ситуациях плохие, т.е. если будет паразитная засветка или еще какие помехи, будут глюки.
kharlashkin Автор
В точку. Прошу прощения за некоторую простоту (относительно картинки) в моем сообщении. Кому нужно — поняли, что я имею ввиду.
Для человеческого восприятия мне проще употреблять такие аналогии.
3al
Обычный oculus sensor железом разве что занижает разрешение камеры. На ПК через USB идёт обычный поток видео. Вся обработка — софтовая.
zartarn
Не совсем понимаю зачем QR коды. Да и камеры с такими высокими разрешениями. «контроллеры» у нас однозначные, калибруем их без человека, одеваем на нужные места и всё. обычной световой индикации хватит чтоб сопоставить, так как мы знаем что контроллер 1.1 будет в нижней части. Остальное дело техники.
с PsEye и мувами что то подобное на пк и делают www.youtube.com/watch?v=JuQQM32k2mU
rPman
да можно по очереди откалибровать каждый датчик, qr код позволит сделать это быстрее и одновременно все (qr-код тут само собой не обязателен но для него написано очень большое количество готовых библиотек)
zartarn
QR избыточны и не нужны в данном случае совсем. Это в принципе не проблема которая требует особого внимания. Внимание требует совместимость с существующим ПО. Какой смысл от крутой железки в вакууме, в отрыве от имеющейся экосистемы.
kharlashkin Автор
Все таки правильно говорят «утро вечера мудренее». Я все таки понял что вы советуете — поправьте меня, если я не так понял.
В моем проекте «калибровка» — это соответствие положения датчика и человека в позе «Т». Для этого я сейчас использую нажатие кнопки и задержку в пару секунд, чтобы принять положение правильное.
Если мы снимаем на обычную вебку человека, то для сопоставления позы и начальной позиции, достаточно распознать что человек принял нужную стартовую позу. Можно даже обойтись без маркеров, таких как QR-код или светодиод. Отличная идея, спасибо!
rPman
и вам будет достаточно этой точности?
я считал что калибровка — это определение дельты для совмещения реальных координат с теми что выдают датчики.
пример — вы встаете в позу T, вопрос, то что вы при этом руки сделаете не параллельны земле вас не волнует?
kharlashkin Автор
Вот как раз с вектором тяжести (т.е. параллельности земле) все очень хорошо с этими датчиками. Не очень хорошо с магнитным полем ;)
rPman
Это был пример, вы можете встать под углом в каких то 5 градусов к плоскости камеры и не заметить этого, какие это создаст глюки в будущем, наверное отдельный вопрос.
Я пойму игры танцы, там точность не нужна, но вот взаимодействие с виртуальными предметами — вот где вылезут все косяки калибровки.
Slava0072
вообще есть очень дещовый вариант от Клапана Steamvr tracking. Он весьма круто работает и легко вычесляется + по железу нужны лиш TS4112 и контролер для вычисления положения по данным с них.