获取图像大小而无需将图像加载到内存中


113

我了解您可以通过以下方式使用PIL获得图像尺寸

from PIL import Image
im = Image.open(image_filename)
width, height = im.size

但是,我想获取图像的宽度和高度,而不必将图像加载到内存中。那可能吗?我只做图像尺寸的统计,并不关心图像内容。我只是想加快处理速度。


8
我不是100%肯定,但是我不相信这.open()会将整个文件读入内存...(就是这样.load())-就我所知-这和使用的一样好PIL
乔恩·克莱门茨

5
即使您认为您具有仅读取图像标题信息的功能,文件系统预读代码仍可能加载整个图像。除非您的应用程序要求,否则担心性能不会产生任何效果。
2013年

1
我对您的回答深信不疑。感谢@JonClements和斯塔克
Sami A. Haija

9
pmap用于监视进程使用的内存的快速内存测试向我显示,确实PIL没有将整个图像加载到内存中。
Vincent Nivoliers

Answers:


63

正如注释所暗示的那样,PIL在调用时不会将图像加载到内存中.open。查看的文档PIL 1.1.7,文档字符串.open说:

def open(fp, mode="r"):
    "Open an image file, without loading the raster data"

源代码中有一些文件操作,例如:

 ...
 prefix = fp.read(16)
 ...
 fp.seek(0)
 ...

但是这些几乎不构成读取整个文件。实际上,.open仅在成功时返回文件对象和文件名。另外文档说:

打开(文件,模式=“ r”)

打开并标识给定的图像文件。

这是一个懒惰的操作;此功能可识别文件,但在尝试处理数据(或调用load方法)之前,不会从文件中读取实际图像数据。

深入研究,我们看到.open调用_open是特定于图像格式的重载。每个实现_open都可以在新文件中找到,例如。.jpeg文件位于中JpegImagePlugin.py。让我们深入研究一下。

这里的事情似乎有些棘手,其中有一个无限循环,当找到jpeg标记时,该循环就会中断:

    while True:

        s = s + self.fp.read(1)
        i = i16(s)

        if i in MARKER:
            name, description, handler = MARKER[i]
            # print hex(i), name, description
            if handler is not None:
                handler(self, i)
            if i == 0xFFDA: # start of scan
                rawmode = self.mode
                if self.mode == "CMYK":
                    rawmode = "CMYK;I" # assume adobe conventions
                self.tile = [("jpeg", (0,0) + self.size, 0, (rawmode, ""))]
                # self.__offset = self.fp.tell()
                break
            s = self.fp.read(1)
        elif i == 0 or i == 65535:
            # padded marker or junk; move on
            s = "\xff"
        else:
            raise SyntaxError("no marker found")

看起来如果文件格式错误,它可以读取整个文件。但是,如果读取信息标记“确定”,则应尽早爆发。该功能handler最终设置self.size图像的尺寸。


1
确实如此,但是是否open获得了图像的大小,或者这也是一个懒惰的操作吗?而且,如果它很懒,它是否可以同时读取图像数据?
Mark Ransom 2013年

doc链接指向PIL中的Pillow a fork。但是,我在网上找不到官方的文档链接。如果有人将其发布为评论,我将更新答案。报价可以在文件中找到Docs/PIL.Image.html
上钩

@MarkRansom我试图回答您的问题,但是要100%确保看起来我们必须深入研究每种特定于图像的实现。.jpeg只要找到标题,该格式就可以。
2013年

@Hooked:非常感谢您对此进行调查。我接受您的观点是正确的,尽管我非常喜欢下面的Paulo极小的解决方案(尽管公平地说,OP并没有提及希望避免依赖PIL)
Alex Flint 2013年

@AlexFlint没问题,戳代码总是很有趣。我想说Paulo赢得了赏金,这是他在那儿为您写的一个不错的摘录。
钩上2013年

88

如果您不关心图像内容,则PIL可能是一个过大的选择。

