Uzyskaj rozmiar obrazu BEZ ładowania obrazu do pamięci


113

Rozumiem, że możesz uzyskać rozmiar obrazu za pomocą PIL w następujący sposób

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

Chciałbym jednak uzyskać szerokość i wysokość obrazu bez konieczności ładowania obrazu do pamięci. Czy to jest możliwe? Robię tylko statystyki dotyczące rozmiarów obrazów i nie dbam o zawartość obrazu. Chcę tylko przyspieszyć przetwarzanie.


8
Nie jestem pewien w 100%, ale nie wierzę, że .open()odczytuje cały plik do pamięci ... (to właśnie .load()) robi - o ile wiem - jest tak dobre, jak to tylko możliwePIL
Jon Clements

5
Nawet jeśli myślisz, że masz funkcję, która odczytuje tylko informacje z nagłówka obrazu, kod ponownego ładowania systemu plików może nadal ładować cały obraz. Martwienie się o wydajność jest bezproduktywne, chyba że aplikacja tego wymaga.
ostry

1
Przekonałem się o twoich odpowiedziach. Dzięki @JonClements i ostro
Sami A. Haija

9
Szybki test pamięci używany pmapdo monitorowania pamięci używanej przez proces pokazuje mi, że rzeczywiście PILnie ładuje całego obrazu do pamięci.
Vincent Nivoliers

Odpowiedzi:


63

Jak wskazują komentarze, PIL nie ładuje obrazu do pamięci podczas wywoływania .open. Patrząc na dokumenty z PIL 1.1.7, dokumentacja .openmówi:

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

W źródle jest kilka operacji na plikach, takich jak:

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

ale to prawie nie stanowi przeczytania całego pliku. W rzeczywistości .openpo pomyślnym zakończeniu zwraca obiekt pliku i nazwę pliku. Ponadto doktorzy mówią:

otwórz (plik, tryb = ”r”)

Otwiera i identyfikuje podany plik obrazu.

To jest leniwa operacja; ta funkcja identyfikuje plik, ale rzeczywiste dane obrazu nie są odczytywane z pliku, dopóki nie spróbujesz przetworzyć danych (lub wywołasz metodę ładowania ).

Kopiąc głębiej, widzimy, że .openwywołania _opensą przeciążeniem specyficznym dla formatu obrazu. Każdą z implementacji _openmożna znaleźć w nowym pliku, np. Pliki .jpeg są w formacie JpegImagePlugin.py. Przyjrzyjmy się temu szczegółowo.

Tutaj sprawy wydają się nieco skomplikowane, w tym jest nieskończona pętla, która zostaje zerwana po znalezieniu znacznika 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")

Wygląda na to, że mógłby odczytać cały plik, gdyby był źle sformułowany. Jeśli jednak odczyta znacznik informacyjny OK, powinien wybuchnąć wcześnie. Funkcja handlerostatecznie ustala self.sizewymiary obrazu.


1
To prawda, ale czy openuzyskuje rozmiar obrazu, czy też jest to leniwa operacja? A jeśli jest leniwy, czy odczytuje dane obrazu w tym samym czasie?
Mark Ransom

Link do dokumentu wskazuje na Pillow a fork from PIL. Nie mogę jednak znaleźć oficjalnego linku do dokumentu w sieci. Jeśli ktoś opublikuje to jako komentarz, zaktualizuję odpowiedź. Cytat można znaleźć w pliku Docs/PIL.Image.html.
Hooked

@MarkRansom Próbowałem odpowiedzieć na twoje pytanie, jednak aby mieć 100% pewności, wygląda na to, że musimy zagłębić się w każdą implementację specyficzną dla obrazu. .jpegFormat wygląda OK, dopóki główka została znaleziona.
Hooked

@Hooked: Bardzo dziękuję za przyjrzenie się temu. Akceptuję, że masz rację, chociaż podoba mi się raczej minimalne rozwiązanie Paula poniżej (chociaż szczerze mówiąc, OP nie wspomniał o chęci uniknięcia zależności PIL)
Alex Flint

@AlexFlint Żaden problem, zawsze fajnie jest przeglądać kod. Powiedziałbym jednak, że Paulo zasłużył na swoją nagrodę, to miły fragment, który dla ciebie napisał.
Hooked

