【问题标题】:High performance variable blurring in very big images using Python使用 Python 在非常大的图像中进行高性能变量模糊
【发布时间】:2015-09-10 08:05:54
【问题描述】:

我有大量想要模糊处理的大图像(例如 15000x15000 像素)。我需要使用距离函数模糊图像,因此我离图像中的某些区域越远,模糊应该越重。我有一张距离图,描述了给定像素与这些区域的距离。

由于图片数量众多,我必须考虑性能。我看过 NumPY/SciPY,它们有一些很棒的功能,但它们似乎使用固定的内核大小,我需要根据与前面提到的区域的距离来减小或增加内核大小。

如何在 python 中解决这个问题?


更新:到目前为止,我的解决方案基于 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

测试:

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

使用上述设置的方法运行大约需要 186 秒。这是我可以期望最终从方法中挤出来的,还是我可以使用优化来进一步提高性能(仍然使用 Python)?

【问题讨论】:

  • 这并不能回答您的问题,但如果您真的关心性能,您应该尝试 C++ 和 OpenCV。
  • 你想要什么样的模糊?高斯?盒子模糊?模糊是否必须在数学上准确,还是为了艺术效果和“如果看起来足够好,就足够好了”?此外,模糊量是否与复杂 3D 场景中的深度值有关?您是否需要担心不会将焦点附近物体的颜色值溢出到远离焦点的表面等?
  • @coincoin:由于我缺乏 C++ 知识,我想远离 C++,但我的想法完全一样 :) @samgak:我需要图像具有无缝模糊,它应该只是看起来足够好,这是为了视觉目的。我只需要所选像素周围最近的X 像素的平均值,不需要花哨的东西。图片是二维照片。
  • 作为旁注,您可以return data_blurred.base 以避免调用np.asarray 的需要。同样使用cpdef 而不是def 可能更可取(对性能没有影响)。最后,对于任何高性能 C 代码,请检查您的编译标志。例如在我的系统(Linux Gentoo)上,我惊讶地发现在setup.py 中设置extra_compile_args=['-O3', '-march=native']extra_link_args=['-O3', '-march=native'] 导致某些代码的速度提高了~2 倍,而在Windows 上的同一台机器上使用Ananconda,速度提升可以忽略不计。

标签: python image-processing numpy scipy cython


【解决方案1】:

正如您所指出的,相关的scipy 函数不支持可变大小模糊。您可以使用 for 循环在纯 python 中实现这一点,然后使用 Cython、Numba 或 PyPy 来获得类似 C 的性能。

这是一个低级的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)

计算具有可变内核大小的像素的算术平均值。就 numpy 而言,这是一个糟糕的实现,从某种意义上说,它不是矢量化的。但是,这样可以方便地移植到其他高性能解决方案:

  • 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.
    

    完整示例请参见 this post,以及 compilation notes

  • Numba:用@jit decorator 包装上述函数应该就足够了。

  • PyPy:安装 PyPy + 实验性的 numpy branch,可能是另一个值得尝试的替代方案。不过,您将不得不对所有代码使用 PyPy,目前这可能是不可能的。

一旦你有一个快速的实现,你可以使用multiprocessing等来并行处理不同的图像,如果需要的话。甚至在 Cython 中与 OpenMP 并行化外部 for 循环。

【讨论】:

  • 谢谢,非常详细。我会尝试让 Cython 进入我的曲目,看看效果如何。
  • 我终于有时间调查 Cython 和您的答案。我已经用我所做的更新了我的问题。如果您有任何其他想法,我想听听他们:)
  • @Chau 我看不到明显的优化。您可以尝试用prange 替换range 的外部循环(请参阅Cython documentation),但总的来说,我认为这几乎与您将获得的一样快:您确实处理了相当大的数组。
  • 我确实处理大量数据,因此我需要快速计算。非常感谢您的帮助,我以后一定会考虑更多地使用 Cython!
【解决方案2】:

我在谷歌搜索时遇到了这个问题,我想我会分享我自己的解决方案,该解决方案主要是矢量化的,不包括像素上的任何 for 循环。您可以通过连续多次运行框模糊来近似高斯模糊。所以我决定使用的方法是对图像进行迭代框模糊,但使用加权函数来改变每个像素的迭代次数。

如果需要较大的模糊半径,迭代次数会呈二次方增长,因此请考虑增加 ksize。

这里是实现

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)

这是一个例子

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

剧情是:

  • 左上角:源图片
  • 右上角:可变模糊
  • 左下:高斯模糊
  • 右下:近似高斯模糊

【讨论】:

  • 感谢您的解决方案。这是一个相当老的问题,如果我回到类似的问题,我一定会尝试你的解决方案。
猜你喜欢
  • 1970-01-01
  • 2013-06-08
  • 2020-04-22
  • 2013-04-17
  • 1970-01-01
  • 1970-01-01
  • 2018-01-18
  • 2015-11-05
  • 2013-06-30
相关资源
最近更新 更多