使用带有scipy.ndimage.geometric_transform的双变量样条来注册图像

时间:2018-07-27 17:53:02

标签: python numpy image-processing scipy scikit-image

我正在尝试对齐数组(图像)。由于非线性失真,这些数组无法共享同质坐标,因此affine transformation不够。

幸运的是,在数组之间找到匹配特征的坐标很简单,这些坐标用于计算初始仿射变换。我使用坐标对之间的残差来拟合Smooth Bivariate Splines,然后对xy中依赖于位置的偏移量进行建模,以便将一个图像转换为另一个图像

将这些样条线与geometric_transform()配合使用时会出现问题-尽管对齐效果非常好,但速度非常慢(数组大小约为50M)。

我创建了一个样条,表示在xy坐标中所需的移动(此处img1_coo是第一张图像img2_coo中由xy坐标组成的Nx2数组对于第二张图片(仿射变换之后)是相同的:

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)

在50M阵列上大约需要10分钟。我看到使用50M大小的数组评估SmoothBivariateSpline.ev(x,y)的速度非常快:

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像素偏移)

enter image description here

我尝试降低插值的顺序等,但是没有发现速度增加。欢迎加快geometric_transform()的使用,或者是否还有其他实现来执行图像配准和处理复杂几何形状/变形的帮助?

(我曾经尝试过使用skimage.warpPolynomialTransform,但是对齐效果不佳,并且对齐速度也很慢,但不如geometric_transform()慢)

1 个答案:

答案 0 :(得分: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需要一段时间的原因是calling functions in Python is slow。 Pauli Virtanen在SciPy中创建了LowLevelCallable接口,以确保可以在没有Python开销的情况下调用C / Cython / Numba函数。如果可以用C或Numba代码表示映射功能,则可以大大提高速度。 geometric_transform docs告诉您所需的函数签名。这是一个简单的2D转换示例(示例:Kira Evans):

在Cython中:

#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

然后,假设您已在Python中将Cython模块导入为mapping

# 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,根据您的用例,它可能或多或少具有吸引力。有关更多信息,请参见hereherehere