【问题标题】:3D NumPy array which contains the neighbors of every value in a 2D NumPy array?3D NumPy 数组包含 2D NumPy 数组中每个值的邻居?
【发布时间】:2020-11-09 07:30:12
【问题描述】:

我想构建一个形状为 (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 个尺度上所有方向的信息。 p>

【问题讨论】:

  • 大量降低复杂性将取决于您之后对N, E, S, W 向量的处理。看起来像是蛮力卷积算法的开始,但在不了解后续操作的情况下,我们无法建议使用卷积核与 scipy.signal.convolve2d 一起替换它。
  • 或者更确切地说是scipy.ndimage.convolve或者一些自制的as_strided解决方案,如果你只能使用numpy
  • 我编辑了我的答案,以包括我之后对这些向量所做的事情。有没有这样的内核?
  • 。 . .啊。是的,这可以卷积但是。 . .这可能需要一些修补。开始工作,希望问题处于其他人现在可以提供帮助的状态。

标签: python arrays numpy time-complexity convolution


【解决方案1】:

您的代码无法运行(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)

【讨论】:

  • 谢谢。你是对的,我的代码不适用于 RGB 图像;我忘了提到我正在处理灰色图像,所以我只有一个通道而不是三个。另一方面,你能解释一下 for 循环内部发生了什么吗?
【解决方案2】:

我在此处发布此内容,以便可能遇到此问题的人可以全面了解。我在创建用于提取多尺度特征的 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 获得所需的邻域信息,该变量将在预定义的窗口半径上进行迭代。这个变量允许我访问(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")

因此,不是迭代每个像素并多次计算我想要的东西,而是迭代想要的directionsNEWS 在我的幼稚方法中)和计算每个像素所需的一切,而不需要实际迭代每个像素。

当然,性能上的差异是令人兴奋的。我用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 中查看我的两个版本的代码。请记住,此代码是专门为灰色图像编写的。

【讨论】:

  • 如果您要扩展我的答案,通常最好将您的改进编辑到其中。我很高兴接受这样的编辑。抱歉,我没能就 RGB 问题回复您,过去几个月我的 SO 时间非常有限。
  • 对不起,如果这看起来很粗鲁。我实际上不知道您是否仍然活跃,或者是否有人会接受编辑,所以我想我会发布另一个答案并在那里绘制整个画面。另外,由于我的案例实际上是关于灰色图像,而您的答案涵盖了 RGB 图像,因此编辑不会提供更多,但会采取一些措施。您为我指明了正确的方向,我发现 np.rot90 有点整洁。我什至不知道这个函数的存在。
  • 不,没关系,仅供参考。
猜你喜欢
  • 1970-01-01
  • 2017-03-24
  • 1970-01-01
  • 2012-12-09
  • 1970-01-01
  • 2019-07-14
  • 2021-12-29
相关资源
最近更新 更多