无需将图像加载到内存中即可获得图像大小

我知道你可以通过以下方式使用 PIL 获得图像大小

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

但是,我想得到的图像宽度和高度 没有必须加载内存中的图像。这可能吗?我只是做图像大小的统计,并不关心图像的内容。我只是想让我的处理速度更快。

79278 次浏览

正如注释中提到的,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只是在成功时返回一个 file 对象和文件名。此外,医生表示:

Open (file,mode = “ r”)

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

这是一个延迟操作; 这个函数标识文件,但是直到您尝试处理数据(或者调用 装弹方法) ,才会从文件中读取实际的图像数据。

深入挖掘,我们看到 .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

如果你不关心图像内容,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 获取),不需要第三方模块。

Look, Ma! No deps!

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

这个 回答有另一个很好的分辨率,但是缺少 PGM格式。这个 回答已经解析了 PGM。加上 你好

密码在下面

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

我经常在互联网上获取图像大小。当然,您不能下载图像然后加载它来解析信息。太费时间了。我的方法是将块提供给一个图像容器,并测试它是否每次都能解析图像。当我得到我想要的信息时停止循环。

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

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字节就可以得到图像大小。希望这个能帮上忙。

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

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

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

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

结果:

  • 8米23口径
  • 2000年
  • 29秒
  • 29秒
  • 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)))

但他们没有。

此外,这是一个非常古老的问题,我尝试了这些方法中的几个,但没有一个适合我的大型3D Tif-File。因此,这里有一个非常简单快速的解决方案,使用“ tifffile”包“ memmap”函数:

    import tifffile
memmap_image = tifffile.memmap(fp)
memmap_image.shape

我的450GB 32位 Tif 运行时间-图像: 10毫秒