Detect almost grayscale image with Python

Question:

Inspired by this question and this answer (which isn’t very solid) I realized that I often find myself converting to grayscale a color image that is almost grayscale (usually a color scan from a grayscale original). So I wrote a function meant to measure a kind of distance of a color image from grayscale:

import numpy as np
from PIL import Image, ImageChops, ImageOps, ImageStat

def distance_from_grey(img): # img must be a Pillow Image object in RGB mode
    img_diff=ImageChops.difference(img, ImageOps.grayscale(img).convert('RGB'))
    return np.array(img_diff.getdata()).mean()

img = Image.open('test.jpg')
print(distance_from_grey(img))

The number obtained is the average difference among all pixels of RGB values and their grayscale value, which will be zero for a perfect grayscale image.

What I’m asking to imaging experts is:

  • is this approach valid or there are better ones?
  • at which distance an image can be safely converted to grayscale without checking it visually?
Asked By: mmj

||

Answers:

This answer assumes your grayscaling function is idempotent, if this does not hold ignore this answer entirely.

is this approach valid

Depends on how yours inputs looks like and what you are expecting to do, consider certain edge case: you have image which consists of two parts, left half is grayscale and right half is colorful, what should happen?

better ones?

Depends on your definition of better, I suggest experimenting with other function in place of mean, e.g. max or median.

Answered By: Daweo

Given the following 3 images and using Colour:

McDonald Lake
Niagara Falls
Colourful Pencils

import numpy as np
import colour

image_1 = colour.read_image("mcdonald_lake.png")
# "mcdonald_lake.png" is single channel, we convert it to 3
image_1 = colour.utilities.tstack([image_1, image_1, image_1])
image_2 = colour.read_image("niagara_falls.png")
image_3 = colour.read_image("colouring_pencils.png")

# Converting from assumed "sRGB" encoded, i.e. "Output-Referred" to "Oklab" using Colour's Automatic Colour Conversion Graph.
image_1_OkLab = colour.convert(image_1, "Output-Referred RGB", "Oklab")
image_2_OkLab = colour.convert(image_2, "Output-Referred RGB", "Oklab")
image_3_OkLab = colour.convert(image_3, "Output-Referred RGB", "Oklab")

# Converting from "Lightness" and "a", "b" opponent colour dimensions
# to "Lightness", "Chroma" and "Hue".
image_1_OkLab_JCh = colour.models.Jab_to_JCh(image_1_OkLab)
image_2_OkLab_JCh = colour.models.Jab_to_JCh(image_2_OkLab)
image_3_OkLab_JCh = colour.models.Jab_to_JCh(image_3_OkLab)

print(np.mean(image_1_OkLab_JCh[..., 1]))
print(np.mean(image_2_OkLab_JCh[..., 1]))
print(np.mean(image_3_OkLab_JCh[..., 1]))
6.14471772026e-05
0.0292843706963
0.0798391223111

If you want to use ICtCp for example, you can simply change "Oklab" for "ICtCp" above.

It is also possible to get a detailed overview of the computations ran by the graph by using the verbose={"mode": "Long"} argument:

colour.convert(image_1, "Output-Referred RGB", "Oklab", verbose={"mode": "Long"})

Google Colab Notebook: https://colab.research.google.com/drive/1aDyUa4hSeCn-Sj47nUOilRAghl0fpd_W?usp=sharing

Answered By: Kel Solaar