C: smoothing noise with a box-blur convolution

Average each 3×3 neighbourhood of a small image to reduce an isolated bright pixel.

C
#include <stdio.h>

#define N 5

int main(void) {
  int image[N][N];
  for (int r = 0; r < N; r++) {
    for (int c = 0; c < N; c++) {
      image[r][c] = 10;
    }
  }
  image[2][2] = 100;

  for (int r = 1; r < N - 1; r++) {
    for (int c = 1; c < N - 1; c++) {
      int sum = 0;
      for (int kr = -1; kr <= 1; kr++) {
        for (int kc = -1; kc <= 1; kc++) {
          sum += image[r + kr][c + kc];
        }
      }
      printf("%4d", sum / 9);
    }
    printf("\n");
  }
  return 0;
}
Output
  20  20  20
  20  20  20
  20  20  20

Explanation

An image is a two-dimensional array of pixel values. Convolution slides a small kernel over it and, at each position, adds up the products of the kernel weights and the pixels underneath. A box blur uses a kernel of all equal weights, so each output pixel is the average of its neighbourhood.

The image below is flat (value 10) with a single bright pixel (value 100), like salt noise. After blurring, the bright pixel is spread across its neighbours and its peak is much lower. Only positions where the 3×3 window fits completely inside the image are computed, so a 5×5 input gives a 3×3 output.

Explore the dry run
Guided walkthroughReady
1#include <stdio.h>
2
3#define N 5
4
5int main(void) {
6 int image[N][N];
7 for (int r = 0; r < N; r++) {
8 for (int c = 0; c < N; c++) {
9 image[r][c] = 10;
10 }
11 }
12 image[2][2] = 100;
13
14 for (int r = 1; r < N - 1; r++) {
15 for (int c = 1; c < N - 1; c++) {
16 int sum = 0;
17 for (int kr = -1; kr <= 1; kr++) {
18 for (int kc = -1; kc <= 1; kc++) {
19 sum += image[r + kr][c + kc];
20 }
21 }
22 printf("%4d", sum / 9);
23 }
24 printf("\n");
25 }
26 return 0;
27}

Follow the example

Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.

View the complete dry-run table
Prepared trace of the published program. Some rows group repeated iterations.
Step / lineWhat happensState after stepOutput so far
One noisy bright pixel in the middle of a flat area.image[2][2] = 100
—
Add the nine pixels under the window. Every window contains the bright pixel, so each sum is 8·10 + 100 = 180.image[2][2] = 100
—
180 / 9 = 20 at every output position: the spike of 100 has been spread out.image[2][2] = 100
  20  20  20
  20  20  20
  20  20  20

Cost and common mistakes

O(R·C·k²) for an R×C image and a k×k kernel.

  • Integer division drops the fraction. Divide by 9.0 and store the result in a double if you need accuracy.
  • Blurring also blurs real edges. An edge-preserving filter such as the median filter removes salt noise without smearing edges as much.
  • Reading outside the array is undefined behaviour, which is why the loops stay one pixel away from the border.

Try it yourself

What is the blurred value if the bright pixel were on the border instead?

Reveal answer and reasoning

Windows that include it would sum 8·10 + 100 as before, but fewer windows contain a border pixel, so fewer outputs change.

Further reading