高斯模糊与图像处理(Gaussian Blur)

高斯模糊在图像处理中的用途及其广泛,除了常规的模糊效果外,还可用于图像金字塔分解、反走样、高低频分解、噪声压制、发光效果等等等等。正因为高斯模糊太基础,应用太广泛,所以需要尽可能深入认识这个能力,避免在实际应用中无意采坑。

一维高斯核函数

公式

G ( x ) = 1 2 π σ e − ( x − μ ) 2 2 σ 2 G(x) = \frac{1}{\sqrt{2\pi}\sigma}e^{-\frac{(x-\mu)^2}{2\sigma^2}} G(x)=2π σ1e2σ2(xμ)2

其中 μ \mu μ是对称轴,图像处理中通常不会考虑 μ ≠ 0 \mu \neq0 μ=0的情况,所以该参数可以忽略(本文自此向下均忽略 μ \mu μ)。

σ \sigma σ决定函数的高矮胖瘦, σ \sigma σ越小越高瘦,越大越矮胖。如果用于模糊的话,高瘦意味着中心像素对于计算结果的贡献大,而远处的像素贡献相对较小,也就是说模糊结果更多受中心像素支配,因此模糊程度就低;反之矮胖意味着模糊程度高。所以其他条件不变的情况下,增大 σ \sigma σ会加强模糊,反之会减弱模糊。

e指数前的系数保证了核函数在 ( − ∞ , + ∞ ) (-\infty, +\infty) (,+)的积分为1,不过在图像处理中,我们显然不可能用无穷大的半径,所以会对高斯核的系数做归一化,那么e指数前的系数在归一化中就被约去了,所以干脆就不用计算。

曲线形态

下图是一维高斯核函数的曲线形态。

注意下图仍然计算了e指数前的系数,并验证了核函数的积分,表明无论 σ \sigma σ怎么变化,其积分总是为1。

在这里插入图片描述

# -*- coding: utf-8 -*-
import numpy as np
import matplotlib.pyplot as plt


def gaussian_1d(x, sigma):
    coef = 1. / (np.sqrt(2 * np.pi) * sigma)
    return coef * np.exp(-x * x / (2 * sigma * sigma))


def plot_gaussian_1d():
    dx = 0.01
    xx = np.arange(-10, 10, dx)
    plt.figure(0, figsize=(8, 6))
    for sigma in [0.5, 1, 2]:
        yy = gaussian_1d(xx, sigma)
        ss = np.sum(yy * dx)
        plt.plot(xx, yy, label="sigma = %0.1f, integral = %0.2f" % (sigma, ss))
    plt.legend(loc='best')
    plt.savefig('gaussian_1d.png', dpi=300)
    plt.close(0)


if __name__ == '__main__':
    plot_gaussian_1d()

有效模糊半径

一般而言,增大模糊半径会加强模糊效果。但从曲线形态可以看到,远离对称中心时,高斯核的系数将会变得非常非常小,在做图像模糊时,意味着这些远处的像素对于模糊的贡献将会变得非常小,此时增大半径几乎不会带来模糊程度的增加,但是计算量却丝毫没有减少。

所以这里应当注意一个有效模糊半径的概念。因为 σ \sigma σ会影响曲线的高矮胖瘦,所以容易知道有效模糊半径受 σ \sigma σ影响。为了定义有效模糊半径,我们必须定义一个标准,这个标准应当具有相对性概念。比如简单来说我们针对曲线某个点的数值与最大值的比来进行定义:
arg max ⁡ x ( G ( x , σ ) G ( 0 , σ ) ) > l i m i t \argmax_x(\frac{G(x, \sigma)}{G(0,\sigma)}) > limit xargmax(G(0,σ)G(x,σ))>limit
如果我们把满足上式的最大x称为有效有效模糊半径,那么就可以去找一下其与 σ \sigma σ的关系了。

搞清楚有效模糊半径对于图像处理的实践很重要,避免浪费了算力却并没有得到相应的模糊效果。然而这个概念很少被讨论。

下面是不同的limit情况下sigma与有效模糊半径的关系,很容易看出来这是个线性关系。
因此,假如我们要开发一个“模糊”的功能,只开放一个模糊程度参数,那么一般而言这个参数需要跟半径挂钩,但为了让半径能真正起到作用,就需要适配的sigma,这个sigma可以使用 s i g m a = r a d i u s / a sigma = radius / a sigma=radius/a 来计算得到。针对图像模糊,根据经验,a使用[2, 2.5]之间的数字均可。
请添加图片描述

