如何为普通pgm格式编写PIL图像滤镜?
我该如何为Python图像库编写一个过滤器,以处理pgm纯ASCII格式(P2)呢?这里的问题是,基本的PIL过滤器假设每个像素的字节数是固定的。
我的目标是用Image.open()打开feep.pgm文件。你可以查看这个链接了解更多信息:http://netpbm.sourceforge.net/doc/pgm.html,或者看下面的内容。
另一种解决方案是找到其他文档齐全的ASCII灰度格式,这种格式被PIL和所有主要的图形程序支持。有什么建议吗?
feep.pgm:
P2
# feep.pgm
24 7
15
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 3 3 3 3 0 0 7 7 7 7 0 0 11 11 11 11 0 0 15 15 15 15 0
0 3 0 0 0 0 0 7 0 0 0 0 0 11 0 0 0 0 0 15 0 0 15 0
0 3 3 3 0 0 0 7 7 7 0 0 0 11 11 11 0 0 0 15 15 15 15 0
0 3 0 0 0 0 0 7 0 0 0 0 0 11 0 0 0 0 0 15 0 0 0 0
0 3 0 0 0 0 0 7 7 7 7 0 0 11 11 11 11 0 0 15 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
编辑:谢谢你的回答,确实有效…… 但是我需要一个可以使用Image.open()的解决方案。大多数Python程序都使用PIL来处理图形(可以在谷歌上搜索:python image open)。所以,我需要能够为PIL注册一个过滤器。这样,我就可以使用任何依赖于PIL的软件。我现在主要考虑的是依赖于scipy、pylab等的程序。
编辑 好吧,我想我明白了。下面是pgm2pil.py的封装代码:
import Image
import numpy
def pgm2pil(filename):
try:
inFile = open(filename)
header = None
size = None
maxGray = None
data = []
for line in inFile:
stripped = line.strip()
if stripped[0] == '#':
continue
elif header == None:
if stripped != 'P2': return None
header = stripped
elif size == None:
size = map(int, stripped.split())
elif maxGray == None:
maxGray = int(stripped)
else:
for item in stripped.split():
data.append(int(item.strip()))
data = numpy.reshape(data, (size[1],size[0]))/float(maxGray)*255
return numpy.flipud(data)
except:
pass
return None
def imageOpenWrapper(fname):
pgm = pgm2pil(fname)
if pgm is not None:
return Image.fromarray(pgm)
return origImageOpen(fname)
origImageOpen = Image.open
Image.open = imageOpenWrapper
对misha的回答稍作升级。Image.open必须保存,以防止出现无尽的循环。如果pgm2pil返回None,封装器会调用pgm2pil,而pgm2pil又返回None,这样就会一直循环下去……
下面是测试函数(feep_false.pgm是一个格式错误的pgm文件,比如"P2"变成了"FOO",而lena.pgm就是那个图像文件):
import pgm2pil
import pylab
try:
pylab.imread('feep_false.pgm')
except IOError:
pass
else:
raise ValueError("feep_false should fail")
pylab.subplot(2,1,1)
a = pylab.imread('feep.pgm')
pylab.imshow(a)
pylab.subplot(2,1,2)
b = pylab.imread('lena.png')
pylab.imshow(b)
pylab.show()
1 个回答
我现在处理这个问题的方法是通过numpy库:
- 把图片读入一个二维的
numpy
数组。其实你不一定要用numpy
,但我发现它比普通的Python二维数组更容易使用。 - 用
PIL.Image.fromarray
把这个二维的numpy数组转换成PIL.Image
对象。
如果你坚持要用PIL.Image.open
,你可以写一个包装函数,先尝试加载PGM文件(通过查看文件头)。如果是PGM格式,就按照上面的步骤加载图片;如果不是,就交给PIL.Image.open
处理。
这里有一段我用来把PBM图片转换成numpy数组的代码。
import re
import numpy
def pbm2numpy(filename):
"""
Read a PBM into a numpy array. Only supports ASCII PBM for now.
"""
fin = None
debug = True
try:
fin = open(filename, 'r')
while True:
header = fin.readline().strip()
if header.startswith('#'):
continue
elif header == 'P1':
break
elif header == 'P4':
assert False, 'Raw PBM reading not implemented yet'
else:
#
# Unexpected header.
#
if debug:
print 'Bad mode:', header
return None
rows, cols = 0, 0
while True:
header = fin.readline().strip()
if header.startswith('#'):
continue
match = re.match('^(\d+) (\d+)$', header)
if match == None:
if debug:
print 'Bad size:', repr(header)
return None
cols, rows = match.groups()
break
rows = int(rows)
cols = int(cols)
assert (rows, cols) != (0, 0)
if debug:
print 'Rows: %d, cols: %d' % (rows, cols)
#
# Initialise a 2D numpy array
#
result = numpy.zeros((rows, cols), numpy.int8)
pxs = []
#
# Read to EOF.
#
while True:
line = fin.readline().strip()
if line == '':
break
for c in line:
if c == ' ':
continue
pxs.append(int(c))
if len(pxs) != rows*cols:
if debug:
print 'Insufficient image data:', len(pxs)
return None
for r in range(rows):
for c in range(cols):
#
# Index into the numpy array and set the pixel value.
#
result[r, c] = pxs[r*cols + c]
return result
finally:
if fin != None:
fin.close()
fin = None
return None
你可能需要稍微修改一下这段代码,以适应你的需求,主要是:
- 处理P2(ASCII,灰度)格式,而不是P1(ASCII,二值)。
- 如果不使用numpy,可以用其他的容器。普通的Python二维数组也可以正常工作。
编辑
下面是我处理包装函数的方法:
def pgm2pil(fname):
#
# This method returns a PIL.Image. Use pbm2numpy function above as a
# guide. If it can't load the image, it returns None.
#
pass
def wrapper(fname):
pgm = pgm2pil(fname)
if pgm is not None:
return pgm
return PIL.Image.open(fname)
#
# This is the line that "adds" the wrapper
#
PIL.Image.open = wrapper
我没有写pgm2pil
,因为它会和pgm2numpy
非常相似。唯一的区别是它把结果存储在PIL.Image
中,而不是numpy
数组里。我也没有测试这个包装代码(抱歉,最近有点忙),但这是一种比较常见的方法,所以我认为它应该能工作。
现在,听起来你想让其他使用PIL加载图片的应用程序也能处理PGM格式。使用上面的方法是可以做到的,但你需要确保在第一次调用PIL.Image.open
之前添加这个包装代码。你可以通过把包装代码添加到PIL的源代码中来确保这一点(如果你有权限的话)。