使用FFT进行高斯图像滤波

3
对于图像分割,我使用OpenCV的高斯模糊(GaussianBlur),使用高斯差分特征(范围从0.8到8.43,指数步长为1.4)。我的图像大小为4096 x 2160,因此这需要相当长的时间(在一个核心上需要8秒,处理视频时时间相当长)。
你能给我一些加速的建议吗?目前我正在尝试在FFT中实现高斯滤波。我已经编写了以下代码:
ftimage = np.fft.fft2(image)
ftimage = np.fft.fftshift(ftimage)
kernel = cv2.getGaussianKernel(11, 3)
kernel = kernel * kernel.T
ftkernel = np.fft.fftshift(np.fft.fft2(kernel, (h, w)))
ftimagep = ftimage * gmask
imagep = np.fft.ifft2(ftimagep)
imageq = cv2.GaussianBlur(image, (11,11), 3))

这里的问题在于imagepimageq是彼此平移的版本。 其次,由于高斯函数的傅里叶变换也是高斯函数,我该如何以直接的方式计算ftkernel
以下是回答: 我已经实现了近似过滤:
 def approx_g(image, sigma_g, n=5):
      w = np.sqrt(12*sigma_g**2/n + 1)
      wu = np.ceil(w) if np.ceil(w) % 2 == 1 else np.ceil(w)+1
      wl = np.floor(w) if np.floor(w) % 2 == 1 else np.floor(w)-1
      if w == w//1:
          wl -= 2
          wu += 2
      m = round((12*sigma_g**2 - n*wl**2 - 4*n*wl - 3*n) / (-4*wl - 4))
      wl = int(wl)
      wu = int(wu)
      for num in range(0,int(m)):
          image = cv2.blur(image, (wl, wl))
      for num in range(0,int(n-m)):
          image = cv2.blur(image, (wu, wu))
      return image

当n=4时,L2像素差看起来相当不错:

我还进行了不同sigma的速度比较:


为什么不能在较小的图像尺寸上运行分割? - Amitay Nachmani
我不确定OpenCV中GaussianBlur的具体实现,但是filter2D()(可以轻松地用于实现相同的效果)已经使用基于DFT的算法来处理大于~11x11像素的内核。请考虑这个链接:http://docs.opencv.org/3.0-beta/modules/imgproc/doc/filtering.html#void%20filter2D(InputArray%20src,%20OutputArray%20dst,%20int%20ddepth,%20InputArray%20kernel,%20Point%20anchor,%20double%20delta,%20int%20borderType) - alexisrozhkov
1个回答

4
一个高斯滤波器可以通过一系列的盒子(平均)滤波器来近似实现,如快速近似高斯滤波第二部分所述。此方法需要使用积分图像,并允许更快地应用(近似)高斯滤波,特别是对于高模糊情况。
下面的代码演示了如何使用上面链接的论文中的步骤来完成这个过程。
让滤波器半径为8.43,就像问题中描述的那样。
sigma_g = 8.43
连续应用盒式滤波器的次数决定了逼近的级别。在本例中,我将其设置为5:
n = 5

首先,使用方程3找到盒式滤波器的理想宽度:
w = np.sqrt(12*sigma_g**2/n + 1)

正如论文中所讨论的那样,使用两种不同大小的框式过滤器效果更好。这些过滤器需要是奇数长度以保持对称性,长度相差两个单位。下面的代码取w并找到最接近的奇数整数。(它可能可以更好地编写):

wu = np.ceil(w) if np.ceil(w) % 2 == 1 else np.ceil(w)+1
wl = np.floor(w) if np.floor(w) % 2 == 1 else np.floor(w)-1
if w == w//1:
    wl -= 2
    wu += 2

如果需要进行n次连续应用,则使用宽度为wu的第一个过滤器执行m次,使用宽度为wl的第二个过滤器执行(n-m)次。方程式5展示了如何计算m:
m = round((12*sigma_g**2 - n*wl**2 - 4*n*wl - 3*n) / (-4*wl - 4))

下面是计算水平和垂直积分图像的函数:
def integral_image_1d_hor(image):
    ''' Calculated the 1d horizontal integral
    image of an image.'''
    n1, n2 = np.shape(image)
    int_im = np.zeros((n1, n2))
    for row in range(0,n1):
        int_im[row,0] = image[row,0]

    for row in range(0,n1):
        for col in range(1,n2):
            int_im[row,col] = image[row,col] + int_im[row,col-1]

    return int_im


def integral_image_1d_ver(image):
    ''' Calculated the 1d vertical integral
        image of an image.'''
    n1, n2 = np.shape(image)
    int_im = np.zeros((n1, n2))
    for col in range(0,n2):
        int_im[0,col] = image[0,col]

    for col in range(0,n2):
        for row in range(1,n1):
            int_im[row,col] = image[row,col] + int_im[row-1,col]

    return int_im

要使用积分图像进行过滤,我有以下函数:

def box_1d_filter_hor(int_im_1d, width):
    w = int((width-1)/2)
    fil_im = np.zeros(np.shape(int_im_1d))
    pad = w
    int_im_1d = np.pad(int_im_1d, pad, 'constant')
    n1 = np.shape(int_im_1d)[0]
    n2 = np.shape(int_im_1d)[1]
    for row in range(pad, n1-pad):
        for col in range(pad, n2-pad):
            fil_im[row-pad,col-pad] = (int_im_1d[row,col+w]
                                    - int_im_1d[row,col-w-1])/width
    return fil_im


def box_1d_filter_ver(int_im_1d, width):
    w = int((width-1)/2)
    fil_im = np.zeros(np.shape(int_im_1d))
    pad = w
    int_im_1d = np.pad(int_im_1d, pad, 'constant')
    n1 = np.shape(int_im_1d)[0]
    n2 = np.shape(int_im_1d)[1]
    for col in range(pad, n2-pad):
        for row in range(pad, n1-pad):
            fil_im[row-pad,col-pad] = (int_im_1d[row+w,col]
                                    - int_im_1d[row-w-1,col])/width
    return fil_im

然后我定义了另外两个函数,用于在水平和垂直方向上处理图像:

def process_hor(image, w):
    int_im = integral_image_1d_hor(image)
    fil_im = box_1d_filter_hor(int_im, w)
    return fil_im

def process_ver(image, w):
    int_im = integral_image_1d_ver(image)
    fil_im2 = box_1d_filter_ver(int_im, w)
    return fil_im2

最后,使用之前的所有函数来近似高斯滤波,请使用以下函数:
def approximate_gaussian(image, wl, wu, m, n):
    for num in range(0,int(m)):
        image = process_hor(image, wl)
        image = process_ver(image, wl)
    for num in range(0,int(n-m)):
        image = process_hor(image, wu)
        image = process_ver(image, wu)
    return image

我并没有处理图像的边缘,但这可以通过修改上面的函数来调整。这样应该会更快,特别是对于高斯模糊半径非常大的情况。


我已经尝试使用OpenCV的cv2.blur函数。效果很好! - Philipp H.
box_1d_filter_ver 中,对于 int_im_1d 的索引应该增加 1,因为如果 row == pad,第一个轴的结果是 -1 - Philipp H.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接