包含 2D NumPy 数组中每个值的邻居的 3D NumPy 数组?

3D NumPy array which contains the neighbors of every value in a 2D NumPy array?

我想构建一个形状为 (HEIGHT, WIDTH, 3) 的 NumPy 数组,其中 HEIGHTWIDTH 对应于存储在标准 NumPy 数组中的图像的形状,其中每个 (i, j) 位置有该位置的中间邻居(在某个方向)。例如,如果 A = [[0, 1, 2, 3, 4],[5, 6, 7, 8, 9]] 并且我希望 (1, 2) 的邻居位于北边,我会得到结果 [1, 2, 3]。所以,我的结果矩阵应该在其各自的 (1, 2).

处有 [1, 2, 3] = A[1:4]

现在,我尝试了一种简单的方法,我不使用这样的矩阵,而是遍历数组中的所有值并相应地对其进行切片以获得所需的邻居。尽管如此,如果我可以事先计算出该矩阵,我之后使用的算法可以被矢量化(我没有将这部分包含在问题中,因为它与我的问题无关),这几乎总是以更多的内存使用为代价更快.

    scales = 3
    padded_img = np.pad(img, scales, mode='constant')
    feature_vectors = np.zeros((img.shape[0]*img.shape[1], 4*scales))
    z = 0
    for i in range(scales, padded_img.shape[0] - scales):
        for j in range(scales, padded_img.shape[1] - scales):
            for scale in range(1, scales + 1):
                N = padded_img[i - scale, j - scale: j + scale + 1]
                E = padded_img[i - scale: i + scale + 1, j + scale]
                S = padded_img[i + scale, j - scale: j + scale + 1]
                W = padded_img[i - scale: i + scale + 1, j - scale]

                neighbors = np.vstack((N, E, S, W))
                avgs = np.mean(neighbors, axis=1)
                           
                feature_vectors[z, 4*(scale-1):4*scale] = avgs.flatten()
            z += 1

img 是我原来的 NumPy 数组;我垫它以避免角落出现问题。另一方面,我使用 scales 因为我基本上不仅需要中间邻居,还需要距离某个位置 1 到 scales 的邻居。因为我也对所有可能的方向感兴趣,所以我使用 N, E, S, W 作为循环内邻居的向量。最重要的是,这个想法是为了降低这个算法的时间复杂度。有任何想法吗?谢谢。

编辑:在每次迭代获得这 4 个向量后,我计算它们的平均值,将它们展平并将它们附加到一个特征向量,其行包含所有方向的所有信息4 个秤。

您的代码没有 运行(pad 填充 RGB 维度和 avgs 的形状存在问题,不允许分配给 feature_vectors),所以我无法确定这是否正是您想要的,但这应该可以帮助您入门:

import numpy as np
from scipy.ndimage import convolve

scales = 3

img = np.random.rand(256, 256, 3)

feature_vectors = np.zeros((scales, 4) + img.shape[:-1])

for n in range(scales):
    dim = 2 * n + 3
    orig = np.zeros((dim, dim, 3), dtype = float)
    orig[:, 0, :] = 1 / (dim * 3)
    kernel = np.array([np.rot90(orig, i, (0, 1)) for i in range(4)])
    feature_vectors[n] = convolve( img[None, ...], 
                                   kernel,
                                   mode = 'constant')[..., 1]
    
feature_vectors = feature_vectors.transpose(2, 3, 1, 0).reshape(-1, scales * 4)

feature_vectors.shape
Out[]: (65536, 12)

我把这个贴在这里是为了让可能遇到这个问题的人有一个全面的了解。我在为论文 Segmentation of structural features in cheese micrographs using pixel statistics[=54] 中解释的多尺度特征提取创建 Python 实现时遇到了这个问题=] G. Impoco、L. Tuminello、N. Fucà、M. Caccamo 和 G. Licitra。您可以在 ScienceDirect.

中查看

我首先创建了一个天真的方法,它包含一个不太优雅的三重 for 循环,它是这样的(发布在我的原始问题中):

import numpy as np

padded_img = np.pad(img, scales, mode="constant")
for i in range(scales, padded_img.shape[0] - scales):
    for j in range(scales, padded_img.shape[1] - scales):
        for scale in range(1, scales + 1):
            N = padded_img[i - scale, j - scale : j + scale + 1]
            E = padded_img[i - scale : i + scale + 1, j + scale]
            S = padded_img[i + scale, j - scale : j + scale + 1]
            W = padded_img[i - scale : i + scale + 1, j - scale]

此方法通过使用一个变量 scale 来获取所需的邻域信息,该变量将遍历预定义的 window 半径。这个变量允许我访问 (i, j) 的相邻像素。然而,正如@Daniel F 指出的那样,这可以像这样进行卷积并变得更简单:

import scipy.ndimage

directions = 4
for scale in range(1, scales + 1):
    filter_size = 2 * (scale - 1) + 3
    orig = np.zeros((filter_size, filter_size), dtype=np.float64)
    orig[:, 0] = 1 / filter_size
    for c in range(directions):
        # c = 0 -> North; c = 1 -> East; c = 2 -> South; c = 3 -> West
        correlation_filter = np.rot90(orig, 3 - c, (0, 1))
        convolution_filter = np.flip(correlation_filter)

        directions_img = scipy.ndimage.convolve(img, convolution_filter, cval=0.0, mode="constant")

因此,不是遍历每个像素并多次计算我想要的内容,而是遍历想要的 directionsNEW S 在我天真的方法中)并计算每个像素所需的一切,而不需要实际迭代每个像素。

当然,性能上的差异是令人兴奋的。我用 timeit 模块 (mean ± std. dev. of 7 runs, 1 loop each) 测试了两个版本,并得到了以下带有形状 (630, 630)scales=1:

图像的基准
Naïve approach: 46.4 ± 1.65 s per loop 
Convolution approach: 204 ± 11.7 ms per loop

不同图像尺寸与scales=1

的比较

不同图像尺寸的卷积方法scales=3

代码

您可以在 this GitHub gist 中查看我的两个版本的代码。请记住,这段代码是专门为灰度图像编写的。