C: smoothing noise with a box-blur convolution
Average each 3×3 neighbourhood of a small image to reduce an isolated bright pixel.
On this page
C two-dimensional arrays and nested loops.
- Average each 3×3 neighbourhood of a small image to reduce an isolated bright pixel.
- Predict the sample output and explain the state changes.
- Complete the practice task and check the boundary cases.
The concept
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.
Code example
#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;
}20 20 20 20 20 20 20 20 20
Follow the code
Open the walkthrough to inspect the example one step at a time.
Show guided walkthrough
#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;}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
| Step / line | What happens | State after step | Output 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 |
Behind the scenes
How C processes this code
The C compiler produces a native program. Variables occupy memory, and execution enters main.
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.