Numpy 和 16 位 PGM

发布于 2024-12-03 17:57:01 字数 835 浏览 1 评论 0原文

使用 numpy 在 Python 中读取 16 位 PGM 图像的有效且清晰的方法是什么?

由于 PIL 错误,我无法使用 PIL 加载 16 位 PGM 图像。我可以使用以下代码读取标题:

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')但我有一种感觉,这并不是最好的方法。除此之外,我很难弄清楚如何以 size(header) 的偏移量按 16 位读取 x x y(在本例中为 640x480)的以下数据。

编辑:添加图像

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

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

看起来像这样:

What is an efficient and clear way to read 16-bit PGM images in Python with numpy?

I cannot use PIL to load 16-bit PGM images due to a PIL bug. I can read in the header with the following code:

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

This prints the correct data: ('P5', ' ', '640', ' ', '480', ' ', '65535') But I have a feeling that is not quite the best way. And beyond that, I'm having trouble how to figure out how to read in the following data of x by y (in this case 640x480) by 16-bit with the offset of size(header).

EDIT: IMAGE ADDED

MATLAB code to read and display the image is:

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

And looks like this:

enter image description here

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

若言繁花未落 2024-12-10 17:57:01
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()
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()
巷雨优美回忆 2024-12-10 17:57:01

我不太熟悉 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))

<罢工>

I'm not terribly familar with the PGM format, but generally speaking you'd just use numpy.fromfile. fromfile will start at whatever position the file pointer you pass to it is at, so you can simply seek (or read) to the end of the header, and then use fromfile to read the rest in.

You'll need to use infile.readline() instead of 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))

On a side note, the "foo.pgm" file you pointed to in your comment appears to specify the wrong number of rows in the header.

If you're going to be reading in a lot of files that potentially have that problem, you can just pad the array with zeros or truncate it, like this.

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))

愁以何悠 2024-12-10 17:57:01

事实上,标头后面的“字符串”是文件中的二进制文件。我解决了下面的问题(发现以下内容: ndarray: [2047 2047 2047 ..., 540 539 539]),但还有另一个问题:文件不够长;仅计算 289872 个数字,而不是 640*480...

我为自己的夸张行为感到非常抱歉,为它创建了一个类...

import numpy as np
import Image

class PGM(object):
    def __init__(self, filepath):

        with open(filepath) as f:

            # suppose all header info in first line:
            info = f.readline().split()
            self.type = info[0]
            self.width, self.height, self.maxval = [int(v) for v in info[1:]]
            size = self.width * self.height

            lines = f.readlines()
            dt = [np.int8, np.int16][self.maxval > 255]
            try:
                # this will work if lines are integers separated by e.g. spaces
                self.data = np.array([l.split() for l in lines], dtype=dt).T
            except ValueError:
                # data is binary
                data = np.fromstring(lines[0], dtype=dt)
                if data.size < size:
                    # this is the case for the 'db.tt/phaR587 (foo.pgm)'
                    #raise ValueError('data binary string probably uncomplete')
                    data = np.hstack((data, np.zeros(size-data.size)))
                self.data = data[:size].reshape((self.width, self.height))

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

        self._img = None

    def get_img(self):
        if self._img is None:
            # only executed once
            size = (self.width, self.height)
            mode = 'L'
            data = self.data
            self.img = Image.frombuffer(mode, size, data)

        return self.img

    Image = property(get_img)

mypgm = PGM('foo.pgm')

mypgm.Image

编辑:Joe Kington 的好主意,用零填充图像!

Indeed, the 'string' after the header is a binary in your file. I solved that below (found the following: ndarray: [2047 2047 2047 ..., 540 539 539]) but there is another problem: the file is not long enough; counts only 289872 numbers instead of 640*480...

I am terribly sorry for my exageration by making a class for it...

import numpy as np
import Image

