Holen Sie sich die Bildgröße, ohne das Bild in den Speicher zu laden

113

Ich verstehe, dass Sie die Bildgröße mit PIL auf folgende Weise erhalten können

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

Ich möchte jedoch die Bildbreite und -höhe erhalten, ohne das Bild in den Speicher laden zu müssen. Ist das möglich? Ich mache nur Statistiken über Bildgrößen und kümmere mich nicht um den Bildinhalt. Ich möchte nur meine Verarbeitung beschleunigen.

Sami A. Haija
quelle
8
Ich bin mir nicht 100% sicher, aber ich glaube nicht, dass .open()die gesamte Datei in den Speicher eingelesen wird ... (das ist was .load()) - soweit ich weiß - das ist so gut wie es nur gehtPIL
Jon Clements
5
Selbst wenn Sie glauben, eine Funktion zu haben, die nur die Bildheaderinformationen liest, lädt der Readahead-Code des Dateisystems möglicherweise das gesamte Bild. Die Sorge um die Leistung ist unproduktiv, es sei denn, Ihre Anwendung erfordert dies.
stark
1
Ich wurde von Ihren Antworten überzeugt. Danke @ JonClements und stark
Sami A. Haija
9
Ein schneller Speichertest pmapzur Überwachung des von einem Prozess verwendeten Speichers zeigt mir, dass tatsächlich PILnicht das gesamte Bild in den Speicher geladen wird.
Vincent Nivoliers
Siehe auch: Holen Sie sich Bildmaße mit Python
Martin Thoma

Antworten:

63

Wie in den Kommentaren erwähnt, lädt PIL das Bild beim Aufruf nicht in den Speicher .open. Wenn Sie sich die Dokumente von PIL 1.1.7ansehen, .opensagt der Dokumentstring für :

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

Es gibt einige Dateioperationen in der Quelle wie:

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

aber diese bilden kaum das Lesen der gesamten Datei. Tatsächlich wird .openbei Erfolg einfach ein Dateiobjekt und der Dateiname zurückgegeben. Zusätzlich sagen die Dokumente :

öffnen (Datei, Modus = ”r”)

Öffnet und identifiziert die angegebene Bilddatei.

Dies ist eine faule Operation; Diese Funktion identifiziert die Datei, aber die tatsächlichen Bilddaten werden erst aus der Datei gelesen, wenn Sie versuchen, die Daten zu verarbeiten (oder die Lademethode aufzurufen ).

Wenn wir tiefer graben, sehen wir, dass .openAnrufe _openeine bildformatspezifische Überlastung darstellen. Jede der zu implementierenden Implementierungen _openbefindet sich in einer neuen Datei, z. JPEG-Dateien befinden sich in JpegImagePlugin.py. Schauen wir uns das genauer an.

Hier scheinen die Dinge etwas knifflig zu werden. Darin befindet sich eine Endlosschleife, aus der herausgebrochen wird, wenn der JPEG-Marker gefunden wird:

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

Das sieht so aus, als könnte es die gesamte Datei lesen, wenn sie fehlerhaft wäre. Wenn der Info-Marker jedoch OK lautet, sollte er früh ausbrechen. Die Funktion handlerlegt letztendlich fest, self.sizewelche Abmessungen das Bild hat.

Süchtig
quelle
1
Stimmt, aber openbekommt die Größe des Bildes oder ist das auch eine faule Operation? Und wenn es faul ist, liest es gleichzeitig die Bilddaten?
Mark Ransom
Der Doc-Link zeigt auf Pillow a Fork von PIL. Ich kann jedoch keinen offiziellen Dokumentlink im Web finden. Wenn jemand es als Kommentar veröffentlicht, aktualisiere ich die Antwort. Das Zitat finden Sie in der Datei Docs/PIL.Image.html.
Hooked
@MarkRansom Ich habe versucht, Ihre Frage zu beantworten, aber um 100% sicher zu sein, müssen wir uns mit jeder imagespezifischen Implementierung befassen. Das .jpegFormat sieht in Ordnung aus, solange der Header gefunden wird.
Hooked
@Hooked: Vielen Dank, dass Sie sich damit befasst haben. Ich akzeptiere, dass Sie richtig sind, obwohl ich Paulos eher minimale Lösung unten sehr mag (obwohl das OP, um fair zu sein, nicht erwähnt hat, dass es die PIL-Abhängigkeit vermeiden will)
Alex Flint
@AlexFlint Kein Problem, es macht immer Spaß, im Code herumzustöbern. Ich würde sagen, dass Paulo sein Kopfgeld verdient hat, das ist ein netter Ausschnitt, den er dort für Sie geschrieben hat.
Hooked
87

