在2D numpy数组中的每个NXN子数组上执行计算的最快方法

时间:2018-12-19 15:11:35

标签: python python-3.x numpy vectorization

我有一个二维的numpy数组,代表一个灰度图像。我需要提取该数组中的每个N x N子数组,并在子数组之间指定重叠,然后计算诸如均值,标准差或中位数的属性。

下面的代码执行此任务,但速度很慢,因为它使用Python进行循环。关于如何向量化此计算或以其他方式加快计算的任何想法?

import numpy as np

img = np.random.randn(100, 100)
N = 4
step = 2

h, w = img.shape
out = []
for i in range(0, h - N, step):
    outr = []
    for j in range(0, w - N, step):
        outr.append(np.mean(img[i:i+N, j:j+N]))
    out.append(outr)
out = np.array(out)

4 个答案:

答案 0 :(得分:2)

对于均值和标准差,有一个基于cumsum的快速解决方案。

以下是500x200图像,30x20窗口以及5和3步长的计时。为比较起见,我将skimage.util.view_as_windows与numpy平均值和std一起使用。

mn + sd using cumsum     1.1531693299184553 ms
mn using view_as_windows 3.495307120028883 ms
sd using view_as_windows 21.855629019846674 ms

代码:

import numpy as np
from math import gcd
from timeit import timeit

def wsum2d(A, winsz, stepsz, canoverwriteA=False):
    M, N = A.shape
    m, n = winsz
    i, j = stepsz
    for X, x, s in ((M, m, i), (N, n, j)):
        g = gcd(x, s)
        if g > 1:
            X //= g
            x //= g
            s //= g
            A = A[:X*g].reshape(X, g, -1).sum(axis=1)
        elif not canoverwriteA:
            A = A.copy()
        canoverwriteA = True
        A[x:] -= A[:-x]
        A = A.cumsum(axis=0)[x-1::s]
        A = A.T
    return A

def w2dmnsd(A, winsz, stepsz):
    # combine A and A*A into a complex, so overheads apply only once
    M21 = wsum2d(A*(A+1j), winsz, stepsz, True)
    M2, mean_ = M21.real / np.prod(winsz), M21.imag / np.prod(winsz)
    sd = np.sqrt(M2 - mean_*mean_)
    return mean_, sd

# test
np.random.seed(0)
A = np.random.random((500, 200))
wsz = (30, 20)
stpsz = (5, 3)
mn, sd = w2dmnsd(A, wsz, stpsz)
from skimage.util import view_as_windows
Av = view_as_windows(A, wsz, stpsz) # this emits a warning on my system
assert np.allclose(mn, np.mean(Av, axis=(2, 3)))
assert np.allclose(sd, np.std(Av, axis=(2, 3)))
from timeit import repeat

print('mn + sd using cumsum    ', min(repeat(lambda: w2dmnsd(A, wsz, stpsz), number=100))*10, 'ms')
print('mn using view_as_windows', min(repeat(lambda: np.mean(Av, axis=(2, 3)), number=100))*10, 'ms')
print('sd using view_as_windows', min(repeat(lambda: np.std(Av, axis=(2, 3)), number=100))*10, 'ms')

答案 1 :(得分:1)

您可以使用scikit-image block_reduce

因此您的代码变为:

import numpy as np
import skimage.measure

N = 4

# Your main array
a = np.arange(9).reshape(3,3)

mean = skimage.measure.block_reduce(a, (N,N), np.mean) 
std_dev = skimage.measure.block_reduce(a, (N,N), np.std)
median = skimage.measure.block_reduce(a, (N,N), np.median)

但是,以上代码仅适用于大小为1的步幅/步幅。

对于平均值,您可以使用平均值池,该池在任何现代ML软件包中都可用。至于中位数和标准差,这似乎是正确的方法。

答案 2 :(得分:1)

一般情况下,可以使用scipy.ndimage.generic_filter解决:

import numpy as np

from scipy.ndimage import generic_filter

img = np.random.randn(100, 100)

N = 4
filtered = generic_filter(img.astype(np.float), np.std, size=N)

step = 2
output = filtered[::step, ::step]

但是,这实际上可能不会比简单的for循环快多少。

要应用均值和中值滤波器,可以分别使用skimage.rank.meanskimage.rank.median,它们应该更快。还有scipy.ndimage.median_filter。否则,均值也可以通过简单的卷积与(N,N)数组以值1./N^2进行计算。对于标准差,除非步长大于或等于N,否则您可能必须咬一下子弹并使用generic_filter

答案 3 :(得分:1)

如果Numba是一个选项,唯一要做的就是避免列表附加(它也可以使用列表附加,但是slower。 为了也使用并行化,请重新编写一些实现以避免步长在范围内,这在使用parfor时不支持。

示例

@nb.njit(error_model='numpy',parallel=True)
def calc_p(img,N,step):
  h,w=img.shape

  i_w=(h - N)//step
  j_w=(w - N)//step
  out = np.empty((i_w,j_w))
  for i in nb.prange(0, i_w):
      for j in range(0, j_w):
          out[i,j]=np.std(img[i*step:i*step+N, j*step:j*step+N])
  return out

def calc_n(img,N,step):
  h, w = img.shape
  out = []
  for i in range(0, h - N, step):
      outr = []
      for j in range(0, w - N, step):
          outr.append(np.std(img[i:i+N, j:j+N]))
      out.append(outr)
  return(np.array(out))

时间

所有时序都没有大约0.5s的编译开销(时序中不包括对函数的第一次调用)。

#Data
img = np.random.randn(100, 100)
N = 4
step = 2

calc_n :17ms
calc_p :0.033ms

因为这实际上是滚动的,所以如果N变大,则还有进一步的改进空间。