【问题标题】:Numpy filter to smooth out zero-regions用于平滑零区域的 Numpy 过滤器
【发布时间】:2018-02-17 23:11:34
【问题描述】:

我有一个 0 或更大整数的 2D numpy 数组,其中的值表示区域标签。例如,

array([[9, 9, 9, 0, 0, 0, 0, 1, 1, 1],
       [9, 9, 9, 9, 0, 7, 1, 1, 1, 1],
       [9, 9, 9, 9, 0, 2, 2, 1, 1, 1],
       [9, 9, 9, 8, 0, 2, 2, 1, 1, 1],
       [9, 9, 9, 8, 0, 2, 2, 2, 1, 1],
       [4, 4, 4, 4, 0, 2, 2, 2, 1, 1],
       [4, 6, 6, 4, 0, 0, 0, 0, 0, 0],
       [4, 6, 6, 4, 0, 0, 0, 0, 0, 0],
       [4, 4, 4, 4, 5, 5, 5, 5, 5, 5],
       [4, 4, 4, 4, 5, 5, 5, 5, 5, 5]])

我希望等于 0 的索引(即零区域)采用其邻域中最常见的值。该操作将基本上关闭零区域。我尝试了膨胀、腐蚀、grey-closing 和其他morphology operations 的多种变体,但我无法完全消除零区域(不会笨拙地混合其他区域)。一个不错的方法可能是定义一个仅在零上卷积的内核,并使用过滤器区域中最常见的标签设置值。不过我不确定如何实现。

【问题讨论】:

  • 你愿意使用 Numba 吗?

标签: python numpy convolution mathematical-morphology


【解决方案1】:

基于卷积思想的可能解决方案

from scipy import stats
ar = #Your np array
blank = np.zeros(ar.shape)
#Size to search in for mode values
window_size = 3

for x,y in np.array(np.where(ar == 0)).T:
    window = ar[max(x-window_size,0):x+window_size,max(0,y-window_size):y+window_size]
    oneD = window.flatten()

    #fill blank array with modal value
    blank[x,y] = stats.mode(oneD[oneD != 0])[0]

#fill in the zeros
print ar + blank

我不确定这里是否可以避免循环

【讨论】:

  • zip(np.where(ar == 0)[0],np.where(ar == 0)[1]) 可以简单地为np.array(np.where(ar == 0)).T
【解决方案2】:

这是一个使用 Numba 的有效解决方案,我没有对其进行分析,但应该很快:

import numba
@numba.njit
def nn(arr):
    res = arr.copy()
    zeros = np.where(arr == 0)
    for n in range(len(zeros[0])):
        i = zeros[0][n]
        j = zeros[1][n]
        left = max(i-1, 0)
        right = min(i+2, arr.shape[1])
        top = max(j-1, 0)
        bottom = min(j+2, arr.shape[0])
        area = arr[left:right,top:bottom].ravel()
        counts = np.bincount(area[area != 0])
        res[i,j] = np.argmax(counts)
    return res

它产生:

array([[9, 9, 9, 9, 7, 1, 1, 1, 1, 1],
       [9, 9, 9, 9, 9, 7, 1, 1, 1, 1],
       [9, 9, 9, 9, 2, 2, 2, 1, 1, 1],
       [9, 9, 9, 8, 2, 2, 2, 1, 1, 1],
       [9, 9, 9, 8, 2, 2, 2, 2, 1, 1],
       [4, 4, 4, 4, 2, 2, 2, 2, 1, 1],
       [4, 6, 6, 4, 4, 2, 2, 2, 1, 1],
       [4, 6, 6, 4, 4, 5, 5, 5, 5, 5],
       [4, 4, 4, 4, 5, 5, 5, 5, 5, 5],
       [4, 4, 4, 4, 5, 5, 5, 5, 5, 5]])

这里的内核大小是 3x3,定义为在 ij 中减去 1 并加 2(加 2 是因为 Python 切片需要一个过去的结尾,例如 [0:3] 给你 3元素)。边界条件由minmax 处理。

