Использование двумерного сплайна с scipy.ndimage.geometric_transform для регистрации изображений

1

Я пытаюсь выровнять массивы (изображения). Массивы не разделяют однородные координаты из-за нелинейных искажений, и поэтому такого аффинного преобразования недостаточно.

К счастью, тривиально найти координаты совпадающих признаков между массивами, и они были использованы для вычисления начального аффинного преобразования. Я использовал остаточные различия между координатными парами для соответствия Smooth Bivariate Splines, которые затем моделируют зависящее от позиции смещение по x и y которое необходимо применить, чтобы преобразовать одно изображение в другое.

Проблема возникает при использовании этих сплайнов с геометрическим_транспортом() - хотя результирующие выравнивания превосходны, это очень медленно (массивы размером ~ 50 М).

Я создаю сплайн, представляющий сдвиги, требуемые в координатах x и y (здесь img1_coo является массивом Nx2 координат x и y в первом изображении, img2_coo одинаково для второго изображения (после аффинного преобразования):

from scipy import interpolate
sbs_x = interpolate.SmoothBivariateSpline(img1_coo[:,0], img1_coo[:,1], img1_coo[:,0]-img2_coo[:,0])
sbs_y = interpolate.SmoothBivariateSpline(img1_coo[:,0], img1_coo[:,1], img1_coo[:,1]-img2_coo[:,1])

Вызываемый для geometric_transform() следующий:

def apply_spline(xy):                                                                        
    return xy[0] - sbs_x.ev(xy[0], xy[1]), xy[1] - sbs_y.ev(xy[0], xy[1])

Выполнение преобразования с помощью:

from scipy import ndimage
img2_data_splined = ndimage.geometric_transform(img2_data, apply_spline)

Это занимает ~ 10 минут на массиве 50M. Я вижу, что оценка SmoothBivariateSpline.ev(x,y) с массивом размера 50M выполняется очень быстро:

xx, yy = np.meshgrid(np.arange(8000), np.arange(6000))
%timeit sbs_x.ev(xx,yy)
6.78 s ± 43.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

Поэтому я предполагаю, что geometric_transform() замедляет вызов каждой корекции. Для визуализации здесь приведены сплайновые карты, которые я исправляю (цвета идут от пикселя ~ -1 до смещений пикселей +5)

Изображение 174551

Я попытался сыграть с понижением порядка интерполяции и т.д., Но не нашел увеличения скорости. Любая помощь приветствуется при ускорении geometric_transform() или если есть еще одна реализация для выполнения регистрации изображений и обработки сложных геометрий/искажений?

(Я попытался использовать skimage.warp с помощью PolynomialTransform но выравнивание не так хорошо, и оно также довольно медленно, но не так медленно, как geometric_transform())

Теги:
image-processing
numpy
scipy
scikit-image

1 ответ

2
Лучший ответ

Итак, есть два решения для вашей проблемы, хотя потенциально только одно выполнимое:

1. Используйте ndimage.map_coordinates

Поскольку interpolate.SmoothBivariateSpline.ev векторизована, в вашем окончательном выражении вы уже почти сделали:

from scipy import ndimage as ndi
xx, yy = np.meshgrid(np.arange(8000), np.arange(6000))
coords_in_input = apply_spline((xx, yy))
img2_data_splined = ndi.map_coordinates(img2_data, coords_in_input)

(Примечание: вам может потребоваться выполнить перенос в зависимости от ваших соглашений о координатах.)

2. Используйте LowLevelCallable

Одна из причин, по которым geometric_transform занимает некоторое время, заключается в том, что вызывающие функции в Python медленны. Паули Виртанен создал интерфейс LowLevelCallable в SciPy, чтобы гарантировать, что функции C/Cython/Numba можно вызвать без накладных расходов Python. Если вы можете выразить свою функцию сопоставления в коде C или Numba, вы можете получить большие ускорения. Документы geometric_transform сообщают вам требуемую подпись функции. Вот простой пример (кредит: Кира Эванс) для простого двумерного сдвига:

В Китоне:

#cython: boundscheck=False
#cython: wraparound=False
#cython: cdivision=True
#cython: nonecheck=False
#cython: initializedcheck=False
#cython: binding=False
#cython: infer_types=False

import numpy as np
cimport numpy as cnp

from libc.stdint cimport intptr_t

cdef api int shift(intptr_t* output_coords, double* input_coords,
                   int output_rank, int input_rank,
                   void* user_data):
    cdef:
        Py_ssize_t i

    for i in range(output_rank):
        input_coords[i] = <double> output_coords[i] - (<double*> user_data)[0]

    return 1

Затем, предположив, что вы импортировали модуль Cython в качестве mapping в Python:

# Don't declare the user_data array inline because
# .ctypes.get_as_parameter
# does not keep reference to the array
shift_amount = np.array([42], dtype=np.double)
shift_cy = LowLevelCallable.from_cython(mapping, name='shift',
               user_data=shift_amount.ctypes.get_as_parameter())

Теперь вы можете сделать:

shifted = ndi.geometric_transform(image, shift_cy)

с достойной производительностью.

Вы также можете использовать Numba для этого, что может быть более или менее привлекательным в зависимости от вашего варианта использования. См. Здесь, здесь и здесь для получения дополнительной информации.

  • 1
    Спасатель жизни! Я играл с использованием map_coordinates в функции apply_spline но, конечно, стоимость вызова функции 50M раз была все еще там. Ваш первый ответ решил мою проблему (сейчас занимает ~ 10-15 секунд!) Мне нравится идея ответа 2, я могу рассмотреть в будущем.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню