Java: filtering an image with a convolution kernel
Slide a 3×3 averaging kernel over a small grayscale image to smooth it.
class Main {
public static void main(String[] args) {
int[][] image = {
{10, 10, 10, 10, 10},
{10, 10, 10, 10, 10},
{10, 10, 80, 80, 80},
{10, 10, 80, 80, 80},
{10, 10, 80, 80, 80},
};
int size = image.length - 2;
int[][] out = new int[size][size];
for (int r = 0; r < size; r++) {
for (int c = 0; c < size; c++) {
int sum = 0;
for (int kr = 0; kr < 3; kr++) {
for (int kc = 0; kc < 3; kc++) {
sum += image[r + kr][c + kc];
}
}
out[r][c] = sum / 9;
}
}
for (int[] row : out) {
System.out.println(row[0] + " " + row[1] + " " + row[2]);
}
}
}17 25 33 25 41 56 33 56 80
Explanation
A grayscale image is a grid of numbers where each pixel stores brightness. Convolution slides a small grid of weights, the kernel, over the image. At each position the pixels under the kernel are multiplied by the weights and summed to give one output pixel.
A kernel of all ones divided by 9 replaces each pixel by the average of its 3×3 neighbourhood, which blurs the image. This example computes only positions where the kernel fits entirely inside the image, so a 5×5 image gives a 3×3 result. Real libraries also pad the border so the output keeps the input size.
Explore the dry run
class Main { public static void main(String[] args) { int[][] image = { {10, 10, 10, 10, 10}, {10, 10, 10, 10, 10}, {10, 10, 80, 80, 80}, {10, 10, 80, 80, 80}, {10, 10, 80, 80, 80}, }; int size = image.length - 2; int[][] out = new int[size][size]; for (int r = 0; r < size; r++) { for (int c = 0; c < size; c++) { int sum = 0; for (int kr = 0; kr < 3; kr++) { for (int kc = 0; kc < 3; kc++) { sum += image[r + kr][c + kc]; } } out[r][c] = sum / 9; } } for (int[] row : out) { System.out.println(row[0] + " " + row[1] + " " + row[2]); } }}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 |
|---|---|---|---|
| A 3×3 kernel fits in 3 positions along each side of a 5×5 image. | output is 3×3 | — | |
| Add the nine pixels under the kernel; every weight is 1. | top-left window sum = 160 | — | |
| Divide by 9 to average them: 160 / 9 is 17 with integer division. | out[0][0] = 17 | — | |
| The sharp step between 10 and 80 has become a gradual ramp. | out[0][0] = 17 | 17 25 33 25 41 56 33 56 80 |
Cost and common mistakes
O(R·C·k²) for an R×C image and a k×k kernel.
- Integer division drops fractions. Use double when the exact average matters.
- Strictly, convolution flips the kernel before sliding it; most libraries actually compute correlation. For a symmetric kernel such as this one the two are identical.
- Reading outside the array throws ArrayIndexOutOfBoundsException, which is why the loops stop before the border.
Try it yourself
What is out[2][2]?
Reveal answer and reasoning
All nine pixels under that window are 80, so the average is 80.