Batch Image Processing with Python

November 15, 2008

If you want to make changes to a single image, such as resizing or converting from one file format to another, then you’ll probably load up the image in an editor and manually make the required changes. This approach is great for a single image, but it doesn’t really scale past more than a few images, at which point it becomes time consuming, not to mention boring!

This is where Python and the Python Imaging Library (or PIL) come in, allowing you to write scripts that process images in batch. The PNG2GIF converter I wrote back in August is just one example of batch image processing using Python and PIL. In this post I’m going to explore some other uses, and provide lots of example code.

Batch Processing

No matter what processing we want to do on our images the script outline will be the same: loop over all provided arguments, and perform the processing. The code to do this is shown below:

#!/usr/bin/env python
import sys
import Image

# Loop through all provided arguments
for i in range(1, len(sys.argv)):
	try:
		# Attempt to open an image file
		filepath = sys.argv[i]
		image = Image.open(filepath)
	except IOError, e:
		# Report error, and then skip to the next argument
		print "Problem opening", filepath, ":", e
		continue

	# Perform operations on the image here

This will allow our script to be called in all of the following ways:

batch_process.py image.gif
batch_process.py image1.png image2.gif image3.jpg
batch_process.py *.png
batch_process.py image1.gif, *.png

Processing

With the help of PIL the image processing is really simple. Below is a collection of just some of the operations we could perform. For more details see the PIL documentation.

# Resize an image
image = image.resize((width, height), Image.ANTIALIAS) 

# Convert to greyscale
image = image.convert('L')

# Blur
image = image.filter(ImageFilter.BLUR)

# Sharpen
image = image.filter(ImageFilter.SHARPEN)

Saving

Once we’ve processed the image we need to save the changes. In some instances we might just want to save over the original filename. If that’s the case we can just call the save method of image with its original filename. If we want to save the file under a different name, or as a different filetype we need to do a little more work:

	# Split our original filename into name and extension
	(name, extension) = os.path.splitext(filepath)

	# Save with "_changed" added to the filename
	image.save(name + '_changed' + extension)

	# Save the image as a JPG
	image.save(name + '.jpg')

Notice in the last example how PIL takes care of the conversion to JPG for us. All we do is provide the file extension and PIL does the rest for us.

A Complete Example

Each of the steps above can easily be put together to create a batch image processing script. Below is a complete script which creates thumbnails of all provided images, and saves them as PNG images, not matter what their original format:

#!/usr/bin/env python
# Batch thumbnail generation script using PIL

import sys
import os.path
import Image

thumbnail_size = (28, 28)

# Loop through all provided arguments
for i in range(1, len(sys.argv)):
	try:
		# Attempt to open an image file
		filepath = sys.argv[i]
		image = Image.open(filepath)
	except IOError, e:
		# Report error, and then skip to the next argument
		print "Problem opening", filepath, ":", e
		continue

	# Resize the image
	image = image.resize(thumbnail_size, Image.ANTIALIAS)

	# Split our original filename into name and extension
	(name, extension) = os.path.splitext(filepath)

	# Save the thumbnail as "(original_name)_thumb.png"
	image.save(name + '_thumb.png')

PNG2GIF: Convert PNG images to GIF

August 21, 2008

The PNG image file format is vastly superior to GIF, supporting many more colours and better transparency. IE6, however, doesn’t support PNG transparency without the use of a browser specific hack. Therefore the easiest way to display partly transparent images across multiple browser without the use of a hack is to use a GIF image instead.

The excellent set of free and widely used silk icons from FAMFAMFAM come as a set of over 700 PNG images, and I frequently found myself converting a few icons from PNG to GIF for a web project. Converting more than one or two images at a time quickly gets boring, so I created a Python command line utility, based on the code at Nadia Alramli’s blog, to perform the conversion automatically.

Creating GIF files from all the PNG images in the current directory becomes as simple as:

png2gif *.png

If you want to create the GIF images, and delete the PNG images then:

png2gif -r *.png

And if you’d like to output the GIF images to a different directory then:

png2gif -o gifImages/ *.png

To view a full list of supported options you can run png2gif with -h, which shows:

Usage: png2gif [OPTIONS] <files>

Convert PNG images to GIF format

Options:
-h, --help            show this help message and exit
-o OUTPUTDIR, --outputdir=OUTPUTDIR
Set the output directory in which to put the GIF
images. Defaults to the current directory
-t THRESHOLD, --threshold=THRESHOLD
Set the transparency threshold. Defaults to 0
-r, --replace         Delete the PNG files after converting them to GIF
-v, --verbose         Verbose output

You can download the utility here: png2gif

And you can also download the silk icons in GIF format, as converted by PNG2GIF.

Leaky Redaction Part 1

June 29, 2008

