comp_sci
comp_sci

Reputation: 31

Why are these codes not visually showing the right colors extracted from the image?

so I am working on a program to extract up to 4 of the most common colors, from a picture. Right now, I'm working on it visually showing the most common colors, however, after reading the image, I am:

Any tips or help? I've tried anything that I can, I am not sure why it cannot read the colors well. Thank you.

The code:

import matplotlib.image as img
import matplotlib.pyplot as plt
from scipy.cluster.vq import whiten
from scipy.cluster.vq import kmeans
import pandas as pd
import numpy as np

bimage = img.imread('Images/build2.jpg') #read image (this part works)
print(bimage.shape)

r = []
g = []
b = []
for row in bimage:
    for temp_r, temp_g, temp_b in row:
        r.append(temp_r)
        g.append(temp_g)
        b.append(temp_b)

bimage_df = pd.DataFrame({'red': r,
                          'green': g,
                          'blue': b})

bimage_df['scaled_color_red'] = whiten(bimage_df['red'])               #supposed to give color codes
bimage_df['scaled_color_blue'] = whiten(bimage_df['blue'])
bimage_df['scaled_color_green'] = whiten(bimage_df['green'])

cluster_centers, _ = kmeans(bimage_df[['scaled_color_red',             #to find most common colors
                                       'scaled_color_blue',
                                       'scaled_color_green']], 3)

dominant_colors = []

red_std, green_std, blue_std = bimage_df[['red',
                                          'green',
                                          'blue']].std()

for cluster_center in cluster_centers:
    red_scaled, green_scaled, blue_scaled = cluster_center
    dominant_colors.append((
        red_scaled * red_std / 255,
        green_scaled * green_std / 255,
        blue_scaled * blue_std / 255
    ))

plt.imshow([dominant_colors])
plt.show()

The image I used:

enter image description here

I have tried using this method for an output and another type of chart too, but that gave me all black or purple, unrelated colors. I had referred to geeks4geeks for this, could not troubleshoot either. Any help would be greatly appreciated.

Upvotes: 2

Views: 63

Answers (1)

Rotem
Rotem

Reputation: 32084

The major issue is the usage of whiten method that is not adequate for the sample image:

whiten documentation:

Before running k-means, it is beneficial to rescale each feature dimension of the observation set by its standard deviation (i.e. “whiten” it - as in “white noise” where each frequency has equal power). Each feature is divided by its standard deviation across all observations to give it unit variance.

The normalization method assumes normal distribution of the noise.
The sample image is not a natural image (has no noise), and the normalization procedure does not feat the given image.

Instead of normalization, it is recommended to convert the image to LAB color space, where color distances better match the perceptual distances.
Keeping the colors in RGB format may work good enough...

Swapping the green and the blue channels is another issue.


Instead of using a for loop, we may use NumPy array operations (it's not a bug, just faster):

fimage = bimage.astype(float)  # Convert image from uint8 to float (kmeans requires floats).
r = fimage[:, :, 0].flatten().tolist()  # Convert red elements to list
g = fimage[:, :, 1].flatten().tolist()  # Convert grenn elements to list
b = fimage[:, :, 2].flatten().tolist()  # Convert blue elements to list

bimage_df = pd.DataFrame({'red': r,
                          'green': g,
                          'blue': b})

Apply kmeans with 100 iterations (the default is 20, and may not be enough):

cluster_centers, _ = kmeans(bimage_df[['red',             #Find rhe 4 most common colors
                                       'green',
                                       'blue']], 4, iter=100)  # The default is 20 iterations, use 100 iterations for better convergence

Before using plt.imshow we have to convert the colors to uint8 type (we may also convert to range [0, 1]), otherwize the displayed colors are going to be white (saturated).

dominant_colors = np.round(cluster_centers).astype(np.uint8)  # Round and convert to uint8

plt.imshow([dominant_colors])
plt.show()

Code sample:

import matplotlib.image as img
import matplotlib.pyplot as plt
#from scipy.cluster.vq import whiten
from scipy.cluster.vq import kmeans
import pandas as pd
import numpy as np

bimage = img.imread('Images/build2.jpg') #read image (this part works)
print(bimage.shape)

#r = []
#g = []
#b = []
#for row in bimage:
#    for temp_r, temp_g, temp_b in row:
#        r.append(temp_r)
#        g.append(temp_g)
#        b.append(temp_b)

# Use NumPy array operations, instead of using a for loop.
fimage = bimage.astype(float)  # Convert image from uint8 to float (kmeans requires floats).
r = fimage[:, :, 0].flatten().tolist()  # Convert red elements to list
g = fimage[:, :, 1].flatten().tolist()  # Convert grenn elements to list
b = fimage[:, :, 2].flatten().tolist()  # Convert blue elements to list

bimage_df = pd.DataFrame({'red': r,
                          'green': g,
                          'blue': b})

# Don't use whiten
#bimage_df['scaled_color_red'] = whiten(bimage_df['red'])               #supposed to give color codes
#bimage_df['scaled_color_blue'] = whiten(bimage_df['blue'])
#bimage_df['scaled_color_green'] = whiten(bimage_df['green'])
#cluster_centers, _ = kmeans(bimage_df[['scaled_color_red',             #to find most common colors
#                                       'scaled_color_blue',
#                                       'scaled_color_green']], 3)

cluster_centers, _ = kmeans(bimage_df[['red',             #Find the 4 most common colors
                                       'green',
                                       'blue']], 4, iter=100)  # The default is 20 iterations, use 100 iterations for better convergence

dominant_colors = np.round(cluster_centers).astype(np.uint8)  # Round and convert to uint8
print(dominant_colors)

# Since whiten is not used, we don't need the STD
#red_std, green_std, blue_std = bimage_df[['red',
#                                          'green',
#                                          'blue']].std()
#for cluster_center in cluster_centers:
#    red_scaled, green_scaled, blue_scaled = cluster_center
#    dominant_colors.append((
#        red_scaled * red_std / 255,
#        green_scaled * green_std / 255,
#        blue_scaled * blue_std / 255
#    ))

plt.imshow([dominant_colors])
plt.show()

Result:
enter image description here

Upvotes: 2

Related Questions