【Python】画像の破損をチェックするコード例【Pillow】

JPEG, PNG, GIF などの破損をチェックする Python コード例を書きました。

画像は、『Pillowピロー (PIL) ライブラリ』でチェックすることができました。

画像形式によっては、破損検出に使える仕組みが無かったりもしたので、簡易的なチェックになります。

それでも、今まで気づかなかった破損画像を、いくつか見つけることができました。

破損画像のチェック方法

画像の破損を調べるところの Python コードです。

画像が壊れていた時は、Image.open()im.verify() のところで、例外れいがいが発生しました。

実際には、例外を try ぶんでキャッチして、画面に表示したり、ログファイルに記録したりして活用します。

画像ファイルの場合

ファイルをバイナリーモードで開いてから、Pillow ライブラリで読み込みました。

from pathlib import Path
from PIL import Image # Pillow ライブラリ

# 画像ファイルをバイナリーモードで開きます。
image_file = Path(r'(画像ファイルのパス)')
with image_file.open('rb') as f:

    # PIL で画像データを開きます。
    # PIL が『画像形式を判定できなかった場合』は、
    # UnidentifiedImageError の例外が発生しました。
    im = Image.open(f, 'r')

    # 画像データの破損をチェックします。
    # ベリファイに失敗した場合は、
    # SyntaxError などの例外が発生しました。
    im.verify()

バイナリデータの場合

まず、変数に入っている画像のバイナリーデータを、バイナリーストリームに変換します。

それを Pillow ライブラリで読み込みました。

この方法は、zip ファイルの中にあった画像を調べるときに使用しました。

Python の zipfile ライブラリを使用したら、画像ファイルのバイナリーデータが返ってきたんですよね。

それで必要になりました。

from io import BytesIO
from PIL import Image # Pillow ライブラリ

# 画像のバイナリーデータ image_data を、
# 『バイナリーストリーム』に変換します。
with BytesIO(image_data) as buffer:

    # PIL で画像データを開きます。
    # PIL が『画像形式を判定できなかった場合』は、
    # UnidentifiedImageError の例外が発生しました。
    im = Image.open(buffer, 'r')

    # 画像データの破損をチェックします。
    # ベリファイに失敗した場合は、
    # SyntaxError などの例外が発生しました。
    im.verify()

Pillow マニュアルの場所

Pillowピロー ライブラリは、画像データをチェックするために使用しました。

(Pillow) Windows Installation 『Pillow』のインストール方法。

PIL.Image.open(fp, mode='r', formats=None) 画像ファイルを開く。

Image.verify() 画像の内容をチェックする。

Python マニュアルの場所

class io.BytesIO([initial_bytes]) バイツアイオー(バイツイオ)。

class pathlib.Path(*pathsegments) パスオブジェクトを作成。

Path.glob(pattern) ファイルを列挙する。

Path.is_file() パスがファイルかいなかを判定する。

PurePath.suffix パスから拡張子の部分を取得。

re.match(pattern, string, flags=0) 正規表現で拡張子を判定するときに使用。

re.IGNORECASE 正規表現で大文字と小文字を『区別しない』ようにするためのフラグ。

Path.open(mode='r', buffering=-1, encoding=None, errors=None, newline=None) ファイルを開く。

エフ f から始まる文字列 f'' は、『エフストリング (f-string)』と呼ばれるものでした。

(f-string) フォーマット済み文字列リテラル

getattr(object, name[, default]) ゲットアトリビュート関数。object から属性 (Attribute) を取得する。

traceback.format_exception_only(etype, value) フォーマット エクセプション オンリー。エラーメッセージの最後の行だけを取得。

str.rstrip([chars]) 末尾から指定した文字を除去。

print(*objects, sep=' ', end='\n', file=sys.stdout, flush=False) 普通のプリント関数。

instance.__class__ 特殊属性

definition.__name__ 特殊属性

コード例

『フォルダの中の画像ファイル』を列挙して、画像データをチェックしていく Python コード例です。

"""
画像データ (jpg, png, gif など) の
破損をチェックする Python コード例。
check_image_files.py
"""

from pathlib import Path
import re
from traceback import format_exception_only
from PIL import Image # Pillow ライブラリ


