使用Python读取16位PNG图像文件

23

我想读取一个16位数据类型的PNG图像文件,并将其转换为NumPy数组。但是我不知道如何以“16位”读取该文件。我尝试使用PIL和SciPy,但它们在加载时将16位数据转换为8位。请问有谁能告诉我如何从16位PNG文件中读取数据并将其转换为NumPy数组而不改变数据类型?

以下是我使用的脚本。

from scipy import misc
import numpy as np
from PIL import Image
#make a png file    
a = np.zeros((1304,960), dtype=np.uint16)
a[:] = np.arange(960)
misc.imsave('16bit.png',a)

#read the png file using scipy
b = misc.imread('16bit.png')
print "scipy:" ,b.dtype

#read the png file using PIL
c = Image.open('16bit.png')   
d = np.array(c)
print "PIL:", d.dtype
               
11个回答

17

我建议使用opencv:

pip install opencv-python

import cv2
image = cv2.imread('16bit.png', cv2.IMREAD_UNCHANGED)

  • OpenImageIO相比,opencv可以从pip安装。
  • 读取单个4000x4000 png所需的时间与PIL大致相同,但是PIL使用更多的CPU并需要额外的时间将数据转换回uint16

这应该是最佳答案。也适用于3通道uint16 PNG。 - gebbissimo

11

我这里也有同样的问题。我测试了用png包加载我自己创建的16位图像,它们都可以正确打开。同时,'file '命令的输出结果看起来也没有问题。

但是,使用PIL打开这些图像总是会导致8位的numpy数组。

此外,我用的是Linux环境下Python 2.7.6。

对我而言,以下方法可行:

import png
import numpy as np

