あなたは大量の写真ファイルを持っていて、その中に何枚も同じ写真がノイズのように含まれていて困っている、そんな状況ではないでしょうか?あなた自身がファイルをコピーしたのかもしれませんし、写真管理アプリが勝手に解像度などを変えて複製したのかもしれません。
本記事では、そのような「重複する写真(複製写真)」を特定する手法を提案し、またその手法を実装したPythonスクリプトをご紹介しています。
本記事の手法によって複製写真を特定し、そのリストに基づいて写真データを削除した場合に、いかなる問題が生じたとしても筆者は責任を負いません。ファイル削除などの不可逆的な措置を講じる前に、特定した複製写真リストを手動で確認し、確かにオリジナル写真のコピーであることを視覚的に確認することを強く推奨します。
1. 3ステップの手法
基本的な方針としては、写真ファイルを2つずつ開いて、写真の内容が一致しているかどうかで重複を判定していきます。ただし、N枚の写真ファイルについてこの1対1の比較を繰り返すとすると、全部で N×(N+1)÷2 回(総当たり)の比較をする必要があり、写真ファイルの総数Nが大きくなるほど、2乗のオーダーで比較回数が増大していきます。
N枚の写真ファイルのうち、このグループは重複写真群の可能性がある、というN’枚の小グループをまず見つける(当たりをつける)ことができれば、総比較回数は Σ{N’×(N’+1)÷2} (小グループごとの総当たりの総計)となり、総処理時間を格段に短くすることができます。本記事で提案する手法では、「写真ファイルのファイル名」に基づいて小グループを作ります。
また、グループ内の写真を比較することで重複写真を特定できたあと、そのうちのどれがオリジナル写真で、どれがオリジナル写真でない(複製写真である)かを特定するために、ファイルの撮影日時情報を用います。本記事で提案する手法では、撮影日時が最も古いファイルを、オリジナル写真と見なすこととします。
まとめると、本記事で提案する手法は、大きく以下の3ステップを踏みます。
- 重複写真候補をグルーピング(ファイル名に基づく)
- 一対比較を繰り返して重複写真を特定
- 重複写真の中からオリジナル写真/複製写真を特定(写真ファイルの撮影日時情報に基づく)

2. 結論:Pythonスクリプトを実装しました。
写真ファイル群から重複写真および複製写真を特定するPythonスクリプトを実装し、以下のGitリポジトリにまとめました。実行方法は以下のリポジトリのREADME-ja.mdを参照してください。
このGitリポジトリは2つのPythonスクリプトを提供していて、前章「1. 3ステップの手法」に記載した3ステップの手法に対して、以下の通り対応しています。
- 重複写真候補をグルーピング:
group_file_paths_list_by_its_name.py - 一対比較を繰り返して重複写真を特定:
find_duplicated_images.py - 重複写真の中からオリジナル写真/複製写真を特定:
find_duplicated_images.py
筆者本スクリプトにより特定できた複製写真を削除する場合は、ぜひ以下の記事で紹介しているスクリプト ”move_target_files_into_a_folder.py” を活用ください!複製写真をまとめて一時フォルダに移動でき、最終確認しながら削除することができます。


3. 手法解説
3.1. ステップ①:重複写真候補をグルーピング
筆者の手元にある重複写真を眺めたときに、複製写真のファイル名が、多くの場合「オリジナル (1).jpg」や「オリジナル_1024.png」のように、オリジナル写真のファイル名(拡張子を除く)の末尾に文字列が追加されていることに気づきました。
よって、ある写真の拡張子無しファイル名が、別の写真の拡張子無しファイル名に部分的に含まれている場合、その2つの写真は重複写真の候補と見なせそうです。
実装としては、対象となる全ファイルの拡張子無しファイル名の一覧を作成し、ファイル名の長さが短い順に1つずつ処理します。一覧のうち、処理中のファイル名を名前に含む全てのファイルについて(処理中のファイル自体も含む)、処理中のファイルと同名のグループに割り当てます。その後、より長いファイル名のグループに割り当てられた場合は、グループが上書きされます。
ファイル名が極端に短い場合(e.g. a.jpgなど)、意図せず関係のないファイルをグループにまとめてしまう(e.g. cat.pngも同じグループaになる)ので、ファイル名の最低長を指定して、それ未満の長さはグループ名として扱わないように実装しています。
たとえば、以下のような連番のファイルがあった場合、hoge_1.txtとhoge_10.txtのみグループhoge_1に分類され、それ以外はグループhogeに分類されるので注意が必要です。実用上は、このようなケースにおいてhoge_1.txtからhoge_10.txtが全て重複写真である可能性は低いと思われますが、グルーピング後は念のため、意図しないグルーピングがないかどうかチェックをしてください。
hoge.txt
hoge_1.txt
︙
hoge_9.txt
hoge_10.txt3.2. ステップ②:一対比較を繰り返して重複写真を特定
同じグループ内で総当たりで写真の内容を比較し、同一の写真と判定された写真群を重複写真とします。具体的には、2つの写真のpHash値の差分がしきい値以下の写真を同一と見なし、それ以外を異なる写真と判定しています。その際、回転とリサイズを施した上でpHash値を算出することで、回転・リサイズされた複製写真にも対応できるようにしています。
処理を軽くする工夫として、比較する2つの写真のアスペクト比が異なる場合は、比較処理をスキップして異なる写真と判定しています。また、UnionFindのアルゴリズムを採用することで、写真Aと写真B、写真Aと写真Cがそれぞれ同一の写真と判定された場合は、写真Bと写真Cの比較処理をスキップして同一の写真と判定するようにしています。
3.3. ステップ③:重複写真の中からオリジナル写真/複製写真を特定
ファイルパスとともに与えられた写真の撮影日時の情報から、重複写真のうち最古でないファイルを複製写真としてマークします。同一の時刻の最古のファイルが複数ある場合は、入力されたCSVファイル上で最も上に記載されているファイルがオリジナル写真と判定されるので、ご注意ください。
写真・動画のファイルパス一覧から、EXIFなどのメタデータを読み込んで撮影日時情報の一覧を取得する方法は、以下の記事を参照ください。





本記事は以上です。ここまで読んでいただき、ありがとうございました!







