ygi
ygi

Reputation: 11

How to determine if an image is monochromatic

I have to determine if an image is monochromatic. I mean by monochromatic not only Black and white.. it could be Sepia, like Red or blue shaded, or even mixed Red and blue shaded a.s.o (mono color scaled) ?

I think we can say that the scale goes from black (0,0,0) to White (255,255,255) bu it is not linear.. it's a specific curve on each color channel

I hope I am clear :)

Upvotes: 1

Views: 1661

Answers (1)

Mark Setchell
Mark Setchell

Reputation: 207520

If you think of a greyscale image such as this:

enter image description here

all the RGB triplets present in the image will be on, or near, the straight line joining RGB[0,0,0] i.e. black to RGB[255,255,255] i.e. white in the RGB colour cube:

enter image description here

And that is the case with all monochromatic images - all the RGB triplets will occur near to a straight line in the RGB colour cube. In a cyanotype the straight line will pass through dark blue and light blue. In a sepia image the line will pass through brown and light brown, and in a red-blue duotone the straight line will join the red and blue points within the RGB cube.

Let's look at some images and their scatterplots...

greyscale

enter image description here enter image description here


cyanotype

enter image description here enter image description here


duotone

enter image description here enter image description here


sepia

enter image description here enter image description here


I think a technique that may work for you is to fit a straight line to the point-cloud in the RGB colour-cube of your image and then look at the least squares error and if that is small, the points lie on a line and your image is monochromatic. I think that line is called the "3D Orthogonal Distance Regression (ODR) line" - see here.

I have attempted the maths now. Basically, I believe I can compute the SVD of the triplets and get the variances contained within each of the 3 Principal Components. If all the RGB triplets lie on a straight line, then all the variance will be along that line, in the first Principal Component and it will be 100% and the second and third components will be zero. If the RGB triplets lie largely in the first component with a smaller percentage in the second and none in the third that would mean they fall largely on a single line and only tend to stray in a single perpendicular direction from that line so the point cloud will look like a long flat ellipse. If the second and third components are reasonably large, the point cloud is 3-d cigar shaped. If the first component does not contain a very significant percentage of the variance, the RGB triplets do not fall on a straight line.

#!/usr/local/bin/python3

import sys
import numpy as np
from skimage import io
from skimage.transform import resize

if len(sys.argv) != 2:
   sys.exit("Usage: mono.py filename")

path = sys.argv[1]
im = io.imread(path)

# Down-res image to make SVD time reasonable
im = resize(im,(128,128))

# Form list of all RGB triplets present in image
triplets = im.reshape(-1,3)

# Get mean on all axes
means = triplets.mean(axis=0)

# Calculate SVD
uu, dd, vv = np.linalg.svd(triplets - means)

# dd holds the variance in each of the PCA directions
# The key factor is what percentage of the variance is held in the first direction
PC1var = dd[0]*100./dd.sum()
PC2var = dd[1]*100./dd.sum()
PC3var = dd[2]*100./dd.sum()
print(f"PC1 variance: {PC1var}")
print(f"PC2 variance: {PC2var}")
print(f"PC3 variance: {PC3var}")

Running the above code against my test images gives results of 90+% and gives values around 60% for normal, non-monochromatic photos. Please do some tests before putting this into production!!!


For my own reference as much as anything, I extracted the triplets into CSV files like this:

#!/usr/local/bin/python3

import numpy as np
from skimage import io

images = ["greyscale","tintype", "sepia", "duotone", "cyanotype"]

for i in images:
   # Load image
   im = io.imread(i + ".jpg")

   # Form list of all RGB triplets
   triplets = im.reshape(-1,3)

   with open(i + ".dat",'w') as f:
      for t in triplets:
         f.write(f"{t[0]} {t[1]} {t[2]}\n")

And I did the 3-d scatterplots with gnuplot like this:

#!/usr/bin/env gnuplot --persist

set xrange [0:255]
set yrange [0:255]
set zrange [0:255]
set ticslevel 0
splot "cyanotype.dat" u 1:2:3 with points

Keywords: Python, image processing, monochromatic, cyanotype, tintype, duotone, greyscale, Gnuplot, splot, 3D, 3-d, triplets, cloud. point cloud, RGB colour-cube.

Upvotes: 10

Related Questions