Python和16位PGM

5

我有一些16位的PGM图像,想要在Python中读取。看起来PIL不支持这种格式?

import Image
im = Image.open('test.pgm')
im.show()

大致显示出了图片,但并不正确。在整个图像中都有黑色条纹,且该img被报告为mode=L。我认为这与我早期关于16位TIFF文件的问题有关。16位是否如此罕见以至于PIL根本不支持它?请问有什么建议可以使用PIL或其他标准库或自编代码在Python中读取16位PGM文件呢?

3个回答

4
你需要一个格式为"L;16"的模式。但是,当加载PGM文件时,PIL在File.c中硬编码了"L"模式。如果您想能够读取16位PGM文件,则需要自己编写解码器

然而,支持16位图像仍然不太稳定:

>>> im = Image.fromstring('I;16', (16, 16), '\xCA\xFE' * 256, 'raw', 'I;16') 
>>> im.getcolors()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python2.6/dist-packages/PIL/Image.py", line 866, in getcolors
    return self.im.getcolors(maxcolors)
ValueError: image has wrong mode

我认为PIL可以读取16位图像,但是实际存储和处理这些图像仍处于实验阶段。

>>> im = Image.fromstring('L', (16, 16), '\xCA\xFE' * 256, 'raw', 'L;16') 
>>> im
<Image.Image image mode=L size=16x16 at 0x27B4440>
>>> im.getcolors()
[(256, 254)]

看,它只是将0xCAFE的值解释为0xFE,这并不完全正确。


