In this lesson, we’ll learn about ways to represent and manipulate images in Python. By the end of this lesson, students will be able to:
- Apply
ndarray
arithmetic and logical operators with numbers and other arrays. - Analyze the shape of an
ndarray
and index into a multidimensional array. - Apply arithmetic operators, indexing, and slicing to manipulate RGB images.
We’ll need two new modules: imageio
, which provides utilities to read and write images in Python, and numpy
, which provides the data structures for representing images in Python.
import imageio.v3 as iio
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
Reading an image¶
Let’s use imageio’s imread
method to load a color picture as a grid of pixels. To then show an image, we can plot its pixels using the matplotlib function imshow
.
jax = iio.imread("jax.jpg")
plt.imshow(jax)
We could also read this image in as a black-and-white image using the mode="L"
option to imread
and cmap="gray"
to imshow
. “L” stands for “luminance,” a method to convert color to grayscale. “cmap” stands for “colormap,” saying to treat pixels as grayscale.
jax = iio.imread("jax.jpg", mode="L")
plt.imshow(jax, cmap="gray")
What kind of Python object does imread
return?
type(jax)
Pandas uses NumPy to represent a Series
of values, so many element-wise operations should seem familiar. In fact, we can load an image into a Pandas DataFrame
and see that this grayscale image is really a 2-d array of grayscale values ranging from [0, 255].
pd.DataFrame(jax)
What do you think the colorful DataFrame
should look like?
jax_color = iio.imread("jax.jpg")
pd.DataFrame(jax_color)
Array manipulation¶
Images are represented in Python with the type numpy.ndarray
or “n-dimensional array.” Grayscale images are 2-dimensional arrays with pixel luminance values indicated in each position. Color images are 3-dimensional arrays with pixel color values indicated for each channel (red, green, blue) in each position. Can you set the left and right sides of this picture to 0
so that Jax appears surrounded by black borders?
jax = iio.imread("jax.jpg")
jax[:50, :25] = 0
plt.imshow(jax)
When we’re performing an assignment on 2-dimensions of a 3-dimensional image, NumPy follows broadcasting rules to evaluate the operation. The simplest version of broadcasting are just element-wise operations.
plt.imshow(...)
Let’s try a more complicated example. Using the floor division operator, fill in the imshow
call to decrease only the green channel so that the overall picture is much more purple than before.
jax = iio.imread("jax.jpg")
plt.imshow(...)
Practice: Instafade¶
Write code to apply a fading filter to the image. The fading filter reduces all color values to 77% intensity and then adds 38.25 to each resulting color value. (These numbers are somewhat arbitrarily chosen to get the desired effect.)
The provided code converts the oj
array from integer values to floating-point decimal values. To display the final image, the code converts the numbers in the oj
array back to uint8
before passing the result to imshow
.
oj = iio.imread("oj.jpg").astype("float32")
...
plt.imshow(oj.astype("uint8"))
Practice: Image Color Manipulation¶
Write code to apply the following operations to an image.
- Expand the red colors by 50% by subtracting 128 from each red channel value, multiply the result by 1.5, and then add 128 to restore the original value range.
- Increase the blue colors by 25 by adding 25 to each blue channel value.
- Add black letterboxing bars by setting the top 150 and bottom 150 pixels to black.
- Clip color values outside the range [0, 255] by reassign all values above 255 to 255 and all values below 0 to 0.
oj = iio.imread("oj.jpg").astype("float32")
...
plt.imshow(oj.astype("uint8"))
Optional: Advanced broadcasting¶
What is the result of adding the following two arrays together following the broadcasting rules?
x = np.array([[1], [2], [3]])
x
y = np.array([1, 2, 3])
y