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

Read 16-bit PNG image file using Python

我正在尝试读取以 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
               

我怀疑你的“16 位”PNG 不是 16 位的。 (如果你在 Linux 或 Mac 你可以 运行 file 16bit.png 看看它说了什么)

当我使用 PIL 和 numpy 时,我得到一个包含 16 位值的 32 位数组:

import PIL.Image
import numpy

image = PIL.Image.open('16bit.png')   
pixel = numpy.array(image)

print "PIL:", pixel.dtype

print max(max(row) for row in pixel)

输出是:

PIL: int32
65535

我这里也有同样的问题。我什至使用自己创建的 16 位图像对其进行了测试。当我用 png 包加载它们时,它们都被正确打开。 '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 )

也许有人可以提供更多信息在哪种情况下前一种方法有效? (因为这个速度很慢)

提前致谢。

出现这种情况是因为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)

这将 return

png_array.dtype
dtype('uint16')

我发现一种更简洁的方法是使用 skimage 包。

from skimage import io
im = io.imread(jpg)

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

我正在使用 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)

我一直在使用 PIL 版本 5.3.0 处理此图像:

它读取数据很好:

>>> 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。因此在转换的任何阶段查看您所获得的内容都是无用的。虽然我可以编写一个例程来这样做,甚至可能是 monkeypatch .show(),但我只是 运行 另一个 xterm 中的 display 命令。

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

请注意,转换为模式 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

我找到的最简单的解决方案:

当我打开 16 位单色 PNG Pillow 时,它无法以 I;16 模式正确打开。 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 中测试

您还可以使用优秀的 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

OpneImageIO 在 VFX 行业中被广泛使用,因此大多数 Linux 发行版都带有它的原生包。不幸的是,其他优秀的文档是 PDF 格式的(我个人更喜欢 HTML),请在 /usr/share/doc/OpenImageIO.

中查找它

我推荐使用 opencv:

pip install opencv-python

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

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

根据 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

来自 png.Reader.read_flat() 的 dtype 是正确的 uint16 并且 np.ndarray 的重塑将其放入(高度,宽度,通道)格式。

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