Python openCV: I get an unchanged image when I use cvtColor
Question: I have an image as a numpy array with the shape (480,640) in grayscale.

I want to lay a colored mask over the image and need to get the image in the
same shape to do it, which is (480,640,3).

Here is what I tried:

    
    
    print str(img.shape) +' '+ str(type(img)) +' '+ str(img.dtype)
    # prints: (480, 640) <type 'numpy.ndarray'> uint8
    
    cv2.cvtColor(img, cv2.COLOR_GRAY2BGR, img, 3)
    # this line seems to have no effect although I set it to 3 channels
    
    print str(img.shape) +' '+ str(type(img)) +' '+ str(img.dtype)
    # prints: (480, 640) <type 'numpy.ndarray'> uint8
    
    rowCounter = 0
    while rowCounter < img.shape[0]:
        columnCounter = 0
        while columnCounter < img.shape[1]:
            if img[rowCounter, columnCounter] == 0:
                img[rowCounter, columnCounter] = [0, 0, 0]
            else:
                img[rowCounter, columnCounter] = [255, 255, 255]
            columnCounter += 1
        rowCounter += 1
    

Ok, the code stops on the line where I want to assign the three values ([0, 0,
0]) instead of the single value (0). The error message reads as follows:

    
    
    ValueError: setting an array element with a sequence.
    

How can I change from single value to three values? Is there a function I am
not finding?

Thank you!


Answer: The main thing is that you need to assign the converted image to a new name.

I'm not sure if using the c++ format of providing the target image as an
argument works. I would just do it the usual python (cv2) way of assigning to
a name (same name is fine).

Also, you don't need to assign the number of channels. The conversion type
takes care of that.

    
    
    #            cv2.cvtColor(img, cv2.COLOR_GRAY2BGR, img, 3)
    color_mask = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
    

Does that get you the image you want?

By the way, as long as you are using numpy/opencv you probably want to look
into ways of making it more efficient. If you do individual pixel access
across a whole image/numpy array, that's a red flag (for opencv in python).

Below is code that shows the conversion but then ignores that and shows (as I
understand it) how to apply a more efficient mask.

# Copy-Paste Working (and More Efficient) Example

    
    
    import cv2
    import numpy as np
    
    # setup an original image (this will work for anyone without needing to load one)
    shape = (480, 640)
    img_gray = np.ndarray(shape, dtype=np.uint8)
    img_gray.fill(127)
    img_gray[0:40, 100:140] = 0  # some "off" values
    cv2.imshow('original grayscale image', img_gray)
    cv2.waitKey(0)  # press any key to continue
    
    # convert the gray image to color (not used. just to demonstrate)
    img_color = cv2.cvtColor(img_gray, cv2.COLOR_GRAY2BGR)
    cv2.imshow('color converted grayscale image (not used. just to show how to use cvtColor)', img_color)
    cv2.waitKey(0)  # press any key to continue
    
    # a simplified version of what your code did to apply a mask
    # make a white image.
    # and then set it to black wherever the original grayscale image is 0
    img_color = np.ndarray(img_gray.shape + (3,), dtype=np.uint8)
    img_color.fill(255)
    cv2.imshow('base color image', img_color)
    cv2.waitKey(0)  # press any key to continue
    
    # this is not the fastest way, but I think it's more logical until you need more speed
    # the fastest way specifically to black out parts of the image would
    # be np.bitwise_and(...)
    black_points = np.where(img_gray == 0)
    print('found {} points to mask'.format(len(black_points[0])))
    img_color[black_points] = (0, 0, 0)
    
    # a more efficient and still straightforward method you could use:
    img_color[img_gray==0] = (0, 0, 0)
    cv2.imshow('masked color image', img_color)
    cv2.waitKey(0)  # press any key to continue
    
    # clean up explicitly
    cv2.destroyAllWindows()
    

