В кадре весь класс, но слушаться надо учителя: как M2 распознаёт свои руки среди чужих

Прокомментировать Просмотры: 0

Привет, SE7EN! Это первая публикация в блоге M2 Holo Lab. Раньше я писал с личного аккаунта: сначала рассказывал, как управлять 3D-моделями голыми руками с помощью обычной веб-камеры, а затем — про мозг, мышь, дом и ДНК.

Коротко о проекте: M2 — это 3D-лаборатория, которая работает в браузере. Камера отслеживает руки, а пользователь жестами вращает модели, разбирает их и собирает обратно. За распознавание отвечает MediaPipe, за отрисовку — Three.js. Видеопоток с камеры никуда не передаётся.

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

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

Как было раньше: «распознаём любые две руки»

Раньше M2 запрашивал у нейросети две руки и считал, что обе принадлежат пользователю. В MediaPipe HandLandmarker для этого достаточно указать один параметр:

const HAND_O = { runningMode: 'VIDEO', numHands: 2, /* ... */ };

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

Из-за этого возникают две проблемы:

  • Перехват управления. Учитель держит деталь двигателя, на мгновение опускает руку — и её место тут же занимает чья-то рука на заднем плане. Модель резко перемещается на другой край экрана.

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

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

Визуализация на синтетических руках: так работал прежний принцип «любые две руки»

Идея: автопилот не меняет полосу каждую секунду

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

Тогда я обратился к идее, которую использует автомобильный автопилот. Он не выбирает «лучшую полосу» заново на каждом кадре, а сначала определяет свою и затем следит за её траекторией. Если разметка ненадолго исчезает, машина не перестраивается в соседнюю полосу, а ждёт её там, где видела в последний раз.

С руками можно поступить так же. Теперь M2 не пытается каждый раз выяснить, какие руки в кадре главные. Вместо этого он ищет те, которые уже распознал как руки пользователя. На этом основаны три правила:

  1. Новую руку принимаем, только если она достаточно крупная.

  2. Рука остаётся своей, если продолжает движение в соответствии с предыдущими кадрами.

  3. Если своя рука исчезла, её место некоторое время остаётся занятым.

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

Правило 1: руки ведущего крупнее

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

Измерять всю руку не стоит: у одного человека кулак и раскрытая ладонь отличаются по размеру в несколько раз, хотя именно на таких жестах основано управление в M2. Поэтому я измеряю только ладонь, не учитывая пальцы. Для этого использую два расстояния между точками MediaPipe: от запястья до основания среднего пальца и от основания указательного пальца до основания мизинца. В качестве размера беру большее значение. Если ладонь повернута к камере ребром, её ширина уменьшается, но длина сохраняется.

// точки MediaPipe: 0 — запястье, 9 — основание среднего, 5 и 17 — основания указательного и мизинца
const palmSize = (p) => Math.max(
  dist(p[0], p[9]),
  1.15 * dist(p[5], p[17])
);

Теперь нужен размер-ориентир. В M2 уже была калибровка: перед началом работы пользователь показывает ладонь, щипок и кулак, чтобы приложение подстроилось под его руки. Теперь во время демонстрации ладони система также запоминает её медианный размер в кадре. Если калибровку пропустить, ориентир постепенно формируется по рукам, которые уже распознаны как свои. Он обновляется плавно — на 4% за кадр. Так система адаптируется, если учитель отходит от камеры, но не реагирует на случайные скачки.

M2 принимает новую руку за свою, только если она соответствует двум условиям:

  • её размер составляет не менее 62% от эталона;

  • она не меньше 55% самой крупной руки в кадре. Это запасной критерий на случай, если эталон ещё не установлен.

У человека, сидящего за партой в нескольких метрах позади учителя, рука выглядит заметно меньше и обычно не достигает 62% от эталонного размера. Если же включён режим 3D по положению головы, появляется дополнительная проверка. M2 уже определяет положение лица главного пользователя, поэтому новая рука должна находиться от него не дальше чем в шести ширинах лица. Рука на другом краю кадра точно принадлежит кому-то ещё.

Правило 2: своей считается рука, продолжающая движение

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

Поэтому для уже принятой руки действует более гибкое правило. В каждом кадре M2 ищет поблизости от её предыдущего положения руку сопоставимого размера:

  • «Поблизости» — это расстояние не больше 2,2 размера ладони, но как минимум 18% ширины кадра. Нижняя граница нужна для резких движений: за один кадр рука может переместиться далеко.

  • «Сопоставимого размера» — размер составляет от 0,45 до 2,2 от размера руки в предыдущем кадре.

Здесь важна разница между порогами. Чтобы стать своей, рука должна быть не меньше 62% эталона. Чтобы оставаться своей, ей достаточно не уменьшиться более чем вдвое за один кадр. Учитель может спокойно отходить: его руки будут постепенно становиться меньше, но управление останется у него. А чужая рука такого же размера не пройдёт фильтр — у неё нет истории движения.

В одном из тестов руки за 40 кадров уменьшились до 60% исходного размера — ниже порога для новых рук. При этом обе сохранили статус своих.

