
Рассматривать предметный образец под микроскопом — всё равно что пытаться увидеть концертный зал через замочную скважину: в поле зрения попадает только небольшой фрагмент, а общей картины не видно.
Поэтому предметное стекло постепенно перемещают под камерой, а система записывает видео и собирает отдельные кадры в одно большое изображение, которое можно сохранить и использовать для исследования.
Сделать это простым наложением кадров нельзя: во время движения они могут смазываться, освещение меняется, а похожие участки легко перепутать.
Мы с коллегами сравнили несколько подходов к совмещению соседних кадров. Один подход лучше учитывал сложное движение, а другой быстрее обрабатывал кадры, но не учитывал вращение. Но обо всём по порядку.
Задача
Заказчику нужен был инструмент, который позволяет сканировать предметное стекло с помощью камеры микроскопа и получать цельное изображение всей исследуемой области.
Целевой сценарий выглядел так:
пользователь подключает камеру микроскопа или использует мобильное устройство;
перемещает образец под камерой и видит, какие участки уже отсканированы;
после завершения сохраняет изображение всей исследуемой области в нужном формате.
Главная задача заключалась в том, чтобы собрать единое изображение из видеопотока и подготовить его для дальнейшего анализа и исследовательской работы.
Ранее похожий функционал был доступен в приложении из App Store. После его удаления заказчик решил разработать собственное решение. Проект мы реализовали в составе команды Singularis.

Почему кадры нельзя было просто склеить
На первый взгляд задача напоминает сборку панорамы: нужно найти общие области на соседних кадрах, определить их взаимное положение и соединить в одно изображение. Но при работе с микроскопическим видео условия заметно сложнее.
Некоторые области почти не содержат выраженных деталей, по которым можно определить смещение. Камера видит только небольшой участок образца. Во время движения кадры могут смазываться, освещение меняется, а разные фрагменты изображения оказываются похожими друг на друга.
Итоговое изображение получается очень большим. При разработке нужно было учитывать расход памяти, стабильность обработки и особенности форматов хранения.
Система должна была работать не с заранее подготовленными фотографиями, а с реальным видеопотоком, в котором качество кадров, скорость движения и освещение меняются прямо во время съёмки.
Решение
Это скрипт, который получает видеопоток с камеры микроскопа и по мере перемещения образца собирает отдельные кадры в одно большое изображение:
анализирует кадры из видеопотока;
определяет их взаимное положение;
восстанавливает траекторию движения камеры относительно образца;
размещает кадры на единой рабочей плоскости;
уменьшает видимость швов между фрагментами;
выравнивает различия в яркости и цветопередаче;
сохраняет готовое изображение в форматах GeoTIFF и DICOM.
В результате из неупорядоченного видео получается цельное изображение всей отсканированной области.
Какие подходы проверили
Во многих CV-задачах изображения совмещают с помощью ключевых точек. Алгоритм ищет характерные детали на соседних кадрах, сопоставляет их и на основе найденных совпадений вычисляет смещение.
Для микроскопических изображений этот подход оказался недостаточно надёжным. В кадре могло быть мало устойчивых ориентиров, а разные участки образца иногда выглядели слишком похожими. Из-за ошибочных совпадений кадры размещались неправильно, и итоговое изображение собиралось с искажениями.
Поэтому мы проверили два альтернативных подхода к оценке движения между соседними кадрами.
Оптический поток
Оптический поток лучше подходил для более сложного движения и потенциально позволял учитывать вращение образца. На наших данных он работал примерно на 40 FPS, но оказался чувствительным к размытию.
Когда пользователь перемещал образец слишком быстро и кадр терял резкость, точность определения движения снижалась.
Фазовая корреляция
Фазовая корреляция быстрее оценивала сдвиг кадра и могла работать до 200 FPS. При этом она могла определять только смещение кадра без учёта вращения.
Для выбранного сценария это ограничение было приемлемым: важнее было быстро и устойчиво определять смещение между кадрами.
По величине корреляционного отклика система могла оценивать надёжность найденного смещения. Это помогало выявлять сильно размытые кадры и не использовать их при построении траектории.
Как решали проблему швов
При соединении кадров соседние фрагменты должны были выглядеть как единое изображение.
Если просто наложить кадры друг на друга, становятся заметны границы, перепады яркости и участки с разной резкостью.
Стандартное смешивание изображений сглаживало переходы, но давало нежелательный эффект: итоговое изображение становилось более размытым. Для микроскопии это критично, поскольку вместе со швами могут исчезнуть полезные детали образца.
Поэтому мы выбрали использовать преимущественно пиксели из центральных областей кадров.
Центральная часть кадра обычно меньше страдает от оптических искажений, потемнения по краям и снижения резкости. Это помогло уменьшить видимость швов без дополнительного размытия итогового изображения.

