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:

image from opencv

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:

image output from matplotlib

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!

Asked By: Daniel Agam

||

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

Answered By: Daniel Agam

You can use the following steps:

  1. load the data from the file
  2. truncate the data into 0-255 values, same type as original (float64)
  3. filtering using the == operator, which gives True/False values, then multiplying by 255 to get 0/255 integer values
  4. 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)
Answered By: ishahak

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

enter image description here

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).

image.astype(np.uint8)

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.

Answered By: Christoph Rackwitz