141

我了解您可以通过以下方式使用 PIL 获取图像大小

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

但是,我想获得图像的宽度和高度,而不必将图像加载到内存中。那可能吗?我只对图像大小进行统计,并不关心图像内容。我只是想让我的处理速度更快。

4

7 回答 7

108

如果您不关心图像内容,那么 PIL 可能是矫枉过正。

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

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

于 2013-09-26T18:06:55.030 回答
78

正如评论所暗示的,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")

如果格式错误,它看起来可以读取整个文件。但是,如果它读取信息标记 OK,它应该会提前爆发。该函数handler最终设置self.size图像的尺寸。

于 2013-09-26T17:37:46.777 回答
48

pypi 上有一个名为的包imagesize,目前对我有用,虽然它看起来不是很活跃。

安装:

pip install imagesize

用法:

import imagesize

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

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

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

于 2018-09-13T20:18:58.680 回答
11

我经常在 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 字节即可获得图像大小。希望这会有所帮助。

于 2017-02-04T19:02:08.310 回答
3

OP 对“更快”的解决方案很感兴趣,我对最快的解决方案很好奇,我正在尝试用真实世界的基准来回答这个问题。

我在比较:

我在 202897 主要是 JPG 文件上运行以下代码。

"""
pip install opsdroid-get-image-size --user
pip install pymage_size
pip install imagesize
"""

import concurrent.futures
from pathlib import Path

import cv2
import numpy as np
import pandas as pd
from tqdm import tqdm
from PIL import Image
import get_image_size
import imagesize
import pymage_size

files = [str(p.resolve())
         for p in Path("/data/").glob("**/*")
         if p.suffix in {".jpg", ".jpeg", ".JPEG", ".JPG", ".png", ".PNG"}]

def get_shape_cv2(fname):
    img = cv2.imread(fname)
    return (img.shape[0], img.shape[1])

with concurrent.futures.ProcessPoolExecutor(8) as executor:
    results = list(tqdm(executor.map(get_shape_cv2, files), total=len(files)))

def get_shape_pil(fname):
    img=Image.open(fname)
    return (img.size[0], img.size[1])

with concurrent.futures.ProcessPoolExecutor(8) as executor:
    results = list(tqdm(executor.map(get_shape_pil, files), total=len(files)))

def get_shape_scardine_size(fname):
    try:
        width, height = get_image_size.get_image_size(fname)
    except get_image_size.UnknownImageFormat:
        width, height = -1, -1
    return (width, height)

with concurrent.futures.ProcessPoolExecutor(8) as executor:
    results = list(tqdm(executor.map(get_shape_scardine_size, files), total=len(files)))

def get_shape_shibukawa(fname):
    width, height = imagesize.get(fname)
    return (width, height)

with concurrent.futures.ProcessPoolExecutor(8) as executor:
    results = list(tqdm(executor.map(get_shape_shibukawa, files), total=len(files)))

def get_shape_pymage_size(fname):
    img_format = pymage_size.get_image_size(fname)
    width, height = img_format.get_dimensions()
    return (width, height)

with concurrent.futures.ProcessPoolExecutor(8) as executor:
    results = list(tqdm(executor.map(get_shape_pymage_size, files), total=len(files)))

结果:

  • cv2.imread: 8m23s
  • PIL.open: 2m00s
  • opsdroid/image_size: 29 秒
  • shibukawa/imagesize_py: 29 秒
  • kobaltcore/pymage_size: 29 秒

所以 opsdroid、shibukawa 和 kobaltcore 以相同的速度执行。现在对我来说另一个有趣的点是更好地了解哪些库具有最好的格式支持。

[编辑] 所以我继续测试快速库是否提供不同的结果:

# test if the libs provide the same results
def show_size_differences(fname):
    w1, h1 = get_shape_scardine_size(fname)
    w2, h2 = get_shape_pymage_size(fname)
    w3, h3 = get_shape_shibukawa(fname)
    if w1 != w2 or w2 != w3 or h1 != h2 or h2 != h3:
        print(f"scardine: {w1}x{h1}, pymage: {w2}x{h2}, shibukawa: {w3}x{h3}")

with concurrent.futures.ProcessPoolExecutor(8) as executor:
    results = list(tqdm(executor.map(show_size_differences, files), total=len(files)))

他们没有。

于 2021-11-04T16:48:00.423 回答
2

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

import subprocess, re
image_size = list(map(int, re.findall('(\d+)x(\d+)', subprocess.getoutput("file " + filename))[-1]))
于 2017-06-13T09:08:38.737 回答
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
于 2015-09-24T05:20:37.840 回答