Tuesday, January 8, 2019

Image Processing in Python With Pillow

Pillow is a fork of the Python Imaging Library (PIL). PIL is a library that offers several standard procedures for manipulating images. It supports a range of image file formats such as PNG, JPEG, PPM, GIF, TIFF, and BMP. Before installing Pillow, there are some prerequisites that must be satisfied. These vary for different operating systems. You can find the prerequisites for your particular OS in the link shown below:

https://pillow.readthedocs.io/en/3.0.0/installation.html#os-x-installation

After installing the prerequisite libraries, you can install Pillow with pip. On your command prompt type :

pip install Pillow

In Pillow, RGBA values are represented by a tuple of four integer values. For example, the color red is represented by (255, 0, 0, 255). This color has the maximum amount of red, no green or blue, and the maximum alpha value, meaning it is fully opaque. Green is represented by (0, 255, 0, 255), and blue is (0, 0, 255, 255). White, the combination of all colors, is (255, 255, 255, 255), while black, which has no color at all, is (0, 0, 0, 255).

If a color has an alpha value of 0, it is invisible, and it doesn’t really matter what the RGB values are. Pillow uses the standard color names that HTML uses. Pillow offers the ImageColor.getcolor() function so we don’t have to memorize RGBA values for the colors we want to use. This function takes a color name string as its first argument, and the string 'RGBA' as its second argument, and it returns an RGBA tuple. The following program demonstrates this:

from PIL import ImageColor

print(ImageColor.getcolor('red','RGBA'))
print(ImageColor.getcolor('blue','RGBA'))
print(ImageColor.getcolor('yellow','RGBA'))
print(ImageColor.getcolor('green','RGBA'))
print(ImageColor.getcolor('black','RGBA'))

The output of the program is shown below:

(255, 0, 0, 255)
(0, 0, 255, 255)
(255, 255, 0, 255)
(0, 128, 0, 255)
(0, 0, 0, 255)

------------------
(program exited with code: 0)

Press any key to continue . . .

We imported the ImageColor module from PIL. Then we used ImageColor.getcolor() method and passed the color name string as the first argument and the string 'RGBA' as its second argument. The function returns the RGBA tuple. The color names are case insensitive. Pillow supports a huge number of color names, from 'aliceblue' to 'whitesmoke' and we can also pass more unusual color names, like 'chocolate' and 'Cornflower Blue'.

Image and color

A pixel’s RGB setting tells it precisely what shade of color it should display. Images also have an alpha value to create RGBA values. If an image is displayed on the screen over a background image
or desktop wallpaper, the alpha value determines how much of the background we can “see through” the image’s pixel.

Image pixels are addressed with x- and y-coordinates, which respectively specify a pixel’s horizontal and vertical location in an image. The origin is the pixel at the top-left corner of the image and is specified with the notation (0, 0). The first zero represents the x-coordinate, which starts at zero at the origin and increases going from left to right. The second zero represents the y-coordinate, which starts at zero at the origin and increases going down the image.

Most of Pillow’s functions and methods take a box tuple argument which means Pillow is expecting a tuple of four integer coordinates that represent a rectangular region in an image. The four integers are, in order, as follows:

• Left: The x-coordinate of the leftmost edge of the box.
• Top: The y-coordinate of the top edge of the box.
• Right: The x-coordinate of one pixel to the right of the rightmost edge of the box. This integer must    be greater than the left integer.
• Bottom: The y-coordinate of one pixel lower than the bottom edge of the box. This integer must be    greater than the top integer.

In the figure shown below we can see the area represented by the box tuple (3, 1, 9, 6). Note that the box includes the left and top coordinates and goes up to but does not include the right and bottom
coordinates.
The Image Object

A crucial class in the Python Imaging Library is the Image class. It is defined in the Image module and provides a PIL image on which manipulation operations can be carried out. An instance of this class can be created in several ways: by loading images from a file, creating images from scratch or as a result of processing other images. 

To load an image from a file, we use the open() function in the Image module passing it the path to the image. If successful, the above returns an Image object. If there was a problem opening the file, an IOError exception will be raised.

After obtaining an Image object, you can now use the methods and attributes defined by the class to process and manipulate it. Let's make a program now and use the Pillow to manipulate an image. See the code below:

from PIL import Image

myImage = Image.open('Tiger_Cowrie.JPG')

myImage.show()


To load the image, we import the Image module from Pillow and call Image.open(), passing it the image’s filename. The loaded image is stored in a variable myImage and then we call the object's show () method which displays the image on an external viewer (the Paint program on Windows).

The module name of Pillow is PIL to make it backward compatible with an older module called Python Imaging Library, which is why you must run from PIL import Image instead of from Pillow import Image. Because of the way Pillow’s creators set up the pillow module, you must use the from PIL import Image form of import statement, rather than simply import PIL.

We can load an Image object from an image file (of any format) by passing the Image.open()
function a string of the filename. We can get some details about the image using the object's attributes as shown in the program below:

from PIL import Image

myImage = Image.open('Tiger_Cowrie.JPG')

# The file format of the source file.
print(myImage.format)

# The pixel format used by the image. Typical values are “1”, “L”, “RGB”, or “CMYK.”
print(myImage.mode)

# Image size, in pixels. The size is given as a 2-tuple (width, height).
print(myImage.size)

# Colour palette table, if any.
print(myImage.palette)

The output of the program is shown below:

JPEG
JPEG (ISO 10918)
RGB
(1000, 995)
None

------------------
(program exited with code: 0)

Press any key to continue . . .

The format and format_description attributes are strings that describe the image format of the original file (with format_description being a bit more verbose). As we can see that the object’s size attribute contains a tuple of the image’s width and height in pixels, we can assign the values in the tuple to width and height variables in order to access with width and height individually. See the program below:

from PIL import Image

myImage = Image.open('Tiger_Cowrie.JPG')

width,height = myImage.size

print(width)
print(height)

The output of the program is shown below:

1000
995

------------------
(program exited with code: 0)

Press any key to continue . . .

Any changes we make to the Image object can be saved to an image file (also of any format) with the save() method. All the rotations, resizing, cropping, drawing, and other image manipulations will be done through method calls on this Image object.

Changing Image Type

When we are done processing an image, we can save it to file with the save()method, passing in the name that will be used to label the image file. When saving an image, we can specify a different extension from its original and the saved image will be converted to the specified format. See the example below:

from PIL import Image

myImage = Image.open('Tiger_Cowrie.JPG')

myImage.save('Tiger_Cowrie1.PNG')

The above creates an Image object loaded with the Tiger_Cowrie.JPG image and saves it to a new file Tiger_Cowrie.png. Pillow sees the file extension has been specified as PNG and so it converts it to PNG before saving it to file. Now you should have two images, Tiger_Cowrie.JPG and Tiger_Cowrie.png, on your hard drive. While these files are based on the same image, they are not identical because of their different formats.

Resizing Images

To resize an image, you call the resize() method on it, passing in a two-integer tuple argument representing the width and height of the resized image. The function doesn't modify the used image, it instead returns another Image with the new dimensions. See the example below:

from PIL import Image

myImage = Image.open('Tiger_Cowrie.JPG')
new_image = myImage.resize((400, 400))
new_image.save('Tiger_Cowrie_400.jpg')
print(myImage.size) 
print(new_image.size) 

The output of the program is shown below:

(1000, 995)
(400, 400)

------------------
(program exited with code: 0)

Press any key to continue . . .

If you want to resize images and keep their aspect ratios, then you should instead use the thumbnail() function to resize them. This also takes a two-integer tuple argument representing the maximum width and maximum height of the thumbnail. See the example below:

from PIL import Image

myImage = Image.open('Tiger_Cowrie.JPG')
myImage.thumbnail((400, 400))
myImage.save('Tiger_Cowrie_thumbnail.jpg')
print(myImage.size) 

The output of the program is shown below:

(400, 398)

------------------
(program exited with code: 0)

Press any key to continue . . .

The above program will result in an image sized 400x398 shown below, having kept the aspect ratio of the original image. 



Another significant difference between the resize() and thumbnail()functions is that the resize() function "blows out" an image if given parameters that are larger than the original image, while the thumbnail() function doesn't. For example, given an image of size 500x300, a call to resize((1200, 600))will create a larger sized image 1200x600, thus the image will have lost some definition and is likely to be blurry compared to the original. On the other hand, a call to thumbnail((1200, 600)) using the original image will result in an image that keeps its size 500x300 since both the width and height are less than the specified maximum width and height.

Cropping

When an image is cropped, a rectangular region inside the image is selected and retained while everything else outside the region is removed. With the Pillow library, you can crop an image with the crop() method of the Image class. The method takes a box tuple that defines the position and size of cropped region and returns an Image object representing the cropped image. The coordinates for the box are (left, upper, right, lower). The cropped section includes the left column and the upper row of pixels and goes up to (but doesn't include) the right column and bottom row of pixels. See the example below:

from PIL import Image

myImage = Image.open('Tiger_Cowrie.JPG')
box = (150, 200, 600, 600)
cropped_image = myImage.crop(box)
cropped_image.save('Tiger_Cowrie_cropped.jpg')
 
The output of the program is shown below:


The Python Imaging Library uses a coordinate system that starts with (0, 0) in the upper left corner. The first two values of the box tuple specify the upper left starting position of the crop box. The third and fourth values specify the distance in pixels from this starting position towards the right and bottom direction respectively. The coordinates refer to positions between the pixels, so the region in the above example is exactly 450x400 pixels.

Pasting an Image Onto Another Image

Pillow enables you to paste an image onto another one. Some example use cases where this could be useful is in the protection of publicly available images by adding watermarks on them, the branding of images by adding a company logo and in any other case where there is a need to merge two images.

Pasting is done with the paste() function. This modifies the Image object in place, unlike the other processing functions we've looked at so far that return a new Image object. Because of this, we'll first make a copy our demo image before performing the paste, so that we can continue with the other examples with an unmodified image. See the program below:

from PIL import Image

myImage = Image.open('Tiger_Cowrie.JPG')

image1 = Image.open('logo.png')
image_copy = myImage.copy()
position = ((image_copy.width - image1.width), (image_copy.height - image1.height))
image_copy.paste(image1, position)
image_copy.save('pasted_image3.jpg')

The output of the program is shown below:



In the above program, we load in two images Tiger_Cowrie.JPG and logo.png , then make a copy of the former with copy(). We want to paste the m2 image onto the copied image and we want it to be placed on the bottom right corner. This is calculated and saved in a tuple. The tuple can either be a 2-tuple giving the upper left corner, a 4-tuple defining the left, upper, right, and lower pixel coordinate, or None (same as (0, 0)). We then pass this tuple to paste() together with the image that will be pasted.

By default, when you perform a paste, transparent pixels are pasted as solid pixels, thus the black (white on some OSs) box surrounding the logo. Most of the times, this isn't what you want. You can't have your watermark covering the underlying image's content. We would rather have transparent pixels appear as such.

To achieve this, you need to pass in a third argument to the paste() function. This argument is the transparency mask Image object. A mask is an Image object where the alpha value is significant, but its green, red, and blue values are ignored. If a mask is given, paste() updates only the regions indicated by the mask. You can use either 1, L or RGBA images for masks. Pasting an RGBA image and also using it as the mask would paste the opaque portion of the image but not its transparent background. If you modify the paste as shown below, you should have a pasted logo with transparent pixels.

See the program below:

from PIL import Image

myImage = Image.open('Tiger_Cowrie.JPG')

image1 = Image.open('logo.png')
image_copy = myImage.copy()
position = ((image_copy.width - image1.width), (image_copy.height - image1.height))
image_copy.paste(image1, position,image1)
image_copy.save('pasted_image4.jpg')

The output of the program is shown below:



Rotating Images

We can rotate images with Pillow using the rotate() method. This takes an integer or float argument representing the degrees to rotate an image and returns a new Image object of the rotated image. The rotation is done counterclockwise. See the code below:

from PIL import Image

myImage = Image.open('Tiger_Cowrie.JPG')
image_rot_90 = myImage.rotate(90)
image_rot_90.save('image_rot_90.jpg')
image_rot_180 = myImage.rotate(180)
image_rot_180.save('image_rot_180.jpg')

The output of the program is shown below:




By default, the rotated image keeps the dimensions of the original image. This means that for angles other than multiples of 180, the image will be cut and/or padded to fit the original dimensions. If you look closely at the first image above, you'll notice that some of it has been cut to fit the original height and its sides have been padded with a black background (transparent pixels on some OSs) to fit the original width. The example below shows this more clearly:

from PIL import Image

myImage = Image.open('Tiger_Cowrie.JPG')
myImage.rotate(18).save('image_rot_18.jpg')


The output of the program is shown below:


Flipping Images

We can also flip images to get their mirror version. This is done with the transpose() function. It takes one of the following options: PIL.Image.FLIP_LEFT_RIGHT, PIL.Image.FLIP_TOP_BOTTOM, PIL.Image.ROTATE_90, PIL.Image.ROTATE_180, PIL.Image.ROTATE_270 or PIL.Image.TRANSPOSE. The example below shows this more clearly:

from PIL import Image

myImage = Image.open('Tiger_Cowrie.JPG')

image_flip = myImage.transpose(Image.FLIP_LEFT_RIGHT)
image_flip.save('image_flip.jpg')

The output of the program is shown below:



Drawing on Images

With Pillow, you can also draw on an image using the ImageDraw module. You can draw lines, points, ellipses, rectangles, arcs, bitmaps, chords, pieslices, polygons, shapes and text.

Pillow also provides the Image.new() function, which returns an Image object—much like Image.open(), except the image represented by Image.new()’s object will be blank. The arguments to Image.new() are as follows:

• The string 'RGBA', which sets the color mode to RGBA. (There are other modes that this book          doesn’t go into.)
• The size, as a two-integer tuple of the new image’s width and height.
• The background color that the image should start with, as a four integer tuple of an RGBA value.         We can use the return value of the ImageColor.getcolor() function for this argument. Alternatively,
   Image.new() also supports just passing the string of the standard color name. See the example below:

from PIL import Image, ImageDraw

blank_image = Image.new('RGBA', (400, 300), 'white').convert('RGB')
img_draw = ImageDraw.Draw(blank_image)
img_draw.rectangle((70, 50, 270, 200), outline='red', fill='green')
img_draw.text((70, 250), 'Using Image Draw module', fill='blue')
blank_image.save('drawn_image.jpg')


In the above example, we create an Image object with the new() method. This returns an Image object with no loaded image. We then add a rectangle and some text to the image before saving it.

The output of the program is shown below:

Color Transforms

The Pillow library enables you to convert images between different pixel representations using the convert() method. It supports conversions between L (greyscale), RGB and CMYK modes.

In the example below we convert the image from RGBA to L mode which will result in a black and white image.

from PIL import Image

image = Image.open('covri.jpg')
greyscale_image = image.convert('L')
greyscale_image.save('covri_greyscale.jpg')

The output of the program is shown below:


Drawing on Images

We can draw lines, rectangles, circles, or other simple shapes on an image, use Pillow’s ImageDraw module. Let's make a program that draw various kinds of shapes on the image. See the code below:

from PIL import Image,ImageDraw

image = Image.new('RGBA', (200, 200), 'white')
draw = ImageDraw.Draw(image)
draw.line([(0, 0), (199, 0), (199, 199), (0, 199), (0, 0)], fill='black')
draw.rectangle((20, 30, 60, 60), fill='blue')
draw.ellipse((120, 30, 160, 60), fill='red')
draw.polygon(((57, 87), (79, 62), (94, 85), (120, 90), (103, 113)),fill='brown')

for i in range(100, 200, 10):
draw.line([(i, 0), (200, i - 100)], fill='green')

image.save('drawings.png')

The output of the program is shown below:



First we made an Image object for a 200×200 white image then passing it to ImageDraw.Draw() we got an ImageDraw object which we stored in the variable draw. Next we made a thin black outline at the edges of the image, then a blue rectangle with its top-left corner at (20, 30) and bottom-right corner at (60, 60), a red ellipse defined by a box from (120, 30) to (160, 60), a brown polygon with five points and a pattern of green lines drawn with a for loop. There are several other shape-drawing methods for ImageDraw objects.

The ImageDraw object also has a text() method for drawing text onto an image. The text() method takes four arguments: xy, text, fill, and font.

• The xy argument is a two-integer tuple specifying the upper-left corner of the text box.
• The text argument is the string of text you want to write.
• The optional fill argument is the color of the text.
• The optional font argument is an ImageFont object, used to set the typeface and size of the text. 


As it’s often hard to know in advance what size a block of text will be in a given font, the ImageDraw module also offers a textsize() method. Its first argument is the string of text we want to measure, and its second argument is an optional ImageFont object. The textsize() method will then return a two-integer tuple of the width and height that the text in the given font would be if it were written onto the image. We can use this width and height to help us calculate exactly where we want to put the text on your image.

The first three arguments for text() are straightforward. Before we use text() to draw text onto an image, let’s look at the optional fourth argument the ImageFont object. Both text() and textsize() take an optional ImageFont object as their final arguments. To use this object we need to import Pillow’s ImageFont module. 

Once we have the ImageFont object we can call the ImageFont.truetype() function, which takes two arguments. The first argument is a string for the font’s TrueType file—this is the actual font file that
lives on your hard drive. A TrueType file has the .ttf file extension and can usually be found in the C:\Windows\Fonts folder.

The second argument to ImageFont.truetype() is an integer for the font size in points (rather than, say, pixels). Pillow creates PNG images that are 72 pixels per inch by default, and a point is 1/72 of an inch.

Let's make a program and use the ImageFont object, see the code below:

from PIL import Image,ImageDraw,ImageFont
import os

image = Image.new('RGBA', (200, 200), 'white')
draw = ImageDraw.Draw(image)
draw.text((20, 150), 'Learn', fill='blue')
fontsFolder = 'C:\Windows\Fonts'
arialFont = ImageFont.truetype(os.path.join(fontsFolder, 'arial.ttf'), 32)
draw.text((100, 150), 'Python', fill='gray', font=arialFont)
image.save('text.png')

The output of the program is shown below:


After importing Image, ImageDraw, ImageFont, and os, we make an Image object for a new 200×200 white image and make an ImageDraw object from the Image object We use text() to draw Learn at (20, 150) in blue. To set a typeface and size, we first store the folder name (C:\Windows\Fonts) in fontsFolder. Then we call ImageFont.truetype(), passing it the .ttf file for the font we want, followed by an integer font size Store the Font object you get from ImageFont.truetype() in a variable like arialFont, and then pass the variable to text() in the final keyword argument. The text() call draws Python at (100, 150) in gray in 32-point Arial.

With this today's post comes to an end. If you are willing to explore Pillow further you may visit 
https://pillow.readthedocs.io/en/3.0.x/handbook/tutorial.html. Till we meet next keep practicing and learning Python as Python is easy to learn!





Share:

0 comments:

Post a Comment