Получить размер изображения БЕЗ загрузки изображения в память

115

Я понимаю, что вы можете получить размер изображения с помощью PIL следующим образом

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

Однако я хотел бы получить ширину и высоту изображения, не загружая изображение в память. Это возможно? Я занимаюсь статистикой только по размерам изображений и не забочусь о содержимом изображений. Я просто хочу ускорить обработку.

Сами А. Хайджа
источник
8
Я не уверен на 100%, но я не верю, что это .open()считывает весь файл в память ... (это то, что .load()) делает - насколько я знаю - это настолько хорошо, насколько это возможноPIL
Джон Клементс
5
Даже если вы думаете, что у вас есть функция, которая только читает информацию заголовка изображения, код упреждающего чтения файловой системы может загружать все изображение целиком. Беспокойство о производительности непродуктивно, если этого не требует ваше приложение.
Stark
1
Убедился в ваших ответах. Спасибо @JonClements и stark
Sami A. Haija
9
Быстрый тест памяти с использованием pmapдля мониторинга памяти, используемой процессом, показал мне, что действительно PILне загружает все изображение в память.
Винсент Нивольерс

Ответы:

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в случае успеха просто возвращает объект файла и имя файла. Кроме того, в документах говорится:

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размеры изображения.

Зацепил
источник
1
Достаточно верно, но openполучает ли размер изображения или это тоже ленивая операция? А если ленив, читает ли при этом данные изображения?
Марк Рэнсом,
Ссылка на документ указывает на Pillow - вилку из PIL. Однако я не могу найти официальную ссылку на документ в Интернете. Если кто-то опубликует это как комментарий, я обновлю ответ. Цитату можно найти в файле Docs/PIL.Image.html.
изменен
@MarkRansom Я попытался ответить на ваш вопрос, однако, чтобы быть на 100% уверенным, похоже, мы должны погрузиться в каждую реализацию, специфичную для изображения. .jpegФормат выглядит ОК, пока найден заголовок.
изменен
@Hooked: Большое спасибо, что разобрались с этим. Я согласен с тем, что вы правы, хотя мне очень нравится довольно минимальное решение Пауло ниже (хотя, честно говоря, OP не упомянул о желании избежать зависимости от PIL)
Alex Flint
@AlexFlint Нет проблем, всегда интересно ковыряться в коде. Я бы сказал, что Пауло получил свою награду, это хороший фрагмент, который он написал для вас там.
изменено
88

Если вас не волнует содержимое изображения, 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

Пауло Скардин
источник
3
Я также добавил возможность извлекать количество каналов (не путать с битовой глубиной) в комментарии после версии, которую @EJEHardenberg предоставляет выше .
Грег Крамида
2
Великая вещь. Я добавил поддержку растровых изображений в проект GitHub. Спасибо!
Mallard
2
ПРИМЕЧАНИЕ: текущая версия у меня не работает. @PauloScardine имеет обновленную рабочую версию на github.com/scardine/image_size
DankMasterDan
2
Загрузите UnicodeDecodeError: 'utf-8' codec can't decode byte 0x89 in position 0: invalid start byteMacOS, включите python3 data = input.read(25), fileизображение даетPNG image data, 720 x 857, 8-bit/color RGB, non-interlaced
mrgloom
3
Кажется, код с raw.githubusercontent.com/scardine/image_size/master/… работает.
mrgloom
26

На 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 image, чтобы получить фактический размер изображения по timeit. Результаты показали, что скорость imagesize.get (0,019 с)> PIL (0,104 с)> магия с регулярным выражением (0,1699 с).
RyanLiu
9

Я часто ищу размеры изображений в Интернете. Конечно, вы не можете загрузить изображение, а затем загрузить его для анализа информации. Слишком много времени. Мой метод - загружать фрагменты в контейнер изображений и каждый раз проверять, может ли он анализировать изображение. Остановите цикл, когда я получу нужную мне информацию.

Я извлек ядро ​​своего кода и изменил его для анализа локальных файлов.

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 байт, чтобы получить размер изображения. Надеюсь, это поможет.

lovetl2002
источник
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