Saltar al contenido

Implementando Gaussian Blur – Cómo calcular la matriz de convolución (núcleo)

Ricardo, miembro de este equipo, nos hizo el favor de escribir esta crónica ya que conoce muy bien el tema.

Solución:

Puede crear un núcleo gaussiano desde cero como se indica en la documentación de MATLAB de fspecial. Lea la fórmula de creación del núcleo gaussiano en la parte de algoritmos de esa página y siga el código a continuación. El código es crear una matriz m-by-n con 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

Tenga en cuenta que esto se puede hacer con el fspecial como sigue:

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

Creo que es sencillo implementar esto en cualquier idioma que desee.

EDITAR: Permítanme agregar también los valores de h1 y h2 para el caso dado, ya que es posible que no esté familiarizado con meshgrid si codificas en 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

Es tan simple como suena:

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;

Para implementar el desenfoque gaussiano, simplemente tome la función gaussiana y calcule un valor para cada uno de los elementos en su kernel.

Por lo general, desea asignar el peso máximo al elemento central de su kernel y valores cercanos a cero para los elementos en los bordes del kernel. Esto implica que el kernel debe tener una altura impar (resp. ancho) para garantizar que realmente haya un elemento central.

Para calcular los elementos reales del núcleo, puede escalar la campana gaussiana a la cuadrícula del núcleo (elija una arbitraria, por ejemplo, sigma = 1 y un rango arbitrario, por ejemplo -2*sigma ... 2*sigma) y normalizarlo, st los elementos suman uno. Para lograr esto, si desea admitir tamaños de kernel arbitrarios, es posible que desee adaptar el sigma al tamaño de kernel requerido.

Aquí hay un ejemplo de C++:

#include 
#include 
#include 
#include 

double gaussian( double x, double mu, double sigma ) 
    const double a = ( x - mu ) / sigma;
    return std::exp( -0.5 * a * a );


typedef std::vector kernel_row;
typedef std::vector 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';
  

La salida es:

$ 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 

Como simplificación, no necesita usar un kernel 2d. Más fácil de implementar y también más eficiente de calcular es usar dos núcleos 1d ortogonales. Esto es posible debido a la asociatividad de este tipo de convolución lineal (separabilidad lineal). Es posible que también desee ver esta sección del artículo de wikipedia correspondiente.


Aquí está lo mismo en Python (con la esperanza de que alguien pueda encontrarlo útil):

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]

produce el núcleo:

['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']

valoraciones y comentarios

Si haces scroll puedes encontrar las explicaciones de otros desarrolladores, tú también tienes la libertad de dejar el tuyo si dominas el tema.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *