Obtenga el tamaño de la imagen SIN cargar la imagen en la memoria

113

Entiendo que puede obtener el tamaño de la imagen usando PIL de la siguiente manera

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

Sin embargo, me gustaría obtener el ancho y el alto de la imagen sin tener que cargar la imagen en la memoria. ¿Es eso posible? Solo estoy haciendo estadísticas sobre tamaños de imágenes y no me importa el contenido de la imagen. Solo quiero que mi procesamiento sea más rápido.

Sami A. Haija
fuente
8
No estoy 100% seguro, pero no creo que eso .open()lea todo el archivo en la memoria ... (eso es lo que .load()) hace, hasta donde yo sé, esto es tan bueno como se usaPIL
Jon Clements
5
Incluso si cree que tiene una función que solo lee la información del encabezado de la imagen, el código del cabezal de lectura del sistema de archivos puede cargar toda la imagen. Preocuparse por el rendimiento es improductivo a menos que su aplicación lo requiera.
Stark
1
Me convencí de tus respuestas. Gracias @JonClements y stark
Sami A. Haija
9
Una prueba rápida de memoria que usa pmappara monitorear la memoria utilizada por un proceso me muestra que, de hecho PIL, no carga la imagen completa en la memoria.
Vincent Nivoliers
Véase también: Obtener dimensiones de imagen con Python
Martin Thoma

Respuestas:

63

Como aluden los comentarios, PIL no carga la imagen en la memoria al llamar .open. Mirando los documentos de PIL 1.1.7, la cadena de documentos para .opendice:

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

Hay algunas operaciones de archivo en la fuente como:

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

pero estos difícilmente constituyen la lectura de todo el expediente. De hecho, .opensimplemente devuelve un objeto de archivo y el nombre de archivo en caso de éxito. Además, los documentos dicen:

abrir (archivo, modo = "r")

Abre e identifica el archivo de imagen dado.

Esta es una operación perezosa; esta función identifica el archivo, pero los datos de la imagen real no se leen del archivo hasta que intente procesar los datos (o llamar al método de carga ).

Profundizando, vemos que las .openllamadas _openson una sobrecarga específica del formato de imagen. Cada una de las implementaciones _opense puede encontrar en un nuevo archivo, por ejemplo. Los archivos .jpeg están en formato JpegImagePlugin.py. Veamos eso en profundidad.

Aquí las cosas parecen ponerse un poco complicadas, hay un bucle infinito que se rompe cuando se encuentra el marcador 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")

Lo que parece que podría leer todo el archivo si estuviera mal formado. Sin embargo, si lee bien el marcador de información, debería aparecer temprano. La función determina en handlerúltima instancia self.sizecuáles son las dimensiones de la imagen.

Enganchado
fuente
1
Es cierto, pero ¿ openobtiene el tamaño de la imagen o también es una operación perezosa? Y si es perezoso, ¿lee los datos de la imagen al mismo tiempo?
Mark Ransom
El enlace del documento apunta a Pillow, un tenedor de PIL. Sin embargo, no puedo encontrar un enlace de documento oficial en la web. Si alguien lo publica como comentario, actualizaré la respuesta. La cotización se puede encontrar en el archivo Docs/PIL.Image.html.
Enganchado el
@MarkRansom He intentado responder a su pregunta, sin embargo, para estar 100% seguro, parece que tenemos que profundizar en cada implementación específica de la imagen. El .jpegformato parece correcto siempre que se encuentre el encabezado.
Enganchado el
@Hooked: Muchas gracias por investigar esto. Acepto que tiene razón, aunque me gusta bastante la solución mínima de Paulo a continuación (aunque, para ser justos, el OP no mencionó que quería evitar la dependencia de PIL)
Alex Flint
@AlexFlint No hay problema, siempre es divertido hurgar en el código. Sin embargo, diría que Paulo se ganó su recompensa, es un buen fragmento que escribió para ti allí.
Enganchado el
88

Si no le importa el contenido de la imagen, PIL probablemente sea una exageración.

Sugiero analizar la salida del módulo mágico de 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')

Se trata de un contenedor de libmagic que lee la menor cantidad de bytes posible para identificar una firma de tipo de archivo.

Versión relevante del script:

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

[actualizar]

Hmmm, desafortunadamente, cuando se aplica a jpegs, lo anterior da "'Datos de imagen JPEG, estándar EXIF ​​2.21'". ¡Sin tamaño de imagen! - Alex Flint

Parece que los JPEG son resistentes a la magia. :-)

Puedo ver por qué: para obtener las dimensiones de la imagen para archivos JPEG, es posible que deba leer más bytes de los que le gusta leer a libmagic.

Me arremangué y vino con este fragmento sin probar (obténgalo de GitHub) que no requiere módulos de terceros.

¡Mira, mamá!  ¡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

[actualización 2019]

Consulte una implementación de Rust: https://github.com/scardine/imsz

Paulo Scardine
fuente
3
También agregué la capacidad de recuperar la cantidad de canales (que no debe confundirse con la profundidad de bits) en el comentario después de la versión que @EJEHardenberg proporciona arriba .
Greg Kramida
2
Gran cosa. Agregué soporte para mapas de bits en el proyecto GitHub. ¡Gracias!
Mallard
2
NOTA: la versión actual no me funciona. @PauloScardine tiene una versión de trabajo actualizada en github.com/scardine/image_size
DankMasterDan
2
Obtenga UnicodeDecodeError: 'utf-8' codec can't decode byte 0x89 in position 0: invalid start byteen MacOS, python3 en data = input.read(25), fileen imágenes daPNG image data, 720 x 857, 8-bit/color RGB, non-interlaced
mrgloom
3
Parece que el código de raw.githubusercontent.com/scardine/image_size/master/… funciona.
mrgloom
24

Hay un paquete en pypi llamado imagesizeque actualmente funciona para mí, aunque no parece que esté muy activo.

Instalar en pc:

pip install imagesize

Uso:

import imagesize

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

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

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

Jonathan
fuente
3
Comparé la velocidad imagesize.get, magic.from_file y la imagen PIL para obtener el tamaño real de la imagen por timeit. Los resultados mostraron que la velocidad imagesize.get (0.019s)> PIL (0.104s)> magic con regex (0.1699s).
RyanLiu
9

A menudo busco tamaños de imágenes en Internet. Por supuesto, no puede descargar la imagen y luego cargarla para analizar la información. Consume demasiado tiempo. Mi método es alimentar fragmentos a un contenedor de imágenes y probar si puede analizar la imagen cada vez. Detenga el ciclo cuando obtenga la información que quiero.

Extraje el núcleo de mi código y lo modifiqué para analizar archivos locales.

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)

Salida:

(2240, 1488)
38912

El tamaño real del archivo es 1,543,580 bytes y solo lee 38,912 bytes para obtener el tamaño de la imagen. Espero que esto ayude.

lovetl2002
fuente
1

Otra forma corta de hacerlo en sistemas Unix. Depende de la salida de la fileque no estoy seguro si está estandarizada en todos los sistemas. Esto probablemente no debería usarse en código de producción. Además, la mayoría de los archivos JPEG no informan del tamaño de la imagen.

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

Esta respuesta tiene otra buena resolución, pero falta el formato pgm . Esta respuesta ha resuelto el pgm . Y agrego el bmp .

Los códigos están debajo

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
fuente
imghdrsin embargo, maneja ciertos jpegs bastante mal.
martixy