Search code examples
pythonimagecolor-depth

Converting RGB images into greyscale using a color palette


I ran a algorithm that gave me rgb images, representing the estimated depth of the pixels of the imgs. Since it's representing a value, i wanted to convert it to greyscale. However, the rgb palette isnt linear with the level of grey.

Said palette and Sample image

I tried to use the image.convert line from the PIL library with the palette argument,as follows, but it didn't work.

At first i tried

    import os
from PIL import Image

directory = "folder/path"

for filename in os.listdir(directory):
    if filename.endswith(".jpg") or filename.endswith(".png"):
        file_path = os.path.join(directory, filename)

        image = Image.open(file_path)

        image_gray = image.convert('L')

        image_gray.save(file_path)

but that gave me a regular rgb to grayscale conversion, so i tried to use the custom scale like this :

    import os
from PIL import Image

directory = "folder/path"

custom_scale_path = "custom-scale/path"

custom_scale = Image.open(custom_scale_path).convert('L')

for filename in os.listdir(directory):
    if filename.endswith(".jpg") or filename.endswith(".png"):
        file_path = os.path.join(directory, filename)

        image = Image.open(file_path)

        image_gray = image.convert('P', palette=custom_scale)

        image_gray.save(file_path)

Any suggestion ?


Solution

  • You can solve this by building a lookup table from the given color scale and then finding the closest point for every pixel to it. Assumption would be that the image does not contain any RGB that has no representation in the given scale.

    Here is how I did it:

    First, read the image and the scale and transform it to arrays.

    from pylab import * # I am sorry for this...
    from PIL import Image
    
    # read files
    filename = "DVKmP.png" # file
    filename_sc = "cdHiq.png" # file with scale
    
    im = Image.open(filename)
    im_sc = Image.open(filename_sc)
    
    # convert image to pixels -> I am sure there are other ways...
    pixels = list(im.getdata())
    width, height = im.size
    pixels = np.array([pixels[i * width:(i + 1) * width] for i in range(height)])
    pixels_sc = list(im_sc.getdata())
    width_sc, height_sc = im_sc.size
    pixels_sc = np.array([pixels_sc[i * width_sc:(i + 1) * width_sc] for i in range(height_sc)])
    

    Next, construct the lookup table (lut) from your scale.

    # create a lookup table for the RGB to height values
    lut_c = mean(pixels_sc[:,:-1], axis=0) # colors -> the last column of your color scale was zero, so I skipped it
    lut_h = linspace(0, 1, len(lut_c)) # heights -> insert your actual values here
    # test plot
    figure()
    plot(lut_h, lut_c[:,0], color='r')
    plot(lut_h, lut_c[:,1], color='g')
    plot(lut_h, lut_c[:,2], color='b')
    xlabel('x - insert your scale here')
    ylabel('RGB values')
    title('Your lookup table for conversion')
    

    enter image description here

    At this point, you need to put in actual numbers for the height (or depth) map, that is connected to your scale. I simply used 0 and 1 here. What we can see is that above 0.6, you will not be able to discriminate values anymore with the given color scale, since all RGB values are constant.

    Now comes the conversion function. It calculates the minimum distance of any given RGB tuple to the values in the lookup table and returns the corresponding value.

    # function to map RGB to grayscale: it uses the closest point to your color scale
    mapRGBgrayscale = vectorize(
        lambda r,g,b: lut_h[argmin((r - lut_c[:,0])**2 
                                 + (g - lut_c[:,1])**2
                                 + (b - lut_c[:,2])**2)])
    

    Now apply it to your data file. This takes a while in python for larger pictures, so you may have to put in some additional work to make it faster.

    # this is your conversion
    h_map = mapRGBgrayscale(pixels[:,:,0], pixels[:,:,1], pixels[:,:,2])
    

    And plot or use elsewhere...

    # ... and the result
    figure(dpi=150)
    imshow(h_map)
    colorbar(label='height - put in your scale here')
    

    enter image description here

    (I did not use the grayscale color map for the plot, since there is this one area, far away, which would visualy dominate the image.)