2

For my work I need to replace the pseudo-color RGB values in a JPG into gray-scale for processing. The 'color' image has a specific range of RGB colors (either 20 or 255, depending of the origin) that need to be linked to a gray-scale value in order to do some measurement on the output image. Working with the raw RGB values has been quite difficult, as the specified range is only working fine in PNG or tiff format, but conversion to JPG creates some RGB values that are a bit off from the intended RGB value and this renders my existing analysis useless.

I have found this nice piece of code in one of the answers as a solution to round the color of a certain point to its nearest similar color in a dictionary.

def distance(c1, c2):
   (r1,g1,b1) = c1
   (r2,g2,b2) = c2
   return math.sqrt((r1 - r2)**2 + (g1 - g2) ** 2 + (b1 - b2) **2)

colors = list(rgb_code_dictionary.keys())
closest_colors = sorted(colors, key=lambda color: distance(color, point))
closest_color = closest_colors[0]
code = rgb_code_dictionary[closest_color]

Python PIL - Finding Nearest Color (Rounding Colors)

rgb_code_dictionary is replaced with a dictionary of rgb values with he corresponding grayvalue that I would like to apply.

This works really great, it produces the output I need. This code is however quite slow if applied to a full image (it only takes 18sec per image, but I have a few thousand images to process), as I need to feed each pixel to the function in order to get the closest match.

i have found another piece of code that works very fast in replacing colours:

Replacing RGB values in numpy array by integer is extremely slow

# Extract color codes and their IDs from input dict
colors = np.array(_color_codes.keys())
color_ids = np.array(_color_codes.values())

# Initialize output array
result = np.empty((img_arr.shape[0],img_arr.shape[1]),dtype=int)
result[:] = -1

# Finally get the matches and accordingly set result locations
# to their respective color IDs
R,C,D = np.where((img_arr == colors[:,None,None,:]).all(3))
result[C,D] = color_ids[R]

But this one only works if the RGB values in the dictionary are a perfect match to the RGB values in the image. Ideally I would merge the two together in order to get a fast and reliable function to replace the colors. But i have problems to translate the first piece of code into a "where" statement in numpy, in order to speed up the whole process of exchanging RGB values with a gray scale value.

As I'm not a programmer by education (and really new to python), I have troubles to see how to fix this. Searching on many fora has not helped in finding a good solution (that I understand and can implement myself), hence my question.

Is there a function available to do what I intend? Or is there a way to merge the 2 functions into one, so the conversion goes both fast and correct?

Any help is really appreciated (but keep it a bit simple if possible :-) )

Thanks

EDITED: I ended up with another solution altogether, as I had some difficulties to install the solution proposed by Mark Setchell. I am pretty convinced that using ImageMAgick is the best solution for my specific problem, sadly enough I couldn't install the solution due to safety settings on my PC.

The solution I came up with is basically converting the colour image to grayscale and then applying another grayscale value to each pixel, where the new grayscale will span +/- 6 grayscale values from the old grayscale value. (Eg everything from 242 to 255 will be replaced with 255) This solution doesn't really work fast, nor does it give the best results, but for now this is all that seems to be feasible in the short future.

thanks for the answers, it helped me to rethink the process.

KJohnson
  • 35
  • 8

2 Answers2

3

Mmmm... JPEG is rarely a good choice for "data" type images because of the inherent lossiness - always prefer PNG, TIFF, or for the ultimate simplicity, the venerable NetPBM formats (PGM, PPM, PAM) and PFM for float.

Anyway, you can remap an image extremely fast, and with no coding necessary, at the command line using ImageMagick which is installed on most Linux distros and is also available for macOS and Windows.

So, save your dictionary of desired colours (just a single row of pixels) as a PNG file called LUT.png (Look Up Table). I'll make a little one with Red, Lime Green, Blue, Black and White using ImageMagick to show you how it looks:

convert xc:red xc:lime xc:blue xc:white xc:black +append LUT.png

enter image description here

Aside: If you have the colours for your Look Up Table as RGB triplets, rather than as named colours, you can use them like this instead:

convert xc:"rgb(255,0,0)" xc:"rgb(0,255,0)" xc:"rgb(0,0,255)" +append LUT.png

Now take one of your images, say Mr Bean:

enter image description here

and map the contained colours to the lookup table:

convert MrBean.jpg +dither -map LUT.png result.png

enter image description here

Note that from v7 onwards of ImageMagick, the convert command becomes magick, i.e.:

magick MrBean.jpg +dither -map LUT.png result.png

If you have hundreds to do, you can get them all done nice and fast in parallel using all those lovely CPU cores you paid Intel so handsomely for, by using GNU Parallel:

parallel convert {} +dither -map LUT.png {.}.png ::: *.jpg

where:

  • {} represents the file currently being processed, and
  • {.} represents that file without extension, and
  • ::: indicates the start of the filenames to be processed

Obviously, make a backup first and just work on a sub-set of your data before using anything strange folk on the Internet have suggested ;-)

Mark Setchell
  • 146,975
  • 21
  • 182
  • 306
  • I will give this a try, an extra question, do you know if it is possible to run this from a python script? Ideally the whole thing is wrapped in one big "program" so i can just start it and have the program do all the hard work. As Ideally is not always available, a two step process would obviously be better than no process at all :-) – KJohnson Feb 07 '18 at 09:29
  • You can certainly *"shell out"* from Python to run `convert`, like this https://stackoverflow.com/a/89243/2836621 – Mark Setchell Feb 07 '18 at 10:11
1

Why do you not use the PIL library for that:

greyImage = Image.open(colorImageName).convert('L')

The 'L' stands for luminance.

The pixel-data are available via the getdata() method.

Johan van Breda
  • 473
  • 7
  • 10
  • Would luminance reflect the gray values linked to the colours values? (the list of RGB values i start with?) Or is this a conversion like RGB to HSV, where I would only select a single channel? It is important that a specific RGB is linked to a specific Grayscale value in order to get the correct output. – KJohnson Feb 07 '18 at 09:26
  • It is not documented - as far as I could see. However, I use this and see the same result as if you apply grey = (r * 11 + g * 16 + b * 5)/32 – Johan van Breda Feb 07 '18 at 10:15
  • @KJohnson: in https://github.com/whatupdave/pil/blob/master/PIL/Image.py we can find "When translating a colour image to black and white (mode "L"), the library uses the ITU-R 601-2 luma transform: L = R * 299/1000 + G * 587/1000 + B * 114/100" – Jongware Feb 11 '18 at 13:47