为什么平均邻居如此慢?

why is averaging neighbours so incredible slow?

任务是在 3X3 window.

中根据邻居的值对图像像素进行平均

该图像是 2.5 MB 的标准图像。

为了避免边缘情况,我给它们一个值 -1 并过滤掉它们。

当 运行 程序在 624 秒后终止。为什么这么慢?

该程序看起来非常简约,但我确定我错过了一些东西。

import scipy
import numpy as np
import scipy.misc
import scipy.ndimage
import timeit




def average_neighbours(mat):

    interesting = mat[mat>=0]
    return np.average(interesting)


def run_program():
    vienna = scipy.misc.imread('kaertnerstrasse.jpg')

    vienna1 = scipy.ndimage.filters.generic_filter(vienna, function=average_neighbours, size=(3,3,1),mode="constant",cval=-1.0)

    scipy.misc.imsave('kaertnerstrasse3-3.jpg', vienna1)

if __name__ == "__main__":
    start = timeit.default_timer()

    run_program()

    stop = timeit.default_timer()

    print stop - start 

这是探查器数据。看起来我想做的标准操作需要很多时间。有没有可能做得更快?

      479086307 function calls (479086303 primitive calls) in 739.517 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.004    0.004  739.517  739.517 <string>:1(<module>)
        1    0.000    0.000    0.000    0.000 BmpImagePlugin.py:173(DibImageFile)
        1    0.001    0.001    0.001    0.001 BmpImagePlugin.py:27(<module>)
        1    0.000    0.000    0.000    0.000 BmpImagePlugin.py:55(_accept)
        1    0.000    0.000    0.000    0.000 BmpImagePlugin.py:61(BmpImageFile)
        1    0.006    0.006    0.006    0.006 GifImagePlugin.py:28(<module>)
        1    0.000    0.000    0.000    0.000 GifImagePlugin.py:47(_accept)
        1    0.000    0.000    0.000    0.000 GifImagePlugin.py:54(GifImageFile)
        1    0.000    0.000    0.268    0.268 Image.py:1394(save)
        1    0.000    0.000    0.014    0.014 Image.py:1750(new)
        1    0.000    0.000    0.029    0.029 Image.py:1786(fromstring)
        1    0.000    0.000    0.011    0.011 Image.py:1943(open)
        5    0.000    0.000    0.000    0.000 Image.py:2082(register_open)
        3    0.000    0.000    0.000    0.000 Image.py:2094(register_mime)
        5    0.000    0.000    0.000    0.000 Image.py:2104(register_save)
       10    0.000    0.000    0.000    0.000 Image.py:2114(register_extension)
        1    0.000    0.000    0.000    0.000 Image.py:219(_conv_type_shape)
        2    0.003    0.001    0.011    0.005 Image.py:290(preinit)
        2    0.000    0.000    0.000    0.000 Image.py:371(_getdecoder)
        2    0.000    0.000    0.000    0.000 Image.py:387(_getencoder)
        3    0.000    0.000    0.000    0.000 Image.py:449(__init__)
        1    0.000    0.000    0.000    0.000 Image.py:460(_new)
      6/2    0.003    0.000    0.224    0.112 Image.py:503(__getattr__)
        1    0.000    0.000    0.221    0.221 Image.py:522(tostring)
        1    0.000    0.000    0.015    0.015 Image.py:577(fromstring)
        4    0.000    0.000    0.000    0.000 Image.py:606(load)
        5    0.000    0.000    0.000    0.000 Image.py:83(isStringType)
        5    0.000    0.000    0.000    0.000 Image.py:92(isTupleType)
        1    0.000    0.000    0.000    0.000 Image.py:98(isImageType)
        1    0.000    0.000    0.183    0.183 ImageFile.py:124(load)
        1    0.000    0.000    0.004    0.004 ImageFile.py:227(load_prepare)
        1    0.000    0.000    0.000    0.000 ImageFile.py:236(load_end)
        1    0.000    0.000    0.000    0.000 ImageFile.py:254(StubImageFile)
        1    0.000    0.000    0.000    0.000 ImageFile.py:283(_ParserFile)
        1    0.000    0.000    0.000    0.000 ImageFile.py:30(<module>)
        1    0.000    0.000    0.000    0.000 ImageFile.py:330(Parser)
        1    0.000    0.000    0.266    0.266 ImageFile.py:466(_save)
        9    0.000    0.000    0.000    0.000 ImageFile.py:516(_safe_read)
        1    0.000    0.000    0.000    0.000 ImageFile.py:69(ImageFile)
        1    0.000    0.000    0.000    0.000 ImageFile.py:72(__init__)
        1    0.000    0.000    0.000    0.000 JpegImagePlugin.py:121(SOF)
        2    0.000    0.000    0.000    0.000 JpegImagePlugin.py:168(DQT)
        1    0.000    0.000    0.000    0.000 JpegImagePlugin.py:261(_accept)
        1    0.000    0.000    0.000    0.000 JpegImagePlugin.py:267(JpegImageFile)
        1    0.000    0.000    0.000    0.000 JpegImagePlugin.py:272(_open)
        1    0.000    0.000    0.000    0.000 JpegImagePlugin.py:35(<module>)
       24    0.000    0.000    0.000    0.000 JpegImagePlugin.py:41(i16)
        1    0.000    0.000    0.266    0.266 JpegImagePlugin.py:420(_save)
        5    0.000    0.000    0.000    0.000 JpegImagePlugin.py:50(Skip)
        1    0.000    0.000    0.000    0.000 JpegImagePlugin.py:54(APP)
        1    0.000    0.000    0.000    0.000 PngImagePlugin.py:151(PngInfo)
        1    0.000    0.000    0.000    0.000 PngImagePlugin.py:169(PngStream)
        1    0.000    0.000    0.000    0.000 PngImagePlugin.py:308(PngImageFile)
        1    0.001    0.001    0.001    0.001 PngImagePlugin.py:34(<module>)
        1    0.000    0.000    0.000    0.000 PngImagePlugin.py:453(_idat)
        1    0.000    0.000    0.000    0.000 PngImagePlugin.py:75(ChunkStream)
        1    0.000    0.000    0.000    0.000 PpmImagePlugin.py:18(<module>)
        1    0.000    0.000    0.000    0.000 PpmImagePlugin.py:46(PpmImageFile)
 29942784   64.011    0.000   81.737    0.000 _methods.py:43(_count_reduce_items)
 29942784  126.522    0.000  398.092    0.000 _methods.py:53(_mean)
        1    0.000    0.000    0.000    0.000 _ni_support.py:38(_extend_mode_to_code)
        2    0.000    0.000    0.000    0.000 _ni_support.py:55(_normalize_sequence)
        1    0.000    0.000    0.000    0.000 _ni_support.py:70(_get_output)
 29942784  151.188    0.000  701.104    0.000 ex1.py:12(average_neighbours)
        1    0.007    0.007  739.513  739.513 ex1.py:18(run_program)
        1    0.000    0.000  738.947  738.947 filters.py:1115(generic_filter)
 29942784   67.220    0.000  549.916    0.000 function_base.py:436(average)
        1    0.000    0.000    0.000    0.000 genericpath.py:85(_splitext)
        1    0.000    0.000    0.000    0.000 ntpath.py:161(splitext)
        1    0.000    0.000    0.000    0.000 numeric.py:141(ones)
 29942789   19.069    0.000   50.684    0.000 numeric.py:394(asarray)
 29942784   14.466    0.000   37.300    0.000 numeric.py:464(asanyarray)
        1    0.000    0.000    0.000    0.000 numeric.py:773(flatnonzero)
        1    0.000    0.000    0.247    0.247 pilutil.py:103(imread)
        1    0.002    0.002    0.313    0.313 pilutil.py:130(imsave)
        1    0.000    0.000    0.236    0.236 pilutil.py:174(fromimage)
        1    0.000    0.000    0.042    0.042 pilutil.py:206(toimage)
        1    0.000    0.000    0.000    0.000 pilutil.py:34(bytescale)
        1    0.000    0.000    0.000    0.000 re.py:188(compile)
        1    0.000    0.000    0.000    0.000 re.py:226(_compile)
        5    0.000    0.000    0.000    0.000 sre_compile.py:178(_compile_charset)
        5    0.000    0.000    0.000    0.000 sre_compile.py:207(_optimize_charset)
        1    0.000    0.000    0.000    0.000 sre_compile.py:32(_compile)
        1    0.000    0.000    0.000    0.000 sre_compile.py:359(_compile_info)
        2    0.000    0.000    0.000    0.000 sre_compile.py:472(isstring)
        1    0.000    0.000    0.000    0.000 sre_compile.py:478(_code)
        1    0.000    0.000    0.000    0.000 sre_compile.py:493(compile)
        4    0.000    0.000    0.000    0.000 sre_parse.py:138(append)
        1    0.000    0.000    0.000    0.000 sre_parse.py:140(getwidth)
        1    0.000    0.000    0.000    0.000 sre_parse.py:178(__init__)
        7    0.000    0.000    0.000    0.000 sre_parse.py:182(__next)
        1    0.000    0.000    0.000    0.000 sre_parse.py:195(match)
        6    0.000    0.000    0.000    0.000 sre_parse.py:201(get)
        4    0.000    0.000    0.000    0.000 sre_parse.py:257(_escape)
        1    0.000    0.000    0.000    0.000 sre_parse.py:301(_parse_sub)
        1    0.000    0.000    0.000    0.000 sre_parse.py:379(_parse)
        1    0.000    0.000    0.000    0.000 sre_parse.py:67(__init__)
        1    0.000    0.000    0.000    0.000 sre_parse.py:675(parse)
        1    0.000    0.000    0.000    0.000 sre_parse.py:90(__init__)
       11    0.000    0.000    0.000    0.000 string.py:220(lower)
       24    0.000    0.000    0.000    0.000 string.py:229(upper)
        1    0.000    0.000    0.012    0.012 string.py:308(join)
        2    0.000    0.000    0.000    0.000 type_check.py:237(iscomplexobj)
        1    0.014    0.014    0.014    0.014 {PIL._imaging.fill}
        1    0.004    0.004    0.004    0.004 {PIL._imaging.new}
        1    0.000    0.000    0.000    0.000 {_sre.compile}
        4    0.000    0.000    0.000    0.000 {apply}
       41    0.193    0.005    0.193    0.005 {built-in method decode}
        1    0.266    0.266    0.266    0.266 {built-in method encode_to_file}
      548    0.025    0.000    0.025    0.000 {built-in method encode}
        3    0.000    0.000    0.000    0.000 {built-in method pixel_access}
        2    0.000    0.000    0.000    0.000 {built-in method setimage}
       26    0.000    0.000    0.000    0.000 {chr}
        1    0.000    0.000    0.000    0.000 {divmod}
        4    0.000    0.000    0.000    0.000 {getattr}
 29942787    9.386    0.000    9.386    0.000 {hasattr}
 89828369   34.448    0.000   34.448    0.000 {isinstance}
 29942786   12.578    0.000   12.578    0.000 {issubclass}
       40    0.000    0.000    0.000    0.000 {len}
        3    0.000    0.000    0.000    0.000 {max}
      591    0.000    0.000    0.000    0.000 {method 'append' of 'list' objects}
        1    0.001    0.001    0.001    0.001 {method 'close' of 'file' objects}
        1    0.000    0.000    0.000    0.000 {method 'copy' of 'dict' objects}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
        1    0.000    0.000    0.000    0.000 {method 'fileno' of 'file' objects}
        2    0.000    0.000    0.000    0.000 {method 'flush' of 'file' objects}
       11    0.000    0.000    0.000    0.000 {method 'get' of 'dict' objects}
       13    0.000    0.000    0.000    0.000 {method 'has_key' of 'dict' objects}
        1    0.000    0.000    0.000    0.000 {method 'items' of 'dict' objects}
        1    0.012    0.012    0.012    0.012 {method 'join' of 'str' objects}
       11    0.000    0.000    0.000    0.000 {method 'lower' of 'str' objects}
 29942784   19.872    0.000  417.964    0.000 {method 'mean' of 'numpy.ndarray' objects}
        1    0.000    0.000    0.000    0.000 {method 'nonzero' of 'numpy.ndarray' objects}
        1    0.000    0.000    0.000    0.000 {method 'ravel' of 'numpy.ndarray' objects}
       79    0.001    0.000    0.001    0.000 {method 'read' of 'file' objects}
 29942784  114.221    0.000  114.221    0.000 {method 'reduce' of 'numpy.ufunc' objects}
        3    0.000    0.000    0.000    0.000 {method 'rfind' of 'str' objects}
        2    0.000    0.000    0.000    0.000 {method 'seek' of 'file' objects}
        2    0.000    0.000    0.000    0.000 {method 'sort' of 'list' objects}
        1    0.013    0.013    0.013    0.013 {method 'tostring' of 'numpy.ndarray' objects}
       24    0.000    0.000    0.000    0.000 {method 'upper' of 'str' objects}
        2    0.000    0.000    0.000    0.000 {min}
 59885574   54.461    0.000   54.685    0.000 {numpy.core.multiarray.array}
        1    0.000    0.000    0.000    0.000 {numpy.core.multiarray.copyto}
        1    0.000    0.000    0.000    0.000 {numpy.core.multiarray.empty}
        1    0.000    0.000    0.000    0.000 {numpy.core.multiarray.zeros}
        2    0.001    0.000    0.001    0.000 {open}
       63    0.000    0.000    0.000    0.000 {ord}
 29942785   13.675    0.000   13.675    0.000 {range}
        1   37.843   37.843  738.947  738.947 {scipy.ndimage._nd_image.generic_filter}
        1    0.000    0.000    0.000    0.000 {zip}


