Search code examples
pythonnumpyimage-processingpython-imaging-libraryscikit-image

Convert boolean numpy array to pillow image


I'm currently working with image processing in python using the scikit-image library. I'm trying to make a binary image using sauvola thresholding with the following code:

from PIL import Image
import numpy
from skimage.color import rgb2gray
from skimage.filters import threshold_sauvola

im = Image.open("test.jpg")
pix = numpy.array(im)
img = rgb2gray(pix)

window_size = 25
thresh_sauvola = threshold_sauvola(img, window_size=window_size)
binary_sauvola = img > thresh_sauvola

Which gives the following result: enter image description here

the output is a numpy array with data type of this image is a bool

[[ True  True  True ...  True  True  True]
 [ True  True  True ...  True  True  True]
 [ True  True  True ...  True  True  True]
 ...
 [ True  True  True ...  True  True  True]
 [ True  True  True ...  True  True  True]
 [ True  True  True ...  True  True  True]]

The problem is that I need to convert this array back to a PIL image using the following line of code:

image = Image.fromarray(binary_sauvola)

which makes the image look like this:

enter image description here

I also tried to change the data type from bool to uint8 but then I'll get the following exception:

AttributeError: 'numpy.ndarray' object has no attribute 'mask'

So far I haven't found a solution to get a PIL image which looks like the result of the thresholding.


Solution

  • Update

    This bug has now been solved in Pillow==6.2.0. The link to the issue on GitHub is here.

    If you cannot update to the new version of Pillow, please see below.


    PIL's Image.fromarray function has a bug with mode '1' images. This Gist demonstrates the bug, and shows a few workarounds. Here are the best two workarounds:

    import numpy as np
    from PIL import Image
    
    # The standard work-around: first convert to greyscale 
    def img_grey(data):
        return Image.fromarray(data * 255, mode='L').convert('1')
    
    # Use .frombytes instead of .fromarray. 
    # This is >2x faster than img_grey
    def img_frombytes(data):
        size = data.shape[::-1]
        databytes = np.packbits(data, axis=1)
        return Image.frombytes(mode='1', size=size, data=databytes)
    

    Also see Error Converting PIL B&W images to Numpy Arrays.