6 of 9 for Introduction to Image Processing

Chromaticity and Image Differencing for Image Segmentation

How do we segment objects in the image based on their chromaticity and temporal movement?

Cyril Benedict Lugod
7 min readJun 17, 2023

Segmentation from the previous blog post involved explicit colors which are somewhat easily described. However, for colors which are complex combinations of the primary colors, such segmentations may not suffice.

In this post, we will discuss more image segmentation by chromaticity and image differencing.

Chromaticity Segmentation

Segmentation by chromaticity uses the Red-Green (RG) chromaticity space which is a visualization of the RGB color space sans the intensity. The red and green color channels are normalized with respect to their sum.

RG chromaticity space shows the normalized RGB in 2 dimensions

Only the red and green channels are presented in the chart since the values for the three color channels sum to 1 per the following equations.

Conversion between RGB and RG chromaticity

Two approaches for chromaticity segmentation will be discussed: parametric and nonparametric approach.

Parametric Approach

To demonstrate the parametric approach of this segmentation method, let’s use a vibrant photo of the Garden of Morning Calm taken on a cold winter night.

The Garden of Morning Calm in Korea has hundreds of trees lit up in different colors

When we take all the colors in this photo and project them into the RG chromaticity space, we can see that most of the colors are bluish but close to white.

morn_calm_R = morn_calm[:, : ,0] * 1.0 / morn_calm.sum(axis=2)
morn_calm_G = morn_calm[:, : ,1] * 1.0 / morn_calm.sum(axis=2)

plt.figure(figsize=(5, 5))
plt.hist2d(morn_calm_R.flatten(), morn_calm_G.flatten(), bins=100, cmap='binary')
plt.xlim(0,1)
plt.ylim(0,1)
plt.xlabel('red', color='r')
plt.ylabel('green', color='g')
plt.show()
2D histogram showing distribution of photo colors in RG chromaticity space

Let’s assume that we are interested in segmenting trees lit up in blue similar to the blue tree in the righthand side of the image. Consider the following patch boxed in red.

Patch boxed in red (righthand side, top of blue tree)
patch = morn_calm[750:800, 3300:3350, :]
imshow(patch)
plt.show()
Onset of patch

When we look at the projection of this patch onto the RG chromaticity space from earlier, it is well within the space inside the blue section of the chromaticity space as show in the histogram below.

patch_R = patch[:, :, 0] * 1.0 / patch.sum(axis=2)
patch_G = patch[:, :, 1] * 1.0 / patch.sum(axis=2)

plt.figure(figsize=(5, 5))
plt.hist2d(patch_R.flatten(), patch_G.flatten(), bins=100, cmap='binary')
plt.xlim(0, 1)
plt.ylim(0, 1)
plt.xlabel('red', color='r')
plt.ylabel('green', color='g')
plt.show()
2D histogram showing distribution of patch colors in RG chromaticity space

Using this histogram, we are going to fit a Gaussian distribution over the distribution in this patch histogram.

std_patch_R = np.std(patch_R.flatten())
mean_patch_R = np.mean(patch_R.flatten())

std_patch_G = np.std(patch_G.flatten())
mean_patch_G = np.mean(patch_G.flatten())

We can define the Gaussian functions as follows:

def gaussian(p, mean, std):
return np.exp(-(p-mean)**2/(2*std**2))*(1/(std*((2*np.pi)**0.5)))

Plotting the two Gaussian functions of the two color channels on top of each other per the RG chromaticity space shows the enclosed area bounded by these two Gaussian functions. This area will be the color of interest to us.

x_R = np.linspace(0, 1)
y_R = gaussian(x, mean_patch_R, std_patch_R)
x_G = np.linspace(0, 1)
y_G = gaussian(x, mean_patch_G, std_patch_G)
fig, ax = plt.subplots()
plt.xlabel('red', color='r')
plt.ylabel('green', color='g')
ax.plot(x_R, y_R, 'r')
ax.plot(y_G, x_G, 'g')
plt.show()
Gaussian distributions for the red and green channels

We can apply this to the image of the Garden of Morning Calm by taking the probabilities of the color appearing in our image using the R and G channels, independently.

prob_R = gaussian(morn_calm_R, mean_patch_R, std_patch_R)
plt.imshow(prob_R)
plt.axis('off')
plt.show()
Bright areas indicate high probability in the red channel
prob_G = gaussian(morn_calm_G, mean_patch_G, std_patch_G)
plt.imshow(prob_G)
plt.axis('off')
plt.show()
Bright areas indicate high probability in the green channel

Performing element-wise multiplication of the values in the probabilities for both red and green channels will give us the area bounded by the two Gaussian functions. This will be the final probability matrices which captures the probabilities for the entire RG color band.

This is already a mask which we can apply to the original image to highlight where our color of interest possibly appears in the image.

masked_morn_calm = morn_calm.copy()

prob = prob_R * prob_G

prob = prob.astype(np.uint8)

masked_morn_calm[:, :, 0] *= prob.clip(0, 1)
masked_morn_calm[:, :, 1] *= prob.clip(0, 1)
masked_morn_calm[:, :, 2] *= prob.clip(0, 1)

