0

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 )
alyssaeliyah
  • 2,214
  • 6
  • 33
  • 80
  • 1
    As usual [MCVE] to reproduce the problem, rather than couple of lines of code ripped out of context. In this particular case, the problem lies in the contents of `rgb_transform`, which we can only guess about. Anyway, my guess right now is that `rgb_transform` is `CV_32FC3`, but the values are not in range [0,1] -- `imshow` scales that by 255, `imwrite` doesn't. – Dan Mašek Apr 11 '18 at 12:08
  • @DanMašek --> Thank you for the advise. Please see my updated post. – alyssaeliyah Apr 11 '18 at 14:22
  • 1
    Yeah, like I guessed. `R`, `G`, and `B` have elements of type `float64`, but the range of values is [0.0,255.0]. `rgb_transform` is the same scenario, as it's just the 3 arrays merged. The solution would be to round the values to integers and convert back to `uint8`. As in `rgb_transform = np.uint8(np.around(rgb_transform))` before you call `imshow`. – Dan Mašek Apr 11 '18 at 14:34
  • 1
    BTW: `rgb_transform.shape`, `rgb_transform.dtype`, `rgb_transform.min()`, `rgb_transform.max()` -- handy properties of the numpy arrays to inspect (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. – Dan Mašek Apr 11 '18 at 14:44
  • Wow, perfect thanks. You can put your comment to the answer section, so that I can mark it right. Thank you so much. – alyssaeliyah Apr 11 '18 at 14:53

1 Answers1

1

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.

Dan Mašek
  • 17,852
  • 6
  • 57
  • 85