Color Spaces

Learn about the color representations of images.

Color space refers to the internal representation of colors in an image. The human eye perceives color as a mixture of signals from three types of sensors in the retina, tuned to a narrow spectral band. In a digital image, the color of a pixel gets encoded as a triple of numbers. That’s why the shape of a color image is (H, W, C) and the number of channels (C) is 3.

The interpretation of these three numbers differs from one color space to another. We can think of it as analogous to the two numbers used as coordinates of a point in a plane. Cartesian coordinates interpret the coordinates as the distances from a given point to the two reference axes. In polar coordinates, the first coordinate represents the distance from a given point to the origin. The second coordinate represents the angle formed by the point’s radius from (0,0)(0, 0) and one of the reference axes. The same goes for color spaces. They encode color as a triple of numbers whose signification or ordering varies from one space to another. This lesson will focus on the color spaces most often encountered in automated inspection projects.

BGR and RGB, the most common color spaces, encode colors as cartesian coordinates in a 3D space.

Cartesian coordinates in a 3D space
Cartesian coordinates in a 3D space

HLS and its cousin HLV encode colors as cylindrical coordinates, the hue (H) representing an angle.

BGR and RGB

The OpenCV library encodes by default the three color channels as BGR. The channel values lie in the range 0 to 255.

  • Channel 0 corresponds to the blue plane.

  • Channel 1 corresponds to the green plane.

  • Channel 2 corresponds to the red plane.

Let’s see what we get when we split the channels of a color image:

Press + to interact
import cv2
image = cv2.imread('images/color/bgr.png')
image_0 = image[:, :, 0] # Extracts channel 0
image_1 = image[:, :, 1] # Extracts channel 1
image_2 = image[:, :, 2] # Extracts channel 2
# Save the 4 images to './output'
cv2.imwrite('./output/image.png', image)
cv2.imwrite('./output/image_0.png', image_0)
cv2.imwrite('./output/image_1.png', image_1)
cv2.imwrite('./output/image_2.png', image_2)

In lines 4–6, we extract the channels 0, 1, and 2. The correspond respectively to the blue, green, and red channels.

As expected, because the letters in the original image are purely blue, green, or red, the three channels have values of 255 in the area of the letter and zero everywhere else. The individual channels are saved as monochrome images, so the letters appear white.

The OpenCV library uses BGR by default, but we can interact with another image library that expects RGB images. To convert from BGR to RGB, we can swap the blue and red channels:

Press + to interact
import cv2
image_bgr = cv2.imread('images/color/bgr.png')
image_rgb = image_bgr[:, :, (2, 1, 0)] # Swaps the channels 0 and 2
image_rgb_0 = image_rgb[:, :, 0] # Extracts channel 0
image_rgb_1 = image_rgb[:, :, 1] # Extracts channel 1
image_rgb_2 = image_rgb[:, :, 2] # Extracts channel 2
# Save the 3 channels to './output'
cv2.imwrite('./output/image_rgb_0.png', image_rgb_0)
cv2.imwrite('./output/image_rgb_1.png', image_rgb_1)
cv2.imwrite('./output/image_rgb_2.png', image_rgb_2)

In line 4, we create an RGB image by swapping the channels 0 and 2.

We can get the same result by using the cv2.cvtColor() function. We’ll use this function instead of manually swapping the channel order because cv2.cvtColor() can perform any color conversion through the code argument.

Press + to interact
import cv2
image_bgr = cv2.imread('images/color/bgr.png')
image_rgb = cv2.cvtColor(image_bgr, code=cv2.COLOR_BGR2RGB)
# Save the 4 images to './output'
cv2.imwrite('./output/image_rgb_0.png', image_rgb[:, :, 0])
cv2.imwrite('./output/image_rgb_1.png', image_rgb[:, :, 1])
cv2.imwrite('./output/image_rgb_2.png', image_rgb[:, :, 2])

