Teaching: 70 min
Exercises: 50 min
How can the skimage Python computer vision library be used to work with images?
Read and save images with imageio.
Display images with matplotlib.
Resize images with skimage.
Perform simple image thresholding with NumPy array operations.
Extract sub-images using array slicing.
We have covered much of how images are represented in computer software. In this episode we will learn some more methods for accessing and changing digital images.
Reading, displaying, and saving images
Imageio provides intuitive functions for reading and writing (saving) images.All of the popular image formats, such as BMP, PNG, JPEG, and TIFF are supported,along with several more esoteric formats. Check theSupported Formats docsfor a list of all formats.Matplotlib provides a large collection of plotting utilities.
Let us examine a simple Python program to load, display,and save an image to a different format.Here are the first few lines:
""" * Python program to open, display, and save an image. *"""import imageio.v3 as iio# read imageimage = iio.imread(uri="data/chair.jpg")
First, we import the
v3 module of imageio (
iio sowe can read and write images.Then, we use the
iio.imread() function to read a JPEG image entitled chair.jpg.Imageio reads the image, converts it from JPEG into a NumPy array,and returns the array; we save the array in a variable named
Next, we will do something with the image:
import matplotlib.pyplot as pltfig, ax = plt.subplots()plt.imshow(image)
Once we have the image in the program,we first call
plt.subplots() so that we will havea fresh figure with a set of axis independent from our previous calls.Next we call
plt.imshow() in order to display the image.
Now, we will save the image in another format:
# save a new version in .tif formatiio.imwrite(uri="data/chair.tif", image=image)
The final statement in the program,
iio.imwrite(uri="data/chair.tif", image=image),writes the image to a file named
chair.tif in the
imwrite() function automatically determines the type of the file,based on the file extension we provide.In this case, the
.tif extension causes the image to be saved as a TIFF.
Remember, as mentioned in the previous section, images saved with
imwrite()will not retain all metadata associated with the original imagethat was loaded into Python!If the image metadata is important to you, be sure to always keep an unchangedcopy of the original image!
Extensions do not always dictate file type
iio.imwrite()function automatically uses the file type we specify inthe file name parameter’s extension.Note that this is not always the case.For example, if we are editing a document in Microsoft Word,and we save the document as
paper.docx,the file is not saved as a PDF document.
Named versus positional arguments
When we call functions in Python,there are two ways we can specify the necessary arguments.We can specify the arguments positionally, i.e.,in the order the parameters appear in the function definition,or we can use named arguments.
For example, the
iio.imwrite()function definitionspecifies two parameters,the resource to save the image to (e.g., a file name, an http address) andthe image to write to disk.So, we could save the chair image in the sample code aboveusing positional arguments like this:
Since the function expects the first argument to be the file name,there is no confusion about what
"data/chair.jpg"means. The same goesfor the second argument.
The style we will use in this workshop is to name each argument, like this:
This style will make it easier for you to learn how to use the variety offunctions we will cover in this workshop.
Resizing an image (10 min)
import skimage.utilto your list of imports.Using the
chair.jpgimage located in the data folder,write a Python script to read your image into a variable named
image.Then, resize the image to 10 percent of its current size using these lines of code:
new_shape = (image.shape // 10, image.shape // 10, image.shape)small = skimage.transform.resize(image=image, output_shape=new_shape)small = skimage.util.img_as_ubyte(small)
As it is used here,the parameters to the
skimage.transform.resize()function arethe image to transform,
image,the dimensions we want the new image to have,
Image files on disk are normally stored as whole numbers for space efficiency,but transformations and other math operations often result inconversion to floating point numbers.Using the
skimage.util.img_as_ubyte()method converts it back to whole numbersbefore we save it back to disk.If we don’t convert it before saving,
iio.imwrite()may not recognise it as image data.
Next, write the resized image out to a new file named
resized.jpgin your data directory.Finally, use
plt.imshow()with each of your image variables to displayboth images in your notebook.Don’t forget to use
fig, ax = plt.subplots()so you don’t overwritethe first image with the second.Images may appear the same size in jupyter,but you can see the size difference by comparing the scales for each.You can also see the differnce in file storage size on disk byhovering your mouse cursor over the originaland the new file in the jupyter file browser, using
ls -lin your shell,or the OS file browser if it is configured to show file sizes.
Here is what your Python script might look like.
""" * Python script to read an image, resize it, and save it * under a different name."""import imageio.v3 as iioimport matplotlib.pyplot as pltimport skimage.transformimport skimage.util# read in imageimage = iio.imread(uri="data/chair.jpg")# resize the imagenew_shape = (image.shape // 10, image.shape // 10, image.shape)small = skimage.transform.resize(image=image, output_shape=new_shape)small = skimage.util.img_as_ubyte(small)# write out imageiio.imwrite(uri="data/resized.jpg", image=small)# display imagesfig, ax = plt.subplots()plt.imshow(image)fig, ax = plt.subplots()plt.imshow(small)
The script resizes the
data/chair.jpgimage by a factor of 10 in both dimensions,saves the result to the
data/resized.jpgfile,and displays original and resized for comparision.
In the Image Basics episode,we individually manipulated the colours of pixels by changing the numbers storedin the image’s NumPy array. Let’s apply the principles learned therealong with some new principles to a real world example.
Suppose we are interested in this maize root cluster image.We want to be able to focus our program’s attention on the roots themselves,while ignoring the black background.
Since the image is stored as an array of numbers,we can simply look through the array for pixel colour values that areless than some threshold value.This process is called thresholding,and we will see more powerful methods to perform the thresholding task inthe Thresholding episode.Here, though, we will look at a simple and elegant NumPy method for thresholding.Let us develop a program that keeps only the pixel colour values in an imagethat have value greater than or equal to 128.This will keep the pixels that are brighter than half of “full brightness”,i.e., pixels that do not belong to the black background.We will start by reading the image and displaying it.
"""* Python script to ignore low intensity pixels in an image.*"""import imageio.v3 as iio# read input imageimage = iio.imread(uri="data/maize-root-cluster.jpg")# display original imagefig, ax = plt.subplots()plt.imshow(image)
Now we can threshold the image and display the result.
# keep only high-intensity pixelsimage[image < 128] = 0# display modified imagefig, ax = plt.subplots()plt.imshow(image)
The NumPy command to ignore all low-intensity pixels is
image[image < 128] = 0.Every pixel colour value in the whole 3-dimensional array with a value lessthat 128 is set to zero.In this case,the result is an image in which the extraneous background detail has been removed.
Converting colour images to grayscale
It is often easier to work with grayscale images, which have a single channel,instead of colour images, which have three channels.Skimage offers the function
skimage.color.rgb2gray() to achieve this.This function adds up the three colour channels in a way that matcheshuman colour perception,see the skimage documentation for details.It returns a grayscale image with floating point values in the range from 0 to 1.We can use the function
skimage.util.img_as_ubyte() in order to convert it back to theoriginal data type and the data range back 0 to 255.Note that it is often better to use image values represented by floating point values,because using floating point numbers is numerically more stable.
The Carpentries generally prefers UK English spelling,which is why we use “colour” in the explanatory text of this lesson.However,
skimagecontains many modules and functions that includethe US English spelling,
color.The exact spelling matters here,e.g. you will encounter an error if you try to run
skimage.colour.rgb2gray().To account for this, we will use the US English spelling,
color,in example Python code throughout the lesson.You will encounter a similar approach with “centre” and
"""* Python script to load a color image as grayscale.*"""import imageio.v3 as iioimport skimage.color# read input imageimage = iio.imread(uri="data/chair.jpg")# display original imagefig, ax = plt.subplots()plt.imshow(image)# convert to grayscale and displaygray_image = skimage.color.rgb2gray(image)fig, ax = plt.subplots()plt.imshow(gray_image, cmap="gray")
We can also load colour images as grayscale directly bypassing the argument
"""* Python script to load a color image as grayscale.*"""import imageio.v3 as iioimport skimage.color# read input image, based on filename parameterimage = iio.imread(uri="data/chair.jpg", mode="L")# display grayscale imagefig, ax = plt.subplots()plt.imshow(image, cmap="gray")
Keeping only low intensity pixels (10 min)
A little earlier, we showed how we could use Python and skimage to turnon only the high intensity pixels from an image, while turning all the lowintensity pixels off.Now, you can practice doing the opposite - keeping allthe low intensity pixels while changing the high intensity ones.
data/sudoku.pngis an RGB image of a sudoku puzzle:
Your task is to turn all of the white pixels in the image to a light gray colour,say with the intensity of each formerly white pixel set to 0.75.The results should look like this:
Hint: this is an instance where it is helpful to convert the image from RGB to grayscale.
First, load the image file in and convert it to grayscale:
import imageio.v3image = iio.imread(uri="data/sudoku.jpg", mode="L")
Then, change all high intensity pixel values (> 0.75) to 0.75:
image[image > 0.75] = 0.75
Finally, display modified image:
fig, ax = plt.subplots()plt.imshow(image, cmap="gray", vmin=0, vmax=1)
Plotting single channel images (cmap, vmin, vmax)
Compared to a colour image, a grayscale image contains only a singleintensity value per pixel. When we plot such an image with
plt.imshow,matplotlib uses a colour map, to assign each intensity value a colour.The default colour map is called “viridis” and maps low values to purpleand high values to yellow. We can instruct matplotlib to map low valuesto black and high values to white instead, by calling
cmap="gray".The documentation contains an overview of pre-defined colour maps.
Furthermore, matplotlib determines the minimum and maximum values ofthe colour map dynamically from the image, by default. That means, that inan image, where the minimum is 0.25 and the maximum is 0.75, those valueswill be mapped to black and white respectively (and not dark gray and lightgray as you might expect). If there are defined minimum and maximum vales,you can specify them via
vmaxto get the desired output.
If you forget about this, it can lead to unexpected results. Try removingthe
vmaxparameter from the sudoku challenge solution and see what happens.
Access via slicing
As noted in the previous lesson skimage images are stored as NumPy arrays,so we can use array slicing to select rectangular areas of an image.Then, we can save the selection as a new image, change the pixels in the image,and so on.It is important toremember that coordinates are specified in (ry, cx) order and that colour valuesare specified in (r, g, b) order when doing these manipulations.
Consider this image of a whiteboard, and suppose that we want to create asub-image with just the portion that says “odd + even = odd,” along with thered box that is drawn around the words.
Using the same display technique we have used throughout this course,we can determine the coordinates of the corners of the area we wish to extractby hovering the mouse near the points of interest and noting the coordinates.If we do that, we might settle on a rectangulararea with an upper-left coordinate of (135, 60)and a lower-right coordinate of (480, 150),as shown in this version of the whiteboard picture:
Note that the coordinates in the preceding image are specified in (cx, ry) order.Now if our entire whiteboard image is stored as an skimage image named
image,we can create a new image of the selected region with a statement like this:
clip = image[60:151, 135:481, :]
Our array slicing specifies the range of y-coordinates or rows first,
60:151,and then the range of x-coordinates or columns,
135:481.Note we go one beyond the maximum value in each dimension,so that the entire desired area is selected.The third part of the slice,
:,indicates that we want all three colour channels in our new image.
A script to create the subimage would start by loading the image:
""" * Python script demonstrating image modification and creation via * NumPy array slicing."""import imageio.v3 as iio# load and display original imageimage = iio.imread(uri="data/board.jpg")fig, ax = plt.subplots()plt.imshow(image)
Then we use array slicing tocreate a new image with our selected area and then display the new image.
# extract, display, and save sub-imageclip = image[60:151, 135:481, :]fig, ax = plt.subplots()plt.imshow(clip)iio.imwrite(uri="data/clip.tif", image=clip)
We can also change the values in an image, as shown next.
# replace clipped area with sampled colorcolor = image[330, 90]image[60:151, 135:481] = colorfig, ax = plt.subplots()plt.imshow(image)
First, we sample a single pixel’s colour at a particular location of theimage, saving it in a variable named
color,which creates a 1 × 1 × 3 NumPy array with the blue, green, and red colour valuesfor the pixel located at (ry = 330, cx = 90).Then, with the
img[60:151, 135:481] = color command,we modify the image in the specified area.From a NumPy perspective,this changes all the pixel values within that range to array saved inthe
color variable.In this case, the command “erases” that area of the whiteboard,replacing the words with a beige colour,as shown in the final image produced by the program:
Practicing with slices (10 min - optional, not included in timing)
Using the techniques you just learned, write a script thatcreates, displays, and saves a sub-image containingonly the plant and its roots from “data/maize-root-cluster.jpg”
Here is the completed Python program to select only the plant and rootsin the image.
""" * Python script to extract a sub-image containing only the plant and * roots in an existing image."""import imageio.v3 as iio# load and display original imageimage = iio.imread(uri="data/maize-root-cluster.jpg")fig, ax = plt.subplots()plt.imshow(image)# extract, display, and save sub-image# WRITE YOUR CODE TO SELECT THE SUBIMAGE NAME clip HERE:clip = image[0:400, 275:550, :]fig, ax = plt.subplots()plt.imshow(clip)# WRITE YOUR CODE TO SAVE clip HEREiio.imwrite(uri="data/clip.jpg", image=clip)
Images are read from disk with the
We create a window that automatically scales the displayed image with matplotlib and calling
show()on the global figure object.
Colour images can be transformed to grayscale using
skimage.color.rgb2gray()or, in many cases, be read as grayscale directly by passing the argument
We can resize images with the
NumPy array commands, such as
image[image < 128] = 0, can be used to manipulate the pixels of an image.
Array slicing can be used to extract sub-images or modify areas of images, e.g.,
clip = image[60:150, 135:480, :].
Metadata is not retained when images are loaded as skimage images.