739.529455192

function_base.py:436(平均)这一行很有趣。看来要花大部分时间了。

我会尝试用更通用且可能更适合的解决方案替换 generic_filter 方法。您基本上想要做的是与大小为 3x3x1 且值为 1/9 的内核进行卷积:

import numpy as np
import scipy
import scipy.misc
import scipy.ndimage
import scipy.signal
import timeit

def run_program():
    my_image = scipy.misc.imread('my_image.png')

    kernel = np.ones((3, 3, 1))
    kernel /= kernel.size

    my_image_smoothed = scipy.signal.fftconvolve(my_image, kernel, mode='valid')
    scipy.misc.imsave('my_image_3x3.png', my_image_smoothed)

if(__name__ == '__main__'):
    start = timeit.default_timer()

    run_program()

    print timeit.default_timer() - start 

[更新]

为了尊重原始图像大小,您可以对 fftconvolve 使用模式 'same' 而不是 'valid'。这将自动对您的图像进行零填充。但是,为了在图像边界处获得更好的结果,使用 numpy 的 pad 函数

推广的模式之一填充图像
numpy.pad(array, pad_width, mode=<'constant'|'edge'|'reflect'|'symmetric'|...>)

并在交替填充的图像上使用 fftconvolve 模式'valid'。

padding = [(shape // 2, shape // 2) for shape in kernel.shape]
my_padded_image = np.pad(my_image, shape, mode='edge')
my_image_smoothed = scipy.signal.fftconvolve(my_padded_image, kernel, mode='valid')

[/UPDATE]

我不确定它是否会 运行 快很多,但它至少应该补偿方法调用的开销。 在 4876x2278 像素的测试图像上,代码需要 ~40 秒(硬盘驱动器是 SSD)

此致