[Rod Stephens Books]
Index Books Python Examples About Rod Contact
[Mastodon] [Bluesky] [Facebook]
[Build Your Own Python Action Arcade!]

[Build Your Own Ray Tracer With Python]

[Beginning Database Design Solutions, Second Edition]

[Beginning Software Engineering, Second Edition]

[Essential Algorithms, Second Edition]

[The Modern C# Challenge]

[WPF 3d, Three-Dimensional Graphics with WPF and C#]

[The C# Helper Top 100]

[Interview Puzzles Dissected]

Title: Use image filters to blur and sharpen images, and to detect edges in Python

[A picture of Terry Pratchett after applying a blurring filter in Python]

This example adds new filters to the previous example Use image filters to emboss images in Python. See that example for basic information about applying filters to images.

This post explains how you can blur and sharpen an image, and how you can detect edges.

Blurring

To blur an image, the kernel should average the values of the pixels in an area. The simplest type of blurring filter, the box filter, uses a kernel where every entry has the same value and the filter's weight equals the sum of those values. This kind of kernel is particularly easy to create in Python.

Here's the Filter class's box_filter factory method.

@classmethod def box_filter(cls, size): '''Create a {size}x{size} low-pass box filter.''' return cls([[1 for _ in range(size)] for _ in range(size)])

This code uses a list comprehension to create a list of lists holding all 1s. The size of the filter is passed into the method so it's easy to create box filters of any size.

If you don't pass the Filter class's constructor a weight, the constructor sets weight equal to the sum of the values in the filter. That means the box filter's weight is size×size and the filter takes a simple average of the pixel values underneath it.

The following picture shows an image plus the result after blurring with box filters having widths of 3, 5, and 7.

[A picture after blurring with box filters having widths of 3, 5, and 7 in Python]
Gaussian filters also average the pixels under the kernel but they take a weighted average that gives greater weight to the pixels near the center of the kernel. The following code shows the Filter class's gaussian_filter factory method.

@classmethod def gaussian_filter(cls, size): '''Create a {size}x{size} low-pass Gaussian filter.''' # See also: scipy.ndimage.gaussian_filter # Make weight depend on distance to the center cell. mid_x = size // 2 mid_y = size // 2 kernel = [ [size - abs(y - mid_y) - abs(x - mid_x) for y in range(size)] for x in range(size)] return cls(kernel)

This code finds the kernel's middle row and column. It then uses a list comprehension to build a kernel where an entry's value equals the kernel size minus the distance from the entry's row and column to the center row and column. For example, the central entry is at the central row and column so its value is size. A corner entry is size / 2 positions away from the central vertically and horizontally, so its weight is size - size // 2 - size // 2. Because size is odd, 2 * size // 2 equals size - 1 so the corner kernel entries have weight 1.

As with the box kernels, the gaussian_filter method doesn't specify the filter's weight so the constructor sets it equal to the total of the kernel values so it produces a weighted average.

The following picture shows an image plus the result after blurring with Gaussian filters having widths of 3, 5, and 7.

[A picture after blurring with Gaussian filters having widths of 3, 5, and 7 in Python]
If you compare these pictures to the previous ones very carefully, you'll see that the Gaussian filter creates a result that's slightly closer to the original.

Note that these Gaussian kernels don't actually use a Gaussian distribution of values to set the kernel weights. You could use a Gaussian distribution, but the difference between this kernel and the previous ones is already pretty small so I'm not sure it would make a big difference.

Blurring filters allow low-frequency changes (areas where the image's color changes slowly) to pass into the final result, so these filters are also called "low-pass filters."

Sharpening

Basic sharpening filters give positive weight to the central pixel and negative weights to pixels farther from the center. That increases the differences between those pixels so changes to color are emphasized. Here are the Filter class's sharpening factory methods.

@classmethod def sharpen_filter1(cls): '''Create a 3x3 high-pass filter.''' return cls( [ [ 0, -1, 0], [-1, 5, -1], [ 0, -1, 0], ]) @classmethod def sharpen_filter2(cls): '''Create a 3x3 high-pass filter.''' return cls( [ [-1, -1, -1], [-1, 9, -1], [-1, -1, -1], ]) @classmethod def sharpen_filter3(cls): '''Create a 5x5 high-pass filter.''' return cls( [ [-1, -1, -1, -1, -1], [-1, 1, 2, 1, -1], [-1, 2, 5, 2, -1], [-1, 1, 2, 1, -1], [-1, -1, -1, -1, -1], ])

The following picture shows an image plus the result after sharpening by these three filters.
[A picture after sharpening with filters having widths of 3, 3, and 5 in Python]
The first sharpened image enhances the highlights on Sir Terry's glasses and jacket, and adds detail to his beard. The other two results are increasingly sharp to the point where they don't really look realistic.

Sharpening filters enhance high-frequency changes (areas where the image's color changes quickly), so these filters are also called "high-pass filters."

In my next post, I'll show how you can perform another kind of sharpening that provides a better result.

Detecting Edges

Edge detection filters are similar to sharpening filters except the central kernel entry is set to make the total of the entries equal zero. Places where the central pixel differs from the edge pixels produce relatively large results. Places where the central pixel has a value similar to the other pixels tend to give small results. Because the sum of the entries is zero, the areas where there is little change in color give a result near 0, so they are black.

Here are the program's edge filter factory methods.

@classmethod def edge_filter1(cls): '''Create an edge detection filter.''' # Get a sharpening filter. filter = cls.sharpen_filter1() # Set the middle entry to make the total 0. filter.kernel[1][1] = filter.kernel[1][1] - filter.get_total() return filter @classmethod def edge_filter2(cls): '''Create an edge detection filter.''' # Get a sharpening filter. filter = cls.sharpen_filter2() # Set the middle entry to make the total 0. filter.kernel[1][1] = filter.kernel[1][1] - filter.get_total() return filter @classmethod def edge_filter3(cls): '''Create an edge detection filter.''' # Get a sharpening filter. filter = cls.sharpen_filter3() # Set the middle entry to make the total 0. filter.kernel[2][2] = filter.kernel[2][2] - filter.get_total() return filter

Each of them starts with a sharpening filter and then sets the central kernel weight to make the total of all weights equal to zero.

The following picture shows an image plus the result after applying these three filters.

[A picture after applying edge detection filters in Python]
The largest sharpening filter created a pretty unrealistic result when we used it for sharpening, but the result when we use it for edge detection may be useful in some applications. For example, it might help a machine vision program identify objects in a picture.

Depending on the arrangement of pixels, some of the resulting red, green, and blue color components may be smaller than zero. The original implementation of the Filter class's apply method adjusts results so they lie between 0 and 255, so negative values are mapped to 0. An alternative approach would be to take the absolute value of the results. Then colors that are converted into negative values produce something you can see.

If you make that change and then apply the edge detection filters, you do get more light-colored pixels so the edges are thicker. The following picture shows the results of this approach.

[Taking the absolute values of the results makes edges thicker]
The result may be useful or the previous results may be better, depending on what you want to do with the edges.

Conclusion

This example demonstrates blurring, sharpening, and edge detection filters. Download the example to see additional details and to experiment with it. For example, you might want to try creating some filters of your own.

In my next post, I'll demonstrate another kind of sharpening filter that I think works in a surprising way and that often produces better results than the filters described here.

© 2025 Rocky Mountain Computer Consulting, Inc. All rights reserved.