class PGM(object):
    def __init__(self, filepath):

        with open(filepath) as f:

            # suppose all header info in first line:
            info = f.readline().split()
            self.type = info[0]
            self.width, self.height, self.maxval = [int(v) for v in info[1:]]
            size = self.width * self.height

            lines = f.readlines()
            dt = [np.int8, np.int16][self.maxval > 255]
            try:
                # this will work if lines are integers separated by e.g. spaces
                self.data = np.array([l.split() for l in lines], dtype=dt).T
            except ValueError:
                # data is binary
                data = np.fromstring(lines[0], dtype=dt)
                if data.size < size:
                    # this is the case for the 'db.tt/phaR587 (foo.pgm)'
                    #raise ValueError('data binary string probably uncomplete')
                    data = np.hstack((data, np.zeros(size-data.size)))
                self.data = data[:size].reshape((self.width, self.height))

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

        self._img = None

    def get_img(self):
        if self._img is None:
            # only executed once
            size = (self.width, self.height)
            mode = 'L'
            data = self.data
            self.img = Image.frombuffer(mode, size, data)

        return self.img

    Image = property(get_img)

mypgm = PGM('foo.pgm')

mypgm.Image

edit: great Idea from Joe Kington to fill image with zeros!

漆黑的白昼 2024-12-10 17:57:01

这里我知道标题信息可以用空格、回车符或其他分隔。如果您的数据以空格分隔(如果不是,请告诉我)您可以这样做:

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')

from here I understand that the header information can be separated by either spaces, carriage returns or others. If yours is separated by spaces (inform me if otherwise) you can do:

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

your data is now an array in int16 format!

Suppose you are still interested in the header information, you can do:

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])

so that you can check the image data against the read lines:

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

Edit: with the image data being binary, the file has to be opened as 'rb' and read from after the header information:

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')
有木有妳兜一样 2024-12-10 17:57:01

感谢@joe-kington 的回答帮助解决这个问题。解决方案如下。

需要做一些额外的工作才能不对已知的标头长度(17 个字节)进行硬编码
这种情况),而是从标题中确定它。 PGM 标准规定标头通常以换行符结尾,但也可以以任何空格结尾。我认为这段代码会在使用非换行符空格作为标头末尾分隔符的 PGM 上崩溃。在这种情况下,标头大小将由保存宽度、高度和最大大小的变量的大小确定,加上两个字节的“P5”,再加上 4 个字节的空白。

其他可能会中断的情况是宽度或高度大于整数(非常大的图像)。或者,如果 PGM 是 8 位而不是 16 位(可以根据 maxval 以及可能的宽度、高度和文件大小确定)。

#!/usr/bin/python
import numpy as np
import matplotlib.pyplot as plt

file='foo.pgm'
infile = open(file,'r')
header = next(infile)
width, height, maxval = [int(item) for item in header.split()[1:]]
infile.seek(len(header))
image = np.fromfile(infile, dtype=np.uint16).reshape((height, width))
print width, height, maxval
plt.figimage(image)

Thanks to the answer by @joe-kington for helping figure this out. The solution follows.

There is a little bit of extra work to not hard-code the known header length (17 bytes in
this case), but to determine it from the header. The PGM standard says that the header usually ends with a newline but can end with any whitespace. I think this code will break on a PGM that uses non-newline whitespace for the end-of-header delimeter. Header size in this case would be determined by the size of variables holding width, height, and maxsize, plus two bytes for 'P5', plus 4 bytes of whitespace.

Other cases where this might break are if the width or height are larger than an int (very big image). Or if the PGM is 8-bit rather than 16-bit (which can be determined from maxval, and possible width, height, and the filesize).

#!/usr/bin/python
import numpy as np
import matplotlib.pyplot as plt

file='foo.pgm'
infile = open(file,'r')
header = next(infile)
width, height, maxval = [int(item) for item in header.split()[1:]]
infile.seek(len(header))
image = np.fromfile(infile, dtype=np.uint16).reshape((height, width))
print width, height, maxval
plt.figimage(image)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文