The Underhanded C Contest 2008 is underway. This year the contest is to write code that appears to redact (black out) rectangular regions of an image, but actually allows the redacted content to be recoverable to a certain degree. They call this “Leaky Reaction”. Sample code is provided that can be used in the solution, which includes a number of helpful functions such as reading and writing PPM image files, and getting pixel values from the loaded data.

I thought a good place to start would be with some code that actually does redact the data in a non-recoverable way. Making use of their sample code I quickly came up with the following function:

void redact(int left, int bottom, int right, int top, image * out)
{
	pixel p;
	int x,y;

	p.rgb[0] = 0;
	p.rgb[1] = 0;
	p.rgb[2] = 0;

	for( x=left; x<right; x++ ) {
		for( y=bottom; y<top; y++ ) {
			put_pixel( *out, x, y, p );
		}
	}
}

The image below shows an image before and after applying the above redact function to it:

For this contest we don’t want to redact the image though, we just want to appear to have redacted it. A simple approach we could take would be to divide the existing pixel an arbitrary value
so that the area appears black (if we divide by a large enough number), but in actual fact it is only almost black, and can be returned to the original value by multiplying the new pixel value with the number we previously divided by.

Below is a redact function that divides the pixel value by 50:

void redact(int left, int bottom, int right, int top, image * out)
{
	pixel p;
	int x,y;

	for( x=left; x<right; x++ )
		for( y=bottom; y<top; y++ ) {
			pixel p = get_pixel( *out, x, y );
			p.rgb[0] /= 50;
			p.rgb[1] /= 50;
			p.rgb[2] /= 50;
			put_pixel( *out, x, y, p );
		}
}

And to reverse the redaction:

void unredact(image * out)
{
	pixel p;
	int x,y;

	for( x=0; x<out->width; x++ ) {
		for( y=0; y<out->height; y++ ) {
			p = get_pixel( *out, x, y);
			if(p.rgb[0] * 50 <= 256
			&& p.rgb[1] * 50 <= 256
			&& p.rgb[2] * 50 <= 256)
			{
				p.rgb[0] *= 50;
				p.rgb[1] *= 50;
				p.rgb[2] *= 50;
				put_pixel(*out, x, y, p);
			}
		}
	}
}

The image below shows the original image, the image after calling the redact function, and then that image after calling unredact. The final image isn’t identical to the first because we loose some information with the integer division, but it is close enough for us to be able to recognise the details. If we used a value smaller than 50 we would have lost less information, but the redacted image wouldn’t appear so black.

While the approach discussed above clearly allows us to reproduce the “redacted” data it is highly unlikely to be a contest winner! Anyone casually glancing at the code can see that we aren’t really redacting the image at all, and working out the code to undo the redaction would be relatively trivial.

In the next part I’ll discuss some more “leaky redaction” techniques I have tried. In the mean time here is the complete source code listing for the leaky redaction application, which makes use of sample code:

// very simple leaky redaction code for the Underhanded C Contest 2008
// www.coderholic.com
#include <stdio.h>
#include <stdlib.h>
#include "ppm.c"

int main( void )   /* usage:  redact < in.ppm > out.ppm */
{
	image in, out;

	if( get_ppm_from_file( &in, stdin ) && copy_ppm( in, &out ) ) {
		redact(110, 70, 201, 100, &out);

		put_ppm_to_file( out, stdout );
		dealloc_ppm( in );
		dealloc_ppm( out );
	}
	return 0;
}

// perform leaky redaction on an image
void redact(int left, int bottom, int right, int top, image * out)
{
	pixel p;
	int x,y;

	for( x=left; x<right; x++ )
		for( y=bottom; y<top; y++ ) {
			pixel p = get_pixel( *out, x, y );
			p.rgb[0] /= 50;
			p.rgb[1] /= 50;
			p.rgb[2] /= 50;
			put_pixel( *out, x, y, p );
		}
}

// undo our leaky redaction
void unredact(image * out)
{
	pixel p;
	int x,y;

	for( x=0; x<out->width; x++ ) {
		for( y=0; y<out->height; y++ ) {
			p = get_pixel( *out, x, y);
			if(p.rgb[0] * 50 <= 256
			&& p.rgb[1] * 50 <= 256
			&& p.rgb[2] * 50 <= 256)
			{
				p.rgb[0] *= 50;
				p.rgb[1] *= 50;
				p.rgb[2] *= 50;
				put_pixel(*out, x, y, p);
			}
		}
	}
}

Content Aware Image Resizing

August 23, 2007

Over the last couple of days a number of demo videos have been posted to YouTube that show Dr. Ariel Shamir’s Content Aware Image Resizing. A paper describing the technology is available on his website, along with a hi-res copy of the video.

I’m hoping to read the paper in some detail over the coming weeks and write some code. The effect is amazing, and immediately struck me as the way a resize should work!