Правило 3: если рука исчезла, её место остаётся за ней

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

Теперь после исчезновения своя рука сохраняет за собой слот на 1,5 секунды. M2 запоминает её последнее положение и размер. Посторонняя рука не займёт этот слот, даже если больше в кадре никого нет. А когда своя рука снова появится рядом с местом исчезновения, система подхватит её согласно правилу 2.

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

Почему выбрано именно полторы секунды? При меньшем интервале чужая рука может успеть перехватить управление, пока учитель перекладывает деталь. Если увеличить его, учитель может опустить руку, поднять её в другом месте и слишком долго ждать, пока M2 её распознает.

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

Две неожиданные сложности

Холодный старт. Учитель ещё не подошёл к камере, но ученик с задней парты уже машет рукой. Эталона пока нет, сравнивать не с чем, поэтому чужая рука вполне может первой попасть в список своих. Дальше правило 2 срабатывает против нас: оно удерживает эту руку, и к моменту появления учителя один слот уже занят.

Решение основано на простом наблюдении: ладонь учителя обычно одна из самых крупных в кадре. Если уже принятая рука оказывается меньше 55% от самой крупной, значит, в пустой кадр попала чужая. M2 удаляет её и освобождает слот для настоящих рук.

Сосед с крупной рукой. Ученик подходит к учителю и тянется к экрану. Его рука оказывается примерно того же размера, и по этому признаку отличить её уже нельзя. Здесь помогает ограничение по слотам: пока в кадре видны обе руки учителя, M2 не будет принимать третью. А дальше срабатывает честная граница подхода — о ней расскажу ниже.

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

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

Как тестировать, если класса рядом нет

Собирать учеников ради каждой настройки порогов было бы неудобно, поэтому фильтр я проверял на синтетических руках. Тестовый скрипт на Node извлекает функцию-фильтр прямо из рабочего файла index.html. Так проверяется настоящий код приложения, а не его копия. Каждая тестовая рука — это 21 точка с заданным центром и размером ладони. Сценарий представляет собой последовательность кадров с интервалом 33 мс, как при частоте 30 кадров в секунду.

Сценарий

Ожидаемый результат

У учителя две руки, за ним — два человека

распознаны только руки учителя

Правая рука учителя исчезла, в кадре есть чужая

чужая рука не принимается

Правая рука вернулась на прежнее место

рука снова распознана

У соседа появилась крупная третья рука

она игнорируется

Учитель откинулся назад, руки уменьшились до 60%

обе руки остались своими

Холодный старт: в кадре только чужая рука

рука принята — других вариантов нет

Затем в кадре появился учитель

чужая рука отброшена, руки учителя приняты

Учитель с двумя руками, посторонних нет

распознаны обе руки

Обе руки исчезли на полсекунды, в кадре появилась чужая

новые руки не принимаются

Обе руки вернулись

обе снова распознаны как свои

После калибровки в кадре появилась чужая рука размером 55% от ладони учителя

рука не принимается

Все 11 сценариев проходят успешно. Затем я загрузил страницу в headless Chrome, подключил фальшивую камеру и запустил фильтр уже внутри приложения. Результат оказался таким же, ошибок на странице не возникло.

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

Что пока остаётся за рамками решения

У этого подхода есть ограничения — о них стоит сказать прямо:

  • Человек рядом с учителем. Если ученик стоит на таком же расстоянии от камеры, размер его руки не поможет отличить её от руки учителя. Пока обе руки ведущего видны в кадре, ситуацию спасает лимит слотов. Но если учитель уберёт руку дольше чем на полторы секунды, сосед сможет занять освободившееся место. Есть и другой нюанс, который обнаружился, когда я готовил GIF для статьи: если рука соседа находится всего в паре ладоней от места, где исчезла рука учителя, M2 принимает её за ту же руку и сразу передаёт ей управление, не дожидаясь окончания полутора секунд. В этом случае правило 2 работает против нас.

  • Передача управления. Иногда учитель хочет, чтобы двигатель собрал ученик. Сейчас для этого ученику нужно просто встать на место учителя — отдельного жеста для передачи управления пока нет.

  • Проверка в настоящем классе. В реальных условиях этот режим я ещё не тестировал. Синтетические сценарии помогают проверить логику, но они не учитывают освещение, положение камеры и десятки одновременно движущихся рук. Порог в 62% и интервал в 1,5 секунды, скорее всего, придётся скорректировать после анализа записей с уроков. Именно для этого и нужен пилот со школой.

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

Сейчас M2 проходит закрытое тестирование. Если вы преподаёте и хотите попробовать систему на уроке, на сайте m2holo.com можно оставить заявку на пилот. А если вы решали похожую задачу иначе — например, отслеживали положение всего тела или оценивали глубину с помощью одной камеры, — расскажите в комментариях. Будет интересно сравнить подходы.

О том, как устроены сами модели — мозга, сердца, ДНК, атома и двигателей, — я пишу в статьях на сайте: https://m2holo.com/ru/articles

 

Источник

Похожие статьи

Поиск по играм, новостям и статьям…

Введите не менее двух символов

Введите не менее двух символов