Image fromarray is showing a greyed out image

433 views Asked by At

I am taking an array from a camera, thresholding the values and want to save the new image. I am using PIL.Image.fromarry() to turn an array into an image but I am only seeing greyed out images. The image is there because when I import into another program I can see what I want. When I show the image with plt.show(data) that is how I would like to see the image. How can I change the color values?

from flirpy.camera.lepton import Lepton
import matplotlib.pyplot as plt
import numpy as np
import cv2
from PIL import Image as im

camera = Lepton()

for i in range(5):
    image = camera.grab()
    cimage = np.around(image/100 - 273.15, 2)
    cimage = np.where(cimage < 20, 20, cimage)
    cimage = np.where(cimage > 30, 30, cimage)
    
    data = im.fromarray(cimage.astype(np.uint8),'L')
    data.save(f'test_{i:04}.png')
    plt.imshow(data)
    plt.show()

camera.close()

Matplotlib:

Matplotlib

PIL:

PIL

1

There are 1 answers

0
Mark Setchell On BEST ANSWER

Matplotlib applies a colormap to your image and, under certain circumstances also scales the values to the full colour range. PIL doesn't do that, so if you give it a greyscale image (single channel) with all the values in the range 20..30, it will come out black, because it really expects values in the range 0..255 and it doesn't colorise the grey for you either.

Here's how you can convert a Matplotlib cmap into a palette and push it into a PIL Image:

#!/usr/bin/env python3

import matplotlib.pyplot as plt
from PIL import Image, ImageMath

def cmap2palette(cmapName='viridis'):
    """Convert a Matplotlib colormap to a PIL Palette"""
    cmap = plt.get_cmap(cmapName)
    palette = [int(x*255) for entry in cmap.colors for x in entry]
    return palette

# Generate a linear ramp
im = Image.linear_gradient('L')
im.save('DEBUG-ramp.png')            # just debug, not required

# Generate the viridis Matplotlib colourmap push into our PIL Image
pal = cmap2palette('viridis')
im.putpalette(pal)
im.save('DEBUG-viridis.png')

# Just for fun...

# Generate the inferno Matplotlib colourmap push into our PIL Image
pal = cmap2palette('inferno')
im.putpalette(pal)
im.save('DEBUG-inferno.png')


# Now with your image
im = Image.open('w9jUR.png')

# Contrast-stretch to full range
gMin, gMax = im.getextrema()
print(F'Greyscale min: {gMin}, greyscale max: {gMax}')
im = ImageMath.eval('int(255*(g-gMin)/(gMax-gMin))', g=im, gMin=gMin, gMax=gMax).convert('L')

# Generate the viridis Matplotlib colourmap push into our PIL Image
pal = cmap2palette('viridis')
im.putpalette(pal)
im.save('DEBUG-result.png')

That gives this as the input greyscale image:

enter image description here

and this as the result with the viridis and inferno colormaps:

enter image description here enter image description here

Note also that you don't need to make 2 passes over your image to clip it to the range 20..30 like this:

cimage = np.where(cimage < 20, 20, cimage)
cimage = np.where(cimage > 30, 30, cimage)

You should be able to use np.clip():

np.clip(cimage, 20, 30, out=cimage)

If we apply the code above to your image:

enter image description here