88

Jeśli nie dbasz o zawartość obrazu, PIL jest prawdopodobnie przesadą.

Proponuję przeanalizować dane wyjściowe modułu magicznego Pythona:

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

Jest to opakowanie wokół libmagic, które odczytuje jak najmniej bajtów, aby zidentyfikować sygnaturę typu pliku.

Odpowiednia wersja skryptu:

https://raw.githubusercontent.com/scardine/image_size/master/get_image_size.py

[aktualizacja]

Hmmm, niestety, po zastosowaniu do plików jpeg, powyższe daje "'Dane obrazu JPEG, standard EXIF ​​2.21'". Brak rozmiaru obrazu! - Alex Flint

Wygląda na to, że pliki JPEG są odporne na magię. :-)

Rozumiem dlaczego: aby uzyskać wymiary obrazu dla plików JPEG, być może będziesz musiał przeczytać więcej bajtów, niż lubi czytać libmagic.

Podwinąłem rękawy i przyszedłem z tym bardzo nieprzetestowanym fragmentem (pobierz go z GitHub), który nie wymaga modułów innych firm.

Spójrz, mamo!  Bez 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

[aktualizacja 2019]

Sprawdź implementację Rusta: https://github.com/scardine/imsz


3
Dodałem również możliwość pobierania liczby kanałów (nie mylić z głębokością bitową) w komentarzu po wersji @EJEHardenberg powyżej .
Greg Kramida,

2
Wspaniała rzecz. Dodałem obsługę bitmap w projekcie GitHub. Dzięki!
Krzyżówka

2
UWAGA: obecna wersja u mnie nie działa. @PauloScardine ma zaktualizowaną działającą wersję na github.com/scardine/image_size
DankMasterDan

2
Wejdź UnicodeDecodeError: 'utf-8' codec can't decode byte 0x89 in position 0: invalid start bytena MacOS, python3 na data = input.read(25), filena obrazie dajePNG image data, 720 x 857, 8-bit/color RGB, non-interlaced
mrgloom


24

Na pypi jest pakiet o nazwie, imagesizektóry obecnie działa dla mnie, chociaż nie wygląda na bardzo aktywny.

Zainstalować:

pip install imagesize

Stosowanie:

import imagesize

width, height = imagesize.get("test.png")
print(width, height)

Strona domowa: https://github.com/shibukawa/imagesize_py

PyPi: https://pypi.org/project/imagesize/


3
Porównałem prędkość imagesize.get, magic.from_file i obraz PIL, aby uzyskać rzeczywisty rozmiar obrazu według timeit. Wyniki pokazały, że prędkość imagesize.get (0,019 s)> PIL (0,104 s)> magia z wyrażeniem regularnym (0,1699 s).
RyanLiu

9

Często pobieram rozmiary obrazów w Internecie. Oczywiście nie można pobrać obrazu, a następnie załadować go w celu przeanalizowania informacji. To zbyt czasochłonne. Moja metoda polega na podawaniu fragmentów do kontenera obrazu i sprawdzaniu, czy za każdym razem może on przeanalizować obraz. Zatrzymaj pętlę, gdy otrzymam potrzebne informacje.

Wyodrębniłem rdzeń mojego kodu i zmodyfikowałem go, aby analizować pliki lokalne.

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)

Wynik:

(2240, 1488)
38912

Rzeczywisty rozmiar pliku to 1 543 580 bajtów, a do uzyskania rozmiaru obrazu wystarczy odczytać tylko 38 912 bajtów. Mam nadzieję, że to pomoże.


1

Kolejny krótki sposób na zrobienie tego w systemach uniksowych. Zależy to od wyjścia, filektórego nie jestem pewien, jest znormalizowane we wszystkich systemach. To prawdopodobnie nie powinno być używane w kodzie produkcyjnym. Ponadto większość plików JPEG nie podaje rozmiaru obrazu.

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

DajeIndexError: list index out of range
mrgloom

0

Ta odpowiedź ma inną dobrą rozdzielczość, ale brakuje formatu pgm . Ta odpowiedź rozwiązała problem pgm . I dodaję bmp .

Kody są poniżej

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

imghdrjednak radzi sobie z niektórymi jpegami dość słabo.
martixy
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.