高效的复杂蒙版移动窗口分析

时间:2018-10-03 09:33:05

标签: python arrays moving-average sliding-window

我正在用Python开发一种移动窗口算法,该算法将用于在大型numpy数组上滑动(例如,我的测试数组尺寸为6349x9849)。我需要在25 x 25的移动窗口(每个像素位置由9x9窗口遮盖)中计算许多不同的统计信息。

有一个警告也使我停止使用卷积运算(例如,在移动窗口中计算平均值的速度非常快):如果移动窗口中的中心像素为0,则将统计值设置为-9999作为标志,或者如果25x25移动窗口包含超过一半的0值,则将统计值设置为-1作为标志。然后,我可以稍后处理这些标志。

我已经编写了Python代码来完成这项工作,但是这是我第一次学习Python,因为我处理了大量的数据,因此遇到效率问题-使用我的代码需要很长时间已经写过了(每个统计数据大约要花6个小时来讨论这个图片大小!!)。

我想问我如何更有效地执行此操作的任何建议。我想先优化代码,然后再投入更多的计算能力(我相信我可以使用多处理模块来执行此操作)。

我的代码如下,其中一个示例为统计量(标准差)。我为每个统计数据重复此行(我有6个统计数据要计算):

# Calculate the standard deviation of the masked moving window
stats_std = [-9999 if ds_array[row,column] == 0
else -1 if np.count_nonzero(ds_array[row-border_buff:row+border_buff+1,column-border_buff:column+border_buff+1]) < (outer_box**2)/2
else np.std([i for i in np.ma.compressed(np.ma.masked_array(ds_array[row-border_buff:row+border_buff+1,column-border_buff:column+border_buff+1],mask)) if i!=0])
for row in range(border_buff,m-border_buff)
for column in range(border_buff,n-border_buff)]

# Reshape the list into the image dimensions
stats_std = np.reshape(stats_std,(m-2*border_buff,n-2*border_buff))

如果需要,我可以提供样本子集数组和所需的输出以进行试用,但是不确定提供此方法的最佳方法,请让我知道是否需要使以上内容更清楚。

P.s。我尝试了一种方法,将2d数组转换为该数组的所有25x25子集的3d,然后每个子集一步有效地计算6个统计数据,以期节省大量计算。.但这导致了MemoryError。

1 个答案:

答案 0 :(得分:0)

pyvips使您可以在大图像efficiently and using very little memory上计算复杂的事物。它是LGPL,可以在Linux,macOS和Windows上运行,并且可以在每个版本的Python上运行。大多数Linux在软件包管理器中都有它。

当您在pyvips中执行类似a + b的操作时,它实际上并未进行任何处理,只是将另一个节点添加到图像处理操作图中。当您最终将结果写到某处时,整个图形都会求值,并且它将图像分成一组小块并并行地流过您的系统。

因为中间图像实际上并不存在,所以您只需要少量的内存,并且因为它是并行的,所以速度很快。

例如,您可以像这样进行sdev计算:

import sys
import pyvips

# load the input image ... the access hint means we will only make a single
# top-to-bottom pass over image, and it can therefore be streamed 
image = pyvips.Image.new_from_file(sys.argv[1], access='sequential')

# our convolution ... total pixels in an M x M window
# it's a simple box filter, so we can use a seperable convolution 
# (two 1D filters at 90 degrees)
window_size = 25
size = window_size * window_size
sum_mask = pyvips.Image.new_from_array([1] * window_size)

# standard deviation ... sum and sum of squares
s = image.convsep(sum_mask)
s2 = (image * image).convsep(sum_mask)
sdev = ((s2 - (s * s / size)).abs() / (size - 1)) ** 0.5

# find all zero input pixels ... these become -9999 in the output
sdev = (image == 0).ifthenelse(-9999, sdev)

# find all pixels where more than half of the window is zero ... these become
# -1 in the output
# pyvips uses 255 for TRUE and 0 for FALSE
more_than_half_zero = (image == 0).convsep(sum_mask) > 255 * size / 2
sdev = more_than_half_zero.ifthenelse(-1, sdev)

sdev.write_to_file(sys.argv[2])

我可以这样运行它:

$ vipsheader x.jpg 
x.jpg: 10000x10000 uchar, 1 band, b-w, jpegload
$ /usr/bin/time -f %M:%e python3 sdev.py x.jpg x.pfm
81432:4.11

要从10,000 x 10,000像素jpg图像制作10,000 x 10,000像素的PFM(一种可以存储浮点值的简单格式),其中PFM中的每个像素都是对应的25 x 25窗口的sdev为零。在此台式机上花费4s,最大内存为81mb。

我仅使用一个线程就可以减少内存使用量,但是当然要慢很多:

$ VIPS_CONCURRENCY=1 /usr/bin/time -f %M:%e python3 sdev.py x.jpg x.pfm
54128:16.92

现在只有54mb的内存,但是只有17s的时间。

您还可以在numpy数组中读写图像,请参见chapter in the docs