Pergunta

I have to write a test case in Python to check whether a jpg image is in color or grayscale. Can anyone please let me know if there is any way to do it with out installing extra libraries like OpenCV?

Foi útil?

Solução 2

You can check every pixel to see if it is grayscale (R == G == B)

from PIL import Image

def is_grey_scale(img_path):
    img = Image.open(img_path).convert('RGB')
    w, h = img.size
    for i in range(w):
        for j in range(h):
            r, g, b = img.getpixel((i,j))
            if r != g != b: 
                return False
    return True

Outras dicas

Can be done as follow:

from scipy.misc import imread, imsave, imresize
image = imread(f_name)
if(len(image.shape)<3):
      print 'gray'
elif len(image.shape)==3:
      print 'Color(RGB)'
else:
      print 'others'

There is more pythonic way using numpy functionality and opencv:

import cv2
def isgray(imgpath):
    img = cv2.imread(imgpath)
    if len(img.shape) < 3: return True
    if img.shape[2]  == 1: return True
    b,g,r = img[:,:,0], img[:,:,1], img[:,:,2]
    if (b==g).all() and (b==r).all(): return True
    return False

For faster processing, it is better to avoid loops on every pixel, using ImageChops, (but also to be sure that the image is truly grayscale, we need to compare colors on every pixel and cannot just use the sum):

from PIL import Image,ImageChops

def is_greyscale(im):
    """
    Check if image is monochrome (1 channel or 3 identical channels)
    """
    if im.mode not in ("L", "RGB"):
        raise ValueError("Unsuported image mode")

    if im.mode == "RGB":
        rgb = im.split()
        if ImageChops.difference(rgb[0],rgb[1]).getextrema()[1]!=0: 
            return False
        if ImageChops.difference(rgb[0],rgb[2]).getextrema()[1]!=0: 
            return False
    return True

A performance-enhance for fast results: since many images have black or white border, you'd expect faster termination by sampling a few random i,j-points from im and test them? Or use modulo arithmetic to traverse the image rows. First we sample(-without-replacement) say 100 random i,j-points; in the unlikely event that isn't conclusive, then we scan it linearly.

Using a custom iterator iterpixels(im). I don't have PIL installed so I can't test this, here's the outline:

import Image

def isColor(r,g,b): # use tuple-unpacking to unpack pixel -> r,g,b
    return (r != g != b)

class Image_(Image):
    def __init__(pathname):
        self.im = Image.open(pathname)
        self.w, self.h = self.im.size
    def iterpixels(nrand=100, randseed=None):
        if randseed:
            random.seed(randseed) # For deterministic behavior in test
        # First, generate a few random pixels from entire image
        for randpix in random.choice(im, n_rand)
            yield randpix
        # Now traverse entire image (yes we will unwantedly revisit the nrand points once)
        #for pixel in im.getpixel(...): # you could traverse rows linearly, or modulo (say) (im.height * 2./3) -1
        #    yield pixel

    def is_grey_scale(img_path="lena.jpg"):
        im = Image_.(img_path)
        return (any(isColor(*pixel)) for pixel in im.iterpixels())

(Also my original remark stands, first you check the JPEG header, offset 6: number of components (1 = grayscale, 3 = RGB). If it's 1=grayscale, you know the answer already without needing to inspect individual pixels.)

I faced a similar situation, where I tried the following approaches:

  1. Reading using IMREAD_UNCHANGEDand checking for image.shape
  2. Splitting B,G,R channels and checking if they are equal

Both of these approaches got me only like 53% accuracy in my dataset. I had to relax the condition for checking pixels in different channels and create a ratio to classify it as grey or color. With this approach, I was able to get 87.3% accuracy on my dataset.

Here is the logic which worked for me:

import cv2
import numpy as np

###test image
img=cv2.imread('test.jpg')

### splitting b,g,r channels
b,g,r=cv2.split(img)

### getting differences between (b,g), (r,g), (b,r) channel pixels
r_g=np.count_nonzero(abs(r-g))
r_b=np.count_nonzero(abs(r-b))
g_b=np.count_nonzero(abs(g-b))

### sum of differences
diff_sum=float(r_g+r_b+g_b)

### finding ratio of diff_sum with respect to size of image
ratio=diff_sum/img.size

if ratio>0.005:
    print("image is color")
else:
    print("image is greyscale")

Why wouldn't we use ImageStat module?

from PIL import Image, ImageStat

def is_grayscale(path="image.jpg")

    im = Image.open(path).convert("RGB")
    stat = ImageStat.Stat(im)

    if sum(stat.sum)/3 == stat.sum[0]:
        return True
    else:
        return False

stat.sum gives us a sum of all pixels in list view = [R, G, B] for example [568283302.0, 565746890.0, 559724236.0]. For grayscale image all elements of list are equal.

In case of a grayscale image, all channels in a certain pixel are equal (if you only have one channel, then you don't have a problem). So basically, you can list all the pixels with their three channel values to check if each pixel has all three channels equal.

Image.getcolors() returns an unsorted list of (count, pixel) values.

im = Image.open('path_to_image.whatever')
color_count = im.getcolors()

If len(color_count) exceeds 256 (default max value), this function returns None, meaning you had more than 256 color options in your pixel list, hence it is a colored image (grayscale can only have 256 colors, (0,0,0) to (255,255,255)).

So after that you only need :

if color_count: 
    # your image is grayscale
else:
    # your images is colored

Note this will work only when using the default parameter value of getcolors().

Documentation: https://pillow.readthedocs.io/en/stable/reference/Image.html#PIL.Image.Image.getcolors

Old question but I needed a different solution. Sometimes 3 channel images (eg RGB) might be almost grayscale without every pixel being identical in 3 channels. This checks every pixel but you can also subsample the image if needed. I used slope here but you can use checks on most of these parmaters from the regression. Linear regressions are usually very fast due to internal matrix multiply solution.

import glob
import scipy
import cv2
THRESH = 0.01
BASEDIR = 'folder/*.jpg'

files = glob.glob(BASEDIR)
for file in files:
    img = cv2.imread(file)
    slope1, intercept1, r1, p1, se1 = scipy.stats.linregress(img[:,:,0].flatten(),img[:,:,1].flatten())
    slope2, intercept2, r2, p2, se2 = scipy.stats.linregress(img[:,:,0].flatten(),img[:,:,2].flatten())
    if abs(slope1 - 1) > THRESH or abs(slope2 - 1) > THRESH:
        print(f'{file} is colour')
    else:
        print(f'{file} is close to grey scale')

Here is a version of Alexey Antonenko answer using PIL.image instead of cv2. In case you have float images I think it is safer to use the np.allclose function.

from PIL import Image
import numpy as np
def isgray(imgpath):
    img_pil = Image.open(imgpath)
    img = np.asarray(img_pil)
    if len(img.shape) < 3: return True
    if img.shape[2]  == 1: return True
    r,g,b = img[:,:,0], img[:,:,1], img[:,:,2]
    if np.allclose(r,g) and np.allclose(r,b): return True
    return False

As you are probably correct, OpenCV may be an overkill for this task but it should be okay to use Python Image Library (PIL) for this. The following should work for you:

import Image
im = Image.open("lena.jpg")

EDIT As pointed out by Mark and JRicardo000, you may iterate over each pixel. You could also make use of the im.split() function here.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top