def effective_radius():
    dx = 0.01
    xx = np.arange(0, 100, dx)
    limits = [0.1, 0.01, 0.001]
    colors = ['red', 'green', 'blue']

    sigmas = np.arange(0.1, 10, 0.1)

    plt.figure(0, figsize=(8, 6))
    for i, limit in enumerate(limits):
        effective_r = []
        for sigma in sigmas:
            yy = gaussian_1d(xx, sigma)
            yy = yy / np.max(yy)
            radius = np.max(np.argwhere(yy >= limit))
            effective_r.append(radius * dx)
        plt.plot(sigmas, effective_r, color=colors[i],
                 label='limit=%0.3f' % limit)

    plt.legend(loc='best')
    plt.grid()
    plt.xlabel('sigma')
    plt.ylabel('effective radius')
    plt.savefig('effective radius', dpi=300)
    plt.close(0)

二维高斯核函数

公式

G ( x , y ) = 1 2 π σ x σ y e − ( ( x − μ x ) 2 2 σ x 2 + ( y − μ y ) 2 2 σ y 2 ) G(x,y) = \frac{1}{2\pi\sigma_x\sigma_y}e^{-(\frac{(x-\mu_x)^2 }{2\sigma_x^2} + \frac{(y-\mu_y)^2 }{2\sigma_y^2})} G(x,y)=2πσxσy1e(2σx2(xμx)2+2σy2(yμy)2)

对于图像模糊来说,我们不关心平均值 μ \mu μ和前面的系数,所以公式可以简化为:
G ( x , y ) = e − ( x 2 2 σ x 2 + y 2 2 σ y 2 ) G(x,y) = e^{-(\frac{x^2 }{2\sigma_x^2} + \frac{y^2 }{2\sigma_y^2})} G(x,y)=e(2σx2x2+2σy2y2)

下面是生成kernel的代码和结果

# -*- coding: utf-8 -*-
import cv2
import numpy as np


def gaussian_2d(radius, sigma_x, sigma_y):
    coord = np.arange(-radius, radius, 0.01)
    xx, yy = np.meshgrid(coord, coord)
    res = np.exp(
        -(xx * xx / (sigma_x * sigma_x) + yy * yy / (sigma_y * sigma_y)) / 2)
    res = res / np.sum(res)
    return res


def plot_gaussian_2d():
    radius = 10
    sigma_x = 1.0
    sigma_y = 1.0

    kernel = gaussian_2d(radius, sigma_x, sigma_y)
    kernel = kernel / np.max(kernel)
    kernel = np.clip(np.round(kernel * 255), 0, 255)
    kernel = np.uint8(kernel)

    cv2.imwrite('radius=%d, sigma=(%d, %d).png' % (radius, sigma_x, sigma_y),
                kernel)

在这里插入图片描述

行列嵌套循环的高斯模糊

# -*- coding: utf-8 -*-
import cv2
import numpy as np


def gaussian_2d(radius, sigma_x, sigma_y, d_radius=1):
    coord = np.arange(-radius, radius + d_radius, d_radius)
    xx, yy = np.meshgrid(coord, coord)
    res = np.exp(-(xx * xx / (sigma_x * sigma_x) +
                   yy * yy / (sigma_y * sigma_y)) / 2)
    res = res / np.sum(res)
    return res


