Отримайте розмір зображення БЕЗ завантаження зображення в пам'ять


113

Я розумію, що розмір зображення можна отримати за допомогою PIL наступним чином

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

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


8
Я не на 100% впевнений, але не вірю, що .open()читає весь файл в пам'ять ... (саме це .load()) робить - наскільки я знаю - це так добре, як це отримує використанняPIL
Джон Клементс

5
Навіть якщо ви думаєте, що у вас є функція, яка зчитує лише інформацію заголовка зображення, код для перечитання файлової системи все ще може завантажувати все зображення. Турбуватися про продуктивність малопродуктивно, якщо цього не вимагає ваша заявка.
старт

1
Я переконався у ваших відповідях. Дякуємо @JonClements і старт
Самі А. Хаджа

9
Швидкий тест пам'яті, який використовується pmapдля моніторингу пам'яті, що використовується процесом, показує мені, що насправді PILне завантажується в пам'ять все зображення.
Вінсент Нівольє

Відповіді:


63

Як натякають коментарі, PIL не завантажує зображення в пам'ять під час дзвінка .open. Дивлячись на документи PIL 1.1.7, docstring для .openговорить:

def open(fp, mode="r"):
    "Open an image file, without loading the raster data"

У джерелі є кілька файлових операцій, таких як:

 ...
 prefix = fp.read(16)
 ...
 fp.seek(0)
 ...

але вони навряд чи є читанням всього файлу. Насправді .openпросто повертає файловий об’єкт та ім’я файлу на успіх. Крім того, документи говорять:

відкрити (файл, режим = ”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отримує розмір зображення чи це теж лінива операція? І якщо це лінь, чи читає вони зображення зображення одночасно?
Марк Рансом

Документ посилання вказує на Подушку виделкою з PIL. Однак я не можу знайти офіційне посилання на документи в Інтернеті. Якщо хтось опублікує це як коментар, я оновлю відповідь. Цитату можна знайти у файлі Docs/PIL.Image.html.
Зачепили

@MarkRansom Я намагався відповісти на ваше запитання, але, щоб бути впевненим на 100%, схоже, що ми маємо зануритися в кожну реалізацію зображення. .jpegФормат виглядає ОК, поки знайдений заголовок.
Зачепили

@Hooked: Дякую дуже багато за те, що вивчив це. Я погоджуюсь, що ви праві, хоча мені дуже подобається досить мінімальне рішення Паулу внизу (навіть якщо справедливіше, ОП не згадував про те, щоб уникнути залежності від ПІЛ)
Алекс Флінт,

@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

[оновлення]

Хммм, на жаль, коли застосовується до jpegs, вищезазначене дає "" дані зображення JPEG, стандарт EXIF ​​2,21 "". Без розміру зображення! - Олексій Флінт

Схоже, jpegs стійкі до магії. :-)

Я можу зрозуміти, чому: для отримання розмірів зображення для файлів JPEG, можливо, вам доведеться прочитати більше байтів, ніж любить читати.

Закатав мої рукави і прийшов із цим дуже неперевіреним фрагментом (дістань його від 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
Я також додав можливість отримати кількість каналів (не плутати w / бітову глибину) у коментарі після того, як версія @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 byteна MacOS, 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/… працює.
мрглоом

24

Є пакет на 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, щоб отримати фактичний розмір зображення за часом. Результати показали, що швидкість imagesize.get (0.019s)> PIL (0.104s)> магія з регулярним виразом (0.1699s).
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 байт, щоб отримати розмір зображення. Сподіваюсь, це допоможе.


1

Ще один короткий спосіб зробити це в системах Unix. Це залежить від того, вихід fileякого я не впевнений, стандартизований для всіх систем. Це, мабуть, не повинно використовуватися у виробничому коді. Більш того, більшість JPEG не повідомляють про розмір зображення.

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

ДаєIndexError: list index out of range
мрглоом

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проте обробляє певні jpegs досить погано.
martixy
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.