Obtenha o tamanho da imagem SEM carregar a imagem na memória

113

Eu entendo que você pode obter o tamanho da imagem usando PIL da seguinte maneira

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

No entanto, gostaria de obter a largura e altura da imagem sem ter que carregar a imagem na memória. Isso é possível? Estou apenas fazendo estatísticas sobre o tamanho das imagens e não me importo com o conteúdo das imagens. Só quero tornar meu processamento mais rápido.

Sami A. Haija
fonte
8
Não tenho 100% de certeza, mas não acredito que isso .open()leia todo o arquivo na memória ... (isso é o que .load()) faz - pelo que eu sei - isso é o melhor que posso usarPIL
Jon Clements
5
Mesmo se você achar que tem uma função que apenas lê as informações do cabeçalho da imagem, o código de leitura à frente do sistema de arquivos ainda pode carregar a imagem inteira. A preocupação com o desempenho é improdutiva, a menos que seu aplicativo exija.
Stark
1
Fiquei convencido de suas respostas. Obrigado @JonClements e stark
Sami A. Haija
9
Um teste rápido de memória usando pmappara monitorar a memória usada por um processo me mostra que de fato PILnão carrega a imagem inteira na memória.
Vincent Nivoliers
Veja também: Obtenha as dimensões da imagem com Python
Martin Thoma

Respostas:

63

Como os comentários aludem, PIL não carrega a imagem na memória ao chamar .open. Olhando para a documentação de PIL 1.1.7, a docstring para .opendiz:

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

Existem algumas operações de arquivo na fonte, como:

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

mas isso dificilmente constitui a leitura de todo o arquivo. Na verdade, .opensimplesmente retorna um objeto de arquivo e o nome do arquivo em caso de sucesso. Além disso, os documentos dizem:

aberto (arquivo, modo = ”r”)

Abre e identifica o arquivo de imagem fornecido.

Esta é uma operação preguiçosa; esta função identifica o arquivo, mas os dados da imagem real não são lidos do arquivo até que você tente processar os dados (ou chamar o método de carregamento ).

Indo mais fundo, vemos que as .openchamadas _opensão uma sobrecarga específica do formato de imagem. Cada uma das implementações _openpode ser encontrada em um novo arquivo, por exemplo. Arquivos .jpeg estão em JpegImagePlugin.py. Vamos dar uma olhada nisso em profundidade.

Aqui as coisas parecem ficar um pouco complicadas, nele há um loop infinito que é interrompido quando o marcador jpeg é encontrado:

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

Parece que ele poderia ler todo o arquivo se estivesse malformado. Se ler o marcador de informações OK, no entanto, ele deve sair antes. A função, em handlerúltima análise, define self.sizequais são as dimensões da imagem.

Fisgado
fonte
1
É verdade, mas openobtém o tamanho da imagem ou é uma operação preguiçosa também? E se for preguiçoso, ele lê os dados da imagem ao mesmo tempo?
Mark Ransom
O link do documento aponta para Descansar um garfo de PIL. No entanto, não consigo encontrar um link de documento oficial na web. Se alguém postar como um comentário, atualizarei a resposta. A citação pode ser encontrada no arquivo Docs/PIL.Image.html.
Hooked
@MarkRansom Tentei responder à sua pergunta, no entanto, para ter 100% de certeza, parece que temos que mergulhar em cada implementação específica de imagem. O .jpegformato parece OK, desde que o cabeçalho seja encontrado.
Hooked
@Hooked: Muito obrigado por investigar isso. Aceito que você está correto, embora goste da solução mínima de Paulo abaixo (embora, para ser justo, o OP não mencionou o desejo de evitar a dependência do PIL)
Alex Flint
@AlexFlint Não tem problema, é sempre divertido mexer no código. Eu diria que Paulo ganhou sua recompensa, é um trecho legal que ele escreveu para você lá.
Hooked
88

Se você não se preocupa com o conteúdo da imagem, PIL é provavelmente um exagero.

Sugiro analisar a saída do módulo mágico do 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')

Este é um wrapper para libmagic que lê o mínimo de bytes possível para identificar uma assinatura de tipo de arquivo.

Versão relevante do script:

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

[atualizar]

Hmmm, infelizmente, quando aplicado a jpegs, o texto acima fornece "'Dados de imagem JPEG, padrão EXIF ​​2.21'". Sem tamanho de imagem! - Alex Flint

Parece que os jpegs são resistentes à magia. :-)

Eu posso ver o porquê: para obter as dimensões da imagem para arquivos JPEG, você pode ter que ler mais bytes do que o libmagic gosta de ler.

Arregacei as mangas e vim com este snippet não testado (pegue no GitHub) que não requer módulos de terceiros.

Olha, mãe!  Sem dependências!

#-------------------------------------------------------------------------------
# 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

[atualização 2019]

Confira uma implementação do Rust: https://github.com/scardine/imsz

Paulo Scardine
fonte
3
Eu também adicionei a capacidade de recuperar o número de canais (não deve ser confundido com a profundidade de bits) no comentário após a versão que @EJEHardenberg fornece acima .
Greg Kramida
2
Grande coisa. Eu adicionei suporte para bitmaps no projeto GitHub. Obrigado!
Mallard
2
NOTA: a versão atual não funciona para mim. @PauloScardine tem uma versão de trabalho atualizada em github.com/scardine/image_size
DankMasterDan
2
Obtenha UnicodeDecodeError: 'utf-8' codec can't decode byte 0x89 in position 0: invalid start byteno MacOS, python3 ativado data = input.read(25), filena imagem dáPNG image data, 720 x 857, 8-bit/color RGB, non-interlaced
mrgloom
3
Parece o código de raw.githubusercontent.com/scardine/image_size/master/… funciona.
mrgloom
24

Existe um pacote em pypi chamado imagesizeque atualmente funciona para mim, embora não pareça muito ativo.

Instalar:

pip install imagesize

Uso:

import imagesize

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

Página inicial: https://github.com/shibukawa/imagesize_py

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

Jonathan
fonte
3
Eu comparei a velocidade da imagem imagesize.get, magic.from_file e PIL para obter o tamanho real da imagem por timeit. Os resultados mostraram que a velocidade imagesize.get (0,019s)> PIL (0,104s)> mágica com regex (0,1699s).
RyanLiu
9

Costumo buscar tamanhos de imagem na Internet. Claro, você não pode baixar a imagem e carregá-la para analisar as informações. É muito demorado. Meu método é alimentar pedaços para um contêiner de imagem e testar se ele pode analisar a imagem todas as vezes. Pare o loop quando eu obtiver as informações que desejo.

Extraí o núcleo do meu código e o modifiquei para analisar arquivos locais.

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)

Resultado:

(2240, 1488)
38912

O tamanho real do arquivo é 1.543.580 bytes e você só lê 38.912 bytes para obter o tamanho da imagem. Espero que isso ajude.

lovetl2002
fonte
1

Outra maneira curta de fazer isso em sistemas Unix. Depende da saída da filequal não tenho certeza se é padronizada em todos os sistemas. Isso provavelmente não deve ser usado em código de produção. Além disso, a maioria dos JPEGs não informa o tamanho da imagem.

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

Esta resposta tem outra boa resolução, mas faltando o formato pgm . Esta resposta resolveu o pgm . E eu adiciono o bmp .

Códigos está abaixo

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
fonte
imghdrno entanto, lida muito mal com certos JPEGs.
martixy de