Color Spaces
Learn about the color representations of images.
We'll cover the following
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
BGR and RGB, the most common color spaces, encode colors as 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:
import cv2image = cv2.imread('images/color/bgr.png')image_0 = image[:, :, 0] # Extracts channel 0image_1 = image[:, :, 1] # Extracts channel 1image_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:
import cv2image_bgr = cv2.imread('images/color/bgr.png')image_rgb = image_bgr[:, :, (2, 1, 0)] # Swaps the channels 0 and 2image_rgb_0 = image_rgb[:, :, 0] # Extracts channel 0image_rgb_1 = image_rgb[:, :, 1] # Extracts channel 1image_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.
import cv2image_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.
import cv2bgr_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.
import cv2bgr_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?
If information is lost, why would we convert an image from color to grayscale in the context of automated inspection?
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 of255
.Saturation is how far a color is from gray.
import cv2image = cv2.imread('./images/streets/automn_street.jpg')hls = cv2.cvtColor(image, cv2.COLOR_BGR2HLS)# Save the hue, luminance, and saturation channelscv2.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
What is the best color space for automated inspection tasks?