Search code examples
pythonscikit-image

Why does skimage.color.rgb2gray behave differently on different devices?


I'm having some troubles with the skimage.color.rgb2gray method. I'm using it to turn an image (some simple black lines drawn on a white canvas with Paint) with a Python3 program, which is this:

import matplotlib.image as mpimg
from skimage import color

img = mpimg.imread('Image (1).png')
gray = color.rgb2gray(img)

and I've found out that it returns different arrays on two different devices.

The first one is a Raspberry Pi 3 Model B (OS: Raspbian GNU/Linux 8 (jessie)), and it returns this,

[[0.99999994 0.99999994 0.99999994 ... 0.99999994 0.99999994 0.99999994]
[0.99999994 0.99999994 0.99999994 ... 0.99999994 0.99999994 0.99999994] 
[0.99999994 0.99999994 0.99999994 ... 0.99999994 0.99999994 0.99999994]
...
[0.99999994 0.99999994 0.99999994 ... 0.99999994 0.99999994 0.99999994]
[0.99999994 0.99999994 0.99999994 ... 0.99999994 0.99999994 0.99999994]
[0.99999994 0.99999994 0.99999994 ... 0.99999994 0.99999994 0.99999994]]

which is wrong, since the numbers should be 1.0, that's white.

The second one is a Windows 10 Home (1803 version), where everything runs smoothly:

[[1. 1. 1. ... 1. 1. 1.]
[1. 1. 1. ... 1. 1. 1.]
[1. 1. 1. ... 1. 1. 1.]
...
[1. 1. 1. ... 1. 1. 1.]
[1. 1. 1. ... 1. 1. 1.]
[1. 1. 1. ... 1. 1. 1.]]

Could someone explain me the reason for this? Thanks in advance! If some other details are needed, tell me.


Solution

  • As mentioned in the comments to your question, floating point (ie non-integer) calculations in computers are not exact. The standard way to demonstrate this is to verify that 0.1 + 0.2 == 0.3 returns False. How things are approximated differs between processors, and between numerical libraries (which might perform operations in different orders). You can read more about this here: http://0.30000000000000004.com

    I don't think there is any easy way around this, other than minimising the number of operations being done. In your case, your image is a png, which can be grayscale directly, so you could save your image as a grayscale image from the beginning, removing the need for conversion. Alternatively, if you know that your image is just black-and-white or grayscale (ie has no colour information, other than being saved as an RGB image), then the content is the same in every channel, and you can write:

    from skimage.util import img_as_float
    from skimage.io import imread
    
    image = imread('Image (1).png')  # typically uint8 values in [0, 255]
    gray = image[..., 0]  # 0th channel, red
    gray_float = img_as_float(gray)  # optional, get floats in [0, 1]
    

    For more information about that last conversion, see this page in the scikit-image documentation: http://scikit-image.org/docs/dev/user_guide/data_types.html