Unclear difference in displaying the same image by opencv and matplotlib [with example code & exported .npy file]
Question:
In continue to my previous question (that still not answered here)
Here is a simple code with some unsuccessful attempts and exported image.npy file that illustrates the problem:
'''
conda install -c conda-forge opencv
pip install numpy
pip install matplotlib
'''
import cv2
import numpy as np
import matplotlib.pyplot as plt
if __name__ == "__main__":
image = np.load('image.npy') # Link to the file under this code block
print(image.shape) # output: (256, 256, 1)
print(image.dtype) # output: float64
# Unsuccessful attempts:
# image[np.where(image.max() != 255)] = 0
# max_img = image.max(axis=0)
# int_image = image.astype(int)
Link to download the image.npy file
And when I display it with opencv using the following code:
cv2.imshow('image', image)
cv2.waitKey()
I get an image like the following result:
In contrast, when I display it with matplotlib using the following code:
plt.imshow(image, cmap="gray")
(The ‘cmap’ parameter is not the issue here, It’s only plot the image in Black & White)
I get an image the following result:
The second result is the desired one as far as I’m concerned –
my question is how to make the image like this (by code only and without the need to save to a file and load the image) and make it so that I get the same image in opencv as well.
I researched the issue but did not find a solution.
This reference helps me understand the reason in general but I’m still don’t know how to show the image in opencv like matplotlib view in this case.
Thank you!
Answers:
Finally, I found the solution:
int_image = image.astype(np.uint8)
cv2.imshow('image', int_image)
cv2.waitKey()
plt.imshow(image, cmap="gray")
plt.title("image")
plt.show()
Now – The 2 plots are same.
Hope this helps more people in the future
You can use the following steps:
- load the data from the file
- truncate the data into 0-255 values, same type as original (float64)
- filtering using the
==
operator, which gives True/False values, then multiplying by 255 to get 0/255 integer values
- use
cv2.imshow
combined with astype
to get the required type
import numpy as np
import cv2
if __name__ == '__main__':
data = np.load(r'image.npy')
data2 = np.trunc(data)
data3 = (data2 == 255) * 255
cv2.imshow('title', data3.astype('float32'))
cv2.waitKey(0)
You have very few unique values in image
, and their distribution is severely skewed. The next smallest value below 255 is 3.something, leaving a huge range of values unused.
image.max() == 255
sorted(set(im.flat)) == [0.0, 0.249, 0.499, 0.748, 0.997, 1.246, 1.496, 1.745, 1.994, 2.244, 2.493, 2.742, 2.992, 3.241, 3.490, 3.739, 3.989, 255.0]
cv.imshow
, given floats, will map 0.0 to black and 1.0 to white. That is why your picture looks like
Your options:
- convert to
uint8
using image.astype(np.uint8)
because cv.imshow
, given uint8
data, maps 0 to black and 255 to white
- divide by 255, so your values, being float, range from 0 to 1
- normalize by whatever way you want, but remember how
imshow
behaves given a certain element type and range of values. It also accepts uint16
, but not int
(which would be int32/int64).
The other answer, recommending np.trunc
and stuff, is just messing around. The trunc
doesn’t change any of the 255.0
values (in the comparison), it’s redundant. There’s no need to threshold the data, unless you need such a result. It is also wrong in that it tells you to take a binary array, blow its value range up 0 to 255, and then convert to float, which is pointless, because imshow
, given float, expects a value range of 0 to 1. Either the *255
step is pointless, or the astype
step should have used uint8
.
In continue to my previous question (that still not answered here)
Here is a simple code with some unsuccessful attempts and exported image.npy file that illustrates the problem:
'''
conda install -c conda-forge opencv
pip install numpy
pip install matplotlib
'''
import cv2
import numpy as np
import matplotlib.pyplot as plt
if __name__ == "__main__":
image = np.load('image.npy') # Link to the file under this code block
print(image.shape) # output: (256, 256, 1)
print(image.dtype) # output: float64
# Unsuccessful attempts:
# image[np.where(image.max() != 255)] = 0
# max_img = image.max(axis=0)
# int_image = image.astype(int)
Link to download the image.npy file
And when I display it with opencv using the following code:
cv2.imshow('image', image)
cv2.waitKey()
I get an image like the following result:
In contrast, when I display it with matplotlib using the following code:
plt.imshow(image, cmap="gray")
(The ‘cmap’ parameter is not the issue here, It’s only plot the image in Black & White)
I get an image the following result:
The second result is the desired one as far as I’m concerned –
my question is how to make the image like this (by code only and without the need to save to a file and load the image) and make it so that I get the same image in opencv as well.
I researched the issue but did not find a solution.
This reference helps me understand the reason in general but I’m still don’t know how to show the image in opencv like matplotlib view in this case.
Thank you!
Finally, I found the solution:
int_image = image.astype(np.uint8)
cv2.imshow('image', int_image)
cv2.waitKey()
plt.imshow(image, cmap="gray")
plt.title("image")
plt.show()
Now – The 2 plots are same.
Hope this helps more people in the future
You can use the following steps:
- load the data from the file
- truncate the data into 0-255 values, same type as original (float64)
- filtering using the
==
operator, which gives True/False values, then multiplying by 255 to get 0/255 integer values - use
cv2.imshow
combined withastype
to get the required type
import numpy as np
import cv2
if __name__ == '__main__':
data = np.load(r'image.npy')
data2 = np.trunc(data)
data3 = (data2 == 255) * 255
cv2.imshow('title', data3.astype('float32'))
cv2.waitKey(0)
You have very few unique values in image
, and their distribution is severely skewed. The next smallest value below 255 is 3.something, leaving a huge range of values unused.
image.max() == 255
sorted(set(im.flat)) == [0.0, 0.249, 0.499, 0.748, 0.997, 1.246, 1.496, 1.745, 1.994, 2.244, 2.493, 2.742, 2.992, 3.241, 3.490, 3.739, 3.989, 255.0]
cv.imshow
, given floats, will map 0.0 to black and 1.0 to white. That is why your picture looks like
Your options:
- convert to
uint8
usingimage.astype(np.uint8)
becausecv.imshow
, givenuint8
data, maps 0 to black and 255 to white - divide by 255, so your values, being float, range from 0 to 1
- normalize by whatever way you want, but remember how
imshow
behaves given a certain element type and range of values. It also acceptsuint16
, but notint
(which would be int32/int64).
The other answer, recommending np.trunc
and stuff, is just messing around. The trunc
doesn’t change any of the 255.0
values (in the comparison), it’s redundant. There’s no need to threshold the data, unless you need such a result. It is also wrong in that it tells you to take a binary array, blow its value range up 0 to 255, and then convert to float, which is pointless, because imshow
, given float, expects a value range of 0 to 1. Either the *255
step is pointless, or the astype
step should have used uint8
.