我有一个二维的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)
答案 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.mean
和skimage.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
变大,则还有进一步的改进空间。