In OpenCV (Python), why am I getting 3 channel images from a grayscale image?

Question:

I am using Python (2.7) and bindings for OpenCV 2.4.6 on Ubuntu 12.04

I load an image

    image = cv2.imread('image.jpg')

I then check the shape of the image array

    print image.shape

I get (480, 640, 3), which I expect for a 640×480 colour image. I then convert the image to grayscale and check the shape again.

    gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    print gray_image.shape

I get (480, 640, 1), which I expect for a 640×480 grayscale image. I then save the image:

    cv2.imwrite('gray.jpg', gray_image)

I am on linux, so I tried looking at the image with gThumb, which shows all the colour channels. When I bring the gray image back into OpenCV the image has three channels again.
I am aware of this flag for reading images:

    CV_LOAD_IMAGE_GRAYSCALE - If set, always convert image to the grayscale one

But this sounds like it is going to bring in the image as a colour image and then convert it. I am porting this project to RaspberryPi so I don’t want unecessary operatioms happening.

EDIT: I have done some timing checks and I have discovered that loading an image using the CV_LOAD_IMAGE_GRAYSCALE flag set results in the image loading twice as fast, irrespective of the image input.

     Using a 3072 x 4608 x 3 image
     0.196774959564 seconds with default loading
     0.0931899547577 seconds with CV_LOAD_IMAGE_GRAYSCALE

The problem seems to be that OpenCV is creating a 3 channel JPG output whether I have a grayscale image matrix or not!

What other app can I use to make sure I am getting a single 8 bit channel JPG image out??
(Perhaps gThumb is reporting the channels incorrectly).

If the image is not single channel, why is OpenCV saving my grayscale image to a 3 channel image at disk write?

Asked By: Steve

||

Answers:

In openCV reading jpg images result 3 channel images by default. So i’m not sure if you can actually see from jpg file that it’s already grayscaled but you can always load it as grayscaled. It would bring problems only if the image isn’t grayscaled beforehand and for your case i believe that it wouldn’t work. So short answer: you cant save jpg as onechanneled image. So you would need to grayscale it again after reading or figure out new way determine if image is grayscaled or not.

Answered By: Henri Korhonen

Your code is correct, it seems that cv2.imread load an image with three channels unless CV_LOAD_IMAGE_GRAYSCALE is set.

>>> import cv2
>>> image = cv2.imread('foo.jpg')
>>> print image.shape
 (184, 300, 3)
>>> gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
>>> print gray_image.shape 
 (184, 300)
>>> cv2.imwrite('gray.jpg', gray_image)

Now if you load the image:

>>> image = cv2.imread('gray.jpg')
>>> print image.shape
 (184, 300, 3)

It seems that you have saved the image as BGR, however it is not true, it is just opencv, by default it reads the image with 3 channels, and in the case it is grayscale it copies its layer three times. If you load again the image with scipy you could see that the image is indeed grayscale:

>>> from scipy.ndimage import imread
>>> image2 = imread('gray.jpg')
>>> print image2.shape
 (184, 300)

So if you want to load a grayscale image you will need to set CV_LOAD_IMAGE_GRAYSCALE flag:

>>> image = cv2.imread('gray.jpg', cv2.CV_LOAD_IMAGE_GRAYSCALE)
>>> print image.shape
 (184, 300)
Answered By: jabaldonedo

try this:

 img = cv2.imread('gray.jpg',0)

0 for gray and 1 for color

Answered By: Jino

If you want to preserve original number of channels, while loading image with OpenCV (as already mentioned in comments by @Gall):

img_np = cv2.imread("path/to_img", cv2.IMREAD_UNCHANGED)

cv2.IMREAD_GRAYSCALE – converts everything to grayscale.

If you also want to preserve original depth (i.e. int16), instead of implicit int8 conversion (by default):

img_np = cv2.imread(image_path, cv2.IMREAD_ANYDEPTH | cv2.IMREAD_UNCHANGED)
Answered By: apatsekin