Processing images with PIL

Why use Python for image processing if we can use WIMP (http://en.wikipedia.org/wiki/WIMP_(computing)) or WYSIWYG (http://en.wikipedia.org/wiki/WYSIWYG) to achieve the same goal? Python is used because we want to create an automated system to process images in real time without human support, thus optimizing the image pipeline.

Getting ready

Note that the PIL coordinate system assumes that the (0,0) coordinate is in the upper-left corner.

The Image module has a useful class and instance method for performing basic operations over a loaded image object (im):

  • im = Image.open(filename): This opens a file and loads the image into the im object.
  • im.crop(box): This crops the image inside the coordinates defined by box. box defines the left, upper, right, and lower pixel coordinates (for example, box = (0, 100, 100,100)).
  • im.filter(filter): This applies a filter on the image and returns a filtered image.
  • im.histogram(): This returns a histogram list for this image, where each item represents the number of pixels. The number of items in the list is 256 for single channel images, but if the image is not a single channel image, there can be more items in the list. For an RGB image, the list contains 768 items (one set of 256 values for each channel).
  • im.resize(size, filter): This resizes the image and uses a filter for resampling. The possible filters are NEAREST, BILINEAR, BICUBIC, and ANTIALIAS. The default is NEAREST.
  • im.rotate(angle, filter): This rotates an image in the counter clockwise direction.
  • im.split(): This splits the bands of an image and returns a tuple of individual bands. Useful for splitting an RGB image into three single band images.
  • im.transform(size, method, data, filter): This applies transformation on a given image using data and a filter. Transformation can be AFFINE, EXTENT, QUAD, and MESH. You can read more about transformation in the official documentation. Data defines the box in the original image where the transformation will be applied.

The ImageDraw module allows us to draw over the image, where we can use functions such as arc, ellipse, line, pieslice, point, and polygon to modify the pixels of the loaded image.

The ImageChops module contains a number of image channel operations (hence the name Chops) that can be used for image composition, painting, special effects, and other processing operations. Channel operations are allowed only for 8-bit images. Here are some interesting channel operations:

  • ImageChops.duplicate(image): This copies the current image into a new image object
  • ImageChops.invert(image): This inverts an image and returns a copy
  • ImageChops.difference(image1, image2): This is useful for verification that images are the same without visual inspection

The ImageFilter module contains the implementation of the kernel class that allows the creation of custom convolution kernels. This module also contains a set of healthy common filters that allows the application of well-known filters (BLUR and MedianFilter) to our image.

There are two types of filters provided by the ImageFilter module: fixed image enhancement filters and image filters that require certain arguments to be defined, for example, the size of kernel to be used.

Tip

We can easily get the list of all fixed filter names in IPython:

In [1]: import ImageFilter
In [2]: [ f for f in dir(ImageFilter) if f.isupper()]
Out[2]: 
['BLUR',
 'CONTOUR',
 'DETAIL',
 'EDGE_ENHANCE',
 'EDGE_ENHANCE_MORE',
 'EMBOSS',
 'FIND_EDGES',
 'SHARPEN',
 'SMOOTH',
 'SMOOTH_MORE']

The next example shows how we can apply all currently supported fixed filters on any supported image:

import os
import sys
from PIL import Image, ImageChops, ImageFilter


class DemoPIL(object):
    def __init__(self, image_file=None):
        self.fixed_filters = [ff for ff in dir(ImageFilter) if ff.isupper()]

        assert image_file is not None
        assert os.path.isfile(image_file) is True
        self.image_file = image_file
        self.image = Image.open(self.image_file)

    def _make_temp_dir(self):
        from tempfile import mkdtemp
        self.ff_tempdir = mkdtemp(prefix="ff_demo")

    def _get_temp_name(self, filter_name):
        name, ext = os.path.splitext(os.path.basename(self.image_file))
        newimage_file = name + "-" + filter_name + ext
        path = os.path.join(self.ff_tempdir, newimage_file)
        return path

    def _get_filter(self, filter_name):
        # note the use Python's eval() builtin here to return function object
        real_filter = eval("ImageFilter." + filter_name)
        return real_filter

    def apply_filter(self, filter_name):
        print "Applying filter: " + filter_name
        filter_callable = self._get_filter(filter_name)
        # prevent calling non-fixed filters for now
        if filter_name in self.fixed_filters:
            temp_img = self.image.filter(filter_callable)
        else:
            print "Can't apply non-fixed filter now."
        return temp_img

    def run_fixed_filters_demo(self):
        self._make_temp_dir()
        for ffilter in self.fixed_filters:
            temp_img = self.apply_filter(ffilter)
            temp_img.save(self._get_temp_name(ffilter))
        print "Images are in: {0}".format((self.ff_tempdir),)

if __name__ == "__main__":
    assert len(sys.argv) == 2
    demo_image = sys.argv[1]
    demo = DemoPIL(demo_image)
    # will create set of images in temporary folder
    demo.run_fixed_filters_demo()

We can run this easily from the command prompt:

$ pythonch06_rec01_01_pil_demo.py image.jpeg

We packed our little demo in the DemoPIL class, so we can extend it easily while sharing the common code around the run_fixed_filters_demo demo function. Common code here includes opening the image file, testing if the file is really a file, creating a temporary directory to hold our filtered images, building the filtered image filename, and printing useful information to the user. This way the code is organized in a better manner, and we can easily focus on our demo function, without touching other parts of the code.

This demo will open our image file and apply every fixed filter available in ImageFilter to it and save that new filtered image in a unique temporary directory. At the end of the process, the script prints the path of the temporary directory used so that we can check the output of the filters.

As an optional exercise, try extending this demo class to perform other filters available in ImageFilter on the given image.

How to do it...

The example in this section shows how we can process all the images in a certain folder. We specify a target path, and the program that reads all the image files in that target path (images folder) resizes them to a specified ratio (0.1 in this example), and saves each one in a target folder called thumbnail_folder:

import os
import sys
from PIL import Image


class Thumbnailer(object):
    def __init__(self, src_folder=None):
        self.src_folder = src_folder
        self.ratio = .3
        self.thumbnail_folder = "thumbnails"

    def _create_thumbnails_folder(self):
        thumb_path = os.path.join(self.src_folder, self.thumbnail_folder)
        if not os.path.isdir(thumb_path):
            os.makedirs(thumb_path)
    def _build_thumb_path(self, image_path):
        root = os.path.dirname(image_path)
        name, ext = os.path.splitext(os.path.basename(image_path))
        suffix = ".thumbnail"
        return os.path.join(root, self.thumbnail_folder, name + suffix + ext)

    def _load_files(self):
        files = set()
        for each in os.listdir(self.src_folder):
            each = os.path.abspath(self.src_folder + '/' + each)
            if os.path.isfile(each):
                files.add(each)
    return files

    def _thumb_size(self, size):
        return (int(size[0] * self.ratio), int(size[1] * self.ratio))

    def create_thumbnails(self):
        self._create_thumbnails_folder()
        files = self._load_files()

        for each in files:
            print "Processing: " + each
            try:
                img = Image.open(each)
                thumb_size = self._thumb_size(img.size)
                resized = img.resize(thumb_size, Image.ANTIALIAS)
                savepath = self._build_thumb_path(each)
                resized.save(savepath)
            except IOError as ex:
                print "Error: " + str(ex)

if __name__ == "__main__":
    # Usage:
    # ch06_rec01_02_pil_thumbnails.py my_images
    assert len(sys.argv) == 2
    src_folder = sys.argv[1]

    if not os.path.isdir(src_folder):
        print "Error: Path '{0}' does not exits.".format((src_folder))
        sys.exit(-1)
    thumbs = Thumbnailer(src_folder)
    # optionally set the name of each thumbnail folder relative to *src_folder*.
    thumbs.thumbnail_folder = "THUMBS"

    # define ratio to resize image to
    # 0.1 means the original image will be resized to 10% of its size
    thumbs.ratio = 0.1 
    
    # will create set of images in temporary folder
    thumbs.create_thumbnails()

How it works...

For the given folder src_folder, we load all the files in this folder and try to load each file using Image.open(); this is the logic of the create_thumbnails() function. If the file we try to load is not an image, IOError will be thrown, and it will print this error and skip to the next file in the sequence.

If we want to have more control over which files we load, we should change the _load_files() function to only include files with a certain extension (file type):

for each in os.listdir(self.src_folder):
    if os.path.isfile(each) and os.path.splitext(each) is in ('.jpg','.png'):
        self._files.add(each)

This is not foolproof as the file extension does not define the file type, it just helps the operating system to attach a default program to the file. But it works in the majority of cases and is simpler than reading a file header to determine the file content (which still does not guarantee that the file really is the first couple of bytes it says is).

There's more...

With PIL, although not used very often, we can easily convert images from one format to another. This is achievable with two simple operations: first, open an image in a source format using open(), and then save that image in another format using save(). The format is defined either implicitly via the filename extension (.png or .jpeg) or explicitly via the format of the argument passed to the save() function.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.227.183.153