向量化模糊图像的Python函数

时间:2019-09-25 14:45:13

标签: python numpy image-processing vectorization

我已经使用纯Python编写了一个函数来模糊我给出的任何图像。现在,我想使用NumPy并删除所有for循环。

def blur_image(src, dst):
    (h, w, c) = src.shape

    for x in range(h-1):
        for y in range(w-1):
            for z in range(c):
                if x != 0 or y != 0:
                    dst[x, y, z] = (src[x, y, z]
                    + src[x-1, y, z]
                    + src[x+1, y, z]
                    + src[x, y-1, z]
                    + src[x, y+1, z]
                    + src[x-1, y-1, z]
                    + src[x-1, y+1, z]
                    + src[x+1, y-1, z]
                    + src[x+1, y+1, z]) / 9
                if x == 0:
                    dst[x, y, z] = (src[x, y, z]
                    + src[x, y, z]
                    + src[x+1, y, z]
                    + src[x, y-1, z]
                    + src[x, y+1, z]
                    + src[x, y-1, z]
                    + src[x, y+1, z]
                    + src[x+1, y-1, z]
                    + src[x+1, y+1, z]) / 9
                if y == 0:
                    dst[x, y, z] = (src[x, y, z]
                    + src[x-1, y, z]
                    + src[x+1, y, z]
                    + src[x, y, z]
                    + src[x, y+1, z]
                    + src[x-1, y, z]
                    + src[x-1, y+1, z]
                    + src[x+1, y, z]
                    + src[x+1, y+1, z]) / 9
    return dst

如何使用NumPy改进此代码?最好的情况是删除所有for循环吗?有提示吗?

编辑:我正试图在没有任何库的情况下解决此问题,以帮助我模糊图像。我使用纯python对其进行模糊处理,现在正在寻找提示,以便所有计算量大的位都使用numpy数组。没有使用numpy://

的丰富经验

2 个答案:

答案 0 :(得分:1)

如果我正确阅读了您的代码,则说明您正在使用3×3棚车过滤器进行过滤。如果您关心性能,那么使用现有库可能是个好主意,因为它比您可以轻松实现的任何东西都要优化得多。

例如,您正在做的事可以通过卷积来实现,卷积在scipy.signal中可用。因此,您可以为每个通道执行此操作,然后堆叠结果数组:

import numpy as np
import scipy.signal as ss

arr = np.random.random((100, 100))  # Some fake data.
kernel = np.ones((3, 3)) / 9  # The 'boxcar'.

ss.convolve(arr, kernel, mode='same')

PIL中也可以使用模糊处理:

import numpy as np
from PIL import Image, ImageFilter

arr = np.random.randint(0, 256, size=((100, 100, 3)), dtype=np.uint8)
img = Image.fromarray(arr)

img.filter(ImageFilter.BoxBlur(radius=3))

还有许多其他方法可以做到这一点,包括在傅立叶空间(one example)中做到这一点。

这些其他方法的一个不错的功能是它们不会将过滤器设计硬编码到代码中,因此尝试使用另一个内核(例如高斯模型)很容易。

答案 1 :(得分:0)

只需编译

我假设您想加快功能的速度。最简单而且可能也是最快的方法是使用像Cython或Numba这样的编译器。

问题同样令人尴尬地并行。

示例

import numpy as np
import numba as nb

@nb.njit(error_model="numpy",parallel=True)
def blur_image_nb(src, dst):
    (h, w, c) = src.shape
    for x in nb.prange(h-1):
        for y in range(w-1):
            for z in range(c):
                if x != 0 or y != 0:
                    dst[x, y, z] = (src[x, y, z]
                    + src[x-1, y, z]
                    + src[x+1, y, z]
                    + src[x, y-1, z]
                    + src[x, y+1, z]
                    + src[x-1, y-1, z]
                    + src[x-1, y+1, z]
                    + src[x+1, y-1, z]
                    + src[x+1, y+1, z]) / 9
                if x == 0:
                    dst[x, y, z] = (src[x, y, z]
                    + src[x, y, z]
                    + src[x+1, y, z]
                    + src[x, y-1, z]
                    + src[x, y+1, z]
                    + src[x, y-1, z]
                    + src[x, y+1, z]
                    + src[x+1, y-1, z]
                    + src[x+1, y+1, z]) / 9
                if y == 0:
                    dst[x, y, z] = (src[x, y, z]
                    + src[x-1, y, z]
                    + src[x+1, y, z]
                    + src[x, y, z]
                    + src[x, y+1, z]
                    + src[x-1, y, z]
                    + src[x-1, y+1, z]
                    + src[x+1, y, z]
                    + src[x+1, y+1, z]) / 9
    return dst

修改

当然,有一些简单的优化可能,例如不必要的除以恒定值。您还可以乘以仅计算一次的因子。

#You can achieve the same with fastmath=True
#Divisions are quite costly
@nb.njit(error_model="numpy",parallel=True)
def blur_image_nb_2(src, dst):
    (h, w, c) = src.shape
    fact=1./9.
    for x in nb.prange(h-1):
        for y in range(w-1):
            for z in range(c):
                if x != 0 or y != 0:
                    dst[x, y, z] = (src[x, y, z]
                    + src[x-1, y, z]
                    + src[x+1, y, z]
                    + src[x, y-1, z]
                    + src[x, y+1, z]
                    + src[x-1, y-1, z]
                    + src[x-1, y+1, z]
                    + src[x+1, y-1, z]
                    + src[x+1, y+1, z]) *fact
                if x == 0:
                    dst[x, y, z] = (src[x, y, z]
                    + src[x, y, z]
                    + src[x+1, y, z]
                    + src[x, y-1, z]
                    + src[x, y+1, z]
                    + src[x, y-1, z]
                    + src[x, y+1, z]
                    + src[x+1, y-1, z]
                    + src[x+1, y+1, z]) *fact
                if y == 0:
                    dst[x, y, z] = (src[x, y, z]
                    + src[x-1, y, z]
                    + src[x+1, y, z]
                    + src[x, y, z]
                    + src[x, y+1, z]
                    + src[x-1, y, z]
                    + src[x-1, y+1, z]
                    + src[x+1, y, z]
                    + src[x+1, y+1, z]) *fact
    return dst

时间

src=np.random.rand(1024,1024,3)
dst=np.empty((1024,1024,3))

%timeit blur_image(src, dst)
8.08 s ± 52.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit blur_image_nb(src, dst)
5.19 ms ± 954 µs per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit blur_image_nb_2(src, dst)
3.13 ms ± 167 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

#Just for comparison
%timeit res=np.copy(src)
11.1 ms ± 28.6 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
%timeit np.copyto(dst, src)
2.44 ms ± 53.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

import numpy as np
from PIL import Image, ImageFilter
import scipy.signal as ss

src=(np.random.rand(1024,1024,3)*255).astype(np.uint8)
img = Image.fromarray(src.astype(np.uint8))

%timeit img.filter(ImageFilter.BoxBlur(radius=3))
16.1 ms ± 65 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

kernel = np.ones((3, 3)) / 9  # The 'boxcar'.
#3x in a example where src.shape[2]==3
%timeit ss.convolve(src[:,:,0], kernel, mode='same')
76.6 ms ± 16.6 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

结论

通常,您将简单地使用已经可用的函数,例如他的答案中显示的@kwinkunks,但是简单地编译函数将比PIL表现出更好的性能(在数据类型方面也有一些限制),并且卷积更为普遍。 / p>