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.

Sami A. Haija
źródło
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
Zobacz także: Uzyskaj wymiary obrazu za pomocą Pythona
Martin Thoma

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.

Haczykowaty
źródło
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

Paulo Scardine
źródło
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
3
Wygląda na to, że kod z raw.githubusercontent.com/scardine/image_size/master/… działa.
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/

Jonathan
źródło
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.

lovetl2002
źródło
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]))
Lenar Hoyt
źródło
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
Yantao Xie
źródło
imghdrjednak radzi sobie z niektórymi jpegami dość słabo.
martixy