简体   繁体   English

如何在 numpy 中有效地计算高斯 kernel 矩阵?

[英]How to calculate a Gaussian kernel matrix efficiently in numpy?

def GaussianMatrix(X,sigma):
    row,col=X.shape
    GassMatrix=np.zeros(shape=(row,row))
    X=np.asarray(X)
    i=0
    for v_i in X:
        j=0
        for v_j in X:
            GassMatrix[i,j]=Gaussian(v_i.T,v_j.T,sigma)
            j+=1
        i+=1
    return GassMatrix
def Gaussian(x,z,sigma):
    return np.exp((-(np.linalg.norm(x-z)**2))/(2*sigma**2))

This is my current way.这是我目前的方式。 Is there any way I can use matrix operation to do this?有什么办法可以使用矩阵运算来做到这一点? X is the data points. X 是数据点。

I myself used the accepted answer for my image processing, but I find it (and the other answers) too dependent on other modules.我自己将接受的答案用于我的图像处理,但我发现它(和其他答案)过于依赖其他模块。 Therefore, here is my compact solution:因此,这是我的紧凑解决方案:

import numpy as np
   
def gkern(l=5, sig=1.):
    """\
    creates gaussian kernel with side length `l` and a sigma of `sig`
    """
    ax = np.linspace(-(l - 1) / 2., (l - 1) / 2., l)
    gauss = np.exp(-0.5 * np.square(ax) / np.square(sig))
    kernel = np.outer(gauss, gauss)
    return kernel / np.sum(kernel)

Edit: Changed arange to linspace to handle even side lengths编辑:将 arange 更改为 linspace 以处理均匀的边长

Edit: Use separability for faster computation, thank you Yves Daoust .编辑:使用可分离性进行更快的计算,谢谢Yves Daoust

Do you want to use the Gaussian kernel for eg image smoothing?你想使用高斯核来进行图像平滑吗? If so, there's a function gaussian_filter() in scipy:如果是这样,scipy 中有一个函数gaussian_filter()

Updated answer更新答案

This should work - while it's still not 100% accurate, it attempts to account for the probability mass within each cell of the grid.这应该有效 - 虽然它仍然不是 100% 准确,但它试图解释网格每个单元格内的概率质量。 I think that using the probability density at the midpoint of each cell is slightly less accurate, especially for small kernels.我认为在每个单元格的中点使用概率密度稍微不太准确,尤其是对于小内核。 See https://homepages.inf.ed.ac.uk/rbf/HIPR2/gsmooth.htm for an example.有关示例,请参见https://homepages.inf.ed.ac.uk/rbf/HIPR2/gsmooth.htm

import numpy as np
import scipy.stats as st

def gkern(kernlen=21, nsig=3):
    """Returns a 2D Gaussian kernel."""

    x = np.linspace(-nsig, nsig, kernlen+1)
    kern1d = np.diff(st.norm.cdf(x))
    kern2d = np.outer(kern1d, kern1d)
    return kern2d/kern2d.sum()

Testing it on the example in Figure 3 from the link:通过链接在图 3 中的示例上对其进行测试:

gkern(5, 2.5)*273

gives

array([[ 1.0278445 ,  4.10018648,  6.49510362,  4.10018648,  1.0278445 ],
       [ 4.10018648, 16.35610171, 25.90969361, 16.35610171,  4.10018648],
       [ 6.49510362, 25.90969361, 41.0435344 , 25.90969361,  6.49510362],
       [ 4.10018648, 16.35610171, 25.90969361, 16.35610171,  4.10018648],
       [ 1.0278445 ,  4.10018648,  6.49510362,  4.10018648,  1.0278445 ]])

The original (accepted) answer below accepted is wrong The square root is unnecessary, and the definition of the interval is incorrect.接受下面的原始(接受)答案是错误的 平方根是不必要的,并且区间的定义是错误的。

import numpy as np
import scipy.stats as st

def gkern(kernlen=21, nsig=3):
    """Returns a 2D Gaussian kernel array."""

    interval = (2*nsig+1.)/(kernlen)
    x = np.linspace(-nsig-interval/2., nsig+interval/2., kernlen+1)
    kern1d = np.diff(st.norm.cdf(x))
    kernel_raw = np.sqrt(np.outer(kern1d, kern1d))
    kernel = kernel_raw/kernel_raw.sum()
    return kernel

