ユニファ開発者ブログ

ユニファ株式会社プロダクトデベロップメント本部メンバーによるブログです。

Perceptual Hashを使って画像の類似度を計算してみる

最近、引越しをしたWebエンジニアの本間です。 引越しの作業は大変面倒でしたが、新しい街に来た時のワクワク感がやっぱりいいなーと感じております。

さて、弊社のサービスである「写真サービス るくみー」では、毎日たくさんの写真をアップロードしていただいているのですが、中には内容がほとんど同じ写真が入ってしまうことがあります。 これらの写真がそのまま販売されてしまうと、写真を選ぶ際に邪魔になったり、間違って複数枚購入してしまうことがあるため、可能な限り避けたい事象です。 「同じ内容」の写真を自動で判別する方法がないか調査していたところ「Perceptual Hash」という手法を見つけました。 Pythonでの画像処理の勉強も兼ねて、今回この手法を紹介してみようと思います。

Perceptual Hashとは

ハッシュ値は、「あるデータをハッシュ関数に入れて得られる値」で「同じデータからは常に同じ値が生成される」特徴があります。 この特徴を生かして、データの改変や破損のチェックに広く使われています。MD5やSHA-1といったアルゴリズムが有名です。

Perceptual Hashはマルチメディア(音声、画像、動画など)データ用のハッシュ値の1つなのですが、上記に加え、似ていると認識されるデータからは近い値が生成されるという特徴があります。

例えば、以下の3つの画像のmd5とPerceptual Hashの1つであるaHashの値は以下のようになっています。 md5の値は3つのファイルで全然異なるのに対して、aHashはLennaとLenna(Watermark)は値がかなり近いことがわかります。

f:id:ryu39:20171124012053j:plainf:id:ryu39:20171124012057j:plainf:id:ryu39:20171124012101j:plain

テスト画像(左から「Lenna」「Lenna(Watermark)」「Parrots」)

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で計算した時のファイルをアップロードしています。

元画像はこちら
f:id:ryu39:20171124012053j:plain

  1. グレースケールに変換
    f:id:ryu39:20171124111024j:plain
  2. 9 x 8サイズに縮小
    f:id:ryu39:20171124111210j:plain
  3. 自分の一つ右隣のピクセルと値を比較し、右隣より小さければ「1」同じか大きければ「0」をセットする。
    f:id:ryu39:20171124112401p:plain
    0を黒、1を白で表した結果
    f:id:ryu39:20171124115452j:plain
  4. 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」の間では潜在的に似ている可能性がある画像と記載されています。

f:id:ryu39:20171124012053j:plainf:id:ryu39:20171124012057j:plainf:id:ryu39:20171124012101j:plain
Title dHash distance from Lenna
Lenna 7670795b33135a38 0
Lenna(Watermark) 7670795b33135a38 0
Parrots 59199958d0f9ecec 33

もう一つの例として、ぱっと見は同じ画像に見える(でも、少し異なる)以下の2つの画像のdHashの値と距離を計算してみました。

f:id:ryu39:20171124154927j:plainf:id:ryu39:20171124154931j:plain

dHashの値は「e1cd8d8e2f35b378」「e98d8c870d17ba60」、距離は「13」でした。10を越えると異なる画像と判断できるとのことなので、異なる画像と判断されています。

なお、今回紹介したハッシュ値や距離の計算はimagehashというライブラリで一発で計算することができます。すごい!!

まとめ

今回、画像の類似度を計算する手法の1つ「Perceptual Hash」を紹介しました。 今後は、弊社に実際にアップロードされた写真に適用してみて、「類似写真の除去に利用可能なのか?」「適切な閾値は?」「誤検知がどのくらいの割合で発生するか?」といったことを試していきたいなと思っています。

あと今回はじめてPythonを使って画像処理をしてみましたが、すごく使いやすかったです。 jupyter-notebookのインタラクティブな表現力と各種ライブラリの豊富さが凄まじかったです。今後も、ちょっとずつ勉強してこうかなーと思いました。

それでは、最後までご覧いただきありがとうございました。

参考サイト一覧