我建议解析python magic模块的输出:

>>> t = magic.from_file('teste.png')
>>> t
'PNG image data, 782 x 602, 8-bit/color RGBA, non-interlaced'
>>> re.search('(\d+) x (\d+)', t).groups()
('782', '602')

这是围绕libmagic的包装,该包装读取尽可能少的字节以标识文件类型签名。

脚本的相关版本:

https://raw.githubusercontent.com/scardine/image_size/master/get_image_size.py

[更新]

不幸的是,嗯,当应用于jpeg时,上面给出的是“'JPEG图像数据,EXIF标准2.21'”。没有图像尺寸!–亚历克斯·弗林特

似乎jpeg具有抗魔性。:-)

我可以看到原因:为了获得JPEG文件的图像尺寸,您可能需要读取比libmagic喜欢读取的字节更多的字节。

卷起袖子,附带这个未经测试的代码段(从GitHub获取),不需要第三方模块。

妈你看  不行!

#-------------------------------------------------------------------------------
# Name:        get_image_size
# Purpose:     extract image dimensions given a file path using just
#              core modules
#
# Author:      Paulo Scardine (based on code from Emmanuel VAÏSSE)
#
# Created:     26/09/2013
# Copyright:   (c) Paulo Scardine 2013
# Licence:     MIT
#-------------------------------------------------------------------------------
#!/usr/bin/env python
import os
import struct

class UnknownImageFormat(Exception):
    pass

def get_image_size(file_path):
    """
    Return (width, height) for a given img file content - no external
    dependencies except the os and struct modules from core
    """
    size = os.path.getsize(file_path)

    with open(file_path) as input:
        height = -1
        width = -1
        data = input.read(25)

        if (size >= 10) and data[:6] in ('GIF87a', 'GIF89a'):
            # GIFs
            w, h = struct.unpack("<HH", data[6:10])
            width = int(w)
            height = int(h)
        elif ((size >= 24) and data.startswith('\211PNG\r\n\032\n')
              and (data[12:16] == 'IHDR')):
            # PNGs
            w, h = struct.unpack(">LL", data[16:24])
            width = int(w)
            height = int(h)
        elif (size >= 16) and data.startswith('\211PNG\r\n\032\n'):
            # older PNGs?
            w, h = struct.unpack(">LL", data[8:16])
            width = int(w)
            height = int(h)
        elif (size >= 2) and data.startswith('\377\330'):
            # JPEG
            msg = " raised while trying to decode as JPEG."
            input.seek(0)
            input.read(2)
            b = input.read(1)
            try:
                while (b and ord(b) != 0xDA):
                    while (ord(b) != 0xFF): b = input.read(1)
                    while (ord(b) == 0xFF): b = input.read(1)
                    if (ord(b) >= 0xC0 and ord(b) <= 0xC3):
                        input.read(3)
                        h, w = struct.unpack(">HH", input.read(4))
                        break
                    else:
                        input.read(int(struct.unpack(">H", input.read(2))[0])-2)
                    b = input.read(1)
                width = int(w)
                height = int(h)
            except struct.error:
                raise UnknownImageFormat("StructError" + msg)
            except ValueError:
                raise UnknownImageFormat("ValueError" + msg)
            except Exception as e:
                raise UnknownImageFormat(e.__class__.__name__ + msg)
        else:
            raise UnknownImageFormat(
                "Sorry, don't know how to get information from this file."
            )

    return width, height

[2019年更新]

检验Rust的实现:https : //github.com/scardine/imsz


3
上述@EJEHardenberg版本之后,我还在注释中添加了检索通道数(不要混淆位深度)的功能。
Greg Kramida 2014年

2
好东西 我在GitHub项目中添加了对位图的支持。谢谢!
绿头野鸭2015年

2
注意:当前版本不适用于我。@PauloScardine有一个更新的工作版本上github.com/scardine/image_size
DankMasterDan

