Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python PIL deteriorates image when converting to RGBA

I am processing images with PIL. When I open and convert some images to RGBA they get completely deteriorated.

One example of such an image is the following one:

Input image

$ file input_image.png
input_image.png: PNG image data, 502 x 1180, 16-bit grayscale, non-interlaced

After the conversion I get the following:

Output image

I run the following code.

from PIL import Image

img = Image.open("input_image.png")
img_rgba = img.convert("RGBA")
img_rgba.save("output_image.png")

I would expect that the output image looks like the input image.

like image 285
Xabier Avatar asked Nov 19 '25 15:11

Xabier


2 Answers

Your original image is a 16-bit greyscale image. You can see that if you print the image:

im = Image.open(...)
print(im)

You'll see it is in I mode, i.e. 32-bit because that's what PIL uses for 16-bit images.

<PIL.PngImagePlugin.PngImageFile image mode=I size=502x1180>

You can also see that with exiftool, if you run:

exiftool YOURIMAGE

When you convert it to RGBA mode, PIL makes it into RGBA8888 - i.e. 4 channels of 8 bits each and you lose data.


If you want to convert from GREY16 to RGBA8888 with solid blacks and whites, you need to rescale to range 0..255 like this:

from PIL import Image
import numpy as np

# Open 16-bit image and make Numpy array version
im = Image.open('image.png')
na = np.array(im)

# Rescale to range 0..255
res = (na - na.min())*255/(na.max()-na.min())

# Make back into uint8 PIL Image
pi = Image.fromarray(res.astype(np.uint8))

# Convert to RGBA8888
pi = pi.convert('RGBA')
pi.save('result.png')

enter image description here

like image 95
Mark Setchell Avatar answered Nov 21 '25 05:11

Mark Setchell


PIL opening 16-bit grayscale PNG images as 32-bit, is a known issue.

But that's not the only problem. Even first converting it to I;16 mode (with .convert("I;16"), won't work, since PIL won't properly scale the grayscale values to RGB either way.

You can scale the values with the .point method.
Assuming you will read images in other formats, you should do a check, if it needs this fix.

img = Image.open(input_image)
if (img.format == "PNG" and img.mode == "I") or img.mode == "I;16":
    img = img.point(lambda p: p * (1 / 256))
img_rgba = img.convert("RGBA")
img_rgba.save(output_image)
like image 24
gre_gor Avatar answered Nov 21 '25 05:11

gre_gor



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!