bincount 想法的功劳:https://stackoverflow.com/a/6252400/4323

【讨论】:

    【解决方案3】:

    这里提出了一种矢量化方法。步骤是:

    1. 获取内核大小的 2D 滑动窗口,生成 4D 数组。我们可以用 skimage's view_as_windows 将它们作为视图,从而避免创建 任何额外的内存。

    2. 通过索引到 4D 数组来选择以零为中心的窗口。这会强制复制。但是假设零的数量比输入数组中元素的总数相对较小,这应该没问题。

    3. 对于每个选定的窗口,用适当的偏移量偏移每个窗口,以便使用np.bincount 执行计数。因此,使用bincount 并获得不包括零的最大计数。最大计数的 argmax 应该是我们的人!

    这是涵盖这些步骤的实现 -

    from skimage.util import view_as_windows as viewW
    
    def fill_zero_regions(a, kernel_size=3):
        hk = kernel_size//2 # half_kernel_size    
    
        a4D = viewW(a, (kernel_size,kernel_size))
        sliced_a = a[hk:-hk,hk:-hk]
        zeros_mask = sliced_a==0
        zero_neighs = a4D[zeros_mask].reshape(-1,kernel_size**2)
        n = len(zero_neighs) # num_zeros
    
        scale = zero_neighs.max()+1
        zno = zero_neighs + scale*np.arange(n)[:,None] # zero_neighs_offsetted
    
        count = np.bincount(zno.ravel(), minlength=n*scale).reshape(n,-1)
        modevals = count[:,1:].argmax(1)+1
        sliced_a[zeros_mask] = modevals
        return a
    

    示例运行 -

    In [23]: a
    Out[23]: 
    array([[9, 9, 9, 0, 0, 0, 0, 1, 1, 1],
           [9, 9, 9, 9, 0, 7, 1, 1, 1, 1],
           [9, 9, 9, 9, 0, 2, 2, 1, 1, 1],
           [9, 9, 9, 8, 0, 2, 2, 1, 1, 1],
           [9, 9, 9, 8, 0, 2, 2, 2, 1, 1],
           [4, 4, 4, 4, 0, 2, 2, 2, 1, 1],
           [4, 6, 6, 4, 0, 0, 0, 0, 0, 0],
           [4, 6, 6, 4, 0, 0, 0, 0, 0, 0],
           [4, 4, 4, 4, 5, 5, 5, 5, 5, 5],
           [4, 4, 4, 4, 5, 5, 5, 5, 5, 5]])
    
    In [24]: fill_zero_regions(a)
    Out[24]: 
    array([[9, 9, 9, 0, 0, 0, 0, 1, 1, 1],
           [9, 9, 9, 9, 9, 7, 1, 1, 1, 1],
           [9, 9, 9, 9, 2, 2, 2, 1, 1, 1],
           [9, 9, 9, 8, 2, 2, 2, 1, 1, 1],
           [9, 9, 9, 8, 2, 2, 2, 2, 1, 1],
           [4, 4, 4, 4, 2, 2, 2, 2, 1, 1],
           [4, 6, 6, 4, 4, 2, 2, 2, 1, 0],
           [4, 6, 6, 4, 4, 5, 5, 5, 5, 0],
           [4, 4, 4, 4, 5, 5, 5, 5, 5, 5],
           [4, 4, 4, 4, 5, 5, 5, 5, 5, 5]])
    

    正如所见,我们并没有解决边界情况。如果需要,请使用零填充数组作为输入数组,如下所示:np.pad(a, (k//2,k//2), 'constant')k 作为内核大小(=3 用于示例)。

    【讨论】:

    • 很好的答案,谢谢! +1 用于解释内存使用情况。
    猜你喜欢
    • 2015-04-16
    • 1970-01-01
    • 2017-10-16
    • 2017-02-08
    • 2019-04-22
    • 1970-01-01
    • 2023-03-24
    • 1970-01-01
    相关资源
    最近更新 更多