def convolve_2d(image, kernel, border='transparent'):
    image_height, image_width = image.shape[:2]
    kernel_height, kernel_width = kernel.shape[:2]
    radius_h = kernel_height // 2
    radius_w = kernel_width // 2

    # check: kernel_height and kernel_width must be odd
    if radius_h * 2 + 1 != kernel_height or radius_w * 2 + 1 != kernel_width:
        raise ValueError('kernel_height and kernel_width must be odd')

    res = np.zeros_like(image, dtype=np.float32)
    for row in range(image_height):
        # print(row)
        for col in range(image_width):
            pix = 0.0
            weight = 0
            for i in range(kernel_height):
                for j in range(kernel_width):
                    row_k = row + i - radius_h
                    col_k = col + j - radius_w
                    if border == 'transparent':
                        if 0 <= row_k < image_height and \
                                0 <= col_k < image_width:
                            pix += image[row_k, col_k] * kernel[i, j]
                            weight += kernel[i, j]
                    elif border == 'zero':
                        if 0 <= row_k < image_height and \
                                0 <= col_k < image_width:
                            pix += image[row_k, col_k] * kernel[i, j]
                        weight += kernel[i, j]
                    elif border == 'copy':
                        if row_k < 0:
                            row_k = 0
                        elif row_k >= image_height:
                            row_k = image_height - 1
                        if col_k < 0:
                            col_k = 0
                        elif col_k >= image_width:
                            col_k = image_width - 1
                        pix += image[row_k, col_k] * kernel[i, j]
                        weight += kernel[i, j]
                    elif border == 'reflect':
                        if row_k < 0:
                            row_k = np.abs(row_k)
                        elif row_k >= image_height:
                            row_k = 2 * (image_height - 1) - row_k
                        if col_k < 0:
                            col_k = np.abs(col_k)
                        elif col_k >= image_height:
                            col_k = 2 * (image_width - 1) - col_k
                        pix += image[row_k, col_k] * kernel[i, j]
                        weight += kernel[i, j]
                    else:
                        raise ValueError('border must be one of '
                                         '[transparent, zero, copy, reflect]')
            res[row, col] = np.float32(pix / weight)
    res = np.uint8(np.clip(np.round(res), 0, 255))
    return res


def main_gaussian_blur():
    radius = 20
    sigma_x = 10.0
    sigma_y = 10.0
    borders = ['transparent', 'zero', 'copy', 'reflect']

    image = cv2.imread('lena_std.bmp')
    kernel = gaussian_2d(radius, sigma_x, sigma_y, d_radius=1)

    for border in borders:
        blur_image = convolve_2d(image, kernel, border)
        cv2.imwrite('blur_radius=%d,sigma=(%0.1f,%0.1f),border=%s.png' % (
            radius, sigma_x, sigma_y, border), blur_image)

上面代码仅用于示例计算流程,因为python的for循环特别慢,所以只要kernel稍微大那么一些,上面的代码就要运行非常非常久。由于图像需要在两个方向循环,kernel也需要在两个方向循环,所以整个下来计算量是o(n^4),特别慢。

除了常规的图像高斯模糊外,上面代码还示例了几种边界条件:

  • transparent:超出边界的索引不参与计算,此种边界条件必需累加weight,最后用累加得到的weight做归一化。这种边界条件因为必须要对weight做累加,所以计算量略大一些些,但效果是几种边界条件中最好最稳定的。
  • zero:超出边界的索引用0代替。因为0代表黑色,随着kernel增大,越来越多的0参与到了边界的模糊中,所以这种边界条件会引起黑边,效果最差。但是对于并行加速来讲,其实现是比较容易的。在神经网络的卷积中使用非常多,但是传统图像处理中尽量避免用这种边界条件。
  • copy:超出边界的索引使用最近的边缘像素值,效果中规中矩,实现也简单。
  • reflect:超出边界的索引,以边界为对称中心取图像内部对应位置的像素值,索引的计算会有些麻烦,特别是当kernel尺寸非常大时,以至于镜像的索引超过了对面的边界。但是当kernel尺寸不那么极端时,这种边界条件的效果一般还不错。

下图是4中边界条件的效果,radius=20,sigma=10。
左上:transparent,右上:zero
左下:copy, 右下:reflect
请添加图片描述

行列分离的高斯模糊

高斯模糊使用嵌套循环显然是很差劲的写法。在实践中,一般我们会写成行列分离的形式,此时计算量从o(n^4) 降低到 o(n^3)。
上面我们写过二维高斯核函数为:
G ( x , y ) = e − ( x 2 2 σ x 2 + y 2 2 σ y 2 ) G(x,y) = e^{-(\frac{x^2 }{2\sigma_x^2} + \frac{y^2 }{2\sigma_y^2})} G(x,y)=e(2σx2x2+2σy2y2)
注意此函数可以拆开写成x,y分离的形式。

