Tutorial 6 - Image Processing with Python

View notebook on Github Open In Collab

A digital image is a digital or electronic representation of a visual scene or object, constructed from a grid of individual picture elements, referred to as pixels. Each pixel represents a tiny square or point, that stores data related to the color and luminance of a particular location within the image.

Digital image processing is the computational approach for the analysis, storage, and interpretation of digital content. In modern times, the impact of image processing has expanded to include many fields ranging from medical diagnostics to autonomous navigation.

Popular libraries for digital image processing include Pillow, OpenCV, and scikit-image. Also, machine learning libraries like TensorFlow and PyTorch provide functions for preprocessing images. This tutorial will focus on the Pillow library.

Python Image Processing with Pillow

Pillow is a popular library for performing advanced image processing tasks that do not require extensive expertise in the field. It is also commonly employed for initial experimentation with image-related tasks. Additionally, Pillow benefits from its widespread adoption within the Python community and offers a more gentle learning curve compared to some of the more complex image processing libraries.

To install Pillow in Python use:

pip install pillow

Load and Display an Image with Pillow

Pillow is a modern version of the original image-processing library for Python called PIL, which stands for Python Imaging Library. Because of that, the imports for Pillow still use the namespace PIL, as in the code below from PIL import Image.

Inside the Pillow package, the module Image provide access to methods for image analysis. The method open() in the following cell is used to open an image.

The display() method is part of Jupyter Notebook, and displays the image inline in the current notebook.

[1]:
from PIL import Image

img_name = "images/UI.jpg"

# open the image object
img = Image.open(img_name)

# display the image
display(img)
../../../_images/Lectures_Tutorials_Tutorial_6-Image_Processing_Tutorial_6-Image_Processing_7_0.png

If we are using Python outside Jupyter, display(img) will not work. In that case, we can use img.show() as in the next cell, which will open the image in the system’s default image viewer.

[2]:
img.show()

We can print the type of an image object, and since the file is a JPG, Pillow returns a JpegImageFile object from the PIL.JpegImagePlugin module.

[3]:
type(img)
[3]:
PIL.JpegImagePlugin.JpegImageFile

Save an image

The save() method saves an image by specifying a string with a name for the image file, which can also include the path for saving the image.

[4]:
img.save("saved_img.jpg")

The following code prints the format and size of the image.

[5]:
# print the format of the image
img.format
[5]:
'JPEG'
[6]:
# print the width and height of the image in pixels
img.size
[6]:
(456, 254)

Channels and Mode of an Image

An image is a 2D grid of pixels, with each pixel denoting a specific color. The pixels can be defined using one or more values. In the case of an RGB image, each pixel is described by three values, representing its red, green, and blue components. Consequently, an Image object for an RGB image comprises three channels or bands, each corresponding to a color component.

For instance, in a 200x200-pixel RGB image, the representation is an array of 200 x 200 x 3 values.

An example of an RGB array is shown in the figure.

362217ce89ac441d87720f1180fb2f87

To print the channels of the img object, we use getbands().

[7]:
# print the channels of a RGB image
img.getbands()
[7]:
('R', 'G', 'B')

The mode specifies the type of an image. Pillow offers a broad range of standard modes, such as black-and-white (binary), grayscale, RGB, RGBA, and CMYK.

[8]:
# print the mode of the image
img.mode
[8]:
'RGB'

To convert the RGB image into grayscale mode, we can use convert("L"), where L stands for luminance. In grayscale images, every pixel has a value from 0 (black) to 255 (white). Grayscale images have only 1 channel. The mode "L" is grayscale because it keeps only luminance (brightness), not color.

[9]:
# convert to grayscale image
gray_img = img.convert("L")
display(gray_img)
../../../_images/Lectures_Tutorials_Tutorial_6-Image_Processing_Tutorial_6-Image_Processing_26_0.png

Show the bands of a grayscale mode image. There is only one band, 'L'.

[10]:
gray_img.getbands()
[10]:
('L',)

Split and Merge Channels

You can separate an image into its channels using split() and we can combine the separate channels back into an Image object using merge().

The split() method below returns the channels red, green, and blue as separate Image objects.

[11]:
# Split the channels
red, green, blue = img.split()

In the next cell, the red channel is stored in the variable red. To create an image showing only the red channel, we merge the red channel from the original image with green and blue channels that contain only zeros.

The first argument in merge() determines the mode of the image that we want to create, in this case "RGB" mode. The second argument contains the individual channels that we want to merge into a single image.

