# An article to understand the Gaussian filter, with the principle and implementation process

Gaussian filter is a linear filter that can effectively suppress noise and smooth images. Its working principle is similar to that of the mean filter, in that it takes the mean value of the pixels in the filter window as the output. The coefficient of the window template is different from that of the mean filter. The template coefficient of the mean filter is the same as 1; while the template coefficient of the Gaussian filter decreases as the distance from the center of the template increases. Therefore, the Gaussian filter has a smaller degree of blurring on the image than the mean filter.

Gaussian filter is a linear filter that can effectively suppress noise and smooth images. Its working principle is similar to that of the mean filter, in that it takes the mean value of the pixels in the filter window as the output. The coefficient of the window template is different from that of the mean filter. The template coefficient of the mean filter is the same as 1; while the template coefficient of the Gaussian filter decreases as the distance from the center of the template increases. Therefore, the Gaussian filter has a smaller degree of blurring on the image than the mean filter.

What is a Gaussian filter

Since the name is Gaussian filter, it has a certain relationship with Gaussian distribution (normal distribution). A two-dimensional Gaussian function is as follows: Where (x, y) (x, y) is the point coordinate, which can be considered as an integer in image processing; σσ is the standard deviation. To obtain a template of a Gaussian filter, the Gaussian function can be discretized, and the obtained Gaussian function value is used as the coefficient of the template. For example, to generate a 3×33×3 Gaussian filter template, take the center position of the template as the coordinate origin for sampling. The coordinates of the template at each position are as follows (the x-axis is horizontal to the right, and the y-axis is vertically downward) In this way, the coordinates of each position are brought into the Gaussian function, and the obtained value is the coefficient of the template.

For the size of the window template is (2k+1)×(2k+1), the calculation formula of each element value in the template is as follows: The template thus calculated comes in two forms: decimal and integer.

The template in decimal form is the value obtained directly without any processing;

In the form of integers, normalization is required to normalize the value in the upper left corner of the template to 1, which will be described in detail below. When using an integer template, a coefficient needs to be added in front of the template, and the coefficient is the reciprocal of the sum of the template coefficients. Generation of Gaussian Templates

Knowing the principle of template generation, it is not difficult to implement

void generateGaussianTemplate(double window[], int ksize, double sigma)
{
static const double pi = 3.1415926;
int center = ksize / 2; // the center position of the template, which is the origin of the coordinates
double x2, y2;
for (int i = 0; i {
x2 = pow(i – center, 2);
for (int j = 0; j {
y2 = pow(j – center, 2);
double g = exp(-(x2 + y2) / (2 * sigma * sigma));
g /= 2 * pi * sigma;
window[i][j] = g;
}
}
double k = 1 / window; // normalize the upper left coefficient to 1
for (int i = 0; i {
for (int j = 0; j {
window[i][j] *= k;
}
}
}
A two-dimensional array is required to store the generated coefficients (it is assumed that the maximum size of the template will not exceed 11); the second parameter is the size of the template (do not exceed 11); the third parameter is more important and is Gaussian distributed standard deviation.

In the generation process, first, according to the size of the template, find the center position of the template ksize/2. Then there is the traversal, and the value of each coefficient in the template is calculated according to the function of the Gaussian distribution.

It should be noted that in the final normalization process, the reciprocal of the coefficient in the upper left corner of the template is used as the normalization coefficient (the coefficient value in the upper left corner is normalized to 1), and each coefficient in the template is multiplied by the value (the reciprocal of the coefficient in the upper left corner), and then rounding the obtained value to obtain an integer Gaussian filter template. The screenshot below generates a template with a size of 3×3, σ=0.83×3, σ=0.8 After rounding the above solution results, the following template is obtained:

This template is more familiar, it is a template generated according to the Gaussian function of σ=0.8.

The generation of the decimal form is also relatively simple, the normalization process is removed, and after the solution process, each coefficient of the template is divided by the sum of all coefficients. The specific code is as follows:

void generateGaussianTemplate(double window[], int ksize, double sigma)
{
static const double pi = 3.1415926;
int center = ksize / 2; // the center position of the template, which is the origin of the coordinates
double x2, y2;
double sum = 0;
for (int i = 0; i {
x2 = pow(i – center, 2);
for (int j = 0; j {
y2 = pow(j – center, 2);
double g = exp(-(x2 + y2) / (2 * sigma * sigma));
g /= 2 * pi * sigma;
sum += g;
window[i][j] = g;
}
}
//double k = 1 / window; // normalize the upper left coefficient to 1
for (int i = 0; i {
for (int j = 0; j {
window[i][j] /= sum;
}
}
}
3×3, σ=0.8 decimal template.

The meaning and selection of σσ value

Through the above implementation process, it is not difficult to find that the most important parameter for the generation of the Gaussian filter template is the standard deviation σσ of the Gaussian distribution. The standard deviation represents the degree of dispersion of the data. If σσ is small, the center coefficient of the generated template is large, and the surrounding coefficients are small, so the smoothing effect on the image is not very obvious; on the contrary, if σσ is large, the generated template will be generated. The difference between the coefficients of the template is not very big. Compared with the mean template, the smoothing effect on the image is more obvious.

Look at the probability distribution density map of the next-dimensional Gaussian distribution:

The horizontal axis represents the possible value x, and the vertical axis represents the probability distribution density F(x), then it is not difficult to understand that the area of ​​the graph enclosed by such a curve and the x-axis is 1. σσ (standard deviation) determines the width of this graph. It can be concluded that the larger the σσ, the wider the graph, the smaller the peak, and the smoother the graph; the smaller the σσ, the narrower and more concentrated the graph is. The sharper it is, the more drastic the graphics change. This is actually very easy to understand. If the sigma, that is, the larger the standard deviation, means that the density distribution must be relatively scattered. Since the area is 1, the peak part is reduced, and the width is wider (the distribution is more dispersed). Similarly, when σσ is more If it is small, it means that the density distribution is more concentrated, so the sharper the peak, the narrower the width!

So the following conclusions can be drawn:

The larger the σσ, the more dispersed the distribution, and the proportion of each part is not very different, so the value of each element of the generated template is not very different, similar to the average template;

The smaller the σσ, the more concentrated the distribution, and the proportion of the middle part is much higher than that of the other parts. It is reflected in the Gaussian template that the value of the central element is much larger than the value of other elements, so it is naturally equivalent to the calculation of the middle value.

Implementation based on OpenCV

In the generation of Gaussian templates, its simple implementation is no different from other spatial filters. The specific code is as follows:

void GaussianFilter(const Mat &src, Mat &dst, int ksize, double sigma)
{
CV_Assert(src.channels() || src.channels() == 3); // only handle single-channel or three-channel images
const static double pi = 3.1415926;
// Generate Gaussian filter template based on window size and sigma
// Apply for a two-dimensional array to store the generated Gaussian template matrix
double **templatematrix = new double*[ksize];
for (int i = 0; i templateMatrix[i] = new double[ksize];
int origin = ksize / 2; // take the center of the template as the origin
double x2, y2;
double sum = 0;
for (int i = 0; i {
x2 = pow(i – origin, 2);
for (int j = 0; j {
y2 = pow(j – origin, 2);
// The constant before the Gaussian function does not need to be calculated, it will be eliminated in the process of normalization
double g = exp(-(x2 + y2) / (2 * sigma * sigma));
sum += g;
templateMatrix[i][j] = g;
}
}
for (int i = 0; i {
for (int j = 0; j {
templateMatrix[i][j] /= sum;
cout }
cout }
// apply the template to the image
int border = ksize / 2;
copyMakeBorder(src, dst, border, border, border, border, BorderTypes::BORDER_REFLECT);
int channels = dst.channels();
int rows = dst.rows – border;
int cols = dst.cols – border;
for (int i = border; i {
for (int j = border; j {
double sum = { 0 };
for (int a = -border; a {
for (int b = -border; b {
if (channels == 1)
{
sum += templateMatrix[border + a][border + b] *dst.at(i + a, j + b);
}
else if (channels == 3)
{
Vec3b rgb = dst.at(i + a, j + b);
auto k = templateMatrix[border + a][border + b];
sum += k * rgb;
sum += k * rgb;
sum += k * rgb;
}
}
}
for (int k = 0; k {
if (sum[k] sum[k] = 0;
else if (sum[k] > 255)
sum[k] = 255;
}
if (channels == 1)
dst.at(i, j) = static_cast(sum);
else if (channels == 3)
{
Vec3b rgb = { static_cast(sum), static_cast(sum), static_cast(sum) };
dst.at(i, j) = rgb;
}
}
}
// free the template array
for (int i = 0; i delete[] templateMatrix[i];
delete[] templateMatrix;
}
Only processing single-channel or three-channel images, after the template is generated, its filtering (convolution process) is relatively simple. However, for such a Gaussian filtering process, the number of loop operations is m×n×ksize2, where m, n are the size of the image; ksize is the size of the Gaussian filter. In this way, its time complexity is O(ksize2), which increases squarely with the size of the filter template. When the size of the Gaussian filter is large, its operational efficiency is extremely low. In order to improve the operation speed of filtering, the two-dimensional Gaussian filtering process can be decomposed.

Separate implementation of Gaussian filtering

Due to the separability of the Gaussian function, the Gaussian filter with larger size can be divided into two steps: first convolve the image with a one-dimensional Gaussian function in the horizontal (vertical) direction; then convolve the result in the vertical direction. The (horizontal) direction uses the template obtained by the same one-dimensional Gaussian function for convolution operation. The specific implementation code is as follows:

// separate computation
void separateGaussianFilter(const Mat &src, Mat &dst, int ksize, double sigma)
{
CV_Assert(src.channels()==1 || src.channels() == 3); // Only handle single-channel or three-channel images
// Generate a one-dimensional Gaussian filter template
double *matrix = new double[ksize];
double sum = 0;
int origin = ksize / 2;
for (int i = 0; i {
// The constant before the Gaussian function does not need to be calculated, it will be eliminated in the process of normalization
double g = exp(-(i – origin) * (i – origin) / (2 * sigma * sigma));
sum += g;
matrix[i] = g;
}
// Normalized
for (int i = 0; i matrix[i] /= sum;
// apply the template to the image
int border = ksize / 2;
copyMakeBorder(src, dst, border, border, border, border, BorderTypes::BORDER_REFLECT);
int channels = dst.channels();
int rows = dst.rows – border;
int cols = dst.cols – border;
// horizontal direction
for (int i = border; i {
for (int j = border; j {
double sum = { 0 };
for (int k = -border; k {
if (channels == 1)
{
sum += matrix[border + k] *dst.at(i, j + k); // The row does not change, the column changes; do the convolution in the horizontal direction first
}
else if (channels == 3)
{
Vec3b rgb = dst.at(i, j + k);
sum += matrix[border + k] *rgb;
sum += matrix[border + k] *rgb;
sum += matrix[border + k] *rgb;
}
}
for (int k = 0; k {
if (sum[k] sum[k] = 0;
else if (sum[k] > 255)
sum[k] = 255;
}
if (channels == 1)
dst.at(i, j) = static_cast(sum);
else if (channels == 3)
{
Vec3b rgb = { static_cast(sum), static_cast(sum), static_cast(sum) };
dst.at(i, j) = rgb;
}
}
}
// Vertically
for (int i = border; i {
for (int j = border; j {
double sum = { 0 };
for (int k = -border; k {
if (channels == 1)
{
sum += matrix[border + k] *dst.at(i + k, j); // the column does not change, the row changes; convolution in the vertical direction
}
else if (channels == 3)
{
Vec3b rgb = dst.at(i + k, j);
sum += matrix[border + k] *rgb;
sum += matrix[border + k] *rgb;
sum += matrix[border + k] *rgb;
}
}
for (int k = 0; k {
if (sum[k] sum[k]= 0;
else if (sum[k]> 255)
sum[k]= 255;
}
if (channels == 1)
dst.at(i, j) = static_cast(sum);
else if (channels == 3)
{
Vec3b rgb = { static_cast(sum), static_cast(sum), static_cast(sum) };
dst.at(i, j) = rgb;
}
}
}
delete[] matrix;
}
The code is not refactored long, but its implementation principle is relatively simple. First, get the template of the one-dimensional Gaussian function. In the process of convolution (filtering), keep the rows unchanged, the columns change, and perform the convolution operation in the horizontal direction; then, on the results obtained above, keep the columns without edges and the rows Change, do the convolution operation in the vertical direction. In this way, the time complexity of the algorithm is O(ksize)O(ksize), and the amount of operation and the template size of the filter increase linearly.

There is also a GaussianBlur package for Gaussian filters in OpenCV, which is declared as follows:

CV_EXPORTS_W void GaussianBlur( InputArray src, OutputArray dst, Size ksize,
double sigmaX, double sigmaY = 0,
int borderType = BORDER_DEFAULT );

The standard deviation of the two-dimensional Gaussian function should have a standard deviation in the x and y directions respectively. In the above code, the standards in the x and y directions have been set to be equal. In the Gaussian filter in OpenCV, it can be found in Set different standard deviations in the x and y directions.

The following figure is a comparison of the results of the Gaussian filter implemented by yourself and the GaussianBlur in OpenCV

The above picture is a Gaussian filter of 5×5, σ=0.8, it can be seen that the results obtained by the two implementations are not very different.

Summarize

Gaussian filter is a linear smoothing filter whose template is obtained by discrete two-dimensional Gaussian function. Since the center value of the Gaussian template is the largest and gradually decreases around it, the filtered result is better than the mean filter.

The most important parameter of the Gaussian filter is the standard deviation σσ of the Gaussian distribution. The standard deviation and the smoothing ability of the Gaussian filter have a great ability. The larger the σσ, the wider the frequency band of the Gaussian filter, and the smoother the image. good. By adjusting the σσ parameter, the suppression of the noise of the image and the blurring of the image can be balanced. 