5.31. Сопоставление по смещению

Сопоставление с шаблоном отвечает на вопрос где находится этот фрагмент внутри кадра; оценка схожести отвечает на вопрос насколько в целом похожи эти два изображения. Между ними находится другой вопрос: два кадра показывают одну и ту же сцену, но камера (или сцена) сместилась между ними – насколько? Это проблема смещения, и модуль image решает её единственным методом фазовой корреляции.

5.31.1. Смещение по фазовой корреляции

find_displacement() оценивает жёсткое выравнивание между двумя изображениями одинакового размера, используя фазовую корреляцию – частотный метод, который выполняет быстрое преобразование Фурье (FFT) над каждым изображением, взаимно коррелирует их фазы и находит пик в результате. Положение пика – это сдвиг, который выравнивает два изображения:

d = img.find_displacement(template)

print("shift:", d.x_translation, d.y_translation,
      " response:", d.response)

Возвращаемый объект Displacement несёт x_translation и y_translation – сдвиг в пикселях по каждой оси – плюс response, оценку достоверности от 0.0 до 1.0, где 1.0 – идеальный пик. Отсеивание обнаружений ниже response > 0.3 отбрасывает ложные результаты, в которых фазовая корреляция так и не нашла чёткого пика.

В режиме по умолчанию rotation и scale равны соответственно 0.0 и 1.0; они принимают реальные значения только при logpolar=True (см. ниже).

Метод несёт два практических ограничения. Первое – размеры, кратные степени двойки: FFT, лежащий в основе фазовой корреляции, работает быстрее всего – а на камере и полностью поддерживается только – при размерах вроде 32 на 32, 64 на 64 и 128 на 128. Самый чистый вариант – захватывать напрямую в одном из этих размеров, передавая разрешение в framesize() в виде кортежа:

csi0.framesize((64, 64))

Приложение, которому нужно смещение из более крупного кадра, вместо этого обрезает фрагмент со стороной, кратной степени двойки, из интересующей его области и запускает сопоставитель на нём.

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

5.31.2. Поворот и масштаб через лог-полярное преобразование

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

d = img.find_displacement(template, logpolar=True)

print("rotation rad:", d.rotation,
      " scale:", d.scale,
      " response:", d.response)

При logpolar=True метод выполняет тот же конвейер сопоставления над лог-полярно спроецированными изображениями вместо оригиналов. Поля rotation и scale результата возвращаются заполненными: rotation – это угол в радианах между двумя кадрами, scale – коэффициент масштаба между ними. x_translation и y_translation в этом режиме становятся бессмысленными (сдвиг вдоль лог-полярных осей не соответствует линейному сдвигу в источнике).

Ключевое слово fix_rotation_scale=True покрывает промежуточный случай: два изображения различаются одновременно по сдвигу и по повороту/масштабу, а приложению нужен только сдвиг после коррекции поворота и масштаба. Сопоставитель сначала выполняет лог-полярный проход, чтобы восстановить поворот и масштаб, применяет обратное преобразование к одному из изображений, а затем выполняет проход по сдвигу, чтобы восстановить оставшийся сдвиг. Этот флаг имеет смысл только при logpolar=False – он просит сопоставитель в режиме сдвига сначала устранить поворот/масштаб.

Паттерн из полярных преобразований – декартовы → полярные → сопоставление – это то, что find_displacement() с logpolar=True делает за один вызов. Приложение сохраняет эталонный лог-полярный фрагмент при запуске, захватывает и лог-полярно преобразует каждый текущий кадр, а метод восстанавливает разницу в повороте и масштабе между ними. Для приложений, которым нужен трекер, инвариантный к повороту и масштабу – стыкующийся робот, чья камера наклоняется и приближается к цели, стабилизированный подвес, которому нужно знать, как изображение поворачивается относительно эталона – это стандартная конструкция.

5.31.3. Классическое применение

Самое распространённое применение find_displacement() – это покадровая оценка движения в конвейере, обрабатывающем движущуюся камеру. Камера захватывает небольшой фрагмент со стороной, кратной степени двойки, на кадре N, захватывает фрагмент того же размера на кадре N+1, запускает find_displacement() на этих двух и считывает сдвиг в пикселях между ними. Сдвиг – это оценённое движение камеры (или сцены, в зависимости от того, чья система отсчёта важна) между двумя снимками, полезное для:

  • Сенсорики в стиле оптического потока – зависающий дрон с направленной вниз камерой использует покадровое смещение, чтобы оценить своё боковое движение и передать его обратно в полётный контроллер.

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

  • Выравнивания при инспекции – сканирующая камера, движущаяся вдоль конвейера, использует покадровое смещение, чтобы совместить каждый кадр со следующим и построить сшитый вид всей детали.

Каждое из этих приложений принимает одну и ту же форму: захват, смещение, накопление в текущей оценке, снова захват.