plt.axis('off')
imshow(masked_morn_calm);
Masked image showing only parts with the same shade of blue in the patch

Nonparametric Approach

Sometimes, our region of interest cannot be approximated by a 2D Gaussian curve. Hence, we have to use a nonparametric approach to chromaticity segmentation.

To achieve this, we need to use a 2D histogram of the original image and perform histogram backprojection to mask the original image with the computed histogram of a reference patch.

The nonparametric approach is particularly useful for multi-color segmentation. For this example, we will use the nonparametric approach to segment skin colors of different shades from an image. Let us consider the following image.

Group of people with different skin colors (Photo from Wikipedia)

Say we want to segment skin colors based on the following patch of different skin colors. This would definitely not be feasible using the parametric approach.

skin_patch = imread('skin_patch.png')
plt.axis('off')
imshow(skin_patch)
plt.show()
Sample skin patch covering different ethnicities (Photo from Milja)

We can perform backprojection in order to mask the original image with the computed histogram of the reference skin patch above.

# Convert both images to the HSV color space
patch_hsv = colors.rgb_to_hsv(skin_patch[:, :, :3])
image_hsv = colors.rgb_to_hsv(skin_colors)

# Compute the 2D histogram of the reference patch in the H and S channels
patch_hist, x_edges, y_edges = np.histogram2d(
patch_hsv[:,:,0].flatten(), patch_hsv[:,:,1].flatten(), bins=16, range=[[0, 1], [0, 1]])

# Normalize the histogram to have a maximum value of 1
patch_hist = patch_hist / np.max(patch_hist)

# Compute the 2D histogram of the image in the H and S channels
image_hist, x_edges, y_edges = np.histogram2d(
image_hsv[:,:,0].flatten(), image_hsv[:,:,1].flatten(), bins=16, range=[[0, 1], [0, 1]])

# Normalize the image histogram to have a maximum value of 1
image_hist = image_hist / np.max(image_hist)

# Compute the backprojection of the image histogram using the reference patch histogram
backproj = np.zeros_like(image_hsv[:,:,0])
for i in range(image_hsv.shape[0]):
for j in range(image_hsv.shape[1]):
backproj[i,j] = patch_hist[np.searchsorted(x_edges, image_hsv[i,j,0])-1, np.searchsorted(y_edges, image_hsv[i,j,1])-1]

# Normalize the backprojection to have a maximum value of 1
backproj = backproj / np.max(backproj)

# Display the original image and the backprojection side by side
plt.figure(figsize=(10,5))
plt.subplot(1,2,1)
plt.imshow(skin_colors)
plt.title('Original Image')
plt.subplot(1,2,2)
plt.imshow(backproj)
plt.title('Backprojection')
plt.show()
Backprojection was able to get all of the skin colors based on the patch

Image Differencing

Instead of color, we can also segment images based on movement which assumes that the POV of the camera is stationary and only the objects in the images are moving.

Let’s take a photo of my desk for example. Consider the before and after photo and take note of which items have gone missing on the second photo.

fig, ax = plt.subplots(1, 2, figsize=(12, 10))
img_desk_before = imread('img_diff_before.jpg')
ax[0].imshow(img_desk_before)
ax[0].set_title('Before', fontsize=24)
ax[0].axis('off')
img_diff_after = imread('img_diff_after.jpg')
ax[1].imshow(img_diff_after)
ax[1].set_title('After', fontsize=24)
ax[1].axis('off')
fig.show()
Try noting which items have gone missing on the After photo

Or you can simply run this code and know in less than a second.

img_desk_before_gray = rgb2gray(img_desk_before)
img_desk_after_gray = rgb2gray(img_desk_after)
desk_diff = img_desk_before_gray - img_desk_after_gray

masked_img_desk_before = img_desk_before_gray * desk_diff

plt.imshow(masked_img_desk_before, cmap='gray')
plt.axis('off')
plt.show()
Right away, the three missing items are revealed!

It simply converts both photos to grayscale and subtracts the second photo from the first one. If an object on the first image did not move in the second image, it will simply be a case of the same values getting deduced from the same values, zeroing out those pixels. However, when there is movement or change, this will result to different pixel values for the before and after. These pixels will not be zero and hence will be visible when displayed.

Conclusion

In class, we learned about chromaticity-based image segmentation and image differencing for object detection. Chromaticity segmentation involves representing colors in the RG chromaticity space, which normalizes the red and green color channels. By analyzing the distribution of colors in this space and using techniques like Gaussian fitting, we can identify specific color regions of interest.

On the other hand, image differencing allows us to detect changes between two images by subtracting their grayscale representations. The resulting difference image highlights areas where movement or changes have occurred.

Through these techniques, we can effectively segment objects based on color or detect changes in a scene. By applying these concepts, we gain valuable insights into object segmentation and detection, enabling us to analyze images in various applications such as computer vision and surveillance systems.

References

Wikimedia Foundation. (2023, June 14). Human skin color. Wikipedia. https://en.wikipedia.org/wiki/Human_skin_color

째재와 육아 일기♡ : 네이버 블로그. (n.d.). https://blog.naver.com/hellomilja/220813661310

--

--

Cyril Benedict Lugod

Aspiring Data Scientist | MS Data Science @ Asian Institute of Management