I'm trying to improve on FuzzyDuck's answer here.我正在尝试改进FuzzyDuck 的回答 I think this approach is shorter and easier to understand.我认为这种方法更短也更容易理解。 Here I'm using signal.scipy.gaussian to get the 2D gaussian kernel.在这里,我使用signal.scipy.gaussian来获取 2D 高斯内核。

import numpy as np
from scipy import signal

def gkern(kernlen=21, std=3):
    """Returns a 2D Gaussian kernel array."""
    gkern1d = signal.gaussian(kernlen, std=std).reshape(kernlen, 1)
    gkern2d = np.outer(gkern1d, gkern1d)
    return gkern2d

Plotting it using matplotlib.pyplot :使用matplotlib.pyplot绘制它:

import matplotlib.pyplot as plt
plt.imshow(gkern(21), interpolation='none')

使用 matplotlib 绘制的高斯核

You may simply gaussian-filter a simple 2D dirac function , the result is then the filter function that was being used:您可以简单地高斯过滤一个简单的2D dirac 函数,结果就是正在使用的过滤器函数:

import numpy as np
import scipy.ndimage.filters as fi

def gkern2(kernlen=21, nsig=3):
    """Returns a 2D Gaussian kernel array."""

    # create nxn zeros
    inp = np.zeros((kernlen, kernlen))
    # set element at the middle to one, a dirac delta
    inp[kernlen//2, kernlen//2] = 1
    # gaussian-smooth the dirac, resulting in a gaussian filter mask
    return fi.gaussian_filter(inp, nsig)

A 2D gaussian kernel matrix can be computed with numpy broadcasting,可以使用 numpy 广播计算 2D 高斯核矩阵,

def gaussian_kernel(size=21, sigma=3):
    """Returns a 2D Gaussian kernel.
    Parameters
    ----------
    size : float, the kernel size (will be square)

    sigma : float, the sigma Gaussian parameter

    Returns
    -------
    out : array, shape = (size, size)
      an array with the centered gaussian kernel
    """
    x = np.linspace(- (size // 2), size // 2)
    x /= np.sqrt(2)*sigma
    x2 = x**2
    kernel = np.exp(- x2[:, None] - x2[None, :])
    return kernel / kernel.sum()

For small kernel sizes this should be reasonably fast.对于较小的内核大小,这应该相当快。

Note: this makes changing the sigma parameter easier with respect to the accepted answer.注意:这使得相对于接受的答案更容易更改 sigma 参数。

I tried using numpy only.我尝试只使用 numpy。 Here is the code这是代码

def get_gauss_kernel(size=3,sigma=1):
    center=(int)(size/2)
    kernel=np.zeros((size,size))
    for i in range(size):
       for j in range(size):
          diff=np.sqrt((i-center)**2+(j-center)**2)
          kernel[i,j]=np.exp(-(diff**2)/(2*sigma**2))
    return kernel/np.sum(kernel)

You can visualise the result using:您可以使用以下方法可视化结果:

plt.imshow(get_gauss_kernel(5,1))

这是输出

If you are a computer vision engineer and you need heatmap for a particular point as Gaussian distribution(especially for keypoint detection on image)如果您是计算机视觉工程师,并且需要将特定点的热图作为高斯分布(尤其是图像上的关键点检测)

def gaussian_heatmap(center = (2, 2), image_size = (10, 10), sig = 1):
    """
    It produces single gaussian at expected center
    :param center:  the mean position (X, Y) - where high value expected
    :param image_size: The total image size (width, height)
    :param sig: The sigma value
    :return:
    """
    x_axis = np.linspace(0, image_size[0]-1, image_size[0]) - center[0]
    y_axis = np.linspace(0, image_size[1]-1, image_size[1]) - center[1]
    xx, yy = np.meshgrid(x_axis, y_axis)
    kernel = np.exp(-0.5 * (np.square(xx) + np.square(yy)) / np.square(sig))
    return kernel

The usage and output使用和输出

kernel = gaussian_heatmap(center = (2, 2), image_size = (10, 10), sig = 1)
plt.imshow(kernel)
print("max at :", np.unravel_index(kernel.argmax(), kernel.shape))
print("kernel shape", kernel.shape)

max at : (2, 2)最大在 : (2, 2)

kernel shape (10, 10)内核形状 (10, 10)

均值 (2,2) 和 sigma 1.0 处的高斯分布

kernel = gaussian_heatmap(center = (25, 40), image_size = (100, 50), sig = 5)
plt.imshow(kernel)
print("max at :", np.unravel_index(kernel.argmax(), kernel.shape))
print("kernel shape", kernel.shape)

max at : (40, 25)最大 : (40, 25)

kernel shape (50, 100)内核形状 (50, 100)

高斯分布均值在

linalg.norm takes an axis parameter. linalg.norm采用axis参数。 With a little experimentation I found I could calculate the norm for all combinations of rows with通过一些实验,我发现我可以计算所有行组合的范数

np.linalg.norm(x[None,:,:]-x[:,None,:],axis=2)

It expands x into a 3d array of all differences, and takes the norm on the last dimension.它将x扩展为所有差异的 3d 数组,并在最后一个维度上取范数。

So I can apply this to your code by adding the axis parameter to your Gaussian :因此,我可以通过将axis参数添加到您的Gaussian来将其应用于您的代码:

def Gaussian(x,z,sigma,axis=None):
    return np.exp((-(np.linalg.norm(x-z, axis=axis)**2))/(2*sigma**2))

x=np.arange(12).reshape(3,4)
GaussianMatrix(x,1)

produces产生

array([[  1.00000000e+00,   1.26641655e-14,   2.57220937e-56],
       [  1.26641655e-14,   1.00000000e+00,   1.26641655e-14],
       [  2.57220937e-56,   1.26641655e-14,   1.00000000e+00]])

Matching:匹配:

Gaussian(x[None,:,:],x[:,None,:],1,axis=2)

array([[  1.00000000e+00,   1.26641655e-14,   2.57220937e-56],
       [  1.26641655e-14,   1.00000000e+00,   1.26641655e-14],
       [  2.57220937e-56,   1.26641655e-14,   1.00000000e+00]])

Building up on Teddy Hartanto's answer.以 Teddy Hartanto 的回答为基础。 You can just calculate your own one dimensional Gaussian functions and then use np.outer to calculate the two dimensional one.您可以计算自己的一维高斯函数,然后使用np.outer计算二维一维。 Very fast and efficient way.非常快速有效的方式。

With the code below you can also use different Sigmas for every dimension使用下面的代码,您还可以对每个维度使用不同的西格玛

import numpy as np
def generate_gaussian_mask(shape, sigma, sigma_y=None):
    if sigma_y==None:
        sigma_y=sigma
    rows, cols = shape

    def get_gaussian_fct(size, sigma):
        fct_gaus_x = np.linspace(0,size,size)
        fct_gaus_x = fct_gaus_x-size/2
        fct_gaus_x = fct_gaus_x**2
        fct_gaus_x = fct_gaus_x/(2*sigma**2)
        fct_gaus_x = np.exp(-fct_gaus_x)
        return fct_gaus_x

    mask = np.outer(get_gaussian_fct(rows,sigma), get_gaussian_fct(cols,sigma_y))
    return mask

Adapting th accepted answer by FuzzyDuck to match the results of this website: http://dev.theomader.com/gaussian-kernel-calculator/ I now present you with this definition:调整 FuzzyDuck 接受的答案以匹配本网站的结果: http ://dev.theomader.com/gaussian-kernel-calculator/ 我现在向您展示这个定义:

import numpy as np
import scipy.stats as st

def gkern(kernlen=21, sig=3):
    """Returns a 2D Gaussian kernel."""

    x = np.linspace(-(kernlen/2)/sig, (kernlen/2)/sig, kernlen+1)
    kern1d = np.diff(st.norm.cdf(x))
    kern2d = np.outer(kern1d, kern1d)
    return kern2d/kern2d.sum()

print(gkern(kernlen=5, sig=1))

output:输出:

[[0.003765   0.015019   0.02379159 0.015019   0.003765  ]
 [0.015019   0.05991246 0.0949073  0.05991246 0.015019  ]
 [0.02379159 0.0949073  0.15034262 0.0949073  0.02379159]
 [0.015019   0.05991246 0.0949073  0.05991246 0.015019  ]
 [0.003765   0.015019   0.02379159 0.015019   0.003765  ]]

A good way to do that is to use the gaussian_filter function to recover the kernel.一个很好的方法是使用 gaussian_filter 函数来恢复内核。 For instance:例如:

indicatrice = np.zeros((5,5))
indicatrice[2,2] = 1
gaussian_kernel = gaussian_filter(indicatrice, sigma=1)
gaussian_kernel/=gaussian_kernel[2,2]

This gives这给

array[[0.02144593, 0.08887207, 0.14644428, 0.08887207, 0.02144593],
       [0.08887207, 0.36828649, 0.60686612, 0.36828649, 0.08887207],
       [0.14644428, 0.60686612, 1.        , 0.60686612, 0.14644428],
       [0.08887207, 0.36828649, 0.60686612, 0.36828649, 0.08887207],
       [0.02144593, 0.08887207, 0.14644428, 0.08887207, 0.02144593]]

As I didn't find what I was looking for, I coded my own one-liner.由于没有找到我要找的东西,我编写了自己的单行代码。 You can modify it accordingly (according to the dimensions and the standard deviation).您可以相应地修改它(根据尺寸和标准偏差)。

Here is the one-liner function for a 3x5 patch for example.例如,这是 3x5 补丁的单行函数。

from scipy import signal

def gaussian2D(patchHeight, patchWidth, stdHeight=1, stdWidth=1):
    gaussianWindow = signal.gaussian(patchHeight, stdHeight).reshape(-1, 1)@signal.gaussian(patchWidth, stdWidth).reshape(1, -1)
    return gaussianWindow

print(gaussian2D(3, 5))

You get an output like this:你会得到这样的输出:

[[0.082085   0.36787944 0.60653066 0.36787944 0.082085  ]
[0.13533528  0.60653066 1.         0.60653066 0.13533528]
[0.082085   0.36787944 0.60653066 0.36787944 0.082085  ]]

You can read more about scipy's Gaussian here .您可以在此处阅读有关 scipy 高斯的更多信息。

Yet another implementation.又一个实现。

This is normalized so that for sigma > 1 and sufficiently large win_size , the total sum of the kernel elements equals 1 .这是标准化的,以便对于sigma > 1和足够大的win_size ,内核元素的总和等于1

def gaussian_kernel(win_size, sigma):
    t = np.arange(win_size)
    x, y = np.meshgrid(t, t)
    o = (win_size - 1) / 2
    r = np.sqrt((x - o)**2 + (y - o)**2)
    scale = 1 / (sigma**2 * 2 * np.pi)
    return scale * np.exp(-0.5 * (r / sigma)**2)

To generate a 5x5 kernel:要生成 5x5 内核:

gaussian_kernel(win_size=5, sigma=1)

I took a similar approach to Nils Werner's answer -- since convolution of any kernel with a Kronecker delta results in the kernel itself centered around that Kronecker delta -- but I made it slightly more general to deal with both odd and even dimensions.我对Nils Werner 的回答采取了类似的方法——因为任何 kernel 与 Kronecker delta 的卷积都会导致 kernel 本身以 Kronecker delta 为中心——但我让它稍微更通用一些来处理奇数和偶数维度。 In three lines:三行:

import scipy.ndimage as scim

def gaussian_kernel(dimension: int, sigma: float):
    dirac = np.zeros((dimension,dimension))
    dirac[(dimension-1)//2:dimension//2+1, (dimension-1)//2:dimension//2+1] = 1.0 / (1 + 3 * ((dimension + 1) % 2))
    return scim.gaussian_filter(dirac, sigma=sigma)

The second line creates either a single 1.0 in the middle of the matrix (if the dimension is odd), or a square of four 0.25 elements (if the dimension is even).第二行在矩阵中间创建一个1.0 (如果维数为奇数),或者创建一个由四个0.25元素组成的正方形(如果维数为偶数)。 The division could be moved to the third line too;该师也可以调到三线; the result is normalised either way.结果以任何一种方式归一化。

For those who like to have the kernel the matrix with one (odd) or four (even) 1.0 element(s) in the middle instead of normalisation, this works:对于那些喜欢 kernel 矩阵的人来说,中间有一个(奇数)或四个(偶数) 1.0元素而不是标准化,这有效:

import scipy.ndimage as scim

def gaussian_kernel(dimension: int, sigma: float, ones_in_the_middle=False):
    dirac = np.zeros((dimension,dimension))
    dirac[(dimension-1)//2:dimension//2+1, (dimension-1)//2:dimension//2+1] = 1.0
    kernel = scim.gaussian_filter(dirac, sigma=sigma)
    divisor = kernel[(dimension-1)//2, (dimension-1)//2] if ones_in_the_middle else 1 + 3 * ((dimension + 1) % 2)
    return kernel/divisor

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

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