🦀

Rustのimage_hashクレートを使って画像の類似を調べる

2023/05/06に公開約2,900字

まえがき

最近Rustをちょっとずつ勉強してます

勉強もかねてパソコンの中にある重複した画像を調べるアプリを作ろうと思い製作中です

実はもうすでにQiitaやZENで同じような記事を書いている人がいるので二番煎じみたいな記事ですが、image_hashクレートを使ってたのはあまりなかったのでチラシの裏的に書いていきます

初めてZENで記事書くので見にくかったりしたらゴメンナサイ

要点

やりたいこととしては画像のサイズや圧縮比が違っても正しく比較できるようにしたいので精度の高いpHash(Perseptual Hash)を使って画像ごとのハッシュ値を求め比較していくという方法になります

ハッシュ化というとMD5とかSHAを思い浮かべるかもしれませんがpHashはjpeg圧縮などにも使用されている画像の特徴をハッシュ化したものとなります

詳しいことは後述します

image_hashクレートを使う

もうすでに便利なクレートを作ってくれている人がいます
実用的なメソッドが用意されており使い勝手がとてもいいです

https://github.com/abonander/img_hash

image_hashについて

画像のハッシュ化を手助けしてくれる便利なクレートです
このクレートを使用することで難しいロジックを実装する必要がなく目的を簡単にコードに実装できます

またpHashだけでなくGradient HashやBlockhashなどのアルゴリズムを実験的に組み込んであり
それぞれの特徴を比較することができます
HasherConfigで実装されているhash_algメソッドでハッシュアルゴリズムを指定することで各アルゴリズムを試すことができますが今回はこの説明は割愛いたします

https://github.com/abonander/img_hash/blob/master/src/lib.rs#L199-L201

具体例・実装

使い方

newメソッドを呼び出すことでaHashアルゴリズムを使用することとなり
離散コサイン変換(DCT)による前処理をすることでpHashアルゴリズムになります
これはインスタンスの作成時にpreproc_dctメソッドをつないであげることで有効になります

main.rs
use img_hash::HasherConfig;

fn main() {
  let hasher = HasherConfig::new().preproc_dct().to_hasher();
}

Hash値のハミング距離を比較する

では実際にpHashを使ってどのように画像が類似しているかを調べるかというと
計算したハッシュのハミング距離を比較し、しきい値より小さいかで判断します

これはdistメソッドを使用するだけでハミング距離を計算しu32型で返してくれます

https://github.com/abonander/img_hash/blob/master/src/lib.rs#L452-L454

ハミング距離は0~100の間で返されます
100は完全に不一致
0は完全に一致していると言えます

おおよそ10以下が類似している画像として判断できます

簡単な使用例

DynamicImageはimageクレートを使用しますのでCargo.tomlに以下のように記載します

[dependencies]
image = "0.22.3"
img_hash = "3.2.0"
lib.rs
use image::DynamicImage;
use img_hash::{HasherConfig, ImageHash};

pub fn get_phash(img: DynamicImage) -> ImageHash {
    let hasher = HasherConfig::new().preproc_dct().to_hasher();
    hasher.hash_image(&img)
}

pub fn is_similar(hash1: ImageHash, hash2: ImageHash) -> bool {
    hash1.dist(&hash2) < 10
}
main.rs
use image::DynamicImage;
use package_name::*;

fn main() {
  let image1 = image::open("image1.png").unwrap();
  let image2 = image::open("image2.png").unwrap();
  
  let hash1 = get_phash(image1);
  let hash2 = get_phash(image2);
  
  if is_similar(hash1, hash2) {
    println!("Images match!");
  }
}

結論

image_hashを使うときにpreproc_dct()を使ってpHashを使おう!ということを伝えたいだけの記事でした
難しいコード書かずにシンプルに画像比較ロジックが実装できるのいいですよね

さいごに

実際に動かしてみると精度はいいのですがCPU使ってごりごり計算するので少し時間がかかってしまうのが課題ですね…

GPUで計算できたりしたら早かったりするのかな

まだまだ知識半端ところもありますので間違いありましたら気軽に指摘してください

参考にした記事など

aHash, pHashについて解説してくれている記事
https://qiita.com/mamo3gr/items/b93545a0346d8731f03c

特にRustだと実装からクレートの公開まで詳しくやってくれているこちらの記事が
今回のアイデアの実現と行動に大きく力を貸してくれました

https://qiita.com/po3rin/items/0b4d3d3f8a5dbeec05e2

用語説明

ハミング距離とは - Wikipedia

情報理論において、ハミング距離(ハミングきょり、英: Hamming distance)とは、等しい文字数を持つ二つの文字列の中で、対応する位置にある異なった文字の個数である。

Discussion

ログインするとコメントできます