web-dev-qa-db-fra.com

Obtenir la taille de l'image SANS chargement de l'image en mémoire

Je comprends que vous pouvez obtenir la taille de l'image à l'aide de PIL de la manière suivante

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

Cependant, je voudrais obtenir la largeur et la hauteur de l'image sans avoir à charger l'image en mémoire. Est-ce possible? Je ne fais que des statistiques sur les tailles d'image et ne me soucie pas du contenu de l'image. Je veux juste accélérer mon traitement.

93
Sami A. Haija

Comme l'indiquent les commentaires, PIL ne charge pas l'image en mémoire lors de l'appel de .open. En regardant les docs de PIL 1.1.7, la docstring pour .open dit:

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

Il y a quelques opérations de fichier dans le source comme:

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

mais ceux-ci constituent à peine la lecture du fichier entier. En réalité .open renvoie simplement un objet fichier et le nom du fichier en cas de succès. De plus, les docs disent:

ouvert (fichier, mode = "r")

Ouvre et identifie le fichier image donné.

C'est une opération paresseuse; cette fonction identifie le fichier, mais les données d'image réelles ne sont pas lues à partir du fichier tant que vous n'avez pas essayé de traiter les données (ni appelé la méthode load ).

En creusant plus profond, on voit que .open appels _open qui est une surcharge spécifique au format d’image. Chacune des implémentations à _open peut être trouvé dans un nouveau fichier, par exemple. Les fichiers .jpeg sont en JpegImagePlugin.py. Regardons cela en profondeur.

Ici, les choses semblent devenir un peu délicates, il y a une boucle infinie qui se casse lorsque le marqueur jpeg est trouvé:

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

Ce qui en ressemble pourrait lire tout le fichier s'il était mal formé. Cependant, si le marqueur d’information est OK, il devrait se déclencher tôt. La fonction handler définit finalement self.size qui sont les dimensions de l'image.

55
Hooked

Si vous ne vous souciez pas du contenu de l'image, le PIL est probablement un peu exagéré.

Je suggère d'analyser la sortie du module magique 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')

C'est une enveloppe autour de libmagic qui lit le moins d'octets possible afin d'identifier une signature de type de fichier.

Version pertinente du script:

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

[mise à jour]

Hmmm, malheureusement, lorsqu’il est appliqué à des jpegs, le texte ci-dessus donne "" Données d’image JPEG, norme EXIF ​​2.21 "". Aucune taille d'image! - Alex Flint

On dirait que les jpeg sont résistants à la magie. :-)

Je peux comprendre pourquoi: pour obtenir les dimensions de l’image pour les fichiers JPEG, il se peut que vous deviez lire plus d’octets que libmagic n’aime en lire.

A retroussé mes manches et est venu avec cet extrait très non testé (récupéré auprès de GitHub) qui ne nécessite aucun module tiers.

Look, 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__.__+ msg)
        else:
            raise UnknownImageFormat(
                "Sorry, don't know how to get information from this file."
            )

    return width, height

[mise à jour 2019]

Découvrez une Rust: https://github.com/scardine/imsz

79
Paulo Scardine

Il y a un paquet sur pypi appelé imagesize qui fonctionne actuellement pour moi, bien qu'il ne semble pas très actif.

Installer:

pip install imagesize

Usage:

import imagesize

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

Page d'accueil: https://github.com/shibukawa/imagesize_py

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

8
Jonathan

Je récupère souvent les tailles d’image sur Internet. Bien sûr, vous ne pouvez pas télécharger l’image, puis la charger pour analyser les informations. Cela prend trop de temps. Ma méthode consiste à insérer des fragments dans un conteneur d'image et à vérifier s'il peut analyser l'image à chaque fois. Arrêtez la boucle lorsque je reçois l'information que je veux.

J'ai extrait le noyau de mon code et l'ai modifié pour analyser les fichiers locaux.

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)

Sortie:

(2240, 1488)
38912

La taille réelle du fichier est de 1 543 580 octets et vous ne lisez que 38 912 octets pour obtenir la taille de l'image. J'espère que cela aidera.

6
user2923419

Une autre façon de le faire sur les systèmes Unix. Cela dépend de la sortie de file dont je ne suis pas sûr est standardisé sur tous les systèmes. Cela ne devrait probablement pas être utilisé dans le code de production. De plus, la plupart des fichiers JPEG ne signalent pas la taille de l'image.

import subprocess, re
image_size = list(map(int, re.findall('(\d+)x(\d+)', subprocess.getoutput("file " + filename))[-1]))
1
Lenar Hoyt

Ce réponse a une autre bonne résolution, mais il manque le format pgm . Ceci réponse a résolu le pgm . Et j'ajoute le bmp .

Codes est ci-dessous

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
Yantao Xie