我很高兴只是阅读它们。如果需要编写,我将使用PNG格式。我也可以将它们作为数据在numpy中进行操作,而不是在PIL中作为图像。您的帖子非常有帮助,但您能否扩展一下如何正确读取数据? - mankoff
你的意思是要为PIL编写一个解码器,还是如何解释PGM? - Josh Lee
你斜体的“阅读”让我想到了可能有一些诀窍可以让它原封不动地工作?我正在尝试在这里(https://dev59.com/gWw05IYBdhLWcg3wahRN)适应解决方法,但不会失去位。如果需要自定义解码器,我将根据PIL教程编写它。PGM格式似乎非常基础,所以也许我应该直接将其读入numpy中... - mankoff
我不确定。在我的测试中,它正确解析了文件,但无法完全表示其内容。我认为使用自定义解码器与使用fromstring没有什么不同的结果。Numpy可能是更好的选择。 - Josh Lee

2
以下仅依赖于 numpy 以加载图像,该图像可以是8位或16位的原始PGM / PPM格式。 我还展示了几种不同的查看图像的方法。 使用PIL(import Image)的方法要求首先将数据转换为8位。
#!/usr/bin/python2 -u

from __future__ import print_function
import sys, numpy

def read_pnm_from_stream( fd ):
   pnm = type('pnm',(object,),{}) ## create an empty container
   pnm.header = fd.readline()
   pnm.magic = pnm.header.split()[0]
   pnm.maxsample = 1 if ( pnm.magic == 'P4' ) else 0
   while ( len(pnm.header.split()) < 3+(1,0)[pnm.maxsample] ): s = fd.readline() ; pnm.header += s if ( len(s) and s[0] != '#' ) else ''
   pnm.width, pnm.height = [int(item) for item in pnm.header.split()[1:3]]
   pnm.samples = 3 if ( pnm.magic == 'P6' ) else 1
   if ( pnm.maxsample == 0 ): pnm.maxsample = int(pnm.header.split()[3])
   pnm.pixels = numpy.fromfile( fd, count=pnm.width*pnm.height*pnm.samples, dtype='u1' if pnm.maxsample < 256 else '>u2' )
   pnm.pixels = pnm.pixels.reshape(pnm.height,pnm.width) if pnm.samples==1 else pnm.pixels.reshape(pnm.height,pnm.width,pnm.samples)
   return pnm

if __name__ == '__main__':

## read image
 # src = read_pnm_from_stream( open(filename) )
   src = read_pnm_from_stream( sys.stdin )
 # print("src.header="+src.header.strip(), file=sys.stderr )
 # print("src.pixels="+repr(src.pixels), file=sys.stderr )

## write image
   dst=src
   dst.pixels = numpy.array([ dst.maxsample-i for i in src.pixels ],dtype=dst.pixels.dtype) ## example image processing
 # print("dst shape: "+str(dst.pixels.shape), file=sys.stderr )
   sys.stdout.write(("P5" if dst.samples==1 else "P6")+"\n"+str(dst.width)+" "+str(dst.height)+"\n"+str(dst.maxsample)+"\n");
   dst.pixels.tofile( sys.stdout ) ## seems to work, I'm not sure how it decides about endianness

## view using Image
   import Image
   viewable = dst.pixels if dst.pixels.dtype == numpy.dtype('u1') else numpy.array([ x>>8 for x in dst.pixels],dtype='u1')
   Image.fromarray(viewable).show()

## view using scipy
   import scipy.misc
   scipy.misc.toimage(dst.pixels).show()

使用说明

  • 我最终弄清楚了“它如何决定字节序”——实际上,它将图像存储在内存中作为大端(而不是本机字节序)。这种方案可能会减慢任何非平凡的图像处理速度——尽管 Python 的其他性能问题可能会使这个问题变得微不足道(见下文)。

  • 我在这里提出了与字节序相关的问题here。我还遇到了一些有趣的关于字节序的混淆,因为我正在使用pnmdepth 65535对图像进行预处理,这对于测试字节序来说并不好(因为低位和高位字节可能相同,我没有立即注意到,因为print(array)输出十进制)。我应该也应用pnmgamma来避免一些混淆。

  • 由于Python非常缓慢,numpy试图在应用某些操作时变得狡猾聪明(请参见broadcasting)。使用numpy的效率第一法则是让numpy为您处理迭代(或者换句话说不要编写自己的for循环)。在上面的代码中有趣的事情是,当执行“示例图像处理”时,它只部分遵循了这个规则,因此该行的性能极大地依赖于传递给reshape的参数。

  • 下一个重要的numpy字节序谜团:为什么newbyteorder()似乎会返回一个数组,而它的文档中却说明它返回一个dtype。如果您想使用dst.pixels=dst.pixels.byteswap(True).newbyteorder()将其转换为本机字节序,则这一点非常重要。

  • 关于移植到Python 3的提示:从标准输入读取带有ASCII文本头的二进制输入


为什么尝试编写看似微不足道的Python程序总是导致在Stack Overflow上漫游? - Brent Bradburn
Python 中让我感到疯狂的一件事就是浅拷贝,比如上面的 dst=src。有时候我觉得 Python 对于 C++ 程序员来说太难理解了。 - Brent Bradburn
我发现一些得票最低的答案在这里是最有用的。特别是,看起来我可以通过执行dst=src()来解决上面的问题。 - Brent Bradburn

1

这是一个基于NumPyPyPNG中一个未记录的函数的通用PNM/PAM阅读器。

def read_pnm( filename, endian='>' ):
   fd = open(filename,'rb')
   format, width, height, samples, maxval = png.read_pnm_header( fd )
   pixels = numpy.fromfile( fd, dtype='u1' if maxval < 256 else endian+'u2' )
   return pixels.reshape(height,width,samples)

当然,通常不需要使用库来编写这种图像格式...

我从这个相关问题中借鉴了一些想法。 - Brent Bradburn
关于 'PAM' 支持,这里使用的 read_pnm_header() 函数没有返回 TUPLTYPE,但它确实返回了 DEPTH 的正确值(我称之为 samples)。 - Brent Bradburn
请参考这个问题,了解在使用stdio而不是文件时的重要注意事项。 - Brent Bradburn
已经有一段时间了,我不确定endian应该从哪里来。我认为应该将其替换为'>',以表示文件以大端方式存储(根据标准)。 - Brent Bradburn

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