Также нам нужно было учитывать накопление ошибок. Если размещать каждый следующий кадр только относительно предыдущего, небольшие неточности постепенно складываются и становятся заметны на большом изображении.
Поэтому система одновременно уточняла положение всех кадров, чтобы небольшие ошибки не накапливались по мере движения по образцу.
Отдельно мы экспериментировали с обработкой дефокуса и устранением размытия, но эти решения не вошли в финальную версию.
Как выравнивали цвет
Яркость и цветопередача могли меняться при съемке. Соседние участки итогового изображения выглядели неодинаково: одна область становилась светлее, другая — темнее, а на некоторых участках менялся оттенок.
Мы добавили цветокоррекцию, чтобы сделать итоговое изображение визуально более равномерным. Алгоритм анализировал различия между соседними областями и корректировал цветовые сдвиги.
Это помогало уменьшить перепады на границах и получить более цельное изображение. Для исследовательского сценария это важно: итоговый файл должен быть удобен не только для хранения, но и для последующего просмотра и анализа.

Результат и ограничения
В результате заказчик получил скрипт, который собирает цельное изображение микроскопического образца из видеопотока.
Система обрабатывает кадры с камеры микроскопа, определяет их положение, строит и сохраняет траекторию движения, размещает фрагменты на единой рабочей плоскости, уменьшает видимость швов и выравнивает цвет между участками. Готовое изображение можно сохранить в форматах GeoTIFF и DICOM.
Основное ограничение выбранного подхода заключается в том, что фазовая корреляция рассчитана на поступательное движение и не учитывает вращение образца. Для целевого сценария заказчика это ограничение было допустимым.

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

