読者です 読者をやめる 読者になる 読者になる

perceptual hash(phash)を利用して画像比較をしてみる

突然ですが画像がたくさんあってそれを人の目で分類するのって大変ですよね。

自動でこういったものを分類できないか興味があったので調べてみました。


perceptual hashとは

perceptual hash というのは、ハッシュ関数の実装なのですがSHA1等のハッシュ関数とは違い、以下の様な特徴があります。

  • 得られるハッシュ値は64bit
  • 対象は静止画, 画像, 音声等のマルチメディアデータ
  • コンテンツ内容が類似しているケースでハッシュを得た場合、例えば静止画画像の拡大、縮小といった加工の場合ハッシュ値が全く同じになる
  • また、色調の修正やノイズが加わった場合も得られるハッシュ値間のハミング距離が近くなる
    • 64bitのハッシュ値なので最も遠いハミング距離は64 (=全くコンテンツが異なっている)
    • 逆にハミング距離が0であればperceptual hashで得られた結果上は同一コンテンツ

こういった特徴があるため具体的にWebアプリケーションでの用途を考えると

  • コンテンツの重複判定
  • 類似画像検索

などに使えそうというのは上の特徴でわかるのではないでしょうか。

具体的なアルゴリズムはperceptual hashのオープンソース実装のphashのドキュメントにも記載があるのですが、

  • 対象画像を縮小する
  • 画像の輝度情報に対してDCT(離散コサイン変換)をかける
  • 低周波成分を得て、この部分をハッシュとして扱う

という考え方の様です。DCTまでは理解できたのですが、低周波成分をどの様にハッシュとして扱っているのか理解が届かない...。*1

node.jsで利用する方法

先に書いた http://www.phash.org/ の実装があり、そのラッパーがいくつか公開されています。今回は同名の phash を利用してみます。

で、実際に利用する前には環境にphashとimagemagickがライブラリを入れる必要があるのでMacであれば先に入れておきます。

$ brew install phash imagemagick

で続けてnpmコマンドでインストール。

$ npm install phash

実際に試してみる

では、いくつか画像を用意してハッシュ値の取得と画像の比較を行ってみます。

画像処理ではおなじみのLena画像を利用して実験。その画像をそれぞれ以下の様な加工をしてみて、元画像で得られたハッシュ値とのハミング距離を求めてみます。

  • 画像の縮小 (No.1)
  • 画像のセピア加工 (No.2)
  • 画像のモザイク化 (No.3)
  • 画像上に文字を重ねあわせる(No.4, No.5)
  • 画像を反転させる (No.6)

先ほどインストールしたnpmライブラリを利用してハッシュ値を求めた上で元画像とのハミング距離での比較を行います。

var pHash = require("phash");

var samples = [
  "samples/lena.jpg",
  "samples/lena-small.jpg",
  "samples/lena-sepia.jpg",
  "samples/lena-mosaic.jpg",
  "samples/lena-cut.jpg",
  "samples/lena-cut2.jpg",
  "samples/lena-horizonal-flip.jpg"
];

var hash = [];
samples.forEach(function(sample) {
  hash.push(pHash.imageHashSync(sample));
});

for(var i=0; i<hash.length; i++) {
  console.log("Hash No." + i + " : " + parseInt(hash[i]).toString(16));
  console.log("File:" + samples[i]);
  console.log("Haming distance (vs hash[0]) = " + pHash.hammingDistance(hash[0], hash[i]));
  console.log("-----");
}

以下が得られた結果です。

No 画像 ハッシュ値 No.0とのハミング距離
0 (元画像) c91cb262775a9800 -
1 (縮小) c91cb262775a9800 0
2 (セピア化) c914b262775a9800 2
3 (モザイク) c98cb022375ad800 8
4 (文字追加) c9acb866635a9800 8
5 (文字記号追加) cb8cba66535a9800 8
6 (反転) dc69f7330287e000 34

perceptual hashの利用例等を見るとハミング距離20前後を閾値としているケースが多かったので、反転させたケース以外は全て同一画像として判断できているといってよさそうです。

反転させるという処理は根本的に画像の作りを変える操作(画素の並びを変える) になるのでハッシュ値は全く異なってしまいますね。仮に類似コンテンツを抽出する目的でphashを利用する場合はこの辺りも配慮する必要がありそうです。

まとめ

  • perceptual hash についてそれとなく把握しました
  • phashという実装がありそれをラップしたnpmライブラリを使ってみました
  • 実際に画像をいくつか用意してハッシュを得てハミング距離を比較し、縮小やセピア加工、文字を上に重ねる等の加工であれば同一コンテンツとして検出できた

*1:詳しい人教えてください...