G ( x ) = e − x 2 2 σ x 2 G ( y ) = e − y 2 2 σ y 2 G(x) = e^{-\frac{x^2 }{2\sigma_x^2}} \\[2ex] G(y) = e^{-\frac{y^2 }{2\sigma_y^2}} G(x)=e2σx2x2G(y)=e2σy2y2
那么有:
G ( x , y ) = e − x 2 2 σ x 2 ⋅ e − y 2 2 σ y 2 = G ( x ) G ( y ) G(x,y) = e^{-\frac{x^2 }{2\sigma_x^2}} \cdot e^{-\frac{y^2 }{2\sigma_y^2}}=G(x)G(y) G(x,y)=e2σx2x2e2σy2y2=G(x)G(y)
然后一个高斯模糊的计算过程为:
b l u r ( x , y ) = ∫ ∫ I ( x , y ) G ( x , y ) d x d y = ∫ ∫ I ( x , y ) G ( x ) G ( y ) d x d y = ∫ [ ∫ I ( x , y ) G ( x ) d x ] ⋅ G ( y ) d y ( 写成离散形式 ) = ∑ y i [ ∑ x i I ( x i , y i ) G ( x i ) ] G ( y i ) \begin{aligned} blur(x,y) &= \int \int I(x,y)G(x,y)dxdy \\[2ex] &= \int \int I(x,y)G(x)G(y)dxdy \\[2ex] &=\int [\int I(x,y)G(x)dx] \cdot G(y)dy \\[2ex] & (写成离散形式) \\[2ex] & =\sum_{y_i} [\sum_{x_i} I(x_i,y_i)G(x_i)]G(y_i) \end{aligned} blur(x,y)=∫∫I(x,y)G(x,y)dxdy=∫∫I(x,y)G(x)G(y)dxdy=[I(x,y)G(x)dx]G(y)dy(写成离散形式)=yi[xiI(xi,yi)G(xi)]G(yi)

也就是说在x方向进行模糊,然后对这个中间结果在y方向再做模糊即可,每次只在一个方向做循环,并且这两个循环不是嵌套,而是分离的。下面代码没有再实现多种边界条件,只写了transparent类型的。

# -*- coding: utf-8 -*-
import cv2
import numpy as np


def gaussian_1d(radius, sigma, d_radius=1):
    xx = np.arange(-radius, radius + d_radius, d_radius)
    res = np.exp(-xx * xx / (sigma * sigma) / 2)
    res = res / np.sum(res)
    return res


def convolve_2d_xy(image, kernel_x, kernel_y):
    image_height, image_width = image.shape[:2]
    kernel_x_len = len(kernel_x)
    kernel_y_len = len(kernel_y)
    radius_x = kernel_x_len // 2
    radius_y = kernel_y_len // 2

    # check: kernel_height and kernel_width must be odd
    if radius_x * 2 + 1 != kernel_x_len or radius_y * 2 + 1 != kernel_y_len:
        raise ValueError('kernel size must be odd')

    res = np.zeros_like(image, dtype=np.float32)
    # convolve in x direction
    for row in range(image_height):
        for col in range(image_width):
            pix = 0.0
            weight = 0
            for j in range(kernel_x_len):
                col_k = col + j - radius_x
                if 0 <= col_k < image_width:
                    pix += image[row, col_k] * kernel_x[j]
                    weight += kernel_x[j]
            res[row, col] = pix / weight

    # convolve in y direction
    image = res.copy()
    for col in range(image_width):
        for row in range(image_height):
            pix = 0.0
            weight = 0
            for i in range(kernel_y_len):
                row_k = row + i - radius_y
                if 0 <= row_k < image_height:
                    pix += image[row_k, col] * kernel_y[i]
                    weight += kernel_y[i]
            res[row, col] = pix / weight
    res = np.uint8(np.clip(np.round(res), 0, 255))
    return res


def main_gaussian_blur_xy():
    radius = 20
    sigma_x = 10.0
    sigma_y = 10.0

    image = cv2.imread('lena_std.bmp')
    kernel_x = gaussian_1d(radius, sigma_x, d_radius=1)
    kernel_y = gaussian_1d(radius, sigma_y, d_radius=1)

    blur_image = convolve_2d_xy(image, kernel_x, kernel_y)
    cv2.imwrite('xy_blur_radius=%d,sigma=(%0.1f,%0.1f).png' % (
        radius, sigma_x, sigma_y), blur_image)

行列分离的耗时在我电脑上大概是45s,嵌套循环大概需要1020秒。尽管python的循环特别慢,这个时间对比可能做不得数,但是量级上的差异还是比较能说明问题了。