onuchin
05.08.2026 18:42Вот что подсказывает Глубоко Больной Пациент. Ничего личного.
----------:
Отличная статья, которая честно описывает инженерный компромисс. Однако как инженер-алгоритмист я вижу в предложенном решении несколько уязвимостей, которые не освещены (или сглажены) в тексте, и которые могут выстрелить в продакшене. А также есть как минимум 3 класса альтернативных решений, которые стоило рассмотреть.
Вот конструктивная критика и альтернативные варианты.
Критика предложенного подхода (Phase Correlation)
Проблема №1: Опасное упрощение вращения Вы пишете: «важнее было быстро и устойчиво определять смещение… ограничение приемлемо». Это самое слабое место. В реальности предметное стекло никогда не двигается идеально поступательно без вращения (даже на моторизованном столике есть рыскание (yaw)). Игнорирование вращения означает, что при большой площади склейки вы получите эффект «веера» — края изображения разъедутся на десятки пикселей. Да, у вас есть глобальная оптимизация (Bundle Adjustment), но она корректирует центры кадров, а поворот каждого фрагмента относительно соседа останется нескомпенсированным. Это приведет к двоению контуров на стыках при детальном рассмотрении.
Проблема №2: «Центральные области» — это убийство данных Выбрать только центральную часть — значит отбросить ~30-40% полезной площади каждого кадра. Для большого мозаики это критично увеличивает время сканирования (приходится делать больше оверлапов). Кроме того, если у вас нет весового смешивания (feathering), а просто жесткая обрезка, то в местах стыков останутся геометрические разрывы, если движение было неидеальным. Простое усреднение (averaging) даёт размытие, но жесткая обрезка даёт артефакты в виде полос, если кадр чуть повернут.
Проблема №3: FPS против реальной пропускной способности 200 FPS для фазовой корреляции — это цифра для FFT размера 256x256. Но если у вас камера Full HD (1920x1080), вы вынуждены делать даунсэмплинг или считать по патчам. В статье не указан размер патча. Если считать по всему кадру — 200 FPS на CPU невозможно (только на GPU с cuFFT). Если считать по маленькому патчу — метод теряет устойчивость при резких перепадах фокуса.
Проблема №4: Цветокоррекция «вслепую» Выравнивание цвета по соседним областям без учета эталонного кадра (или без глобальной гистограммной привязки) порождает эффект «зебры» — градиент яркости, ползущий от левого края к правому, если съемка шла долго и лампа микроскопа нагрелась. Без модели изменения освещения во времени (трендовая коррекция) результат будет пестрым.
Другие варианты решения (Альтернативы)
Если бы я проектировал эту систему заново, я бы рассмотрел следующие подходы, разделив их по сценариям.
А. Гибридный подход (Feature + Phase Correlation)
Вместо того, чтобы выбирать одно, объедините их в конвейере:
Грубая оценка сдвига через Phase Correlation по даунсемпленному (x4) изображению.
Уточнение (Refinement) через поиск совпадений одной ключевой точки (например, центр масс бликов) методом NCC (Normalized Cross-Correlation) с подпиксельной интерполяцией (parabolic peak fitting).
Детекция выбросов: Если пик фазовой корреляции низкий (размытие), мы не отбрасываем кадр, а запускаем SIFT/ORB только на этом конкретном переходе как “спасательный круг”. Это даст скорость ~100 FPS, но спасет от сбоев.
Б. Логарифмически-полярное преобразование (Log-Polar Transform)
Если уж мы работаем в частотной области (БПФ), почему мы не используем Log-Polar FFT? Этот метод позволяет за один проход найти и сдвиг, и вращение, и масштаб. Вы переводите изображение в логарифмически-полярные координаты, где поворот и скейлинг становятся простым линейным сдвигом. Плюс: Вы решаете проблему вращения на скорости, лишь в 2-3 раза медленнее, чем обычная фазовая корреляция. Это снимает главное ограничение, указанное в статье.
В. Сшивка на основе глубокого обучения (LoFTR / DKM)
Для микроскопии с бедными текстурами классический CV (даже SIFT) часто проигрывает. Современные матчеры на трансформерах (LoFTR, SuperGlue, DKMv3) специально обучены на парах изображений с низкой текстурой (аэрофотосъемка пустынь, медицинские снимки). Сценарий: Вы берете каждый 5-й кадр, находите на нем 1000 точек через LoFTR (на GPU это 15 FPS), получаете точную аффинную матрицу, а промежуточные кадры привязываете через оптический поток (Farneback) к этим опорным. Это дает абсолютную устойчивость к накоплению ошибки (drift) и учитывает нелинейные дисторсии объектива.
Г. Инкрементальная оптимизация с факторизацией (iSAM / Sliding Window)
В статье упомянут глобальный BA, но для потока это требует хранения всех кадров в памяти, что при размере мозаики в 10k x 10k пикселей приводит к Out-of-Memory. Альтернатива: Использовать EKF (Extended Kalman Filter) на основе показаний Phase Correlation. Это не требует пересчета всех кадров, дает оценку ковариации (мы знаем, где ошибка накапливается), и позволяет строить превью в реальном времени, а глобальный BA запускать только в конце для финального рендера. Это также позволяет “забывать” старые кадры, экономя RAM.
Д. Подход с приоритетом резкости (Sharpness-Weighted Blending)
Вместо жесткого выбора центральных пикселей — используйте взвешивание по карте градиентов (Laplacian variance). Для каждого пикселя каждого кадра вычисляется локальная резкость. В финальное изображение записывается пиксель с максимальной резкостью среди перекрывающихся кадров. Это магическим образом убирает швы (потому что границы кадров обычно нерезкие) и автоматически вырезает смазанные участки, не теряя полезные данные по краям, если они вдруг оказались резче центра (бывает при кривизне образца).
Что стоило добавить в финал (Hardware-in-the-loop)
В статье не учтено, что микроскопы с моторизованным столиком часто отдают координаты XY (энкодеры). Самое лучшее решение — не полагаться только на CV, а сделать Sensor Fusion:
Берем координаты с энкодеров столика (грубо, но без дрейфа).
Берем смещение с Phase Correlation (точно, но с дрейфом).
Сливаем через Калмана. Это дает субликсельную точность, устойчивость к размытию (если кадр смазан — доверяем энкодеру) и позволяет детектировать проскальзывание столика. Именно так делают в промышленных системах (например, Leica или Zeiss), а не в лоб через чистый CV.
Итог: Вы выбрали самый быстрый метод, пожертвовав вращением и памятью. Если заказчик когда-нибудь захочет сканировать мазок крови при 1000x увеличении (где поле зрения крошечное, а стыков сотни), накопленная ошибка по углу разрушит мозаику. Мой выбор — Log-Polar FFT + Sharpness-Weighted Blending + глобальный BA с аффинными матрицами, а при наличии энкодеров — EKF-слияние.
--------------------------------------------
Привлечение совместной энтропии/информации
Отлично, давайте спроектируем гибридный конвейер, который объединяет сильные стороны обоих методов, и разделим его на два режима работы. В итоге вы получите систему, которая в реальном времени строит черновую мозаику (для пользовательского интерфейса), а в офлайне выдаёт бесшовное, геометрически точное изображение для исследований.
Общая идея комбинации
Метод Сильная сторона Слабая сторона Фазовая корреляция (ФК) Скорость (сотни FPS), надёжна на контрастных текстурах Не работает при нелинейном изменении яркости, не учитывает вращение, чувствительна к размытию Взаимная информация (ВИ) Устойчива к изменениям освещения и цветовым сдвигам, может оценивать вращение/масштаб Очень медленная (десятки мс на один расчёт), требует оптимизации
Гибридный принцип:
· ФК используется как грубый детектор — даёт начальное приближение сдвига за 1–2 мс. · ВИ используется как точный корректор — уточняет положение, но только там, где ФК дала сомнительный результат, либо на этапе глобальной оптимизации.
Общий пайплайн (ядро для обоих режимов)
На входе — видеопоток, каждый кадр с временной меткой.
Предобработка кадра: · Преобразование в градации серого (для ФК) и сохранение цветного оригинала (для финального рендера). · Даунсэмплинг в 2–4 раза для ускорения (особенно для ВИ). · Вычисление карты резкости (Laplacian variance) — чтобы оценить качество кадра.
Оценка грубого сдвига (ФК): · Применяем БПФ к двум соседним кадрам (или к текущему и предыдущему). · Получаем пик корреляции и его координаты → грубое смещение (dx, dy) и коэффициент надёжности conf = peak / (mean + std).
Решение о необходимости уточнения (ВИ): · Если conf высок (> 0.6) — считаем, что ФК справилась, и сразу передаём сдвиг в трекер. · Если conf низкий (размытие, плохая текстура, смена яркости) — запускаем локальный поиск максимума ВИ в небольшом окне вокруг (dx, dy) ± 5 пикселей.
Уточнение с помощью ВИ (быстрый вариант): · Строим совместную гистограмму интенсивностей для текущего предполагаемого сдвига (и нескольких соседних). · Используем нормализованную взаимную информацию (NMI) как метрику. · Для поиска максимума применяем параболическую интерполяцию по 3×3 сетке вокруг начального сдвига — это даёт подпиксельную точность без итеративного спуска.
Сохранение результата: · Запоминаем уточнённый сдвиг (и, возможно, угол, если мы его оценивали) и метку качества кадра. · Если сдвиг слишком мал (кадр стоит на месте) — пропускаем его (дубликат).
Это ядро работает и в реальном времени, и в офлайне. Отличие — в том, как часто и как глубоко мы используем ВИ, а также в постобработке.
Режим реального времени (online)
Цель: показывать пользователю превью мозаики с минимальной задержкой, откликаясь на движение столика.
Стратегия:
· 99% кадров обрабатываем только ФК — это даёт скорость >100 FPS на CPU (при размере 512×512 в сером). · ВИ вызываем только при двух условиях: · conf упал ниже порога (например, 0.4) — кадр размыт или освещение резко изменилось. · Каждые 5–10 кадров принудительно — чтобы сбросить накопленный дрейф (делаем «реперную» проверку). · Для ВИ используем сильно уменьшенные изображения (128×128) и ограничиваем поиск окном ±10 пикселей — это укладывается в 5–10 мс на современном CPU (с использованием SSE/AVX или OpenMP). · Трекер движения: · Запускаем простой фильтр Калмана на основе последовательности сдвигов. Он сглаживает шум ФК и предсказывает сдвиг для следующего кадра, что уменьшает поисковое окно для ФК (ускоряет ещё сильнее). · Если ВИ дала поправку, она обновляет состояние Калмана. · Рендеринг: · Каждый новый кадр размещается на полотне по текущей оценке (с использованием весов по резкости центральной области, как в статье). · Поскольку вращение не учитывается, мы компенсируем его предварительной калибровкой траектории — предполагаем, что угол дрейфа мал, и игнорируем его на этапе превью.
Итог по времени: средняя обработка одного кадра — 2–3 мс (ФК) + редкие 10 мс (ВИ). Превью обновляется плавно, пользователь видит процесс сканирования.
Режим постобработки (offline)
Цель: по завершении сканирования получить идеальную мозаику с субликсельной точностью, исправить вращение, цветовые градиенты и артефакты наложения.
Стратегия — двухпроходная:
Проход 1. Построение графа связей с уточнением всех пар
· Для каждого кадра мы имеем грубую траекторию от online-режима (или можем пересчитать заново, если online не сохранил все данные). · Теперь мы не ограничены по времени. Поэтому: · Для каждой пары соседних (и, возможно, через одного) кадров вычисляем точную трансформацию (сдвиг + поворот + масштаб) с помощью максимизации взаимной информации. · Используем многомасштабную пирамиду (от 64×64 до полного разрешения) и оптимизацию Бройдена–Флетчера–Голдфарба–Шанно (BFGS) по 4 параметрам (dx, dy, угол, масштаб). Это занимает 0.5–2 секунды на пару, но мы делаем это только для ключевых пар (например, каждые 10 кадров), а для остальных — интерполируем. · Для ускорения можно использовать GPU-ускорение гистограмм (CUDA) — тогда расчёт ВИ для одной пары занимает <50 мс.
Проход 2. Глобальная оптимизация (Bundle Adjustment)
· Имеем набор относительных трансформаций между кадрами (граф с рёбрами). · Строим факторный граф: · Вершины — положения кадров в глобальной системе координат (свободные параметры: x, y, угол). · Рёбра — измерения (сдвиг, угол) с ковариационной матрицей (вес, обратно пропорциональный неопределённости, которую мы оцениваем по кривизне пика ВИ). · Запускаем нелинейный метод наименьших квадратов (например, Ceres Solver или g2o) для минимизации суммарной ошибки reprojection. · Это одновременно сглаживает дрейф, распределяет ошибку по всем кадрам и вычисляет оптимальные параметры вращения. · После оптимизации все кадры размещаются на итоговом полотне.
Проход 3. Финальный рендеринг (бесшовное смешивание)
· Для каждого пикселя итогового изображения определяем, какие кадры его покрывают. · Используем мультибандовое смешивание (как в панорамах) или взвешивание по резкости (на основе лапласиана), но с учётом того, что теперь у нас есть точные геометрические трансформации. · Для выравнивания цвета применяем глобальную гистограммную привязку — выбираем опорный кадр с медианной яркостью и корректируем все остальные так, чтобы их гистограммы совпадали (это решает проблему дрейфа освещения, которую ВИ только детектирует, но не исправляет).
Итоговый результат: мозаика без швов, с точностью <0.1 пикселя, скомпенсированными поворотами и единой цветопередачей. На всё это уйдёт несколько минут для 1000 кадров — что приемлемо для офлайн-исследований.
Как именно связать ВИ с ФК в офлайн-режиме?
Предлагаю метод гибридной метрики:
· Начальное приближение для ВИ-оптимизации берём из ФК (это сокращает область поиска и гарантирует сходимость). · В качестве финальной метрики используем комбинированный штраф: (1 - α) NCC + α NMI, где α регулируется в зависимости от разницы гистограмм (если гистограммы сильно отличаются, α → 1). Это даёт робастность на всём диапазоне кадров.
Рекомендации по реализации для инженеров
· Для online: используйте библиотеку OpenCV (phaseCorrelate) и собственную быструю реализацию NMI через предварительно вычисленные таблицы. Ограничьте разрешение до 256×256 для ФК и до 128×128 для ВИ. · Для offline: используйте библиотеку SimpleITK или ITK — там уже есть готовые регистраторы на основе взаимной информации с поддержкой многопараметрических трансформаций. Либо реализуйте свой оптимизатор на Eigen + CUDA. · Хранение траектории: сохраняйте все сдвиги (даже online-оценки) в файл, чтобы в офлайне можно было пересчитать только сомнительные участки, а не все пары заново.
Итог: два режима — один код
В коде достаточно сделать флаг is_online.
· Если true — используем ФК + редкий ВИ, рендерим на лету. · Если false — запускаем полный пайплайн оптимизации, используя сохранённые кадры и их online-треки как стартовую точку.
Такая архитектура даёт заказчику гибкость: он может быстро просканировать область, посмотреть черновик, а затем запустить «финишную обработку» на ночь и получить готовый высококачественный файл в GeoTIFF/DICOM. И никаких компромиссов — каждый метод работает там, где он сильнее всего.

x2v0
05.08.2026 18:42Вы там случайно не сговаривались?
Вот комментарий Глубоко Больного Пациента на предыдущую статью
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” для безопасности.
thegriglat
Просто интересно – а Hugin (https://hugin.sourceforge.io) не пробовали? у него хорошая детекция однаковых точек вроде была