コンビニの有人レジがすごい混んでるのに、セルフレジを誰も使わない日本人を理解できない長谷川です。
概要
2つの画像の類似度を算出したい。
得られるハッシュ値は64bit
対象は静止画, 画像, 音声等のマルチメディアデータ
コンテンツ内容が類似しているケースでハッシュを得た場合、例えば静止画画像の拡大、縮小といった加工の場合ハッシュ値が全く同じになる
また、色調の修正やノイズが加わった場合も得られるハッシュ値間のハミング距離が近くなる
64bitのハッシュ値なので最も遠いハミング距離は64 (=全くコンテンツが異なっている)
逆にハミング距離が0であればperceptual hashで得られた結果上は同一コンテンツ
こういった特徴があるため具体的にWebアプリケーションでの用途を考えると
コンテンツの重複判定
類似画像検索
などに使えそうというのは上の特徴でわかるのではないでしょうか。
引用元:http://hideack.hatenablog.com/entry/2015/03/16/194336
計算方法もいくつか種類があります。
・aHash:画像の平均輝度からの差分を使った方法
・pHash:画像を離散コサイン変換(DCT)し周波数領域に変換後、低周波領域に対してaHashと同じ方法で算出する方法
・dHash:隣接領域との差分を使った方法
・wHash:pHashのDCTの代わりに離散ウェーブレット変換(DWT)を用いた方法
参考:http://tech.unifa-e.com/entry/2017/11/27/111546
ここでは aHash
と dHash
を使ってみようと思います。
使用したライブラリはこちら
https://github.com/devedge/imagehash
実装
func main() { const hashLen = 8 src, err := imagehash.OpenImg("/Users/luis/Desktop/src.jpg") if err != nil { panic(err) } srcAHash, err := imagehash.Ahash(src, hashLen) // return []byte if err != nil { panic(err) } srcHex := hex.EncodeToString(srcAHash) fmt.Println(srcHex) // fececc59d3c52d29a6ad852a23461131 }
imagehash.Ahash
を imagehash.Dhash
にするだけで aHash
から dHash
に切り替えることができます。
aHash と dHash の比較
こちらのサイトによると、dHash は aHash と同じ速度にも関わらず、精度が良好らしいです。
ちなみに2つの画像の distance(違う画像だと数値が大きい)
を求めるには
2つの []byte
をループさせて比較して値が違う場合に +1
すればいいだけです。
for i, _ := range laptop1AHash { if laptop1AHash[i] != laptop2AHash[i] { distance += 1 } }
試した画像
laptop1.jpg | laptop2.jpg | iqos.jpg |
laptop1.jpg と laptop2.jpg は似たような画像(若干右にズレてます)、
iqos.jpg は全く違う画像を用いりました。
aHash
平均輝度の差分を使った超シンプルな方法
スマホで撮影した画像の場合、撮影時にタップした場所によって露出が変わるためスマホの画像を取り扱う場合には不適切なような気がする。
似たような画像 laptop1: febaa2a2a2a28282 laptop2: def2a2a2a2a282be Distance: 3 アイコスと、1枚目 laptop1: febaa2a2a2a28282 iqos: f2f0f0c0d0f2e01c Distance: 8 // 1000回ループ richgo run main.go 41.88s user 0.86s system 186% cpu 22.967 total
dHash
隣接領域との差分を使った方法でシンプルで精度も、速度も良いらしい。
グレースケールに変換した後、9×8サイズに縮小する。
右隣のピクセルと値を比較して大きければ1
同じか小さければ0
0を黒、1を白に置換し、1pixelを1bitとして、16進数に変換することで値を取得できる。
aHash
と違い、輝度ではなく実際にピクセルを見るので精度が良いと言われるのも分かる。
似たような画像 laptop1: bf004891010141db3872464646464652 laptop2: 7f11d131234382973246464646465652 Distance: 11 アイコスと、1枚目 laptop1: bf004891010141db3872464646464652 iqos: fececc59d3c52d29a6ad852a23461131 Distance: 15 // 1000回ループ richgo run main.go 57.97s user 0.97s system 210% cpu 28.021 total
aHash と dHash の比較をしてみたけど精度にそこまでの差を見つけられなかった。
まあ今回用いた画像が悪かった。
distance
だけを見ると、dHash
の方が粒度が高いように見えるけど似たような画像と、アイコスと1枚目の差が 4
しかない。
しかし、aHash
では差が 5
ある。
しかも1000回ループした結果では、aHash
の方が16秒速い。
全く違う iqos.jpg
でも真ん中にディスプレイがあり、dhash
の手法だと白黒に変換したタイミングで同じような結果となってしまい、たまたま aHash
と差が内容になってしまったと推測。
どちらを使うにしても distance
の閾値を適切に設定する必要性がありますね。
もっといろんな画像で試してどっちを使うか決めたいと思います。