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


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 alto de la imagen sin tener que cargar la imagen en memoria. Es eso posible? Solo estoy haciendo estadísticas sobre tamaños de imagen y no me importa el contenido de la imagen. Sólo quiero que mi procesamiento sea más rápido.

Author: Martin Thoma, 2013-04-04

6 answers

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

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 archivo. De hecho, .open simplemente devuelve un objeto de archivo y el nombre de archivo en caso de éxito. Además, los documentos dicen:

Open (file, mode="r")

Abre e identifica la archivo de imagen.

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

Cavando más profundo, vemos que .open llama a _open que es una sobrecarga específica del formato de imagen. Cada una de las implementaciones de _open se puede encontrar en un nuevo archivo, por ejemplo. .los archivos jpeg están en JpegImagePlugin.py. Veamos eso en profundidad.

Aquí las cosas parecen ponerse un poco complicado, en él 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")

Como puede leer todo el archivo si era incorrecto. Sin embargo, si lee el marcador de información OK, debería salir temprano. La función handler finalmente establece self.size cuáles son las dimensiones de la imagen.

 42
Author: Hooked,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-09-26 18:14:17

Si no te importa el contenido de la imagen, PIL es probablemente un exceso.

Sugiero analizar la salida del módulo python magic:

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

Esta es una envoltura alrededor de libmagic que lee tan pocos bytes como sea posible para identificar una firma de tipo de archivo.

[actualización]

Hmmm, desafortunadamente, cuando se aplica a jpegs, lo anterior da "'JPEG image data, EXIF standard 2.21'". Sin tamaño de imagen! - Alex Flint

Parece que los jpeg son resistente a la magia. :-)

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

Me subí la manga y vino con este fragmento muy no probado (consíguelo de GitHub) que no requiere módulos de terceros.

Mira, Ma! 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
 58
Author: Paulo Scardine,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-12-07 01:26:52

A menudo busco tamaños de imagen en Internet. Por supuesto, no puede descargar la imagen y luego cargarla para analizar la información. Lleva demasiado tiempo. Mi método es alimentar trozos a un contenedor de imágenes y probar si puede analizar la imagen cada vez. Detén el bucle cuando tenga 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 de 1,543,580 bytes y solo lee 38.912 bytes para obtener el tamaño de la imagen. Espero que esto ayude.

 1
Author: user2923419,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-02-04 19:11:18

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

import subprocess, re
image_size = list(map(int, re.findall('(\d+)x(\d+)', subprocess.getoutput("file " + filename))[-1]))
 1
Author: Lenar Hoyt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-13 09:57:51

Esta respuesta tiene otra buena resolución, pero falta el formato pgm. Este respuesta ha resuelto el pgm. Y añado el bmp .

Los códigos están debajo de

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
 0
Author: Shawn Xie,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:18:14

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

Instalar:

pip install imagesize

Uso:

import imagesize

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

Página de inicio: https://github.com/shibukawa/imagesize_py

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

 0
Author: Jonathan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-09-13 20:18:58