In line 4, we create an RGB image by calling cv2.cvtColor() with the code=cv2.COLOR_BGR2RGB argument.

Monochrome, a.k.a grayscale

A special case often encountered in automated inspection is when an image has a single channel. We say that such an image is monochrome or grayscale. We can convert a color image to a grayscale image with cv2.cvtColor(), passing the code=cv2.COLOR_BGR2GRAY argument.

The conversion from color to grayscale is a linear combination of the three channels, weighted such that the human eye perceives approximately the same level of contrast.

Press + to interact
import cv2
bgr_img = cv2.imread('./images/color/bgr.png')
print(f"bgr_img.shape = {bgr_img.shape}")
grayscale_img = cv2.cvtColor(bgr_img, code=cv2.COLOR_BGR2GRAY)
print(f"grayscale_img.shape = {grayscale_img.shape}")
cv2.imwrite("./output/bgr.png", bgr_img)
cv2.imwrite("./output/grayscale.png", grayscale_img)

In line 5, we create a grayscale image by calling cv2.cvtColor() with the code=cv2.COLOR_BGR2GRAY argument.

Although the three letters were saturated (i.e., they had a value of 255 in their respective channels), the letter “G” is brighter in the grayscale image. This feature reflects the human eye’s greater sensitivity to green light.

The shape of the grayscale image is (152, 418), while that of the color image is (152, 418, 3). We see that the conversion to grayscale made our image single-channel. The shape (152, 418) is the squeezed representation of (152, 418, 1).

As you can guess, we lost information in the process. If we convert back our grayscale image to BGR, we get a three-channel image whose (b, g, r) planes are copies of each other. The color image looks just like the grayscale image.

Press + to interact
import cv2
bgr_img = cv2.imread('./images/color/bgr.png')
grayscale_img = cv2.cvtColor(bgr_img, code=cv2.COLOR_BGR2GRAY)
reconverted_img = cv2.cvtColor(grayscale_img, code=cv2.COLOR_GRAY2BGR)
print(f"reconverted_img.shape = {reconverted_img.shape}")
cv2.imwrite('./output/grayscale.png', grayscale_img)
cv2.imwrite('./output/reconverted.png', reconverted_img)

In line 4, we create a BGR image from the grayscale image by calling cv2.cvtColor() with the code=cv2.COLOR_GRAY2BGR argument.

Why convert to grayscale?

Question

If information is lost, why would we convert an image from color to grayscale in the context of automated inspection?

Show Answer

HLS

HLS stands for hue, luminance, and saturation.

  • Hue is the color tint. It corresponds to the first word that comes to mind when describing a color, for example, orange, purple, lime-green, etc.

  • Luminance is the brightness of the color. Black corresponds to a luminance of 0, and white corresponds to a luminance of 255.

  • Saturation is how far a color is from gray.

Press + to interact
import cv2
image = cv2.imread('./images/streets/automn_street.jpg')
hls = cv2.cvtColor(image, cv2.COLOR_BGR2HLS)
# Save the hue, luminance, and saturation channels
cv2.imwrite('./output/0_original.png', image)
cv2.imwrite('./output/1_hue.png', hls[:, :, 0])
cv2.imwrite('./output/2_luminance.png', hls[:, :, 1])
cv2.imwrite('./output/3_saturation.png', hls[:, :, 2])

In line 4, we create an HLS image by calling cv2.cvtColor() with the code=cv2.COLOR_BGR2HLS argument.

There are many more color spaces (although rarely used in the context of automated inspection):

  • RGBA

  • XYZ

  • YCrCb

  • Lab

The cv2.cvtColor() function allows converting from any of these color spaces to any other one through the code argument cv2.COLOR_[origin_space]2[destination_space], where [origin_space] and [destination_space] must be replaced with the corresponding color spaces.

The best color space for automated inspection

Question

What is the best color space for automated inspection tasks?

Show Answer