最近、引越しをしたWebエンジニアの本間です。 引越しの作業は大変面倒でしたが、新しい街に来た時のワクワク感がやっぱりいいなーと感じております。
さて、弊社のサービスである「写真サービス るくみー」では、毎日たくさんの写真をアップロードしていただいているのですが、中には内容がほとんど同じ写真が入ってしまうことがあります。 これらの写真がそのまま販売されてしまうと、写真を選ぶ際に邪魔になったり、間違って複数枚購入してしまうことがあるため、可能な限り避けたい事象です。 「同じ内容」の写真を自動で判別する方法がないか調査していたところ「Perceptual Hash」という手法を見つけました。 Pythonでの画像処理の勉強も兼ねて、今回この手法を紹介してみようと思います。
Perceptual Hashとは
ハッシュ値は、「あるデータをハッシュ関数に入れて得られる値」で「同じデータからは常に同じ値が生成される」特徴があります。 この特徴を生かして、データの改変や破損のチェックに広く使われています。MD5やSHA-1といったアルゴリズムが有名です。
Perceptual Hashはマルチメディア(音声、画像、動画など)データ用のハッシュ値の1つなのですが、上記に加え、似ていると認識されるデータからは近い値が生成されるという特徴があります。
例えば、以下の3つの画像のmd5とPerceptual Hashの1つであるaHashの値は以下のようになっています。 md5の値は3つのファイルで全然異なるのに対して、aHashはLennaとLenna(Watermark)は値がかなり近いことがわかります。
Title | MD5 | aHash |
---|---|---|
Lenna | 7ebb03cf54391a05d797448202d84a9c | b69cbd89090b8f8e |
Lenna(Watermark) | b703c673e3e8cf015ec8d3d331b41522 | b69c3d89090b0f8e |
Parrots | 6a4519a050adc12f03bcd9e8efbe977d | 81cdcbe3c3c3e0e |
Perceptual Hashの種類
ハッシュ値の計算にMD5やSHA-1といったアルゴリズムがあるように、Perceptual Hashの計算方法もいくつか種類があります。 自分が調査する前に様々な方が調査してくれており、少し検索するだけで十分にリストアップすることができました。 参考にしたサイトは、参考サイト一覧にリストアップしています。
- aHash
- 画像の平均輝度からの差分を使った方法。精度は悪いみたいだが、一番シンプルで実装しやすい。
- pHash
- 画像を離散コサイン変換(DCT)し周波数領域に変換後、低周波領域に対してaHashと同じ方法で算出する方法。精度はよいみたいだが、算出に時間がかかるとのこと。今回実装していない
- dHash
- 隣接領域との差分を使った方法。そこそこの精度と算出が素早い点がよいとのこと。
- wHash
- pHashのDCTの代わりに離散ウェーブレット変換(DWT)を用いたものとのこと。DWTがわかっておりません...。
dHashの計算
Perceptual Hashの理解を深めるため、今回「dHash」を自力で計算してみます。 計算方法は、こちらのサイト に記載されている方法を使用します。
ここ にjupyter-notebookで計算した時のファイルをアップロードしています。
元画像はこちら
- グレースケールに変換
- 9 x 8サイズに縮小
- 自分の一つ右隣のピクセルと値を比較し、右隣より小さければ「1」同じか大きければ「0」をセットする。
0を黒、1を白で表した結果
- 1ピクセルの結果を1bitとし、64ピクセルの結果を結合して64bitの整数を算出する。今回は「0111011001110000011110010101101100110011000100110101101000111000」(16進表記だと「7670795b33135a38」)になる。
Perceptual Hashを使った類似度の計算
計算したPerceptual Hashを使い、画像同士の類似度を計算することができます。 類似度の計算は、Perceptual Hashの値を64次元のベクトルと見なし、2つのベクトルのハミング距離を取ることで算出できます。(値が小さい方が、類似度が高くなる)
最初に表示した3つの画像のdHashの値と「Lenna」画像からの距離は以下のようになりました。 「Lenna(Watermark)」は「Lenna」と全く同じハッシュ値が得られており同一画像と判断できているのに対して、「Parrots」は異なるハッシュ値になっており2つのハッシュ値の距離も離れていることがわかります。 元記事では「0」ならば同じ画像、「10」を超えたら違う画像、「1〜10」の間では潜在的に似ている可能性がある画像と記載されています。
Title | dHash | distance from Lenna |
---|---|---|
Lenna | 7670795b33135a38 | 0 |
Lenna(Watermark) | 7670795b33135a38 | 0 |
Parrots | 59199958d0f9ecec | 33 |
もう一つの例として、ぱっと見は同じ画像に見える(でも、少し異なる)以下の2つの画像のdHashの値と距離を計算してみました。
dHashの値は「e1cd8d8e2f35b378」「e98d8c870d17ba60」、距離は「13」でした。10を越えると異なる画像と判断できるとのことなので、異なる画像と判断されています。
なお、今回紹介したハッシュ値や距離の計算はimagehashというライブラリで一発で計算することができます。すごい!!
まとめ
今回、画像の類似度を計算する手法の1つ「Perceptual Hash」を紹介しました。 今後は、弊社に実際にアップロードされた写真に適用してみて、「類似写真の除去に利用可能なのか?」「適切な閾値は?」「誤検知がどのくらいの割合で発生するか?」といったことを試していきたいなと思っています。
あと今回はじめてPythonを使って画像処理をしてみましたが、すごく使いやすかったです。 jupyter-notebookのインタラクティブな表現力と各種ライブラリの豊富さが凄まじかったです。今後も、ちょっとずつ勉強してこうかなーと思いました。
それでは、最後までご覧いただきありがとうございました。