Wenn Sie sich nicht für den Bildinhalt interessieren, ist PIL wahrscheinlich ein Overkill.

Ich schlage vor, die Ausgabe des Python Magic-Moduls zu analysieren:

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

Dies ist ein Wrapper um libmagic, der so wenig Bytes wie möglich liest, um eine Dateitypsignatur zu identifizieren.

Relevante Version des Skripts:

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

[aktualisieren]

Hmmm, leider, wenn auf JPEGs angewendet, gibt das oben Gesagte "'JPEG-Bilddaten, EXIF-Standard 2.21'". Keine Bildgröße! - Alex Flint

Scheint, als wären JPEGs magieresistent. :-)

Ich kann verstehen, warum: Um die Bildabmessungen für JPEG-Dateien zu erhalten, müssen Sie möglicherweise mehr Bytes lesen, als libmagic gerne liest.

Ich krempelte die Ärmel hoch und kam mit diesem sehr ungetesteten Snippet (von GitHub) , das keine Module von Drittanbietern erfordert.

Schau, Ma!  Keine 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

[Update 2019]

Schauen Sie sich eine Rust-Implementierung an: https://github.com/scardine/imsz

Paulo Scardine
quelle
3
Ich habe auch die Möglichkeit hinzugefügt, die Anzahl der Kanäle (nicht zu verwechseln mit Bittiefe) im Kommentar nach der oben angegebenen Version @EJEHardenberg abzurufen .
Greg Kramida
2
Tolle Sache. Ich habe Unterstützung für Bitmaps im GitHub-Projekt hinzugefügt. Vielen Dank!
Stockente
2
HINWEIS: Die aktuelle Version funktioniert bei mir nicht. @PauloScardine hat eine aktualisierte Arbeitsversion auf github.com/scardine/image_size
DankMasterDan
2
Holen Sie sich UnicodeDecodeError: 'utf-8' codec can't decode byte 0x89 in position 0: invalid start byteauf MacOS, Python3 auf data = input.read(25), fileauf Bild gibtPNG image data, 720 x 857, 8-bit/color RGB, non-interlaced
mrgloom
3
Scheint Code von raw.githubusercontent.com/scardine/image_size/master/… funktioniert.
Mrgloom
24

Es gibt ein Paket auf pypi namens imagesize , das derzeit für mich funktioniert, obwohl es nicht so aussieht, als wäre es sehr aktiv.

Installieren:

pip install imagesize

Verwendung:

import imagesize

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

Startseite: https://github.com/shibukawa/imagesize_py

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

Jonathan
quelle
3
Ich habe die Geschwindigkeit imagesize.get, magic.from_file und PIL verglichen, um die tatsächliche Bildgröße nach Zeit zu ermitteln. Die Ergebnisse zeigten, dass Geschwindigkeit imagesize.get (0,019 s)> PIL (0,104 s)> Magie mit Regex (0,1699 s).
RyanLiu
9

Ich rufe oft Bildgrößen im Internet ab. Natürlich können Sie das Bild nicht herunterladen und dann laden, um die Informationen zu analysieren. Es ist zu zeitaufwändig. Meine Methode besteht darin, einem Bildcontainer Chunks zuzuführen und zu testen, ob das Bild jedes Mal analysiert werden kann. Stoppen Sie die Schleife, wenn ich die gewünschten Informationen erhalte.

Ich habe den Kern meines Codes extrahiert und geändert, um lokale Dateien zu analysieren.

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)

Ausgabe:

(2240, 1488)
38912

Die tatsächliche Dateigröße beträgt 1.543.580 Byte, und Sie lesen nur 38.912 Byte, um die Bildgröße zu erhalten. Hoffe das wird helfen.

lovetl2002
quelle
1

Eine weitere kurze Möglichkeit, dies auf Unix-Systemen zu tun. Es hängt von der Ausgabe ab, von fileder ich nicht sicher bin, ob sie auf allen Systemen standardisiert ist. Dies sollte wahrscheinlich nicht im Produktionscode verwendet werden. Darüber hinaus geben die meisten JPEGs die Bildgröße nicht an.

import subprocess, re
image_size = list(map(int, re.findall('(\d+)x(\d+)', subprocess.getoutput("file " + filename))[-1]))
Lenar Hoyt
quelle
GibtIndexError: list index out of range
mrgloom
0

Diese Antwort hat eine andere gute Auflösung, aber das pgm- Format fehlt . Diese Antwort hat das pgm gelöst . Und ich füge den bmp hinzu .

Codes ist unten

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
quelle
imghdrhandhabt jedoch bestimmte JPEGs ziemlich schlecht.
Martixy