reader = png.Reader( path-to-16bit-png )
pngdata = reader.read()
px_array = np.array( map( np.uint16, pngdata[2] ) 
print( px_array.dtype )

也许有人可以提供更多信息,说明在什么情况下前面的方法有效?(因为那个方法很慢)
提前感谢。

8
我发现最简单的解决方案是:
当我打开一个16位单色PNG Pillow时,因为它是模式,所以无法正确打开。实际上Image.mode被打开成了I(32位)。
因此,将其转换为Numpy数组的最佳方法是将其dtype设置为"int32",然后将其转换为"dtype =“uint16”"。
import numpy as np
from PIL import Image

im = Image.fromarray(np.array(Image.open(name)).astype("uint16"))
print("Image mode: ", im.mode)

使用Python 3.6.8测试,Pillow版本为6.1.0


有关Pillow的错误报告在这里:https://github.com/python-pillow/Pillow/issues/3796 - undefined

3

我正在使用PNG模块:

首先通过以下命令安装PNG:

>pip install pypng

然后:

import png
import numpy as np
reader = png.Reader('16bit.png')
data = reader.asDirect()
pixels = data[2]
image = []
for row in pixels:
  row = np.asarray(row)
  row = np.reshape(row, [-1, 3])
  image.append(row)
image = np.stack(image, 1)
print(image.dtype)
print(image.shape)

3
这是因为PIL不支持16位数据,可以在这里找到解释:http://effbot.org/imagingbook/concepts.htm 我使用osgeo gdal包进行了处理(该包可以读取PNG)。
#Import
import numpy as np
from osgeo import gdal

#Read in PNG file as 16-bit numpy array
lon_offset_px=0
lat_offset_px=0
fn = 'filepath'
gdo = gdal.Open(fn)
band = gdo.GetRasterBand(1)
xsize = band.XSize
ysize = band.YSize
png_array = gdo.ReadAsArray(lon_offset_px, lat_offset_px, xsize, ysize)
png_array = np.array(png_array)

这将返回

png_array.dtype
dtype('uint16')

我发现一种更清晰的方法是使用skimage包。
from skimage import io
im = io.imread(jpg)

'im'将是一个numpy数组。 注意:我没有用PNG测试过,但它适用于TIFF文件。


4
文档进展不幸滞后。Pillow支持16位灰度图像,相应的模式为 I;16 - P-Gn

2

另一个要考虑的选择是根据Fridy先生的答案,使用pypng加载它,代码如下:

import png
pngdata = png.Reader("path/to/16bit.png").read_flat()
img = np.array(pngdata[2]).reshape((pngdata[1], pngdata[0], -1))

您可以使用pip安装pypng:

pip install pypng

pip install pypng

从png.Reader.read_flat()返回的数据类型是uint16,对np.ndarray进行重塑可以将其格式转换为(高度,宽度,通道)格式。


0

你也可以使用优秀的OpenImageIO库的Python API。

import OpenImageIO as oiio
img_input = oiio.ImageInput.open("test.png")    # Only reads the image header
pix = img_input.read_image(format="uint16")     # Reads the pixels into a Numpy array

OpenImageIO 在 VFX 行业中被广泛使用,因此大多数 Linux 发行版都带有本地软件包。不幸的是,尽管文档非常优秀,但它们只提供 PDF 格式(我个人更喜欢 HTML),您可以在 /usr/share/doc/OpenImageIO 中查找。


0

我一直在使用版本为5.3.0的PIL处理这张图片:

enter image description here

它可以很好地读取数据:

>>> image = Image.open('/home/jcomeau/Downloads/grayscale_example.png')
>>> image.mode
'I'
>>> image.getextrema()
(5140, 62708)
>>> image.save('/tmp/test.png')

并且它以正确的模式保存,但是内容并不完全相同:

jcomeau@aspire:~$ diff /tmp/test.png ~/Downloads/grayscale_example.png 
Binary files /tmp/test.png and /home/jcomeau/Downloads/grayscale_example.png differ
jcomeau@aspire:~$ identify /tmp/test.png ~/Downloads/grayscale_example.png 
/tmp/test.png PNG 85x63 85x63+0+0 16-bit sRGB 6.12KB 0.010u 0:00.000
/home/jcomeau/Downloads/grayscale_example.png PNG 85x63 85x63+0+0 16-bit sRGB 6.14KB 0.000u 0:00.000

然而,image.show() 总是转换为 8 位灰度图像,被夹在 0 和 255 之间。因此,在任何转换阶段查看所得到的结果都是无用的。虽然我可以编写一个程序来实现这一点,甚至可能通过猴子补丁的方式修改 .show(),但我只需在另一个 xterm 中运行 display 命令即可。

>>> image.putdata([n - 32768 for n in image.getdata()])
>>> image.getextrema()
(-27628, 29940)
>>> image.save('/tmp/test2.png')

darkened grayscale image

请注意,转换为模式I;16也没有帮助:
>>> image.convert('I;16').save('/tmp/test3.png')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/jcomeau/.local/lib/python2.7/site-packages/PIL/Image.py", line 1969, in save
    save_handler(self, fp, filename)
  File "/home/jcomeau/.local/lib/python2.7/site-packages/PIL/PngImagePlugin.py", line 729, in _save
    raise IOError("cannot write mode %s as PNG" % mode)
IOError: cannot write mode I;16 as PNG

0

imageio库支持16位图像:

from imageio import imread, imwrite
import numpy as np
from PIL import Image

#make a png file    
a = np.arange(65536, dtype=np.uint16).reshape(256,256)
imwrite('16bit.png',a)

#read the png file using imageio
b = imread('16bit.png')
print("imageio:" ,b.dtype)
#imageio: uint16


#read the png file using PIL
c = Image.open('16bit.png')
d = np.array(c)
print("PIL:", d.dtype)
# PIL: int32

使用imagemagick:
>> identify 16bit.png 
16bit.png PNG 256x256 256x256+0+0 16-bit Grayscale Gray 502B 0.000u 0:00.000

0
这是使用png库的最简单方法:
要读取16位的png文件:
import png

pngdata = png.Reader("<file_name>.png").read_flat()
img = np.array(pngdata[2]).reshape((pngdata[1], pngdata[0], -1))

写16位PNG:
with open("<file_name>.png", 'wb') as fp:
    writer = png.Writer(width=img.shape[1], height=img.shape[0], bitdepth=16, greyscale=False)
    writer.write(fp, img.reshape(img.shape[0], -1))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接