Numpy和16位PGM

17 投票
5 回答
27218 浏览
提问于 2025-04-17 01:57

在Python中,如何用numpy高效且清晰地读取16位的PGM图像?

我不能使用PIL来加载16位的PGM图像,因为PIL有个bug。我可以用以下代码读取图像的头部信息:

dt = np.dtype([('type', 'a2'),
               ('space_0', 'a1', ),
               ('x', 'a3', ),
               ('space_1', 'a1', ),
               ('y', 'a3', ),
               ('space_2', 'a1', ),
               ('maxval', 'a5')])
header = np.fromfile( 'img.pgm', dtype=dt )
print header

这段代码输出的数据是正确的:('P5', ' ', '640', ' ', '480', ' ', '65535')。不过我觉得这可能不是最好的方法。而且,我还不知道如何读取接下来的数据,这些数据是640x480的16位图像,读取时需要跳过size(header)的部分。

编辑:已添加图像

用MATLAB读取和显示图像的代码是:

I = imread('foo.pgm'); 
imagesc(I);

图像看起来是这样的:

在这里输入图像描述

5 个回答

2

这里我了解到,文件的头部信息可以用空格、换行符或其他方式分隔。如果你的文件是用空格分开的(如果不是请告诉我),你可以这样做:

with open('img.pgm') as f:
    lines = f.readlines()
    data = np.array([line.split() for line in lines[1:]], dtype=np.int16).T

这样,你的数据现在就变成了一个int16格式的数组!

假设你还想查看头部信息,你可以这样做:

class Header(object):
    def __init__(self, type, width, height, maxval):
        self.type = type
        self.width = int(width)
        self.height = int(height)
        self.maxval = int(maxval)

h = Header(*lines[0].split()[:4])

这样你就可以把读取的行和图像数据进行对比:

assert (h.width, h.height) == data.shape    
assert h.maxval >= data.max()

补充: 由于图像数据是二进制格式,文件需要以'rb'模式打开,并在读取头部信息后再读取数据:

import numpy as np

def as_array(filepath):
    f = open(filepath, 'r')
    w, h = size = tuple(int(v) for v in next(f).split()[1:3])
    data_size = w * h * 2

    f.seek(0, 2)
    filesize = f.tell()
    f.close()
    i_header_end = filesize - (data_size)

    f = open(filepath, 'rb')
    f.seek(i_header_end)
    buffer = f.read()
    f.close()

    # convert binary data to an array of the right shape
    data = np.frombuffer(buffer, dtype=np.uint16).reshape((w, h))

    return data

a = as_array('foo.pgm')
4

我对PGM格式不是特别熟悉,但一般来说,你可以使用 numpy.fromfile 来处理。这个 fromfile 方法会从你给它的文件指针位置开始读取,所以你可以先找到文件头的结束位置,然后再用 fromfile 来读取后面的内容。

你需要用 infile.readline() 而不是 next(infile)

import numpy as np

with open('foo.pgm', 'r') as infile:
    header = infile.readline()
    width, height, maxval = [int(item) for item in header.split()[1:]]
    image = np.fromfile(infile, dtype=np.uint16).reshape((height, width))

顺便提一下,你在评论中提到的 "foo.pgm" 文件似乎在文件头中指定的行数是错误的。

如果你要读取很多可能有这个问题的文件,你可以选择用零填充数组,或者截断它,像这样。

import numpy as np

with open('foo.pgm', 'r') as infile:
    header = next(infile)
    width, height, maxval = [int(item) for item in header.split()[1:]]
    image = np.fromfile(infile, dtype=np.uint16)
    if image.size < width * height:
        pad = np.zeros(width * height - image.size, dtype=np.uint16)
        image = np.hstack([image, pad])
    if image.size > width * height:
        image = image[:width * height]
    image = image.reshape((height, width))

27
import re
import numpy

def read_pgm(filename, byteorder='>'):
    """Return image data from a raw PGM file as numpy array.

    Format specification: http://netpbm.sourceforge.net/doc/pgm.html

    """
    with open(filename, 'rb') as f:
        buffer = f.read()
    try:
        header, width, height, maxval = re.search(
            b"(^P5\s(?:\s*#.*[\r\n])*"
            b"(\d+)\s(?:\s*#.*[\r\n])*"
            b"(\d+)\s(?:\s*#.*[\r\n])*"
            b"(\d+)\s(?:\s*#.*[\r\n]\s)*)", buffer).groups()
    except AttributeError:
        raise ValueError("Not a raw PGM file: '%s'" % filename)
    return numpy.frombuffer(buffer,
                            dtype='u1' if int(maxval) < 256 else byteorder+'u2',
                            count=int(width)*int(height),
                            offset=len(header)
                            ).reshape((int(height), int(width)))


if __name__ == "__main__":
    from matplotlib import pyplot
    image = read_pgm("foo.pgm", byteorder='<')
    pyplot.imshow(image, pyplot.cm.gray)
    pyplot.show()

当然可以!请把你想要翻译的内容发给我,我会帮你用简单易懂的语言解释清楚。

撰写回答