[12]:
# make empty (black) images for green and blue channels
zero = Image.new("L", img.size, 0)

# merge: keep red channel, and set green and blue to zerp
red_merge = Image.merge("RGB", (red, zero, zero))
display(red_merge)
../../../_images/Lectures_Tutorials_Tutorial_6-Image_Processing_Tutorial_6-Image_Processing_33_0.png
[13]:
print('Show the green channel')
green_merge = Image.merge("RGB", (zero, green, zero))
display(green_merge)
Show the green channel
../../../_images/Lectures_Tutorials_Tutorial_6-Image_Processing_Tutorial_6-Image_Processing_34_1.png
[14]:
print('Show the blue channel:')
blue_merge = Image.merge("RGB", (zero, zero, blue))
display(blue_merge)
Show the blue channel:
../../../_images/Lectures_Tutorials_Tutorial_6-Image_Processing_Tutorial_6-Image_Processing_35_1.png

Basic Image Manipulation

The transpose() method in Pillow allows to rotate or flip an image.

Options in the transpose() method include:

  • Image.FLIP_LEFT_RIGHT: Flips the image left to right, resulting in a mirror image.

  • Image.FLIP_TOP_BOTTOM: Flips the image top to bottom.

  • Image.ROTATE_90: Rotates the image by 90 degrees counterclockwise.

  • Image.ROTATE_180: Rotates the image by 180 degrees.

  • Image.ROTATE_270: Rotates the image by 270 degrees counterclockwise, which is the same as 90 degrees clockwise.

  • Image.TRANSPOSE: Transposes the rows and columns using the top-left pixel as the origin, with the top-left pixel being the same in the transposed image as in the original image.

  • Image.TRANSVERSE: Transposes the rows and columns using the bottom-left pixel as the origin, with the bottom-left pixel being the one that remains fixed between the original and modified versions.

An example with FLIP_TOP_BOTTOM.

[15]:
converted_img = img.transpose(Image.FLIP_TOP_BOTTOM)
display(converted_img)
../../../_images/Lectures_Tutorials_Tutorial_6-Image_Processing_Tutorial_6-Image_Processing_38_0.png

Example of rotating the image for 60 degrees counterclockwise.

[16]:
rotated_img = img.rotate(60)
display(rotated_img)
../../../_images/Lectures_Tutorials_Tutorial_6-Image_Processing_Tutorial_6-Image_Processing_40_0.png

The Image object returned is the same size as the original Image. Therefore, the corners of the image are missing in the image displayed above.

We can change this behavior with the parameter expand set to True.

[17]:
rotated_img = img.rotate(45, expand=True)
display(rotated_img)
../../../_images/Lectures_Tutorials_Tutorial_6-Image_Processing_Tutorial_6-Image_Processing_42_0.png

Crop and Resize Images

We can crop images using the crop() method. The arguments in crop() comprise a 4 element tuple that defines the left, upper, right, and bottom edges of the region that we wish to crop. The coordinate system used in Pillow assigns the coordinates (0, 0) to the pixel in the upper-left corner.

[18]:
cropped_img = img.crop((100, 100, 400, 250))
display(cropped_img)
../../../_images/Lectures_Tutorials_Tutorial_6-Image_Processing_Tutorial_6-Image_Processing_44_0.png
[19]:
cropped_img.size
[19]:
(300, 150)

To resize an image, we can change the resolution with the resize() method. For instance, we can set the new width and height to 50 by 50 pixels.

[20]:
low_res_img = img.resize((50, 50))
display(low_res_img)
../../../_images/Lectures_Tutorials_Tutorial_6-Image_Processing_Tutorial_6-Image_Processing_47_0.png

Image Blurring, Sharpening, and Smoothing

We can blur an image by using the filter() method and the ImageFilter module.

[21]:
from PIL import ImageFilter
blur_img = img.filter(ImageFilter.BLUR)
display(blur_img)
../../../_images/Lectures_Tutorials_Tutorial_6-Image_Processing_Tutorial_6-Image_Processing_50_0.png

More advanced blurring methods include:

  • Gaussian Blur using .GaussianBlur(factor) method.

  • Box blur using .BoxBlur(factor) method.

The argument in the blur methods determines how much blurring to apply. The larger the factor, the more blur is added to the image.

