メモリに画像を読み込まずに画像サイズを取得する


113

次の方法でPILを使用して画像サイズを取得できることを理解しています

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

ただし、画像をメモリにロードせずに画像の幅と高さを取得したいと思います。それは可能ですか?私は画像サイズの統計のみを行っており、画像の内容は気にしません。処理を高速化したいだけです。


8
100%確実ではありません.open()が、ファイル全体をメモリに読み込むとは思わない...(それが.load())です-私の知る限り-これは使用するのと同じくらい良いことですPIL
Jon Clements

5
画像のヘッダー情報のみを読み取る関数があると思っても、ファイルシステムの先読みコードは画像全体をロードする可能性があります。アプリケーションが必要としない限り、パフォーマンスを心配することは非生産的です。
スターク

1
私はあなたの答えを確信しました。@JonClementsに感謝し、明快
Sami A. Haija

9
pmapプロセスが使用するメモリを監視するためにを使用した簡単なメモリテストでは、実際にPILイメージ全体をメモリにロードしていないことがわかります。
Vincent Nivoliers 2013

回答:


63

コメントが暗示するように、PILはを呼び出すときにイメージをメモリにロードしません.open。のドキュメントを見るとPIL 1.1.7、のdocstringは.open次のように言っています。

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

ソースには次のようないくつかのファイル操作があります。

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

しかし、これらはファイル全体の読み取りを構成することはほとんどありません。実際.open、成功した場合は、単にファイルオブジェクトとファイル名を返します。さらに、ドキュメントは言う:

open(file、mode =” r”)

指定された画像ファイルを開いて識別します。

これは遅延操作です。この関数はファイルを識別しますが、実際の画像データは、データを処理する(またはloadメソッドを呼び出す)までファイルから読み取られません。

さらに掘り下げてみると、画像形式固有のオーバーロードである.open呼び出し_openが見られます。への各実装_openは、新しいファイルにあります。.jpegファイルはにありJpegImagePlugin.pyます。それを詳しく見てみましょう。

ここでは、少しトリッキーになるようですが、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")

不正な形式の場合、ファイル全体を読み取ることができるようです。ただし、情報マーカーが正常に読み取れる場合は、早期に発生するはずです。この関数は、handler最終的にself.size画像の寸法を設定します。


1
十分本当ですが、画像openサイズを取得しますか、それとも遅延操作ですか?そして、それが怠惰な場合、それは同時に画像データを読み取りますか?
Mark Ransom 2013

docリンクは、PILからのフォークをPillowにポイントします。しかし、ウェブ上に公式のドキュメントリンクが見つかりません。誰かがコメントとして投稿した場合、私は答えを更新します。見積もりはファイルにありますDocs/PIL.Image.html
2013

@MarkRansom私はあなたの質問に答えようとしましたが、100%確実にするには、各画像固有の実装に飛び込む必要があるように見えます。.jpegヘッダが発見されたようなフォーマットは限りOKに見えます。
夢中

@フック:これを調べてくれてありがとう。以下のパウロの最小限の解決策はかなり気に入っていますが、私はあなたが正しいことを受け入れます(公平を期すために、OPはPILの依存関係を避けたいとは触れていません)
Alex Flint

@AlexFlint問題ありません。コードをあちこち探るのはいつも楽しいです。パウロは賞金を稼いだと思いますが、それは彼があなたのために書いた素晴らしいスニペットです。
2013

88

画像の内容を気にしないのであれば、PILは多すぎるでしょう。

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

これはlibmagicのラッパーであり、ファイルタイプシグネチャを識別するために可能な限り少ないバイト数を読み取ります。

スクリプトの関連バージョン:

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

[更新]

残念ながら、jpegに適用すると、上記は「 'JPEG画像データ、EXIF規格2.21'」になります。画像サイズなし!–アレックスフリント

jpegは魔法耐性があるようです。:-)

理由はわかります。JPEGファイルの画像のサイズを取得するには、libmagicが読み取るよりも多くのバイトを読み取る必要がある場合があります。

私の袖をまくり上げて、サードパーティのモジュールを必要としない非常にテストされていないスニペット(GitHubから入手)が付属しています。

ほら、マ! デップス!

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

[2019年更新]

Rust実装を確認してください:https : //github.com/scardine/imsz


3
また、@ EJEHardenbergが上記で提供しているバージョンのコメントにチャネル数(ビット深度と混同しないでください)を取得する機能を追加しました。
Greg Kramida 14年

2
すばらしいことです。GitHubプロジェクトでビットマップのサポートを追加しました。ありがとう!
マガモ

2
注:現在のバージョンでは動作しません。@PauloScardineはgithub.com/scardine/image_sizeで更新された作業バージョンを持っています
DankMasterDan

2
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x89 in position 0: invalid start byteMacOS、python3 on data = input.read(25)file画像の取得PNG image data, 720 x 857, 8-bit/color RGB, non-interlaced
mrgloom '21 / 11/18


24

pypiと呼ばれるパッケージがありimagesize、現在私には問題ありませんが、あまりアクティブではないようです。

インストール:

pip install imagesize

使用法:

import imagesize

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

ホームページ:https : //github.com/shibukawa/imagesize_py

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


3
速度imagesize.get、magic.from_file、PIL画像を比較して、timeit別の実際の画像サイズを取得しました。結果はスピードimagesize.get(0.019s)> PIL(0.104s)>正規表現によるマジック(0.1699s)を示しました。
RyanLiu

9

私はインターネットで画像サイズを取得することがよくあります。もちろん、画像をダウンロードしてから読み込み、情報を解析することはできません。時間がかかりすぎます。私の方法は、チャンクを画像コンテナーにフィードし、毎回画像を解析できるかどうかをテストすることです。必要な情報が得られたらループを停止します。

コードのコアを抽出し、ローカルファイルを解析するように変更しました。

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)

出力:

(2240, 1488)
38912

実際のファイルサイズは1,543,580バイトで、画像サイズを取得するには38,912バイトのみを読み取ります。これがお役に立てば幸いです。


1

Unixシステムでそれを行うもう1つの短い方法。fileすべてのシステムで標準化されているかどうかは不明ですが、出力によって異なります。これはおそらくプロダクションコードでは使用しないでください。さらに、ほとんどのJPEGは画像サイズを報告しません。

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

この回答には別の良い解決策がありますが、pgm形式がありません。この回答pgmを解決しました。そして、私はbmpを追加します。

コードは以下です

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

imghdrただし、特定のJPEGの処理はかなり不十分です。
martixy
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.