def main():
    """メイン関数"""
    print('start')

    # (1/4) 画像ファイルの場所を決めます。
    src_dir = Path(r'F:\project\sample_images')
    print(f'(src_dir) {src_dir}')

    # (2/4) フォルダの中のファイルを列挙します。
    for p in src_dir.glob('*'):
        # ファイルでなければ、スキップします。
        if not p.is_file():
            continue

        # 拡張子が画像でなければ、スキップします。
        # p.suffix: パスの拡張子の部分。
        # re.IGNORECASE: 大文字小文字を無視。
        if not re.match(r'^\.(?:jpg|png|gif)$', p.suffix, re.IGNORECASE):
            continue

        # (3/4) 画像ファイルを開きます。
        with p.open('rb') as f:

            # (4/4) 画像データをチェックします。
            try:
                # PIL で画像データを開きます。
                # PIL が『画像形式を判定できなかった場合』は、
                # UnidentifiedImageError の例外が発生しました。
                im = Image.open(f, 'r')

                # 画像データの破損をチェックします。
                # ベリファイに失敗した場合は、
                # SyntaxError などの例外が発生しました。
                im.verify()
            except Exception as e:
                # 何らかのエラーがあった。
                print(f'NG {p.name} {e.__class__.__name__}')

                # e に msg 属性があれば取得、無ければ None にしておきました。
                msg = getattr(e, 'msg', None)
                print(f'(message) {msg}')

                # トレースバックの最後の行だけを取得してみました。
                ex_text = format_exception_only(type(e), e)[0].rstrip('\n')
                print(f'(exception) {ex_text}')
            else:
                # エラーは検出できなかった。
                print(f'ok {p.name}')

    print('end')
    return


if __name__ == '__main__':
    main()

実行結果

実行結果です。

画像は Windows 10 の『ペイント』で保存したものを使用しました。

NG 判定になっている画像は、バイナリーエディタを使用して、データをわざと書き換えたものです。

ヘッダー部分を書き換えたり、末尾の部分を削ったりしてみました。

start
(src_dir) F:\project\sample_images
ok image_g1.gif
NG image_g2.gif UnidentifiedImageError
(message) None
(exception) PIL.UnidentifiedImageError: cannot identify image file
 <_io.BufferedReader name='F:\\project\\sample_images\\image_g2.gif'>
ok image_j1.jpg
NG image_j2.jpg UnidentifiedImageError
(message) None
(exception) PIL.UnidentifiedImageError: cannot identify image file
 <_io.BufferedReader name='F:\\project\\sample_images\\image_j2.jpg'>
ok image_p1.png
NG image_p2.png SyntaxError
(message) broken PNG file (chunk b'')
(exception)   File "<string>", line None
NG image_p3.png SyntaxError
(message) broken PNG file (bad header checksum in b'IDAT')
(exception)   File "<string>", line None
end

画像データの破損は時々ありました

画像に限らず、データの破損に気が付くことって、時々あるんですよね。

原因としては、その時々の状況からして、SD カードや USB 機器の調子が悪かったり、突然の停電であったり、Web からのダウンロードが中途半端で完了していたり、といった感じでした。

zip ファイルで圧縮しておけば、セブンジップ 7-Zip (7z) というソフトで、簡単に破損チェックをすることができたんですけれどもね。

zip ファイルの場合は、ファイルの一つ一つに CRC32 というチェックサムが付いていたので、そういったことが可能になるようでした。

実際に、バイナリエディタで適当に1箇所だけ、わざとゼロに書き換えてみました。

そして、7-Zip のアーカイブテストを実行したら、該当するファイルのところで『CRC が違います』と教えてくれました。

ですが、の状態の画像ファイルには、そういった仕組みがあまり無いようでした(画像形式による感じ)。

なので、『何とかチェックできないかな?』と思って、破損チェックのコードを書いてみました。

画像形式によっては、検知できなかった破損も多々ありましたが、検知できた破損もありました。

Python 用の Pillow ライブラリは便利でしたね。

バイナリエディタを使って、部分的に書き換えたり、データを削ってみたりした画像をチェックしてみました。

検知できた破損画像の例です。

  • 画像データの先頭付近にある『マジックナンバーとかシグネチャとか呼ばれている領域』が壊れていたケース。
  • 画像の『ヘッダー領域』が壊れていたケース。
  • PNG 画像で、データの後半が壊れていたケース。

このような感じでした。

しかしながら、表示は乱れたけれど『読み込み自体は出来てしまった』というケースが、多々ありました。

それでも、破損部分によっては、検出が可能でした。

簡易的なチェックツールとしては十分でしたね。

試しに、データフォルダの中身をまとめてチェックしてみたところ、今まで気づいていなかった破損画像が、いくつか見つかりました。

実際に画像を開いてみたら、途中からグレーや黒一色になっていたり、開くこと自体ができなかったりしました。

壊れたデータを持っていても意味がないので、サクッと削除しました。

スッキリです。

スポンサーリンク
ブログ
シェアする(押すとSNS投稿用の『編集ページ』に移動します)
フォローする(RSSフィードに移動します)
スポンサーリンク
シラベルノート
タイトルとURLをコピーしました