Reputation: 2244
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:
But after looking at the save file, the image looks like this:
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
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