What is wrong with my 2D Array Gaussian Blur function in C++?

Viewed 942

I am making a simple Gaussian blur function for a 2D array that is supposed to represent an image. The function just prints out the array values at the end (no actual image processing going on here). I was pretty sure that I had implemented everything correct, but the values I am getting for (N=3, sigma=1.5) are much lower than expected based on this calculator: http://dev.theomader.com/gaussian-kernel-calculator/

I am following this equation:

enter image description here

void gaussian_filter(int N, double sigma) {
  
  double k[N][N];
  for(int i=0; i<N; i++) { //Initialize kernal to 0
    for(int j=0; j<N; j++) {
      k[i][j] = 0;
    }
  }
   
  double sum = 0.0; //There is an issue somewhere in this block of code
  int change = (N/2);
  double r, s = change * sigma * sigma;
  for (int x = -change; x <= change; x++) {
    for(int y = -change; y <= change; y++) {
      r = sqrt(x*x + y*y);
      k[x + change][y + change] = (exp(-(r*r)/s))/(M_PI * s);
      sum += k[x + change][y + change];
    }
  }
  
  for(int i = 0; i < N; ++i) { //Normalize
    for(int j = 0; j < N; ++j) {
      k[i][j] /= sum;
    }
  }
  
  for(int i = 0; i < N; ++i) { //Print out array
    for (int j = 0; j < N; ++j)
      cout<<k[i][j]<<"\t";
    }
    cout<<endl;
  }    
}

Here is the expected output for N=3 and Sigma=1.5 enter image description here

Here is the current broken output for N=3 and Sigma=1.5 enter image description here

2 Answers

Why does s depend on change? I think you should do:

double r, s = 2 * sigma * sigma;
// instead of
// double r, s = change * sigma * sigma;

That website computes Gaussian kernels in an unorthodox manner:

The weights are calculated by numerical integration of the continuous gaussian distribution over each discrete kernel tap.

That is, it samples a continuous Gaussian kernel that has been convolved with a uniform (“box”) filter of 1 pixel wide. The resulting Gaussian is wider than advertised. I advise against this method.

The proper way to create a Gaussian kernel is to just sample the Gaussian function at given integer locations, for example x = [-3, -2, -1, 0, 1, 2, 3].

Do note that a 3-pixel kernel is not wide enough to represent a Gaussian. It is important to sample the tail of the curve, without it, the kernel doesn’t have the good properties of the Gaussian kernel. I recommend sampling up to 3 sigma to each side, leading to 2*ceil(3*sigma)+1 pixels. 2 sigma is the bare minimum, useful only when speed is more important than good results.

Do also note that the Gaussian is separable, you can apply two 1D kernels in succession, rather than a single 2D kernel. For the 9x9 kernel you get for sigma=1.5, this translates to 9+9=18 multiplications and additions, compared to 9x9=81 for the 2D kernel. This is a significant saving!

Related