[22]:
print('BoxBlur method with factor of 5')
display(img.filter(ImageFilter.BoxBlur(5)))
BoxBlur method with factor of 5
../../../_images/Lectures_Tutorials_Tutorial_6-Image_Processing_Tutorial_6-Image_Processing_52_1.png
[23]:
print('BoxBlur method with factor of 20')
display(img.filter(ImageFilter.BoxBlur(20)))
BoxBlur method with factor of 20
../../../_images/Lectures_Tutorials_Tutorial_6-Image_Processing_Tutorial_6-Image_Processing_53_1.png
[24]:
print('GaussianBlur method with factor of 5')
display(img.filter(ImageFilter.GaussianBlur(5)))
GaussianBlur method with factor of 5
../../../_images/Lectures_Tutorials_Tutorial_6-Image_Processing_Tutorial_6-Image_Processing_54_1.png

To sharpen an image, we can use the predefined filter ImageFilter.SHARPEN.

[25]:
# original cropped image
display(cropped_img)
../../../_images/Lectures_Tutorials_Tutorial_6-Image_Processing_Tutorial_6-Image_Processing_56_0.png
[26]:
# sharpened image
sharp_img = cropped_img.filter(ImageFilter.SHARPEN)
display(sharp_img)
../../../_images/Lectures_Tutorials_Tutorial_6-Image_Processing_Tutorial_6-Image_Processing_57_0.png

Another more advanced sharpening method is with ImageEnhance.Sharpness(factor). Applying sharpening with a factor greater than 1 applies a sharpening filter to the image, sharpening factor in the range from 0 to 1 blurs the image, and sharpening factor = 1 returns the original image.

[27]:
from PIL import ImageEnhance
sharp_image =ImageEnhance.Sharpness(cropped_img).enhance(4)
display(sharp_image)
../../../_images/Lectures_Tutorials_Tutorial_6-Image_Processing_Tutorial_6-Image_Processing_59_0.png

Change Color, Brightness, Contrast

ImageEnhance.Color(img).enhance(factor) changes the color of the image, where factor > 1 means stronger color, factor < 1 reduces the color, and 0 means grayscale image.

ImageEnhance.Color(im).Brightness(factor) changes the brightness of the image, where factor > 1 makes the image brighter, factor < 1 makes it darker, and 0 means black image.

ImageEnhance.Contrast(im).enhance(factor) changes the contrast, where factor > 1 increases the brightness range, making light colors k-times lighter and dark colors darker. At very high contrast values, every pixel is either black or white, and generally only the basic shapes of the image are visible. Factor less than 1 decreases the brightness range, pulling all the colors towards a middle grey. A factor of 0 results in a completely grey image.

Change the color of an image.

[28]:
# make the color stronger
color_img = ImageEnhance.Color(img).enhance(5)
display(color_img)
../../../_images/Lectures_Tutorials_Tutorial_6-Image_Processing_Tutorial_6-Image_Processing_63_0.png
[29]:
# make the color less strong
less_color_img = ImageEnhance.Color(img).enhance(0.5)
display(less_color_img)
../../../_images/Lectures_Tutorials_Tutorial_6-Image_Processing_Tutorial_6-Image_Processing_64_0.png

Change the brightness of an image.

[30]:
# make the image brighter
brighter_img = ImageEnhance.Brightness(img).enhance(5)
display(brighter_img)
../../../_images/Lectures_Tutorials_Tutorial_6-Image_Processing_Tutorial_6-Image_Processing_66_0.png
[31]:
# make the image darker
darker_img = ImageEnhance.Brightness(img).enhance(.5)
display(darker_img)
../../../_images/Lectures_Tutorials_Tutorial_6-Image_Processing_Tutorial_6-Image_Processing_67_0.png

Change the contrast of an image.

[32]:
# apply more contrast
contra_img = ImageEnhance.Contrast(img).enhance(5)
display(contra_img)
../../../_images/Lectures_Tutorials_Tutorial_6-Image_Processing_Tutorial_6-Image_Processing_69_0.png
[33]:
# apply less contrast
less_contra_img = ImageEnhance.Contrast(img).enhance(.5)
display(less_contra_img)
../../../_images/Lectures_Tutorials_Tutorial_6-Image_Processing_Tutorial_6-Image_Processing_70_0.png

References:

  1. “ELEC_ENG 420: Digital Image Processing | Electrical and Computer Engineering | Northwestern Engineering” available at www.mccormick.northwestern.edu/electrical-computer/academics/courses/descriptions/420.html.

  2. “Image Processing With the Python Pillow Library,” Real Python, available at https://realpython.com/image-processing-with-the-python-pillow-library/.

  3. “Python Pillow Tutorial,” GeeksforGeeks, available at https://www.geeksforgeeks.org/python-pillow-tutorial/.

BACK TO TOP