Implementing Gaussian Blur - How to calculate convolution matrix (kernel)

后端 未结 7 2010
予麋鹿
予麋鹿 2020-12-22 21:14

My question is very close to this question: How do I gaussian blur an image without using any in-built gaussian functions?

The answer to this question is very good,

相关标签:
7条回答
  • 2020-12-22 21:39

    It's as simple as it sounds:

    double sigma = 1;
    int W = 5;
    double kernel[W][W];
    double mean = W/2;
    double sum = 0.0; // For accumulating the kernel values
    for (int x = 0; x < W; ++x) 
        for (int y = 0; y < W; ++y) {
            kernel[x][y] = exp( -0.5 * (pow((x-mean)/sigma, 2.0) + pow((y-mean)/sigma,2.0)) )
                             / (2 * M_PI * sigma * sigma);
    
            // Accumulate the kernel values
            sum += kernel[x][y];
        }
    
    // Normalize the kernel
    for (int x = 0; x < W; ++x) 
        for (int y = 0; y < W; ++y)
            kernel[x][y] /= sum;
    
    0 讨论(0)
  • 2020-12-22 21:53

    Gaussian blur in python using PIL image library. For more info read this: http://blog.ivank.net/fastest-gaussian-blur.html

    from PIL import Image
    import math
    
    # img = Image.open('input.jpg').convert('L')
    # r = radiuss
    def gauss_blur(img, r):
        imgData = list(img.getdata())
    
        bluredImg = Image.new(img.mode, img.size)
        bluredImgData = list(bluredImg.getdata())
    
        rs = int(math.ceil(r * 2.57))
    
        for i in range(0, img.height):
            for j in range(0, img.width):
                val = 0
                wsum = 0
                for iy in range(i - rs, i + rs + 1):
                    for ix in range(j - rs, j + rs + 1):
                        x = min(img.width - 1, max(0, ix))
                        y = min(img.height - 1, max(0, iy))
                        dsq = (ix - j) * (ix - j) + (iy - i) * (iy - i)
                        weight = math.exp(-dsq / (2 * r * r)) / (math.pi * 2 * r * r)
                        val += imgData[y * img.width + x] * weight
                        wsum += weight 
                bluredImgData[i * img.width + j] = round(val / wsum)
    
        bluredImg.putdata(bluredImgData)
        return bluredImg
    
    0 讨论(0)
  • 2020-12-22 21:56

    To implement the gaussian blur you simply take the gaussian function and compute one value for each of the elements in your kernel.

    Usually you want to assign the maximum weight to the central element in your kernel and values close to zero for the elements at the kernel borders. This implies that the kernel should have an odd height (resp. width) to ensure that there actually is a central element.

    To compute the actual kernel elements you may scale the gaussian bell to the kernel grid (choose an arbitrary e.g. sigma = 1 and an arbitrary range e.g. -2*sigma ... 2*sigma) and normalize it, s.t. the elements sum to one. To achieve this, if you want to support arbitrary kernel sizes, you might want to adapt the sigma to the required kernel size.

    Here's a C++ example:

    #include <cmath>
    #include <vector>
    #include <iostream>
    #include <iomanip>
    
    double gaussian( double x, double mu, double sigma ) {
        const double a = ( x - mu ) / sigma;
        return std::exp( -0.5 * a * a );
    }
    
    typedef std::vector<double> kernel_row;
    typedef std::vector<kernel_row> kernel_type;
    
    kernel_type produce2dGaussianKernel (int kernelRadius) {
      double sigma = kernelRadius/2.;
      kernel_type kernel2d(2*kernelRadius+1, kernel_row(2*kernelRadius+1));
      double sum = 0;
      // compute values
      for (int row = 0; row < kernel2d.size(); row++)
        for (int col = 0; col < kernel2d[row].size(); col++) {
          double x = gaussian(row, kernelRadius, sigma)
                   * gaussian(col, kernelRadius, sigma);
          kernel2d[row][col] = x;
          sum += x;
        }
      // normalize
      for (int row = 0; row < kernel2d.size(); row++)
        for (int col = 0; col < kernel2d[row].size(); col++)
          kernel2d[row][col] /= sum;
      return kernel2d;
    }
    
    int main() {
      kernel_type kernel2d = produce2dGaussianKernel(3);
      std::cout << std::setprecision(5) << std::fixed;
      for (int row = 0; row < kernel2d.size(); row++) {
        for (int col = 0; col < kernel2d[row].size(); col++)
          std::cout << kernel2d[row][col] << ' ';
        std::cout << '\n';
      }
    }
    

    The output is:

    $ g++ test.cc && ./a.out
    0.00134 0.00408 0.00794 0.00992 0.00794 0.00408 0.00134 
    0.00408 0.01238 0.02412 0.03012 0.02412 0.01238 0.00408 
    0.00794 0.02412 0.04698 0.05867 0.04698 0.02412 0.00794 
    0.00992 0.03012 0.05867 0.07327 0.05867 0.03012 0.00992 
    0.00794 0.02412 0.04698 0.05867 0.04698 0.02412 0.00794 
    0.00408 0.01238 0.02412 0.03012 0.02412 0.01238 0.00408 
    0.00134 0.00408 0.00794 0.00992 0.00794 0.00408 0.00134 
    

    As a simplification you don't need to use a 2d-kernel. Easier to implement and also more efficient to compute is to use two orthogonal 1d-kernels. This is possible due to the associativity of this type of a linear convolution (linear separability). You may also want to see this section of the corresponding wikipedia article.


    Here's the same in Python (in the hope someone might find it useful):

    from math import exp
    
    def gaussian(x, mu, sigma):
      return exp( -(((x-mu)/(sigma))**2)/2.0 )
    
    #kernel_height, kernel_width = 7, 7
    kernel_radius = 3 # for an 7x7 filter
    sigma = kernel_radius/2. # for [-2*sigma, 2*sigma]
    
    # compute the actual kernel elements
    hkernel = [gaussian(x, kernel_radius, sigma) for x in range(2*kernel_radius+1)]
    vkernel = [x for x in hkernel]
    kernel2d = [[xh*xv for xh in hkernel] for xv in vkernel]
    
    # normalize the kernel elements
    kernelsum = sum([sum(row) for row in kernel2d])
    kernel2d = [[x/kernelsum for x in row] for row in kernel2d]
    
    for line in kernel2d:
      print ["%.3f" % x for x in line]
    

    produces the kernel:

    ['0.001', '0.004', '0.008', '0.010', '0.008', '0.004', '0.001']
    ['0.004', '0.012', '0.024', '0.030', '0.024', '0.012', '0.004']
    ['0.008', '0.024', '0.047', '0.059', '0.047', '0.024', '0.008']
    ['0.010', '0.030', '0.059', '0.073', '0.059', '0.030', '0.010']
    ['0.008', '0.024', '0.047', '0.059', '0.047', '0.024', '0.008']
    ['0.004', '0.012', '0.024', '0.030', '0.024', '0.012', '0.004']
    ['0.001', '0.004', '0.008', '0.010', '0.008', '0.004', '0.001']
    
    0 讨论(0)
  • 2020-12-22 21:57

    OK, a late answer but in case of...

    Using the @moooeeeep answer, but with numpy;

    import numpy as np
    radius = 3
    sigma = radius/2.
    
    k = np.arange(2*radius +1)
    row = np.exp( -(((k - radius)/(sigma))**2)/2.)
    col = row.transpose()
    out = np.outer(row, col)
    out = out/np.sum(out)
    for line in out:
        print(["%.3f" % x for x in line])
    

    Just a bit less of lines.

    0 讨论(0)
  • 2020-12-22 21:58

    You can create a Gaussian kernel from scratch as noted in MATLAB documentation of fspecial. Please read the Gaussian kernel creation formula in the algorithms part in that page and follow the code below. The code is to create an m-by-n matrix with sigma = 1.

    m = 5; n = 5;
    sigma = 1;
    [h1, h2] = meshgrid(-(m-1)/2:(m-1)/2, -(n-1)/2:(n-1)/2);
    hg = exp(- (h1.^2+h2.^2) / (2*sigma^2));
    h = hg ./ sum(hg(:));
    
    h =
    
        0.0030    0.0133    0.0219    0.0133    0.0030
        0.0133    0.0596    0.0983    0.0596    0.0133
        0.0219    0.0983    0.1621    0.0983    0.0219
        0.0133    0.0596    0.0983    0.0596    0.0133
        0.0030    0.0133    0.0219    0.0133    0.0030
    

    Observe that this can be done by the built-in fspecial as follows:

    fspecial('gaussian', [m n], sigma)
    ans =
    
        0.0030    0.0133    0.0219    0.0133    0.0030
        0.0133    0.0596    0.0983    0.0596    0.0133
        0.0219    0.0983    0.1621    0.0983    0.0219
        0.0133    0.0596    0.0983    0.0596    0.0133
        0.0030    0.0133    0.0219    0.0133    0.0030
    

    I think it is straightforward to implement this in any language you like.

    EDIT: Let me also add the values of h1 and h2 for the given case, since you may be unfamiliar with meshgrid if you code in C++.

    h1 =
    
        -2    -1     0     1     2
        -2    -1     0     1     2
        -2    -1     0     1     2
        -2    -1     0     1     2
        -2    -1     0     1     2
    
    h2 =
    
        -2    -2    -2    -2    -2
        -1    -1    -1    -1    -1
         0     0     0     0     0
         1     1     1     1     1
         2     2     2     2     2
    
    0 讨论(0)
  • 2020-12-22 21:58
     function kernel = gauss_kernel(m, n, sigma)
     % Generating Gauss Kernel
    
     x = -(m-1)/2 : (m-1)/2;
     y = -(n-1)/2 : (n-1)/2;
    
     for i = 1:m
         for j = 1:n
             xx(i,j) = x(i);
             yy(i,j) = y(j);
         end
     end
    
     kernel = exp(-(xx.*xx + yy.*yy)/(2*sigma*sigma));
    
     % Normalize the kernel
     kernel  = kernel/sum(kernel(:));
    
     % Corresponding function in MATLAB
     % fspecial('gaussian', [m n], sigma)
    
    0 讨论(0)
提交回复
热议问题