Follow Up: Fun with Image Smoothing in Python

Follow me on Twitter @davidreynolds

I really wanted to finish this article last week, but I just didn't get around to it. This post is going to be on the things you can do with the Python Imaging Library when you implement your own kernels. Remember that depending on the kernel arguments you supply, you may get radically different results. The results may not even be a smoothed image; they could be embossed images, edge maps, etc. With that out of the way, let's get started.

There are a couple different ways you can implement your own image filters using PIL. The first way is easy for one-off filters that you probably won't use throughout your code. I mean, you could if you wanted to keep writing the same line of code over and over, but that's up to you. The way this is done is by making use of PIL's ImageFilter.Kernel class. You can create an object from this class that has all the common image filter arguments (size, kernel, scale, and offset).

# This is a custom image filter that produces exactly the same result as using ImageFilter.SMOOTH 
import sys, Image, ImageFilter 
def with_kernel_object(filename, outfile): 
    kernel = ImageFilter.Kernel((3,3), (1, 1, 1, 1, 5, 1, 1, 1, 1), 13, 0) 
    img = Image.open(filename) 
    img = img.filter(kernel) 
    img.save(outfile) 
 
if __name__ == '__main__': 
    with_kernel_object(sys.argv[1], sys.argv[2]) 

That's the quick and dirty way to implement your own image filters. It uses the same filter arguments as ImageFilter.SMOOTH so it yields an identical result.

The second way to implement your own image filters is to do it just like PIL does. For each image filter class that PIL provides, you'll see that they inherit from the ImageFilter.BuiltinFilter class which in turn inherits from the ImageFilter.Kernel class. The stock implementation for ImageFilter.SMOOTH is as follows:

# This is inside of the ImageFilter module (ImageFilter.py) 
class SMOOTH(BuiltinFilter): 
    name = "Smooth" 
    filterargs = (3, 3), 13, 0, ( 
        1, 1, 1, 
        1, 5, 1, 
        1, 1, 1 
    ) 

Where filterargs is the size of the kernel (3, 3), the scale factor (13), the offset (0), and the kernel itself. Now since the stock PIL filters are boring and I don't want this post to be about how the stock filters are implemented, I implemented my own image filter.

import sys, Image, ImageFilter 
class MYFILTER(ImageFilter.BuiltinFilter): 
    name = "My filter" 
    filterargs = (3, 3), 4, 0, ( 
        1, 1, 1, 
        1, 10, 1, 
        1, 1, 1 
    ) 
 
def with_my_filter(filename, outfile): 
    img = Image.open(filename) 
    img = img.filter(MYFILTER) 
    img.save(outfile) 
 
if __name__ == '__main__': 
    with_my_filter(sys.argv[1], sys.argv[2]) 

The result of running this filter on my picture is this:

Pretty cool, eh? I highly encourage you to implement your own filters and dig into the stock PIL image filters to see how they achieve certain effects. With your own image filters and the stock PIL image filters at your command, you may never have to write your own image processing algorithms at all.