Comments 10
Просто интересно – а Hugin (https://hugin.sourceforge.io) не пробовали? у него хорошая детекция однаковых точек вроде была
Конкретно это решение не пробовали. Пробовали ститчер на основе OpenCV и некоторые более специализированные реализации для изображений с микроскопов, но они оказались бесполезны для нашего случая.
У нас может быть множество кадров, попадающих на одну область, некоторые из них могут быть смазанными. Если ститчеру и удается правильно сопоставить изображения (что тоже не всегда работало), то потом при блендинге получалась полная каша. Т.е. как минимум нужна более хитрая фильтрация кадров и выборка нужных пикселей.
А специализированные решения всегда рассчитаны на известный порядок изображений - змейкой или построчно по сетке.
В Hugin, я так понимаю, тоже имеется Mosaic Mode для этого. Но за наводку спасибо, можно будет поизучать подробнее.
Вы там случайно не сговаривались?
Вот комментарий Глубоко Больного Пациента на предыдущую статью
https://habr.com/ru/articles/1067168/#comment_30302964
А, это его совместный разбор обоих. Ничего личного.
-----------------------------
Отличная статья! Видно, что команда прошла через классический “ад энтузиаста” — от блестящего прототипа до продакшен-ада. Давайте разберём её под микроскопом (простите за каламбур), жёстко покритикуем инженерные решения и проведём параллели с предыдущей темой сшивки изображений.
Критика статьи и технических решений MotionCode
Проблема №1: Самоуспокоенность по поводу “Replay Attack” (Атака повторным воспроизведением)
В статье говорится: «Скриншот или запись старой последовательности быстро теряют практический смысл». Это опасное заблуждение. Если я запишу видео панели длительностью 5 секунд на другой телефон и просто покажу это видео камере целевого устройства, алгоритм CV увидит точно такую же динамику, временные интервалы и яркость. Если вы не добавили рандомный временной челлендж (например, смена состояния должна произойти строго после того, как камера моргнула, или с привязкой к текущей миллисекунде сервера), система уязвима. Ваш CV анализирует только геометрию и яркость — он не видит, что это запись с другого экрана.
Проблема №2: Отказ от нейросети — это победа, но вы потеряли контекст
Вы заменили детектор на поиск ROI и яркость. Однако классический CV слеп к частичным перекрытиям. Если палец пользователя перекроет половину ячейки, ваша яркостная метрика сломается. Более того, при съёмке под углом геометрические искажения делают центр ячейки совсем не там, где вы его ждёте. В статье не упомянута аффинная коррекция перспективы перед сравнением областей — а без неё при наклоне телефона > 30° точность резко падает.
Проблема №3: “Магия” калибровки и отсутствие нормализации
Вы пишете: «Параметры, работавшие на одном телефоне, ломались на другом». Это классическая ошибка — настройка абсолютных порогов яркости. Почему вы не использовали адаптивную нормализацию (например, CLAHE или локальный контраст) прямо в потоке? Вместо подбора 50 параметров вручную, можно было привести гистограмму любого кадра к эталонному распределению за 2 мс на CPU. А ещё проще — инвертировать анализ: искать не абсолютную яркость, а границы (edges) ячеек. Границы устойчивы к экспозиции, в отличие от уровня серого.
Проблема №4: Временная фильтрация “в лоб”
Вы накапливаете состояния кадров, чтобы подтвердить их. Это увеличивает задержку (латентность). Для авторизации это может быть ОК, но в сценарии учета времени (когда сотрудник стоит у панели 5 секунд) — ОК. Но вы упустили детекцию смены состояния (переход 0->1). Самый сложный момент — это именно граница переключения, потому что в этот момент на экране может быть артефакт (половина ячейки перерисовывается). Вы не упомянули, как обрабатываете переходные кадры — скорее всего, они у вас падают в фильтр и вызывают ложные срабатывания.
Жёсткая связь с предыдущей задачей (Сшивка микроскопа)
Читая MotionCode, я узнал ту же самую архитектурную драму, что была в микроскопии. Вот 4 точки пересечения:
Аспект Сшивка микроскопа (прошлая статья) MotionCode (текущая) Общий вывод Выбор подхода Отказались от глубоких нейросетей (детекторы точек) в пользу фазовой корреляции. Отказались от Object Detection в пользу классического CV (яркость + ROI). Золотое правило: В узких задачах с известной геометрией классика на CPU всегда побеждает тяжелые модели на GPU. Проблема времени (Sequence) Боролись с накоплением ошибок (дрейфом) при склейке. Использовали глобальную оптимизацию. Борются с ошибочными кадрами через накопление (фильтр большинством). Обе системы поняли, что один кадр — ничто. Только поток (время) дает истину. Однако в микроскопии вы делали глобальный пересчет (Bundle Adjustment), а здесь — простой подсчет голосов. Это слабее. Калибровка Вы мучились с подбором порогов для Phase Correlation и размеров патчей. Вы мучились с порогами яркости и временными окнами. Это проклятие классического CV. В микроскопии я предлагал гибрид с Mutual Information. Здесь я предлагаю авто-калибровку по первому кадру (захватить эталонную яркость пустой панели и активной). Online / Offline Мы разделили на быстрый Online-превью и тяжелый Offline-рендер. MotionCode — это чистый Online. Offline не предусмотрен. Упущение: Если в микроскопии мы могли пересчитать всё потом, то здесь вы теряете данные. Если сотрудник стоял криво, и код не считался — вы не можете пересчитать историю. Нужен Offline-режим, где по сохраненному видео можно пересчитать код на сервере другим (более тяжелым) алгоритмом.
Что я предлагаю (как связать это с прошлым обсуждением и усилить)
Помните наш гибрид Phase Correlation + Mutual Information? Здесь точно такая же история, но с другими компонентами. Применим ту же философию к MotionCode.
А. Гибрид “Яркость + Края” (аналог ФК + ВИ)
· Грубо (Яркость): Ваш текущий метод анализа центральных областей — быстрый, но ненадежный при свете. · Точно (Края / Гистограмма): Если уверенность упала (блик или тень), запускаем тяжелый, но точный метод — HOG (Histogram of Oriented Gradients) на маленьком патче ячейки + SVM (или просто сравнение распределений градиентов). Это устойчиво к экспозиции. Вызовем его только для 1 из 10 кадров для коррекции дрейфа — ровно как мы делали с Mutual Information в микроскопии.
Б. Введение “Offline-Арбитра” (аналог глобальной оптимизации)
Ваше приложение должно сохранять сырые кадры (или сжатые дескрипторы) в буфер на 5 секунд. Если пользователь получает ошибку “Код не распознан”, приложение должно отправить эти 5 секунд видео на бэкенд. На бэкенде (где нет ограничений по батарее) запускается медленный, но супер-точный алгоритм (например, Lucas-Kanade трекинг всех углов панели + проверка целостности последовательности). Это спасет сценарий, когда телефон нагрелся и просел FPS.
В. Безопасность через “Шум” (как у нас было с вращением)
В микроскопии мы игнорировали вращение, потому что оно было малым. Здесь вы игнорируете видео-повторы. Решение: Добавьте в код зависимость от времени сервера на уровне пикселей. Например, панель показывает не просто ячейки, а волну, бегущую слева направо, и сканер должен считывать фазу этой волны. Записать волну на видео можно, но подделать её фазу в реальном времени без синхронизации с сервером — невозможно. Это делает вашу систему устойчивой к replay.
Итоговая ретроспектива
В обеих историях прослеживается один и тот же путь героя:
Наивный старт: «Возьмем нейросеть/FFT, она всё решит».
Жестокая реальность: Мобильное железо/размытие/освещение убивают прототип.
Откат к классике: Инженеры пишут велосипед на порогах и ROI, получая выигрыш в производительности.
Ад калибровки: Бесконечные правки параметров под разные устройства.
Однако главный вывод, который ваша статья не сформулировала, а мы вынесли из микроскопии: Нельзя строить реальное время, игнорируя физику датчика. В микроскопии это была автокоррекция фокуса и дрейф столика. Здесь — автоэкспозиция камеры.
Советую вам на следующей итерации выключить автоэкспозицию камеры программно (фиксированный ISO и выдержка) или считывать её значение и динамически менять пороги. Это убьёт 90% вашей калибровочной боли — и тогда ваш MotionCode станет неубиваемым. И, пожалуйста, добавьте анти-реплей защиту через анализ мерцания экрана (частоты 60 Гц) — камера видит полосы ШИМ, а запись с другого телефона их не повторяет идеально. Это как раз ваш “Mutual Information” для безопасности.
В лаборатории идеальных кадров не бывает, поэтому устойчивость важнее
А почему бы просто не прикрутить моторизированный stage? Смещаем на 10 микрон, фотаем, смещаем еще, опять фотаем.
На самом деле задача сшивки большого скана из набора маленьких фоток не нова, и уже существуют промышленные решения для этого - Whole Slide Imaging (WSI) scanner. Кладешь образец в сканер, он сам фотографирует и сшивает изображения. Но стоит такой аппарат немало, $10k и более.
Задача была как раз в том, чтобы создать более доступное решение для быстрых исследований. Имея обычный цифровой микроскоп или даже смартфон со специальной линзой и штативом можно уже получать полные сканы образцов. При этом не обязательно снимать всю площадь предметного столика - можно отснять только интересующие части.
Зацепил приём с оценкой надёжности по величине корреляционного отклика — у нас в распознавании кодов камерой похожий дешёвый гейт оказался важнее самого декодера. Кадр отбрасывается до попытки разбора, и только так удаётся держать 8 кадров в секунду: декодировать всё подряд просто не успеваешь. Но у такого гейта есть неприятная особенность: низкий отклик одинаково даёт и смазанный кадр, и честно пустой участок без деталей, а это разные ситуации — во втором случае ронять кадр не нужно, нужно ехать дальше. У нас это вылезло на пороге около 110 точек на код: ниже него отклик падал не от смазывания, а оттого, что различать было нечего, и по одному числу эти два случая не отделялись. Вы как-то разводите «размыто» и «нет деталей», или на микроскопе пустых участков в маршруте практически не бывает?
Совсем пустые участки нам не интересны, обычно это область за пределами образца. Сам образец достаточно насыщен деталями. Однако на нем тоже бывают области с малой контрастностью, и здесь аналогичная головная боль возникает - то ли движение слишком резкое было, получился смаз, и кадр некачественный, то ли действительно в кадре мало деталей, но качество хорошее.
В нашем решении используется относительно высокий порог отклика, потому что мы хотим все-таки собирать хорошие кадры. Ну и локальное повышение контрастности (CLAHE) помогает вытягивать детали из изображения, за счет чего отклик тоже возрастает.
Если же отклик нас не устраивает - мы ждем некоторое количество кадров на случай, если все-таки снова попадется четкая область в той же области. При этом предупреждаем пользователя, что трекинг затруднен - нужно замедлить движение.
Если трек окончательно потерян, тогда запускаем глобальный поиск похожих кадров по ключевым точкам и просим пользователя вернуться в знакомое место. Эта операция уже тяжелее, поэтому за фрейм удается только часть снимков проверить, проверка дробится на несколько фреймов.
Сшивка кадров с микроскопа: как собрать большое изображение из видео