Python Imaging Library is used for loading images in memory and doing various transformations on the image. It supports loading images of various types like PNG, JPEG, etc. It can be used to do various transformations on image and then store that transformed image. PIL is generally used in deep learning where preprocessing of images is required as part of object detection tasks. Image loaded using PIL can be easily converted to Numpy array as many Machine Learning and Deep Learning libraries take numpy array as input. Various deep learning libraries use PIL behind the scene to do image pre-processing.
Note: This tutorial is inspired by Pillow Tutorial
import PIL
from PIL import Image, ImageFilter, ImageEnhance, ImageSequence
import numpy as np
import secrets
import matplotlib.pyplot as plt
np.set_printoptions(precision=4,threshold=5,edgeitems=2)
Image is the main class of PIL which is responsible for representing an image. Whenever you load an image in memory using PIL it'll be of class Image.
open()
method is used to open a file. This method loads file lazily as it reads the header of an image file to find out image format, mode, and size. It does not load the whole image into memory which can be of few MB for images with higher resolution. This can be helpful to find out image metadata without loading it in memory.
Note: Please save a few png, jpeg images in a directory where jupyter notebook is kept to load images in memory and do various operations. Also, change the image name according to the below code to load your images.
Image.open('ml.jpeg')
img = Image.open('ml1.png').resize((380,250)); img2 = Image.open('ml.jpeg').resize((380,250))
type(img), type(img2)
size
attribute of Image instance refers to the size of the image in pixels. (width, height)
mode
attribute refers to in which mode image is loaded which is generally RGB, RGBA, CMYK, L, etc. One can get more information about mode, pallette, bands, etc over hereformat
refers to the format of the source file from which image was loaded, It'll be None
for an image generated after processing an original image. One can read about formats supported over here(img.size,img.mode, img.format, img.info), (img2.size,img2.mode, img2.format, img2.info)
plt.figure(figsize=(15,8))
plt.hist(img.histogram(),bins=256,range=(0,255),color='red', label='Density of pixels within image')
show()
function saves the image and then loads it in default image application on the machine.convert()
function is used to convert an image from one mode to another. Currently conversion only between images with mode types 'L', 'RGB' and 'CMYK'img = img.convert('L')
img.show()
save()
¶save()
method or we can include it in the filename as well. save()
method will fail if the format is not provided as part of a file name or as format parameter.img.save('img_rgb.jpg'); img.save('temp',format="jpeg")
thumbnail()
method of Image class to create thumbnail from an image. We need to provide a size parameter as a tuple (height, width).PIL will create an image that has the same aspect ratio as the original image. The resulting thumbnail will not be the same size as you provided as parameter. But it'll not be larger than that size.thumbnail()
method works in-place
which means that it'll modify original image object rather than returning a new modified object.size
attribute of Image class to check the size of the resulting image after converting to thumbnail.size
attribute of Image class always returns tuple in format (width, height).img.thumbnail((128,128))
img.size
resize()
method is used to resize the method. It requires a new size of an image as a tuple first parameter. One can maximize and minimize image using this method.
img = Image.open('ml1.png').resize((380,250))
img.resize((200,150))
crop()
method is used to crop a particular part of an image. It requires a tuple of 4 integers (left, upper, right, lower). This number must be within a range of size of image otherwise error will be raised while cropping. Image is represented using a coordinated system with (0,0) as upper-left corner and (width, height) as a lower-right corner.
crop()
method is lazy
as it does not actually crop image until load()
function is called to load a cropped image.
size
attribute of Image class always returns tuple in format (width, height).
cropped_img = img.crop((100,100,250,150))
print('size of cropped image : %s'%str(cropped_img.size))
cropped_img
transpose()
function is used to flip/rotate image in various angles. Various flip/rotate types are Image.FLIP_LEFT_RIGHT, Image.FLIP_TOP_BOTTOM, Image.ROTATE_180, Image.ROTATE_90, Image.ROTATE_270, Image.TRANSPOSE and Image.TRANSVERSE
rotate()
function is used to rotate the image by certain degrees. It takes on a parameter which must be between (0,360).
img.transpose(Image.FLIP_TOP_BOTTOM)
img.rotate(45)
We can directly give Image class instance as input to numpy.array()
method. It'll convert image to numpy array based on mode and it'll create channels accordingly.
We can create Image instance from numpy array again with Image.fromarray()
function.
nparr = np.array(img)
nparr.shape, nparr.dtype, nparr
((250, 380, 4), dtype('uint8'), array([[[ 4, 6, 44, 255],
[ 4, 6, 44, 255],
...,
[ 6, 8, 47, 255],
[ 6, 8, 47, 255]],
[[ 5, 7, 45, 255],
[ 5, 7, 45, 255],
...,
[ 6, 8, 47, 255],
[ 6, 8, 47, 255]],
...,
[[ 4, 7, 42, 255],
[ 4, 7, 42, 255],
...,
[ 5, 7, 45, 255],
[ 5, 7, 46, 255]],
[[ 3, 6, 41, 255],
[ 3, 6, 41, 255],
...,
[ 5, 7, 45, 255],
[ 5, 7, 46, 255]]], dtype=uint8))
img2 = Image.fromarray(nparr); Image.fromarray(nparr)
Image.fromarray(np.random.randint(0,255,size=(150, 150, 3), dtype=np.uint8))
frombytes()
function allows us to create an image from a byte stream.tobytes()
function is used to create a byte stream from an image.Image.frombytes('L', size=(100,100), data = secrets.token_bytes(10000))
Image.frombytes('RGB', size=(100,100), data = secrets.token_bytes(30000))
Image.frombytes('RGBA', size=(100,100), data = secrets.token_bytes(40000));
byte_stream = img.tobytes()
print('Size of bytes : %d'%len(byte_stream))
print('Image byte size from dimensions %d'%(img.size[0]*img.size[1]*len(img.getbands())))
Image.frombytes(img.mode, img.size, byte_stream)
split()
method is used to split methods into bands which will (R,G,B) for RGB, (R,G,B,A) for RGBA, (C,M,Y,K) for CMYK and (L,) for L (Luminescence). Each of the bands returned itself will be an instance of Image class again so it makes easy to work on parts and then combine again.
merge()
is used to merge different bands and create one unified image of those bands. It takes the first parameter as mode
and the second parameter as tuple
containing bands
according to mode. bands
must have the same size in order for a merge to work correctly.
img =img.convert('RGB')
r, g, b = img.split()
Image.merge('L', (b,))
Image.merge('RGBA', (r, g, b, b))
Image.merge('RGB', (b, r, g))
paste()
method is used to paste cropped image/image to another image. place
where to paste the image with a tuple (left, upper, right, lower). Users can only provide (left, upper) as well for place and it'll work fine. mask
which can be used to make pasted images with transparency
. The masked image will have values between (0, 255) with 0 values being totally transparent and 255 is opaque.img = Image.open('ml1.png').resize((380,230))
cropped_region = img.crop((100,100,150,150))
img.paste(cropped_region,box=(10,10,60,60))
img.paste(cropped_region,box=(20,70,70,120),mask=Image.fromarray((np.zeros(cropped_region.size)+120).astype(np.uint8)))
img.paste(cropped_region,box=(100,150,150,200),mask=Image.fromarray((np.zeros(cropped_region.size)+200).astype(np.uint8)))
img
filter()
method is used to apply various filters on an image like a blur, smooth, emboss, etc. It accepts one argument which is a filter. The filter is from class ImageFilter
.kernel_size = ImageFilter.BLUR.filterargs[0]
print('Kernel Size : %s'%str(kernel_size))
print('Kernel : %s'%str(np.array(ImageFilter.BLUR.filterargs[3]).reshape(kernel_size)))
img.filter(ImageFilter.BLUR)
kernel_size = ImageFilter.EMBOSS.filterargs[0]
print('Kernel Size : %s'%str(kernel_size))
print('Kernel : %s'%str(np.array(ImageFilter.EMBOSS.filterargs[3]).reshape(kernel_size)))
img.filter(ImageFilter.EMBOSS)
kernel_size = ImageFilter.SMOOTH.filterargs[0]
print('Kernel Size : %s'%str(kernel_size))
print('Kernel : %s'%str(np.array(ImageFilter.SMOOTH.filterargs[3]).reshape(kernel_size)))
img.filter(ImageFilter.SMOOTH)
kernel_size = ImageFilter.CONTOUR.filterargs[0]
print('Kernel Size : %s'%str(kernel_size))
print('Kernel : %s'%str(np.array(ImageFilter.CONTOUR.filterargs[3]).reshape(kernel_size)))
img.filter(ImageFilter.CONTOUR)
point()
function is used to manipulate individual pixels of an image. It takes as input function which takes 1 argument which is pixel value.
One can work on individual bands of image and then combine bands to generate a combined image.
img.point(lambda x: 0 if x < 120 else 255)
img = Image.open('ml1.png').resize((380,230))
bands = list(img.split())[:-1]
bands[0] = bands[0].point(lambda x: x > 120 and 0)
bands[1] = bands[1].point(lambda x: x < 120 and 0)
bands[2] = bands[2].point(lambda x: x > 100 and x < 200 and 255)
Image.merge('RGB',bands)
ImageEnhance
class has various functionalities(Brightness, Color, Contrast, Sharpness) which allows user to enhance an image in various waysImage
instance as input and returns an instance of effect.enhance()
which takes float value between [0.0, 1.0] to represent percent application of the effect.img = Image.open('ml1.png').resize((380,230))
img = ImageEnhance.Brightness(img)
print(type(img))
img.enhance(0.4)
img = Image.open('ml1.png').resize((380,230))
img = ImageEnhance.Color(img)
img.enhance(0.0)
img = Image.open('ml1.png').resize((380,230))
img = ImageEnhance.Contrast(img)
img.enhance(0.6)
img = Image.open('ml1.png').resize((380,230))
img = ImageEnhance.Sharpness(img)
img.enhance(2.0)
The below method rolls image horizontally / vertically by providing pixel values between x-range for Horizontal and y-range for Vertical.
If load() was not called, then the crop operation would not be performed until the images were used in the paste commands. This would mean that part1 would be cropped from the version of the image already modified by the first paste.
def roll_image(img, roll_type='horizontal', delta = 1):
x , y = img.size
if roll_type=='horizontal':
part1 = img.crop((0,0, delta, y ))
part2 = img.crop((delta, 0, x, y))
else:
part1 = img.crop((0, 0, x, delta ))
part2 = img.crop((0, delta, x, y))
part1.load()
part2.load()
img.paste(part2)
img.paste(part1, box = (x-delta, 0) if roll_type=='horizontal' else (0, y-delta))
return img
img = Image.open('ml1.png').resize((380,230))
roll_image(img,delta=200)
img = Image.open('ml1.png').resize((380,230))
roll_image(img, roll_type='vertical' ,delta=100)
img = Image.open('ml1.png').resize((380,230))
def lighten_image(img, delta):
if delta >=0 and delta <=255:
white_image = Image.fromarray(np.zeros((img.size[1],img.size[0]),dtype=np.uint8)+255)
mask = Image.fromarray(np.zeros((img.size[1],img.size[0]),dtype=np.uint8)+delta)
#print(mask.size, white_image.size,img.size)
img.paste(white_image,mask=mask)
else:
print('please enter values between 0-255')
return img
lighten_image(img,40)
from PIL import Image
from PIL import PSDraw
im = Image.open("ml.png")
title = "ml"
box = (1*72, 2*72, 7*72, 10*72) # in points
ps = PSDraw.PSDraw() # default is sys.stdout
ps.begin_document(title)
# draw the image (75 dpi)
ps.image(box, im, 75)
ps.rectangle(box)
# draw title
ps.setfont("HelveticaNarrow-Bold", 36)
ps.text((3*72, 4*72), title)
ps.end_document()
If you are more comfortable learning through video tutorials then we would recommend that you subscribe to our YouTube channel.
When going through coding examples, it's quite common to have doubts and errors.
If you have doubts about some code examples or are stuck somewhere when trying our code, send us an email at coderzcolumn07@gmail.com. We'll help you or point you in the direction where you can find a solution to your problem.
You can even send us a mail if you are trying something new and need guidance regarding coding. We'll try to respond as soon as possible.
If you want to