Contours

In this post we will experiment with drawing contours on shapes. This post is based on the PluralSight course by Janani Ravi.

We will use The Marching Squares algorithm to draw contours. The algorithm is easy-to-implement, is an embarrassingly parallel algorithm that generates contours for a two-dimensional (rectangular) array.

An algorithm is referred to as an Embarrassingly Parallel Algorithm when there is little when no effort is needed to separate the problem into a number of parallel tasks, usually because there is little or no dependency or need for communication between those parallel tasks.

# **** folder of interest ****
cd C:\Documents\_Image Processing\scikit-image-building-image-processing-applications\02\demos

# **** open file of interest using VSCode ****
(base) C:\Documents\_Image Processing\scikit-image-building-image-processing-applications\02\demos>code Countours.py

# **** execute python script of interest ****
(base) C:\Documents\_Image Processing\scikit-image-building-image-processing-applications\02\demos>python Contours.py

We start at the specified folder. We then create a Python script to hold our code. This is done by opening VSCode with the name of our script. We should save the script at least once before calling the following step. We then invoke the Python interpreter to run the script.

# **** ****
import numpy as np                          # for array handling
from matplotlib import pyplot as plt        # for plotting

# **** ****
import skimage.io                           # for reading images
from skimage import measure                 # for finding contours
from skimage import color                   # for gray2rgb
from skimage.util import view_as_blocks     # for block processing

# **** ****
from PIL import Image                       # for reading images


# **** read shapes.png ****
shapes = skimage.io.imread("./images/shapes.png")

# **** display shapes.shape 
#      (PNG images have an additional channel for opacity or alpha) ****
print('shapes.shape = ', shapes.shape)

# **** display shapes ****
plt.imshow(shapes)
plt.title("shapes")
plt.show()

We start by importing the necessary libraries.

If you compare our script with the one in the course, I had to add the Pillow library to address a conversion problem which we will encounter shortly. Not sure how the code in the Jupyter notebook worked. I guess it no longer works due to updates on the different libraries.

We then read an RGB PNG file holding a set of shapes. The contents of the file are displayed.

# **** open PNG image ****
shapes = Image.open('./images/shapes.png')

# **** convert RGBA to RGB ****
shapes = shapes.convert('RGB')

# **** convert RGB PNG image to grayscale ****
shapes = color.rgb2gray(shapes)

# **** display shapes.shape ****
print('shapes.shape = ', shapes.shape)

# **** display shapes ****
plt.imshow(shapes, cmap='gray')
plt.title("shapes - gray")
plt.show()

We continue by opening the RGB PNG and converting it to an RGB image. The RGB image is then converted to grayscale. Note that if you look at the Jupyter notebook, the last dimension for the PNG files is 4 so it cannot be converted as shown in the Jupyter notebook to RGB.

We then display the grayscale shapes image.

 

# **** find contours
#      with level 0.5 we get 4 contours
#      with level 0.9 we get 5 contours ****
contours = measure.find_contours(   shapes,         # image to process
                                    0.9)            # 0.5 is the level value to find contours at

# **** count contours ****
count = len(contours)

# **** display count of contours ****
print("count: ", count)


# **** create a figure and set of subplots ****
fig, axes = plt.subplots(   1,                      # number of rows
                            2,                      # number of columns
                            figsize=(16, 12),       # figure size in inches
                            sharex=True,            # share x axis
                            sharey=True)            # share y axis

# **** ****
ax = axes.ravel()                                   # flatten axes

# **** display original shapes image ****
ax[0].imshow(   shapes,
                cmap='gray')
ax[0].set_title('Original image',                   # set title
                fontsize=20)                        # set font size

# **** display shape images with contours ****
ax[1].imshow(   shapes,
                cmap='gray',
                interpolation='nearest')
ax[1].set_title('Contour',                          # set title
                fontsize=20)                        # set font size

# **** ****
for n, contour in enumerate(contours):
    ax[1].plot( contour[:, 1],                   	# x axis
                contour[:, 0],                  	# y axis
                linewidth=5)                     	# line width

# **** ****
plt.show()                                          # display the figure

We now find the contours using a level of 0.5. Later we will update the value to 0.9 as we will see shortly.

With the value of 0.5 we get 4 contours. The value is displayed. We then display two images next to each other. The first contains the shapes and the second the shapes with the contours in different colors the algorithm found.

If we go back and change the intensity value from 0.5 to 0.9 and regenerate the plot, we will see 5 contours. Please note that the image in the post uses 0.9. If interested in the image with 0.5, you need to edit the script and display the original plot.

# **** ****
A, B = np.ogrid[-np.pi:np.pi:100j, -np.pi:np.pi:100j]

# **** generate complex image using NumPy ****
complex_image = np.sin(np.exp((np.sin(A)**3 + np.cos(B)**3)))

# **** display complex_image.shape ****
print('complex_image.shape: ', complex_image.shape)

# **** display complex_image ****
plt.imshow(complex_image, cmap='gray')              # display the image in gray scale
plt.title("complex_image")                          # set title
plt.show()                                          # display the image

Now we are going to generate a complex image. As stated by Janani, the scope of how the plot is generated is out of the context of this course.

The shape of the complex image is displayed. Note the values associated with the complex_image.shape.

The complex_image is then displayed.

 

# **** find contours ****
contours = measure.find_contours(   complex_image,  # image to process
                                    0.7)            # 0.7 is the level value to find contours

# **** count contours ****
count = len(contours)

# **** display count of contours ****
print("count: ", count)


# **** create a figure and set of subplots ****
fig, axes = plt.subplots(figsize=(12, 8))           # figure size in inches

# **** ****
axes.imshow(    complex_image,
                cmap='gray',
                interpolation='nearest')
axes.set_title('Contour',                           # set title
                fontsize=20)                        # set font size

# **** ****
for n, contour in enumerate(contours):
    axes.plot( contour[:, 1],                       # x axis
                contour[:, 0],                      # y axis
                linewidth=5)                        # line width

# **** ****
plt.show()                                          # display the figure

We now find the contours of the complex_image. The contours are displayed.

Hope you find this post interesting. I have to admit that it was not easy to follow. Several examples use rather complex functions and methods in the NumPy library. The idea is to read and experiment. At some point in time we will find uses for such methods and we should have an idea on how to proceed to get the desired results.

Remember that one of the best ways to learn is to read, experiment, and repeat.

If interested in the code for this post you will find it in the following GitHub repository.

Remember that one of the best ways to learn is to read, experiment, and repeat.

Enjoy,

John

 

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.