alyssaeliyah
alyssaeliyah

Reputation: 2244

Python - Image Appearance in Window is different after it is saved on file using OpenCV

I have this code below:

cv2.imshow('RGB Transform Image', rgb_transform)
cv2.waitKey()
cv2.imwrite('rgb_transformed_image', rgb_transform )

When I displayed the image, it look likes below:

enter image description here

But after looking at the save file, the image looks like this:

enter image description here

It's so weird. Where did I go wrong?

UPDATE :

The context of my code is I have read an image as greyscale, split its RGB then perform transform on every channel then combine it again.

image = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE)
image = imutils.resize(image, width = 500)

a = 255
b = 2 * (np.pi/255)
c = np.pi / 5

R = a * np.absolute(np.sin(b * image))
G = a * np.absolute(np.sin(b * image + c))
B = a * np.absolute(np.sin(b * image + 2 * c))

rgb_transform =  np.stack((R,G,B),2)

cv2.imshow('RGB Transform Image', rgb_transform)    
cv2.waitKey()
cv2.imwrite('rgb_transformed_image', rgb_transform )

Upvotes: 0

Views: 453

Answers (1)

Dan Mašek
Dan Mašek

Reputation: 19041

This kind of a problem tends to occur when you give cv2.imshow and cv2.imwrite something else than an array with elements of type uint8.

The function cv2.imshow does some transformations (scaling) before displaying the images:

The function may scale the image, depending on its depth:

  • If the image is 8-bit unsigned, it is displayed as is.
  • If the image is 16-bit unsigned or 32-bit integer, the pixels are divided by 256. That is, the value range [0,255*256] is mapped to [0,255].
  • If the image is 32-bit or 64-bit floating-point, the pixel values are multiplied by 255. That is, the value range [0,1] is mapped to [0,255].

Even though the documentation is not quite clear on that, cv2.imwrite doesn't do any scaling. At most it will do a conversion to uint8, but never any scaling.


Let's get to where your problem is. You perform something like a * np.absolute(np.sin(b * image)) on the input image which contains values in range [0,255]. The result of those operations is an array of 64bit floating point values (np.float64, or in OpenCV CV_64F). Upon inspection, the range of the values is still [0.0,255.0].

Even though the documentation doesn't seem to explicitly mention this, cv2.imshow will treat 64bit floats just like 32bit floats -- i.e. it will scale the values by 255 (with saturation) before displaying them as a 3 channel 8bit BGR image. That means anything in the source image with intensity > 1 gets clipped, and you see mostly white pixels.

As I mentioned before, cv2.imwrite doesn't do any scaling, it will just convert the data type to something it can work with and save a reasonable looking image.

The solution to this problem would be to round the floating point values to nearest integers (so you use the full range) and cast the result to np.uint8, before you pass it to cv2.imwrite or cv2.imshow:

rgb_transform = np.uint8(np.around(rgb_transform))

As a general suggestion, it's very useful to know what kind of data you're passing into the various functions you use, and cross reference it with the documentation.

When working with numpy arrays, the following few attributes of the arrays are useful to inspect (I'll speak in terms of your rgb_transform, feel free to substitute the variable name as appropriate):

  • rgb_transform.shape -- size and number of channels

  • rgb_transform.dtype -- data type of each element

  • rgb_transform.min() -- minimum value in the array

  • rgb_transform.max() -- maximum value in the array

It's a good idea to inspect their values (either in the interactive interpreter, or say using simple print statements) when you have issues. It's important to know the data you're feeding into other functions, and cross reference it with the documentation.

Upvotes: 1

Related Questions