简体   繁体   English

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

[英]Read 16-bit PNG image file using Python

I'm trying to read a PNG image file written in 16-bit data type.我正在尝试读取以 16 位数据类型编写的 PNG 图像文件。 The data should be converted to a NumPy array.数据应转换为 NumPy 数组。 But I have no idea how to read the file in '16-bit'.但我不知道如何以“16 位”读取文件。 I tried with PIL and SciPy, but they converted the 16-bit data to 8-bit when they load it.我尝试使用 PIL 和 SciPy,但它们在加载时将 16 位数据转换为 8 位。 Could anyone please let me know how to read data from a 16-bit PNG file and convert it to NumPy array without changing the datatype?谁能让我知道如何从 16 位 PNG 文件中读取数据并将其转换为 NumPy 数组而不更改数据类型?

The following is the script that I used.以下是我使用的脚本。

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
               

I have the same problem here.我在这里也有同样的问题。 I tested it even with 16 bit images i created by my own.即使使用我自己创建的 16 位图像,我也对其进行了测试。 All of them were opened correctly when i loaded them with the png package.当我用 png 包加载它们时,所有这些都被正确打开。 Also the output of 'file ' looked okay. 'file' 的输出也看起来不错。

Opening them with PIL always led to 8-bit numpy-arrays.用 PIL 打开它们总是会导致 8 位 numpy 数组。

Working with Python 2.7.6 on Linux btw.顺便说一句,在 Linux 上使用 Python 2.7.6。

Like this it works for me:像这样它对我有用:

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 )

Maybe someone can give more information under which circumstances the former approach worked?也许有人可以提供更多信息,前一种方法在哪种情况下有效? (as this one is pretty slow) (因为这个很慢)

Thanks in advance.提前致谢。

The simplest solution I've founded:我建立的最简单的解决方案:

When I open a 16 bit monochrome PNG Pillow doesn't open it correctly as I;16 mode.当我打开 16 位单色 PNG Pillow 时,它无法像I;16模式那样正确打开。 Image.mode is opened as I (32 bits) Image.mode打开为I (32 位)

So, the best way to convert to numpy Array.因此,转换为 numpy 数组的最佳方法。 It is dtype="int32" so we will convert it to dtype="uint16".它是 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))

Tested in Python 3.6.8 with Pillow 6.1.0在 Python 3.6.8 和 Pillow 6.1.0 中测试

This happens because PIL does not support 16-bit data, explained here: http://effbot.org/imagingbook/concepts.htm发生这种情况是因为 PIL 不支持 16 位数据,解释如下: http : //effbot.org/imagingbook/concepts.htm

I use a work around using the osgeo gdal package (which can read PNG).我使用 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)

This will return这将返回

png_array.dtype
dtype('uint16')

A cleaner way I found is using the skimage package.我发现一种更简洁的方法是使用 skimage 包。

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

Where 'im' will be a numpy array.其中 'im' 将是一个 numpy 数组。 Note: I haven't tested this with PNG but it works with TIFF files注意:我没有用 PNG 测试过这个,但它适用于 TIFF 文件

I'd recommend using opencv:我建议使用 opencv:

pip install opencv-python

and

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

  • in contrast to OpenImageIO , opencv could be installed from pipOpenImageIO 不同,opencv 可以从 pip 安装
  • The time, required to read a single 4000x4000 png is about the same as PIL, but PIL uses more CPU and requires additional time to convert data back to uint16 .读取单个 4000x4000 png 所需的时间与 PIL 大致相同,但 PIL 使用更多的 CPU 并且需要额外的时间将数据转换回uint16

I'm using png module: At first install png by:我正在使用 png 模块:首先通过以下方式安装 png:

>pip install pypng

Then然后

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)

I've been playing with this image using PIL version 5.3.0:我一直在使用 PIL 版本 5.3.0 来处理这个图像:

在此处输入图片说明

it reads the data just fine:它读取数据就好了:

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

and it saves in the right mode, however the contents are not identical:它以正确的模式保存,但内容不相同:

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

however, image.show() always converts to 8-bit grayscale, clamped at 0 and 255. so it's useless for seeing what you've got at any stage of the transformation.然而, image.show()总是转换为 8 位灰度,固定在 0 和 255。所以在转换的任何阶段查看你所得到的都是无用的。 while I could write a routine to do so, and perhaps even monkeypatch .show() , I just run the display command in another xterm.虽然我可以编写一个例程来这样做,甚至可能是monkeypatch .show() ,但我只是在另一个xterm 中运行display命令。

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

变暗的灰度图像

note that converting to mode I;16 doesn't help:请注意,转换为模式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

You can also use the excellent OpenImageIO library's Python API.您还可以使用优秀的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 is used extensively in the VFX industry, so most Linux distros come with a native package for it. OpneImageIO 在 VFX 行业中被广泛使用,因此大多数 Linux 发行版都带有一个本地包。 Unfortunately the otherwise excellent documentation is in PDF format (I personally prefer HTML), look for it in /usr/share/doc/OpenImageIO .不幸的是,其他优秀的文档是 PDF 格式(我个人更喜欢 HTML),请在/usr/share/doc/OpenImageIO

Another option to consider, based on Mr. Fridy's answer , is to load it using pypng like this:根据 Fridy 先生的回答,另一个要考虑的选择是使用 pypng 加载它,如下所示:

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

You can install pypng using pip:您可以使用 pip 安装 pypng:

pip install pypng

The dtype from png.Reader.read_flat() is correctly uint16 and the reshaping of the np.ndarray puts it into (height, width, channels) format.来自 png.Reader.read_flat() 的 dtype 是正确的 uint16 并且np.ndarray的重塑将其放入(高度、宽度、通道)格式。

imageio library supports 16bit images: 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

Using imagemagick:使用 imagemagick:

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

I suspect your "16 bit" PNG is not 16-bit.我怀疑您的“16 位”PNG 不是 16 位。 (if you're on Linux or Mac you could run file 16bit.png and see what it says) (如果您使用的是 Linux 或 Mac,您可以运行file 16bit.png并查看其内容)

When I use PIL and numpy I get a 32-bit array with 16-bit values in it:当我使用 PIL 和 numpy 时,我得到一个 32 位数组,其中包含 16 位值:

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)

the output is:输出是:

PIL: int32
65535

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM