我知道你可以使用PIL以以下方式获取图像大小
from PIL import Image
im = Image.open(image_filename)
width, height = im.size
但是,我希望在不将图像加载到内存中的情况下获得图像的宽度和高度。这可行吗? 我只对图像大小进行统计,不关心图像内容。 我只想让我的处理更快。
我知道你可以使用PIL以以下方式获取图像大小
from PIL import Image
im = Image.open(image_filename)
width, height = im.size
但是,我希望在不将图像加载到内存中的情况下获得图像的宽度和高度。这可行吗? 我只对图像大小进行统计,不关心图像内容。 我只想让我的处理更快。
>>> 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')
https://raw.githubusercontent.com/scardine/image_size/master/get_image_size.py
[更新]
唔,不幸的是,当应用于JPEG格式时,上述代码只会返回“'JPEG image data, EXIF standard 2.21'”,没有图像尺寸! - Alex Flint
似乎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
data = input.read(25)
处出现了UnicodeDecodeError: 'utf-8' codec can't decode byte 0x89 in position 0: invalid start byte
错误。对于图像的file
,它给出了PNG image data, 720 x 857, 8-bit/color RGB, non-interlaced
。 - mrgloom正如评论所示,PIL在调用.open
时不会将图像加载到内存中。查看PIL 1.1.7
的文档,.open
的docstring如下:
def open(fp, mode="r"):
"Open an image file, without loading the raster data"
源代码中有一些文件操作,比如:
...
prefix = fp.read(16)
...
fp.seek(0)
...
但这些几乎不能构成阅读整个文件。实际上,.open
仅在成功时返回文件对象和文件名。此外,文档中写道:
open(file, mode=”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
,这是图像的尺寸。
open
方法是否能获取到图片的大小呢?这个操作是否也是惰性的呢?如果是惰性的,它是否同时读取了图片数据? - Mark RansomDocs/PIL.Image.html
中找到。 - Hooked.jpeg
格式看起来就没问题。 - Hooked在pypi上有一个名为imagesize
的包,目前对我来说能够正常使用,尽管它看起来并不是很活跃。
安装:
pip install imagesize
使用方法:
import imagesize
width, height = imagesize.get("test.png")
print(width, height)
>
符号的反向很令人困惑。难道时间不是“更短”吗? - Gulzar>
符号适用于速度(即更快 > 更慢),但是他在括号中放置了时间,这与符号不一致。 - JonathanCaused by: org.apache.spark.api.python.PythonException: 'cv2.error: OpenCV(4.7.0) /io/opencv/modules/imgcodecs/src/loadsave.cpp:79: error: (-215:Assertion failed) pixels <= CV_IO_MAX_IMAGE_PIXELS in function 'validateInputImageSize''
即使将环境变量CV_IO_MAX_IMAGE_PIXELS设置为非常大的值。现在使用该库以避免处理庞大的图像。谢谢。 - arun原帖提到需要一种更快的解决方案,我对最快的解决方案很感兴趣,并试图通过真实世界的基准测试来回答这个问题。
我正在比较以下内容:
cv2.imread
: https://www.kite.com/python/docs/cv2.imreadPIL.open
: https://pillow.readthedocs.io/en/stable/reference/Image.htmlopsdroid/image_size
: https://github.com/opsdroid/image_size(https://raw.githubusercontent.com/scardine/image_size/master/get_image_size.py 的打包版本)shibukawa/imagesize_py
: https://github.com/shibukawa/imagesize_pykobaltcore/pymage_size
: https://github.com/kobaltcore/pymage_size我正在对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
:8m23sPIL.open
:2m00sopsdroid/image_size
:29sshibukawa/imagesize_py
:29skobaltcore/pymage_size
:29s因此,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)))
他们并没有。
我经常在互联网上获取图像尺寸。当然,你不能下载图像,然后加载它来解析信息。这太费时间了。我的方法是将块馈送到图像容器中,每次测试是否可以解析图像。当我获得想要的信息时停止循环。
我提取了我的代码核心并修改它以解析本地文件。
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]))
这个答案有另一个很好的解决方案,但缺少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虽然这是一个非常老的问题,但我尝试了几种方法,但对于我的大型3D Tif文件都没有用。因此,这里有一个非常简单和快速的解决方案,使用“tifffile”包中的“memmap”函数:
import tifffile
memmap_image = tifffile.memmap(fp)
memmap_image.shape
我的450 GB 32位Tif图像的运行时间:10毫秒
我尝试了以下方法:
我发现:
(-1,-1)
的大小。您可以在下面的.BMP示例中看到这一点。我的结论 - 我会选择PIL
。虽然它不像imagesize
库那样快速,但它更强大,可以处理更多的文件类型。我认为对于大多数用例来说,它已经足够快了。使用re
解析magic
输出是不可靠的,而且比PIL
慢得多。
我的测试
我从硬盘中取出一张保存的图像(636 x 636),并将其保存为6种不同的文件格式(.png、.jpg、.jpeg、.tif、.tiff、.bmp)。这些图像和脚本都保存在同一个目录下。我的测试中每种文件类型的大小都在下面的文件名旁边注释了出来。
脚本:
import os
import re
import timeit
import magic
import imagesize
import time
from PIL import Image
"""
Notes:
- all images are the same image saved as different formats
- file extensions tested are: .png, .jpg, .jpeg, .tif, .tiff, .bmp
- all images in this test are size 636 x 636
- all images are in the same directory as this script
If you want to setup this similar experiment, take a single image,
save it as: png_image.png, jpg_image.jpg, jpeg_image.jpeg, tif_image.tif,
tiff_image.tiff, and bmp_image.bmp (others if you'd like),
in the same directory as this script, and run this script.
Or name the images whatever and modify the script below. You do you.
"""
NUMBER = 10000
REPEAT = 5
def regex(filename):
name,ext = os.path.splitext(filename)
if ext.lower() in ['.tif', '.tiff']:
return '^(?=.*width=(\d+))(?=.*height=(\d+))'
elif ext.lower() in ['.jpg', '.jpeg', '.png']:
return '(\d+)\s?x\s?(\d+)'
elif ext.lower() in ['.bmp']:
return '(\d+)\s?x\s?(\d+)\s?x\s?\d+'
else:
raise Exception('Extension %s is not accounted for.' % ext.lower())
PNG_FILE = 'png_image.png' # 559 KB
JPG_FILE = 'jpg_image.jpg' # 94 KB
JPEG_FILE = 'jpeg_image.jpeg' # 94 KB
TIF_FILE = 'tif_image.tif' # 768 KB
TIFF_FILE = 'tiff_image.tiff' # 768 KB
BMP_FILE = 'bmp_image.bmp' # 1,581 KB
FILENAMES = [PNG_FILE, JPG_FILE, JPEG_FILE, TIF_FILE, TIFF_FILE, BMP_FILE]
now = time.time()
for filename in FILENAMES:
print('#' * 36)
print((" Testing %s" % filename).center(36, "#"))
print('#' * 36)
print('# ' + 'magic library'.center(32) + ' #')
print(' ', 'output:', magic.from_file(filename))
print(' ', "Size:", re.findall(regex(filename), magic.from_file(filename))[-1])
print(' ', "Regex used:", regex(filename))
print('# ' + 'PIL library'.center(32) + ' #')
image = Image.open(filename)
print(' ', image)
print(' ', "Size:", image.size)
print(' ', "Regex used:", 'None')
print('# ' + 'imagesize library'.center(32) + ' #')
image = imagesize.get(filename)
print(' ', "Size:", image)
print(' ', "Regex used:", 'None')
print('-' * 30 + '\n')
print("#################################end#######################################\n")
start = time.time()
for filename in FILENAMES:
print((" Testing %s " % filename).center(36, "#"))
# magic library
magic_timer = timeit.Timer(
stmt="width, height = re.findall(pattern, magic.from_file(filename))[-1]",
setup="import magic; import re; filename='" + filename + "'; pattern=r'" + regex(filename) + "';",
)
magic_timeit = magic_timer.timeit(number=NUMBER)
magic_repeat = magic_timer.repeat(repeat=REPEAT, number=NUMBER)
print('magic'.ljust(12) + ":", "%.15f," % magic_timeit, "%s repeat avg. : %.15f" % (REPEAT, sum(magic_repeat) / REPEAT))
# PIL library
pillow_timer = timeit.Timer(
stmt="width, height = Image.open(filename).size;",
setup="from PIL import Image; filename='" + filename + "';",
)
pillow_timeit = pillow_timer.timeit(number=NUMBER)
pillow_repeat = pillow_timer.repeat(repeat=REPEAT, number=NUMBER)
print('PIL'.ljust(12) + ":", "%.15f," % pillow_timeit, "%s repeat avg. : %.15f" % (REPEAT, sum(pillow_repeat) / REPEAT))
# imagesize library
imagesize_timer = timeit.Timer(
stmt="width, height = imagesize.get(filename);",
setup="import imagesize; filename='" + filename + "';",
)
imagesize_timeit = imagesize_timer.timeit(number=NUMBER)
imagesize_repeat = imagesize_timer.repeat(repeat=REPEAT, number=NUMBER)
print('imagesize'.ljust(12) + ":", "%.15f," % imagesize_timeit, "%s repeat avg. : %.15f" % (REPEAT, sum(imagesize_repeat) / REPEAT))
stop = time.time()
mins, secs = divmod(stop - start, 60)
print('\nTest time: %d minutes %d seconds' % (mins, secs))
print("\n#################################end#######################################\n")
输出:
####################################
####### Testing png_image.png#######
####################################
# magic library #
output: PNG image data, 636 x 636, 8-bit/color RGB, non-interlaced
Size: ('636', '636')
Regex used: (\d+)\s?x\s?(\d+)
# PIL library #
<PIL.PngImagePlugin.PngImageFile image mode=RGB size=636x636 at 0x1EBDE962710>
Size: (636, 636)
Regex used: None
# imagesize library #
Size: (636, 636)
Regex used: None
------------------------------
####################################
####### Testing jpg_image.jpg#######
####################################
# magic library #
output: JPEG image data, JFIF standard 1.01, resolution (DPI), density 96x96, segment length 16, baseline, precision 8, 636x636, frames 3
Size: ('636', '636')
Regex used: (\d+)\s?x\s?(\d+)
# PIL library #
<PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=636x636 at 0x1EBDF3E1810>
Size: (636, 636)
Regex used: None
# imagesize library #
Size: (636, 636)
Regex used: None
------------------------------
####################################
###### Testing jpeg_image.jpeg######
####################################
# magic library #
output: JPEG image data, JFIF standard 1.01, resolution (DPI), density 96x96, segment length 16, baseline, precision 8, 636x636, frames 3
Size: ('636', '636')
Regex used: (\d+)\s?x\s?(\d+)
# PIL library #
<PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=636x636 at 0x1EBDF3E3010>
Size: (636, 636)
Regex used: None
# imagesize library #
Size: (636, 636)
Regex used: None
------------------------------
####################################
####### Testing tif_image.tif#######
####################################
# magic library #
output: TIFF image data, little-endian, direntries=16, height=636, bps=63732, compression=LZW, PhotometricIntepretation=RGB, width=636
Size: ('636', '636')
Regex used: ^(?=.*width=(\d+))(?=.*height=(\d+))
# PIL library #
<PIL.TiffImagePlugin.TiffImageFile image mode=RGBA size=636x636 at 0x1EBDF3E1810>
Size: (636, 636)
Regex used: None
# imagesize library #
Size: (636, 636)
Regex used: None
------------------------------
####################################
###### Testing tiff_image.tiff######
####################################
# magic library #
output: TIFF image data, little-endian, direntries=16, height=636, bps=63732, compression=LZW, PhotometricIntepretation=RGB, width=636
Size: ('636', '636')
Regex used: ^(?=.*width=(\d+))(?=.*height=(\d+))
# PIL library #
<PIL.TiffImagePlugin.TiffImageFile image mode=RGBA size=636x636 at 0x1EBDF3E3160>
Size: (636, 636)
Regex used: None
# imagesize library #
Size: (636, 636)
Regex used: None
------------------------------
####################################
####### Testing bmp_image.bmp#######
####################################
# magic library #
output: PC bitmap, Windows 3.x format, 636 x 636 x 32
Size: ('636', '636')
Regex used: (\d+)\s?x\s?(\d+)\s?x\s?\d+
# PIL library #
<PIL.BmpImagePlugin.BmpImageFile image mode=RGB size=636x636 at 0x1EBDF3E31F0>
Size: (636, 636)
Regex used: None
# imagesize library #
Size: (-1, -1)
Regex used: None
------------------------------
#################################end#######################################
timeit
设置为10,000次,并重复5次。参考资料,运行时间为7分46秒。###### Testing png_image.png #######
magic : 9.280310999951325 , 5 repeat avg. : 8.674063340038993
PIL : 1.069168900023215 , 5 repeat avg. : 1.100983139988966
imagesize : 0.676764299976639 , 5 repeat avg. : 0.658798480057158
###### Testing jpg_image.jpg #######
magic : 7.006248699966818 , 5 repeat avg. : 6.803474060003646
PIL : 1.295019199955277 , 5 repeat avg. : 1.230920840008184
imagesize : 0.709322200040333 , 5 repeat avg. : 0.706342480005696
##### Testing jpeg_image.jpeg ######
magic : 6.531979499966837 , 5 repeat avg. : 6.501230620010756
PIL : 1.263985900091939 , 5 repeat avg. : 1.263613799982704
imagesize : 0.666680400026962 , 5 repeat avg. : 0.701455319998786
###### Testing tif_image.tif #######
magic : 11.265482199960388, 5 repeat avg. : 11.423775779991411
PIL : 3.702962300041690 , 5 repeat avg. : 3.857250300026499
imagesize : 0.764358000014909 , 5 repeat avg. : 0.750753180007450
##### Testing tiff_image.tiff ######
magic : 11.288321400061250, 5 repeat avg. : 11.339019200019539
PIL : 4.116472600027919 , 5 repeat avg. : 3.834464759984985
imagesize : 0.753993199905381 , 5 repeat avg. : 0.758465819992125
###### Testing bmp_image.bmp #######
magic : 16.124460300081410, 5 repeat avg. : 16.291060140007176
PIL : 0.919579099980183 , 5 repeat avg. : 0.928753740014508
imagesize : 0.649574000039138 , 5 repeat avg. : 0.654250180022791
Test time: 7 minutes 46 seconds
#################################end#######################################
注意:我不是时间方面的专家,如果我的时间安排方法似乎无效,请指出。
跳过直接访问EXIF数据并存储整数。
from PIL import Image
img_x = Image.open(image_filename)._getexif()[40962]
img_y = Image.open(image_filename)._getexif()[40963]
请参阅此页面了解EXIF标签。
.open()
并不会将整个文件读入内存...(这是.load()
的作用) - 换句话说,就我所知道的,使用PIL
这样做已经很好了。 - Jon Clementspmap
监控进程使用的内存进行快速记忆测试后,我发现确实PIL
没有将整个图像加载到内存中。 - Vincent Nivoliers