下面左图是之前嵌套循环得到的结果,中间是行列分离得到的结果,右图是两者的差的绝对值再乘以50,结果来看两个模糊的结果完全一样。
请添加图片描述

加上step的高斯模糊

当模糊的radius非常大时候,可以适当加上一些step来节省计算。
此时应当注意,为了达到预想中的模糊效果,不仅image patch与kernel乘加时的索引需要加上step,kernel系数的计算也需要加上step,不然与不加step时候的系数会有很大差异。

# -*- coding: utf-8 -*-
import cv2
import numpy as np
import time


def gaussian_1d(radius, sigma, d_radius=1.0):
    xx = np.arange(-radius, radius + d_radius, d_radius)
    res = np.exp(-xx * xx / (sigma * sigma) / 2)![请添加图片描述](https://img-blog.csdnimg.cn/0d509cd3e63a4f4680df49de7b807309.png)

    res = res / np.sum(res)
    return res


def convolve_2d_xy_step(image, kernel_x, kernel_y, step):
    image_height, image_width = image.shape[:2]
    kernel_x_len = len(kernel_x)
    kernel_y_len = len(kernel_y)
    radius_x = kernel_x_len // 2
    radius_y = kernel_y_len // 2

    # check: kernel_height and kernel_width must be odd
    if radius_x * 2 + 1 != kernel_x_len or radius_y * 2 + 1 != kernel_y_len:
        raise ValueError('kernel size must be odd')

    res = np.zeros_like(image, dtype=np.float32)
    # convolve in x direction
    for row in range(image_height):
        for col in range(image_width):
            pix = 0.0
            weight = 0
            for j in range(kernel_x_len):
                col_k = int(round(col + (j - radius_x) * step))
                if 0 <= col_k < image_width:
                    pix += image[row, col_k] * kernel_x[j]
                    weight += kernel_x[j]
            res[row, col] = pix / weight

    # convolve in y direction
    image = res.copy()
    for col in range(image_width):
        for row in range(image_height):
            pix = 0.0
            weight = 0
            for i in range(kernel_y_len):
                row_k = int(round(row + (i - radius_y) * step))
                if 0 <= row_k < image_height:
                    pix += image[row_k, col] * kernel_y[i]
                    weight += kernel_y[i]
            res[row, col] = pix / weight
    res = np.uint8(np.clip(np.round(res), 0, 255))
    return res


def main_gaussian_blur_xy():
    radius = 20
    sigma_x = 10.0
    sigma_y = 10.0
    step = 4.0

    image = cv2.imread('lena_std.bmp')
    kernel_x = gaussian_1d(radius, sigma_x, d_radius=step)
    kernel_y = gaussian_1d(radius, sigma_y, d_radius=step)

    blur_image = convolve_2d_xy_step(image, kernel_x, kernel_y, step)
    cv2.imwrite('xy_blur_radius=%d,sigma=(%0.1f,%0.1f)_step.png' % (
        radius, sigma_x, sigma_y), blur_image)


下面左图是之前嵌套循环得到的结果,中间是行列分离+step=4得到的结果,右图是两者的差的绝对值再乘以50。
此时计算出来的diff已经不再是全零,如果仔细看中间的结果,显示出了一些块状纹理,表明模糊的品质已经开始受到影响。
请添加图片描述

任意二维高斯核的生成

这里任意高斯核指的是任意长宽比,且存在旋转角度的高斯核。
公式推导的思路是,把高斯核G(x,y)看做是一个图像,然后用图像旋转的思路来改造二维高斯核的公式。

下面我们再次把高斯核公式摆在这里。
G ( x , y ) = e − ( x 2 2 σ x 2 + y 2 2 σ y 2 ) G(x,y) = e^{-(\frac{x^2 }{2\sigma_x^2} + \frac{y^2 }{2\sigma_y^2})} G(x,y)=e(2σx2x2+2σy2y2)
根据图像旋转的原理,当图像中某个点(x, y)以圆心作为旋转中心,沿逆时针旋转后,新的点(x’, y’)的坐标公式为:
{ x ′ = x ∗ c o s θ − y ∗ s i n θ y ′ = x ∗ s i n θ + y ∗ c o s θ \begin{cases} x' = x*cos\theta - y*sin\theta \\ y' = x*sin\theta + y*cos\theta \end{cases} \\[4ex] {x=xcosθysinθy=xsinθ+ycosθ

将其代入高斯核公式,并且方便起见仍以(x, y)来表示,那么有:
G ( x , y ) = e − ( ( x ∗ c o s θ − y ∗ s i n θ ) 2 2 σ x 2 + ( x ∗ s i n θ + y ∗ c o s θ ) 2 2 σ y 2 ) = e − ( x 2 ∗ c o s 2 θ + y 2 ∗ s i n 2 θ − 2 x y ∗ s i n θ c o s θ 2 σ x 2 + x 2 s i n 2 θ + y 2 ∗ c o s 2 θ + 2 x y ∗ s i n θ c o s θ 2 σ y 2 ) = e − ( x 2 ( c o s 2 θ 2 σ x 2 + s i n 2 θ 2 σ y 2 ) + x y ( − s i n 2 θ 2 σ x 2 + s i n 2 θ 2 σ y 2 ) + y 2 ( s i n 2 θ 2 σ x 2 + c o s 2 θ 2 σ y 2 ) ) \begin{aligned} G(x,y) &= e^{-(\frac{(x*cos\theta - y*sin\theta)^2 }{2\sigma_x^2} + \frac{(x*sin\theta + y*cos\theta)^2 }{2\sigma_y^2})} \\[2ex] &=e^{-(\frac{x^2*cos^2\theta + y^2*sin^2\theta -2xy*sin\theta cos\theta}{2\sigma_x^2} + \frac{x^2sin^2\theta + y^2*cos^2\theta + 2xy*sin\theta cos\theta }{2\sigma_y^2})} \\[2ex] &=e^{-(x^2(\frac{cos^2\theta}{2\sigma_x^2} + \frac{sin^2\theta}{2\sigma_y^2}) + xy(-\frac{sin2\theta}{2\sigma_x^2} + \frac{sin2\theta}{2\sigma_y^2}) + y^2( \frac{sin^2\theta}{2\sigma_x^2} + \frac{cos^2\theta}{2\sigma_y^2} ))} \end{aligned} G(x,y)=e(2σx2(xcosθysinθ)2+2σy2(xsinθ+ycosθ)2)=e(2σx2x2cos2θ+y2sin2θ2xysinθcosθ+2σy2x2sin2θ+y2cos2θ+2xysinθcosθ)=e(x2(2σx2cos2θ+2σy2sin2θ)+xy(2σx2sin2θ+2σy2sin2θ)+y2(2σx2sin2θ+2σy2cos2θ))

上述公式就是任意二维高斯核的样子。
下面是python的实现代码和图,将一个沿横向比较长的kernel逆时针旋转了20度。
请添加图片描述

# -*- coding: utf-8 -*-
import cv2
import numpy as np


def generalized_gaussian_kernel(ksize,
                                sigma_x,
                                sigma_y=None,
                                angle=0.0):
    """
    Generate generalized gaussian kernel.

    Parameters
    ----------
    ksize: kernel size, one integer or a list/tuple of two integers, must be
        odd
    sigma_x: standard deviation in x direction
    sigma_y: standard deviation in y direction
    angle: rotate angle, anti-clockwise

    Returns
    -------
    kernel: generalized gaussian blur kernel
    """
    # check parameters
    if not isinstance(ksize, (tuple, list)):
        ksize = [ksize, ksize]
    else:
        ksize = list(ksize)
    ksize[0] = ksize[0] // 2 * 2 + 1
    ksize[1] = ksize[1] // 2 * 2 + 1

    if sigma_y is None:
        sigma_y = sigma_x

    # meshgrid coordinates
    radius_x = ksize[0] // 2
    radius_y = ksize[1] // 2
    x = np.arange(-radius_x, radius_x + 1, 1)
    y = np.arange(-radius_y, radius_y + 1, 1)
    xx, yy = np.meshgrid(x, y)

    # coefficients of coordinates
    angle = angle / 180 * np.pi
    cos_square = np.cos(angle) ** 2
    sin_square = np.sin(angle) ** 2
    sin_2 = np.sin(2 * angle)

    alpha = 0.5 / (sigma_x * sigma_x)
    beta = 0.5 / (sigma_y * sigma_y)
    a = cos_square * alpha + sin_square * beta
    b = -sin_2 * alpha + sin_2 * beta
    c = sin_square * alpha + cos_square * beta

    # generate and normalize kernel
    kernel = np.exp(-(a * xx * xx + b * xx * yy + c * yy * yy))
    kernel = kernel / np.sum(kernel)
    return kernel


if __name__ == '__main__':
    ksize = (511, 511)
    sigma_x = 100.0
    sigma_y = 20.0
    angle = 20.0

    gaussian_kernel = generalized_gaussian_kernel(ksize=ksize,
                                                  sigma_x=sigma_x,
                                                  sigma_y=sigma_y,
                                                  angle=angle)
    gaussian_kernel = gaussian_kernel / np.max(gaussian_kernel)
    gaussian_kernel = np.uint8(np.round(gaussian_kernel * 255))
    cv2.imwrite('generalized_gaussian_kernel_1.png', gaussian_kernel)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/68188.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

AttentionFreeTransformer 源码解析(一):AFTFull、AFTSimple、AFTLocal

我觉得源码写的很好懂&#xff0c;我就不加注释了&#xff0c;直接上计算流程图。 AFTFull class AFTFull(nn.Module):def __init__(self, max_seqlen, dim, hidden_dim64):super().__init__()max_seqlen: the maximum number of timesteps (sequence length) to be fed indim…

DP(区间DP)

石子合并 设有 N 堆石子排成一排&#xff0c;其编号为 1,2,3,…,N。 每堆石子有一定的质量&#xff0c;可以用一个整数来描述&#xff0c;现在要将这 N 堆石子合并成为一堆。 每次只能合并相邻的两堆&#xff0c;合并的代价为这两堆石子的质量之和&#xff0c;合并后与这两堆…

cesium学习记录06-视图、场景与相机

一、视图&#xff08;Viewer&#xff09; viewer是cesium的核心类&#xff0c;是一切的开端。通过new Cesium.Viewer(container, options)来创建一个Viewer对象&#xff0c;而通过这个 Viewer对象&#xff0c;可以添加图层、实体、相机控制等&#xff0c;以及设置一些全局属性…

esp8266使用arduinoJson与tft_espi库发生冲突解决方法

esp8266使用arduinoJson与tft_espi库发生冲突解决方法 arduinoJson与tft_espi库发生冲突解决方法下载arduinoJson5.0版本的&#xff0c;不要用最新版本 示范代码&#xff1a; // Copyright Benoit Blanchon 2014 // MIT License // // Arduino JSON library // https://git…

Unity游戏源码分享-仿帝国时代游戏Demo-uRTS Toolkit

Unity游戏源码分享-仿帝国时代游戏Demo-uRTS Toolkit 游戏的架构值得参考 项目地址&#xff1a;https://download.csdn.net/download/Highning0007/88189905

Pycharm 双击启动失败?

事故 双击 Pycharm 后&#xff0c;出现加载工程&#xff0c;我不想加载这个工程&#xff0c;就点击了弹出的 cancle 取消按钮。然后再到桌面双击 Pycharm 却发现无法启动了。哪怕以管理员权限运行也没用&#xff0c;就是不出界面。 原因未知 CtrlshiftESC 打开后台&#xff…

易服客工作室:如何创建有用的内容日历

利用技巧和工具优化您的内容营销效率和效果。创建一个内容日历&#xff0c;您的整个团队都会从中受益&#xff01; 欢迎来到熙熙攘攘、瞬息万变的内容营销世界&#xff0c;在这里&#xff0c;截止日期到来的速度比喝咖啡的猎豹还要快。 现在&#xff0c;想象一下在没有地图、…

MapBox加载不同风格

初始化MapBox地图&#xff1a; var map new mapboxgl.Map({container: map,zoom: 3,center: [105, 34],//此处更改地图风格style: mapbox://styles/mapbox/satellite-v9,hash: false,});1.户外地图&#xff08;mapbox://styles/mapbox/basic-v9&#xff09;新版&#xff1a;&a…

设计模式之模板方法

一、概述 定义一个操作中的算法的骨架&#xff0c;将一些步骤延迟到子类中。 TemplateMethod使得子类可以不改变一个算法的结构即可重新定义该算法的某些特定步骤。 二、适用性 1.一次性实现一个算法的不变的部分&#xff0c;并将可变的行为留给子类来实现。 2.各子类中公共…

YOLOv5修改注意力机制CBAM

直接上干货 CBAM注意力机制是由通道注意力机制&#xff08;channel&#xff09;和空间注意力机制&#xff08;spatial&#xff09;组成。 传统基于卷积神经网络的注意力机制更多的是关注对通道域的分析&#xff0c;局限于考虑特征图通道之间的作用关系。CBAM从 channel 和 sp…

Kafka与Zookeeper版本对应关系

文章目录 了解版本对应Kafka安装包Kafka源码包 了解 比如&#xff1a; kafka_2.11-1.1.1.jar包 其中2.11表示的是Scala的版本&#xff0c;因为Kafka服务器端代码完全由Scala语音编写。”-“后面的1.1.1表示的kafka的版本信息。遵循一个基本原则&#xff0c;Kafka客户端版本和服…

MySQL5.7数据库、Navicat Premium1.6可视化工具安装教程【详细教程】

文章目录 一、MySQL、Navicat、注册机地址二、安装&#xff08;一&#xff09;、MySQL安装&#xff08;二&#xff09;、Navicat Premium安装&#xff08;三&#xff09;、集活Navicat Premium 三、遇到的问题1、Are you sure your navicat has not beenpatched/modified befor…

Spring 事务管理

目录 1. 事务管理 1.1. Spring框架的事务支持模型的优势 1.1.1. 全局事务 1.1.2. 本地事务 1.1.3. Spring框架的一致化编程模型 1.2. 了解Spring框架的事务抽象&#xff08;Transaction Abstraction&#xff09; 1.2.1. Hibernate 事务设置 1.3. 用事务同步资源 1.3.1…

Malloc动态内存分配

在C语言中我们会使用malloc来动态地分配内存&#xff0c;这样做的一个主要理由是有些数据结构的大小只有在运行时才能确定。例如&#xff0c;如果你正在编写一个程序&#xff0c;需要用户输入一些数据&#xff0c;但你不知道用户会输入多少数据&#xff0c;那么你就需要使用动态…

vue3使用pinia和pinia-plugin-persist做持久化存储

1、安装依赖 pnpm i pinia // 安装 pinia pnpm i pinia-plugin-persist // 安装持久化存储插件2、main.js引入 import App from ./App.vue const app createApp(App)//pinia import { createPinia } from pinia import piniaPersist from pinia-plugin-persist //持久化插件 …

Python中enumerate用法详解

目录 1.简介 2.语法 3.参数 4.返回值 5.详解 6.实例 7.补充 1.简介 enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列&#xff0c;同时列出数据和数据下标&#xff0c;一般用在 for 循环当中。 2.语法 以下是 enumerate() 方法的语…

Qt应用开发(基础篇)——堆栈窗口 QStackedWidget

一、前言 QStackedWidget继承于QFrame&#xff0c;QFrame继承于QWidget&#xff0c;是Qt常用的堆栈窗口部件。 框架类QFrame介绍 QStackedWidget堆栈窗口&#xff0c;根据下标切换&#xff0c;一次显示一个小部件&#xff0c;常用于应用界面切换、图片轮询播放等场景。 二、QSt…

通义千问开源模型部署使用

首先可以参考modelScope社区给出的使用文档&#xff0c;已经足够全面 通义千问-7B-Chat 但在按照文档中步骤部署时&#xff0c;还是有些错误问题发生&#xff0c;可以搜索参考的解决方式不多&#xff0c;所以记录下来 个人电脑部署 这里不太建议使用自己的笔记本部署通义千…

exchange partition global index

EXCHANGE PARTITION with a Table having a UNIQUE INDEX and PK Constraint. (Doc ID 1620636.1)​编辑To Bottom In this Document Symptoms Changes Cause Solution References APPLIES TO: Oracle Database - Enterprise Edition - Version 11.2.0.3 and later Oracle Da…

Spring整合MyBatis(详细步骤)

Spring与Mybatis的整合&#xff0c;大体需要做两件事&#xff0c; 第一件事是:Spring要管理MyBatis中的SqlSessionFactory 第二件事是:Spring要管理Mapper接口的扫描 具体的步骤为: 步骤1:项目中导入整合需要的jar包 <dependency><!--Spring操作数据库需要该jar包…