Реализация Gaussian Blur - Как рассчитать матрицу свертки (ядро)

29

Мой вопрос очень близок к этому вопросу: Как гауссовское размытие изображения без использования встроенных гауссовских функций?

Ответ на этот вопрос очень хороший, но он не дает примера фактического вычисления реального гауссовского ядра фильтра. Ответ дает произвольное ядро ​​и показывает, как применять фильтр, используя это ядро, но не как вычислить реальное ядро. Я пытаюсь реализовать размытие Gaussian в С++ или Matlab с нуля, поэтому мне нужно знать, как вычислить ядро ​​с нуля.

Я был бы признателен, если бы кто-то мог вычислить истинное гауссовское ядро ​​фильтра с использованием любой маленькой примерной матрицы изображений.

  • 0
    Вы читали это: en.wikipedia.org/wiki/Gaussian_function ?
  • 0
    Или даже это: en.wikipedia.org/wiki/Gaussian_blur
Показать ещё 2 комментария
Теги:
filter
gaussian
blur

5 ответов

34
Лучший ответ

Вы можете создать гауссовское ядро ​​с нуля, как указано в документации MATLAB fspecial. Прочтите формулу создания ядра Gaussian в части алгоритмов на этой странице и следуйте приведенному ниже коду. Код состоит в создании матрицы m-by-n с 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

Обратите внимание, что это можно сделать с помощью встроенного fspecial следующим образом:

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

Я думаю, что это просто реализовать на любом языке, который вам нравится.

EDIT: Позвольте мне также добавить значения для h1 и h2 для данного случая, так как вы можете быть не знакомы с meshgrid если вы код на С++.

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
  • 1
    Я набрал [h1, h2] = сетка (- (m-1) / 2: (m-1) / 2, - (n-1) / 2: (n-1) / 2) и получил h1 в диапазоне от От -2 до 2, а не от -1,5 до 1,5. Та же проблема с h2. Но мой результат тот же. Кроме того, почему вы использовали значения сетки в качестве значений в формуле? Что это означает, если вы рассчитывали это для изображения?
  • 0
    Ты прав! Я изменил m и n на 4, чтобы увидеть, работает ли код, а затем скопировал значения для этого случая вместо того, чтобы давать их для значения 5. Я исправил это, спасибо.
Показать ещё 2 комментария
22

Это так просто, как кажется:

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;
  • 11
    Это недостаток: вам нужно также нормализовать ядро, иначе изображение станет темнее в зависимости от W и сигмы. Проще говоря: получите сумму значений ядра и разделите каждое значение ядра на эту сумму.
  • 2
    @ Новичок - я решил изменить этот пост и добавить нормализацию. Это сделано для тех, кто хочет, чтобы решение C / C ++ использовало это прямо. Хороший улов!
Показать ещё 1 комментарий
13

Чтобы реализовать гауссовское размытие, вы просто берете гауссовую функцию и вычисляете одно значение для каждого из элементов в вашем ядре.

Обычно вы хотите назначить максимальный вес центральному элементу вашего ядра и значениям, близким к нулю для элементов на границах ядра. Это означает, что ядро ​​должно иметь нечетную высоту (соответственно ширину), чтобы гарантировать, что на самом деле есть центральный элемент.

Чтобы вычислить фактические элементы ядра, вы можете масштабировать гауссовский колокол в сетке ядра (выберите произвольное, например, sigma = 1 и произвольный диапазон, например -2*sigma ... 2*sigma), и нормализуйте его, s.t. элементы суммируются до единицы. Чтобы достичь этого, если вы хотите поддерживать произвольные размеры ядра, вам может потребоваться адаптировать сигма к требуемому размеру ядра.

Вот пример С++:

#include <cmath>
#include <vector>
#include <iostream>
#include <iomanip>

double gaussian (double x, double mu, double sigma) {
  return std::exp( -(((x-mu)/(sigma))*((x-mu)/(sigma)))/2.0 );
}

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';
  }
}

Вывод:

$ 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 

В качестве упрощения вам не нужно использовать 2d-ядро. Легче реализовать, а также более эффективно вычислить использование двух ортогональных ядер 1d. Это возможно из-за ассоциативности этого типа линейной свертки (линейной сепарабельности). Вы также можете увидеть этот раздел соответствующей статьи в википедии.


Здесь то же самое в Python (в надежде, кому-то это может показаться полезным):

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]

создает ядро:

['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
    @ user2023370 Почему вы думаете, что там должен быть PI?
0
 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

Размытие Gaussian в python с использованием библиотеки изображений PIL. Для получения дополнительной информации прочтите следующее: 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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню