简体   繁体   English

使用 Python 在非常大的图像中进行高性能变量模糊

[英]High performance variable blurring in very big images using Python

I have a large collection of large images (ex. 15000x15000 pixels) that I would like to blur.我有大量想要模糊的大图像(例如 15000x15000 像素)。 I need to blur the images using a distance function, so the further away I move from some areas in the image the more heavier the blurring should be.我需要使用距离函数来模糊图像,所以我离图像中的某些区域越远,模糊越重。 I have a distance map describing how far a given pixel is from the areas.我有一个距离图,描述给定像素距区域的距离。

Due to the large amount of images I have to consider performance.由于图像量很大,我必须考虑性能。 I have looked at NumPY/SciPY, they have some great functions but they seem to use a fixed kernel size and I need to reduce or increase the kernel size depending on the distance to the previous mentioned areas.我看过 NumPY/SciPY,它们有一些很棒的功能,但它们似乎使用固定的内核大小,我需要根据与前面提到的区域的距离来减少或增加内核大小。

How can I solve this problem in python?我如何在python中解决这个问题?


UPDATE: My solution so far based on the answer by rth :更新:到目前为止,我的解决方案基于rth的回答:

# cython: boundscheck=False
# cython: cdivision=True
# cython: wraparound=False

import numpy as np
cimport numpy as np

def variable_average(int [:, ::1] data, int[:,::1] kernel_size):
    cdef int width, height, i, j, ii, jj
    width = data.shape[1]
    height = data.shape[0]
    cdef double [:, ::1] data_blurred = np.empty([width, height])
    cdef double res
    cdef int sigma, weight

    for i in range(width):
        for j in range(height):
            weight = 0
            res = 0
            sigma =  kernel_size[i, j]
            for ii in range(i - sigma, i + sigma + 1):
                for jj in range(j - sigma, j + sigma + 1):
                    if ii < 0 or ii >= width or jj < 0 or jj >= height:
                        continue
                    res += data[ii, jj]
                    weight += 1
            data_blurred[i, j] = res/weight

    return data_blurred

Test:测试:

data = np.random.randint(256, size=(1024,1024))
kernel = np.random.randint(256, size=(1024,1024)) + 1
result = np.asarray(variable_average(data, kernel))

The method using the above settings takes around 186seconds to run.使用上述设置的方法大约需要 186 秒才能运行。 Is that what I can expect to ultimately squeeze out of the method or are there optimizations that I can use to further increase the performance (still using Python)?这是我可以期望最终从该方法中挤出的内容,还是可以使用优化来进一步提高性能(仍在使用 Python)?

As you have noted related scipy functions do not support variable size blurring.正如您所指出的,相关的scipy函数不支持可变大小模糊。 You could implement this in pure python with for loops, then use Cython, Numba or PyPy to get a C-like performance.您可以使用 for 循环在纯 python 中实现它,然后使用 Cython、Numba 或 PyPy 来获得类似 C 的性能。

Here is a low level python implementation, than uses numpy only for data storage,这是一个低级的python实现,而不是仅将numpy用于数据存储,

import numpy as np

def variable_blur(data, kernel_size):
    """ Blur with a variable window size
    Parameters:
      - data: 2D ndarray of floats or integers
      - kernel_size: 2D ndarray of integers, same shape as data
    Returns:
      2D ndarray
    """
    data_blurred = np.empty(data.shape)
    Ni, Nj = data.shape
    for i in range(Ni):
        for j in range(Nj):
            res = 0.0
            weight = 0
            sigma =  kernel_size[i, j]
            for ii in range(i - sigma, i+sigma+1):
                for jj in range(j - sigma, j+sigma+1):
                    if ii<0 or ii>=Ni or jj < 0 or jj >= Nj:
                        continue
                    res += data[ii, jj]
                    weight += 1
            data_blurred[i, j] = res/weight
    return data_blurred

data = np.random.rand(50, 20)
kernel_size = 3*np.ones((50, 20), dtype=np.int)
variable_blur(data, kernel_size)

that calculates an arithmetic average of pixels with a variable kernel size.计算具有可变内核大小的像素的算术平均值。 It is a bad implementation with respect to numpy, in a sense that is it not vectorized.就 numpy 而言,这是一个糟糕的实现,从某种意义上说,它没有被矢量化。 However, this makes it convenient to port to other high performance solutions:但是,这可以方便地移植到其他高性能解决方案:

  • Cython : simply statically typing variables, and compiling should give you C-like performance, Cython :简单地静态输入变量,编译应该给你类似 C 的性能,

     def variable_blur(double [:, ::1] data, long [:, ::1] kernel_size): cdef double [:, ::1] data_blurred = np.empty(data.shape) cdef Py_ssize_t Ni, Nj Ni = data.shape[0] Nj = data.shape[1] for i in range(Ni): # [...] etc.

    see this post for a complete example, as well as the compilation notes .有关完整示例以及编译说明,请参阅此帖子

  • Numba : Wrapping the above function with the @jit decorator , should be mostly sufficient. Numba :用@jit装饰器包装上述函数,应该就足够了。

  • PyPy : installing PyPy + the experimental numpy branch , could be another alternative worth trying. PyPy :安装 PyPy + 实验性numpy 分支,可能是另一种值得尝试的选择。 Although, then you would have to use PyPy for all your code, which might not be possible at present.虽然,那么您将不得不对所有代码使用 PyPy,这在目前可能是不可能的。

Once you have a fast implementation, you can then use multiprocessing , etc. to process different images in parallel, if need be.一旦你有一个快速的实现,你就可以使用multiprocessing等来并行处理不同的图像,如果需要的话。 Or even parallelize with OpenMP in Cython the outer for loop.或者甚至在 Cython 的外部for循环中与 OpenMP 并行化。

I came across this while googling and thought I would share my own solution which is mostly vectorized and doesn't include any for loops on pixels.我在谷歌搜索时遇到了这个问题,并认为我会分享我自己的解决方案,该解决方案主要是矢量化的,不包括任何像素上的循环。 You can approximate a Gaussian blur by running a box blur multiple times in a row.您可以通过连续多次运行框模糊来近似高斯模糊。 So the approach I decided to use is to iteratively box blur the image, but to vary the number of iterations per pixel using a weighting function.所以我决定使用的方法是迭代框模糊图像,但使用加权函数改变每个像素的迭代次数。

If you need a large blur radius, the number of iterations grows quadratically, so consider increasing the ksize.如果需要大的模糊半径,迭代次数呈二次方增长,因此请考虑增加 ksize。

Here is the implementation这是实现

import cv2

def variable_blur(im, sigma, ksize=3):
    """Blur an image with a variable Gaussian kernel.
    
    Parameters
    ----------
    im: numpy array, (h, w)
    
    sigma: numpy array, (h, w)
    
    ksize: int
        The box blur kernel size. Should be an odd number >= 3.
        
    Returns
    -------
    im_blurred: numpy array, (h, w)
    
    """
    variance = box_blur_variance(ksize)
    # Number of times to blur per-pixel
    num_box_blurs = 2 * sigma**2 / variance
    # Number of rounds of blurring
    max_blurs = int(np.ceil(np.max(num_box_blurs))) * 3
    # Approximate blurring a variable number of times
    blur_weight = num_box_blurs / max_blurs

    current_im = im
    for i in range(max_blurs):
        next_im = cv2.blur(current_im, (ksize, ksize))
        current_im = next_im * blur_weight + current_im * (1 - blur_weight)
    return current_im

def box_blur_variance(ksize):
    x = np.arange(ksize) - ksize // 2
    x, y = np.meshgrid(x, x)
    return np.mean(x**2 + y**2)

And here is an example这是一个例子

im = np.random.rand(300, 300)
sigma = 3

# Variable
x = np.linspace(0, 1, im.shape[1])
y = np.linspace(0, 1, im.shape[0])
x, y = np.meshgrid(x, y)
sigma_arr = sigma * (x + y)
im_variable = variable_blur(im, sigma_arr)

# Gaussian
ksize = sigma * 8 + 1
im_gauss = cv2.GaussianBlur(im, (ksize, ksize), sigma)

# Gaussian replica
sigma_arr = np.full_like(im, sigma)
im_approx = variable_blur(im, sigma_arr)

Blurring results模糊结果

The plot is:剧情是:

  • Top left: Source image左上角:源图像
  • Top right: Variable blurring右上角:变量模糊
  • Bottom left: Gaussian blurring左下:高斯模糊
  • Bottom right: Approximated Gaussian blurring右下:近似高斯模糊

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM