Python
Windows
画像処理
python3
Windows10

ImageHashで同じ画像を持つフォルダを検出する

More than 1 year has passed since last update.

目的

スクレイプ先が同一ソースから記事を作成している場合、タイトルが違う同じ画像をダウンロードしてしまうことがある.
タイトルが違うが中身が全く同じ画像フォルダを検出し,削除したい.

使用環境

windows10
Anaconda python3.6.1
jupyter notebook

参考url

Python の類似画像ライブラリ ImageHash を Windows で使う

ImageHashとは

画像の情報をハッシュ化する際に、 画像の大きさや微妙な違いには目を瞑って同じような画像は同じダイジェスト値、似たような画像は似たようなダイジェスト値を得たい場合に用いられる類似画像ライブラリ.
画像の拡張子やサイズによらず類似度を判定してくれる.

モジュールのインストール

Anacondaの場合ImageHashのみのインストールで終了する.

py
pip install numpy
pip install scipy
pip install Pillow
pip install PyWavelets
pip install ImageHash

コード

compimages.py
from PIL import Image,ImageFile
import imagehash,os
from glob import glob
#サイズの大きな画像をスキップしない
ImageFile.LOAD_TRUNCATED_IMAGES = True

#2つの画像のハッシュ値の差分を出力
def d_hash(img,otherimg):
    hash = imagehash.phash(Image.open(img))
    other_hash = imagehash.phash(Image.open(otherimg))
    return hash-other_hash
#画像サイズの小さい方を検出する
def minhash(img,otherimg):
    hash_size = Image.open(img).size
    otherhash_size = Image.open(otherimg).size
    if hash_size<otherhash_size: return 0
    else: return 1

#作業フォルダを指定
directory_dir = r'C:\Users\hogehoge\images'
#フォルダリスト、フォルダパスを取得
folder_list = os.listdir(directory_dir)
folder_dir = [os.path.join(directory_dir,i) for i in folder_list if len(os.listdir(os.path.join(directory_dir,i))) >2 ]

#画像リスト、パスを取得
img_list = [os.listdir(i) for i in folder_dir]
img_list_count = [ len( i ) for i in img_list ]
#二重内包表記でフォルダごとの画像リストを作る
img_dir = [ [ os.path.join(dir,list[i]) for i in range(count) if list[i] in 'jpg' or 'png']  for (count,dir,list) in zip(img_list_count, folder_dir, img_list) ]



i = 0
length = len(img_dir)
delete_file = []

#d_hash(),minhash()でフォルダごとの画像を比較
while i < length:
    #進捗状況
    print('i = ',i+'/'+length)
    for j in range(i+1,length):
        #breakへのフラグ
        switch = 0
        for k in img_dir[j]:
            #ハッシュ値の差分が10以下なら同一の画像として認識
            if d_hash(img_dir[i][1],k)<10:
                print(folder_list[i]+' | vs | '+folder_list[j])
                #画像サイズの小さい方のパスをdeleteリストに保存
                if minhash(img_dir[i][1],k) == 0:
                    delete_file.append(folder_dir[i])
                else: delete_file.append(folder_dir[j])
                i += 1
                switch = 1
                break
        if switch != 0:break
    i += 1

#削除したいフォルダパスの表示
print(delete_file)

#続けて削除したい場合
#import shutil
#for i in delete_file:
#   shutil.rmtree(i)

実行結果

最初のフォルダは時間がかかるが,iの増加に伴い比較フォルダ数が漸減するため,処理が半分まで進めば,フォルダごとの画像比較量も半分まで減る.
とはいえ100のフォルダに10枚画像が含まれているとしてループ総数は50500回.
threadingモジュール等で並列処理できるならば今後実装したい.