2
获取UnicodeDecodeError: 'utf-8' codec can't decode byte 0x89 in position 0: invalid start byte在MacOS上python3 data = input.read(25)file图像给人PNG image data, 720 x 857, 8-bit/color RGB, non-interlaced
mrgloom


24

在pypi上有一个名为的程序包imagesize目前对我有用,尽管它看起来不太活跃。

安装:

pip install imagesize

用法:

import imagesize

width, height = imagesize.get("test.png")
print(width, height)

主页:https//github.com/shibukawa/imagesize_py

PyPi:https://pypi.org/project/imagesize/


3
我比较了imagesize.get,magic.from_file和PIL图像的速度,以按时间获取实际图像大小。结果显示,使用正则表达式(0.1699s)可以使images.size.get(0.019s)> PIL(0.104s)> magic变快。
RyanLiu

9

我经常在Internet上获取图像大小。当然,您不能下载图像然后加载它以解析信息。太浪费时间了。我的方法是将大块数据馈送到图像容器,并测试它是否每次都能解析图像。当我得到我想要的信息时,停止循环。

我提取了代码的核心,并对其进行了修改以解析本地文件。

from PIL import ImageFile

ImPar=ImageFile.Parser()
with open(r"D:\testpic\test.jpg", "rb") as f:
    ImPar=ImageFile.Parser()
    chunk = f.read(2048)
    count=2048
    while chunk != "":
        ImPar.feed(chunk)
        if ImPar.image:
            break
        chunk = f.read(2048)
        count+=2048
    print(ImPar.image.size)
    print(count)

输出:

(2240, 1488)
38912

实际文件大小为1,543,580字节,您仅读取38,912字节即可获取图像大小。希望这会有所帮助。


1

在Unix系统上执行此操作的另一种简短方法。这取决于file我不确定所有系统上的输出是否都标准化。可能不应该在生产代码中使用它。此外,大多数JPEG不会报告图像尺寸。

import subprocess, re
image_size = list(map(int, re.findall('(\d+)x(\d+)', subprocess.getoutput("file " + filename))[-1]))

GivesIndexError: list index out of range
mrgloom

0

这个答案有另一个好的解决方法,但是缺少pgm格式。这个答案解决了pgm。然后我添加了bmp

代码如下

import struct, imghdr, re, magic

def get_image_size(fname):
    '''Determine the image type of fhandle and return its size.
    from draco'''
    with open(fname, 'rb') as fhandle:
        head = fhandle.read(32)
        if len(head) != 32:
            return
        if imghdr.what(fname) == 'png':
            check = struct.unpack('>i', head[4:8])[0]
            if check != 0x0d0a1a0a:
                return
            width, height = struct.unpack('>ii', head[16:24])
        elif imghdr.what(fname) == 'gif':
            width, height = struct.unpack('<HH', head[6:10])
        elif imghdr.what(fname) == 'jpeg':
            try:
                fhandle.seek(0) # Read 0xff next
                size = 2
                ftype = 0
                while not 0xc0 <= ftype <= 0xcf:
                    fhandle.seek(size, 1)
                    byte = fhandle.read(1)
                    while ord(byte) == 0xff:
                        byte = fhandle.read(1)
                    ftype = ord(byte)
                    size = struct.unpack('>H', fhandle.read(2))[0] - 2
                # We are at a SOFn block
                fhandle.seek(1, 1)  # Skip `precision' byte.
                height, width = struct.unpack('>HH', fhandle.read(4))
            except Exception: #IGNORE:W0703
                return
        elif imghdr.what(fname) == 'pgm':
            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)*)", head).groups()
            width = int(width)
            height = int(height)
        elif imghdr.what(fname) == 'bmp':
            _, width, height, depth = re.search(
                b"((\d+)\sx\s"
                b"(\d+)\sx\s"
                b"(\d+))", str).groups()
            width = int(width)
            height = int(height)
        else:
            return
        return width, height

imghdr但是对某些jpeg的处理非常差。
martixy
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.