coin cheung
coin cheung

Reputation: 1107

why does opencv convert color space different from pil?

I am trying to convert an normal image to gray and hsv color-space with opencv and PIL, however, I found the results are not same:

## convert to gray
im_pil = np.array(Image.open(imgpth).convert("L"))
im_cv = cv2.cvtColor(cv2.imread(imgpth), cv2.COLOR_BGR2GRAY)
print(np.sum(im_pil - im_cv))

## convert to hsv
im_pil = np.array(Image.open(imgpth).convert("HSV").split()[0])
im_cv = cv2.cvtColor(cv2.imread(imgpth), cv2.COLOR_BGR2HSV)[:, :, 0]
print(np.sum(im_pil - im_cv))

Where does the difference come from? Can I make the two results same?

Upvotes: 1

Views: 1280

Answers (1)

Mark Setchell
Mark Setchell

Reputation: 208003

As @HansHirse suggests in the comments, the difference in greyscale conversion between OpenCV and PIL is that OpenCV uses nearest integer rounding (nint()) whereas PIL uses rounding down (int()).

The following program demonstrates that by generating 1x1 pixel images of pure red from 0..255, then pure green from 0..255 and then pure blue from 0..255 and converting them with both OpenCV and PIL.

#!/usr/bin/env python3

import cv2
from PIL import Image
import numpy as np

print("Varying red through 0..255, showing red component, OpenCV grey, PIL grey")
for r in range(256):
    ocvim = np.zeros((1,1,3), dtype=np.uint8) 
    ocvim[0,0,0] = r
    ocvgrey = cv2.cvtColor(ocvim,cv2.COLOR_RGB2GRAY)
    pilim = Image.new('RGB',(1,1),(r,0,0)).convert('L')
    print(r,ocvgrey[0,0],pilim.getpixel((0,0)))

print("Varying green through 0..255, showing green component, OpenCV grey, PIL grey")
for g in range(256):
    ocvim = np.zeros((1,1,3), dtype=np.uint8) 
    ocvim[0,0,1] = g
    ocvgrey = cv2.cvtColor(ocvim,cv2.COLOR_RGB2GRAY)
    pilim = Image.new('RGB',(1,1),(0,g,0)).convert('L')
    print(g,ocvgrey[0,0],pilim.getpixel((0,0)))

print("Varying blue through 0..255, showing blue component, OpenCV grey, PIL grey")
for b in range(256):
    ocvim = np.zeros((1,1,3), dtype=np.uint8) 
    ocvim[0,0,2] = b
    ocvgrey = cv2.cvtColor(ocvim,cv2.COLOR_RGB2GRAY)
    pilim = Image.new('RGB',(1,1),(0,0,b)).convert('L')
    print(b,ocvgrey[0,0],pilim.getpixel((0,0)))

If you want to make OpenCV come to the same result as PIL, the easiest way is probably to let OpenCV do the calculation on a float value (rather than a np.uint8) to get the extra precision, then round it down yourself the same way as PIL does. So, instead of:

grey = cv2.cvtColor(uint8im,cv2.COLOR_BGR2GRAY)

you would use:

grey = cv2.cvtColor(uint8im.astype(np.float32),cv2.COLOR_BGR2GRAY).astype(np.uint8)

If you change the formulae in the code at the start of my answer to this format, the greyscale values calculated by OpenCV match those of PIL.

Upvotes: 5

Related Questions