Ottieni la dimensione dell'immagine SENZA caricare l'immagine in memoria


113

Capisco che puoi ottenere la dimensione dell'immagine usando PIL nel modo seguente

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

Tuttavia, vorrei ottenere la larghezza e l'altezza dell'immagine senza dover caricare l'immagine in memoria. È possibile? Sto solo facendo statistiche sulle dimensioni delle immagini e non mi interessa il contenuto dell'immagine. Voglio solo velocizzare la mia elaborazione.


8
Non ne sono sicuro al 100% ma non credo che .open()legga l'intero file in memoria ... (ecco cosa .load()) fa - per quanto ne so - questo è buono come viene utilizzatoPIL
Jon Clements

5
Anche se pensi di avere una funzione che legge solo le informazioni di intestazione dell'immagine, il codice readahead del filesystem potrebbe comunque caricare l'intera immagine. Preoccuparsi delle prestazioni è improduttivo, a meno che l'applicazione non lo richieda.
netto

1
Mi sono convinto delle tue risposte. Grazie @JonClements e stark
Sami A. Haija

9
Un rapido test della memoria utilizzando pmapper monitorare la memoria utilizzata da un processo mi mostra che effettivamente PILnon carica l'intera immagine in memoria.
Vincent Nivoliers

Risposte:


63

Come alludono i commenti, PIL non carica l'immagine in memoria durante la chiamata .open. Guardando i documenti di PIL 1.1.7, la docstring per .opendice:

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

Ci sono alcune operazioni sui file nell'origine come:

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

ma questi difficilmente costituiscono la lettura dell'intero file. Infatti .openrestituisce semplicemente un oggetto file e il nome del file in caso di successo. Inoltre i documenti dicono:

open (file, mode = "r")

Apre e identifica il file immagine specificato.

Questa è un'operazione pigra; questa funzione identifica il file, ma i dati effettivi dell'immagine non vengono letti dal file finché non si tenta di elaborare i dati (o chiamare il metodo di caricamento ).

Scavando più a fondo, vediamo che le .openchiamate _opensono un sovraccarico specifico del formato immagine. Ciascuna delle implementazioni _openpuò essere trovata in un nuovo file, ad es. I file .jpeg sono in formato JpegImagePlugin.py. Diamo un'occhiata a quello in profondità.

Qui le cose sembrano diventare un po 'complicate, in esso c'è un ciclo infinito che viene interrotto quando viene trovato il marcatore 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")

Che sembra che potrebbe leggere l'intero file se fosse malformato. Tuttavia, se legge l'indicatore di informazioni OK, dovrebbe uscire presto. La funzione handlerinfine stabilisce self.sizequali sono le dimensioni dell'immagine.


1
È vero, ma openottiene le dimensioni dell'immagine o è anche un'operazione pigra? E se è pigro, legge i dati dell'immagine allo stesso tempo?
Mark Ransom

Il link doc punta a Pillow un fork di PIL. Tuttavia, non riesco a trovare un link alla documentazione ufficiale sul web. Se qualcuno lo pubblica come commento aggiornerò la risposta. La citazione può essere trovata nel file Docs/PIL.Image.html.
Agganciato il

@MarkRansom Ho tentato di rispondere alla tua domanda, tuttavia per essere sicuro al 100% sembra che dobbiamo immergerci in ogni implementazione specifica dell'immagine. Il .jpegformato sembra corretto finché viene trovata l'intestazione.
Agganciato il

@ Hooked: Grazie mille per aver esaminato questo. Accetto che tu abbia ragione anche se mi piace abbastanza la soluzione piuttosto minimale di Paulo di seguito (anche se per essere onesti l'OP non ha menzionato di voler evitare la dipendenza dal PIL)
Alex Flint

@AlexFlint Nessun problema, è sempre divertente curiosare nel codice. Direi che Paulo ha guadagnato la sua taglia, però, è un bel frammento che ha scritto per te lì.
Agganciato il

88

Se non ti interessa il contenuto dell'immagine, PIL è probabilmente eccessivo.

Suggerisco di analizzare l'output del modulo magico di 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')

Questo è un wrapper attorno a libmagic che legge il minor numero di byte possibile per identificare una firma del tipo di file.

Versione pertinente dello script:

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

[aggiornare]

Hmmm, sfortunatamente, quando applicato a jpeg, quanto sopra dà "'Dati immagine JPEG, EXIF ​​standard 2.21'". Nessuna dimensione dell'immagine! - Alex Flint

Sembra che i jpeg siano resistenti alla magia. :-)

Posso capire perché: per ottenere le dimensioni dell'immagine per i file JPEG, potresti dover leggere più byte di quelli che libmagic ama leggere.

Mi sono rimboccato le maniche e sono arrivato con questo frammento non testato (scaricalo da GitHub) che non richiede moduli di terze parti.

Guarda, mamma!  No 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

[aggiornamento 2019]

Controlla un'implementazione di Rust: https://github.com/scardine/imsz


3
Ho anche aggiunto la possibilità di recuperare il numero di canali (da non confondere con la profondità di bit) nel commento dopo la versione fornita da @EJEHardenberg sopra .
Greg Kramida

2
Grande cosa. Ho aggiunto il supporto per le bitmap nel progetto GitHub. Grazie!
Mallard

2
NOTA: la versione attuale non funziona per me. @PauloScardine ha una versione funzionante aggiornata su github.com/scardine/image_size
DankMasterDan

2
Ottenere UnicodeDecodeError: 'utf-8' codec can't decode byte 0x89 in position 0: invalid start bytesu MacOS, python3 su data = input.read(25), filesull'immagine dàPNG image data, 720 x 857, 8-bit/color RGB, non-interlaced
mrgloom


24

C'è un pacchetto su pypi chiamato imagesizeche attualmente funziona per me, anche se non sembra che sia molto attivo.

Installare:

pip install imagesize

Uso:

import imagesize

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

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

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


3
Ho confrontato la velocità imagesize.get, magic.from_file e l'immagine PIL per ottenere la dimensione effettiva dell'immagine in base al tempo. I risultati hanno mostrato che velocità imagesize.get (0,019 s)> PIL (0,104 s)> magia con regex (0,1699 s).
RyanLiu

9

Recupero spesso le dimensioni delle immagini su Internet. Ovviamente, non puoi scaricare l'immagine e poi caricarla per analizzare le informazioni. È troppo tempo. Il mio metodo consiste nel fornire blocchi a un contenitore di immagini e verificare se è in grado di analizzare l'immagine ogni volta. Ferma il ciclo quando ricevo le informazioni che voglio.

Ho estratto il nucleo del mio codice e l'ho modificato per analizzare i file locali.

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)

Produzione:

(2240, 1488)
38912

La dimensione effettiva del file è 1.543.580 byte e si leggono solo 38.912 byte per ottenere la dimensione dell'immagine. Spero che questo ti aiuti.


1

Un altro modo breve per farlo su sistemi Unix. Dipende dall'output di filecui non sono sicuro sia standardizzato su tutti i sistemi. Questo probabilmente non dovrebbe essere utilizzato nel codice di produzione. Inoltre la maggior parte dei JPEG non riporta la dimensione dell'immagine.

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

Questa risposta ha un'altra buona risoluzione, ma manca il formato pgm . Questa risposta ha risolto il pgm . E aggiungo il bmp .

I codici sono sotto

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

imghdrtuttavia gestisce alcuni file jpeg piuttosto male.
martixy
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.