问题:获取图像大小而无需将图像加载到内存中
我了解您可以通过以下方式使用PIL获得图像尺寸
from PIL import Image
im = Image.open(image_filename)
width, height = im.size
但是,我想获取图像的宽度和高度,而不必将图像加载到内存中。那可能吗?我只做图像尺寸的统计,并不关心图像内容。我只是想加快处理速度。
回答 0
正如注释所暗示的那样,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
如果您不关心图像内容,则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
回答 2
在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
我经常在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字节即可获取图像大小。希望这会有所帮助。
回答 4
在Unix系统上执行此操作的另一种简短方法。这取决于file
我不确定所有系统上的输出是否都标准化。可能不应该在生产代码中使用它。此外,大多数JPEG不会报告图像尺寸。
import subprocess, re
image_size = list(map(int, re.findall('(\d+)x(\d+)', subprocess.getoutput("file " + filename))[-1]))
回答 5
这个答案有另一个好的解决方法,但是缺少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