ブラウザ上のカメラでかざし画像を識別するWasmライブラリ(pHash)

  • 27 January 2022
Post image

 ブラウザ上でカメラを起動して映っている映像を操作することができる。そして映しているものがあらかじめ用意されたどの画像と一致するのかというシステムを作成した。よいライブラリが存在しないか探してみたんだが見当たらなかったので、自分用に作成したものを公開してみた。正直汎用性はないけど参考にしてみて。(Rust,Node必須)

https://github.com/j7w2/image-scanner

全体的な仕様

  • カメラの起動、映像の判定などすべてブラウザ上で動作させ、バックエンドなどとは一切通信しない。
  • QRコードを撮影するように特定の形の対象をカメラにおさめるので機械学習での物体検知や画像の解析は行わない
  • 画像が同一か否かについては"Perseptual Hash (pHash)“で判定
  • hash化の処理に多少の時間がかかるのでRustのwasm(WebAssembly)を作成してJSから呼び出す

pHash値の距離で画像判定

 画像が近似しているかどうかの判定ロジックで手軽なものに画像のhash値の距離で測定するpHashという方法がある。今回これをを使用するが、手軽といっても画像のピクセル値をハッシュ値計算するのは結構なマシンパワーを使用するようだ。なので処理効率が良さそうなRustで画像ハッシュ化するモジュールを作成し、WasmとしてJsから読み込む形をとる。

RustでgRPCが最速か?1msを追い求める
RustでgRPCが最速か?1msを追い求める
目次  リクエストからレスポンスまでの時間はなるだけ短いほうがいい。ユーザー操作性的にも好ましいし、昨今ではリクエスト~レスポンスまでの …
> Read More

 また500px~1000px程度の画像のハッシュ値での精度が、最も高そうなのは(12bit×12bit)程度だったのでデフォルトでhash_sizeは12とした。

extern crate image;
extern crate img_hash;
use img_hash::{HasherConfig, ImageHash};

fn get_hash(target: &image::ImageBuffer<image::Rgba<u8>, std::vec::Vec<u8>>, hash_dim: u32) -> ImageHash {
    let hasher = HasherConfig::new().hash_size(hash_dim, hash_dim).to_hasher(); // hash_dim = 12
    hasher.hash_image(target)
}

 ところが、このhash値の距離での類似判定以下の画像①と画像②の2枚はほとんど似ていないという数値がでてしまうのだ。

  • 画像①
  • 画像②

 対象の位置が大きく変わるとハッシュ値が全然違うものになってしまうのだ。


対象画像の位置をずらしてトリミングした画像を複数作成

 正解画像の位置をずらした複数の画像を作成してそれらの画像と、カメラから読み込まれた画像を判定すると多少の手ぶれや位置のズレは吸収できるはず。以下は3行3列にトリミングした例だ。

 これを5行5列、4行4列、3行3列、2行2列でトリミングした画像を用意すれば精度は十分に高くなる。以下のコードでトリミング画像を作成する。細かいことは ソースを確認してほしい。

let axis = n * 2 + 1;
  for x_axis in 0..axis {
      for y_axis in 0..axis {
          let x = x_axis * sub;
          let y = y_axis * sub;
          let _image =
              image::imageops::crop(target, x, y, size - sub * n * 2, size - sub * n * 2)
                  .to_image();
          let resize_image = image::imageops::resize(
              &_image,
              size / 2,
              size / 2,
              image::imageops::FilterType::Nearest,
          );
      }
  }

処理が重くて追いつかなくなる

 最大5行5列の画像のトリミングを使用する場合、一組で75枚の画像となる。もしカメラでかざした映像を、10種類のうちのいずれか判定したい場合は、750枚の画像をハッシュ化し、それらとの距離を計算する必要がある。

1組の画像枚数
最大4行4列 30枚(4^2 + 3^2 + 2^2 + 1)
最大5行5列 75枚(5^2 + 4^2 + 3^2 + 2^2 + 1)

 いくらRustの処理が速いといっても750枚もの画像のpHashの計算は、かなり時間がかかるし、750枚もの画像をブラウザでロードすること自体が非効率だ。

 なので、予めすべての識別画像をそれぞれ75枚のトリミング画像に分解しそれらすべてのハッシュ値を計算しておき、カメラから取り込まれてくる画像のハッシュ値だけリアルタイムで計算し、比較するという方法なら超高速で計算可能だ。それが以下のコマンドで出力されるようにしている。(README参照)

./lib/target/release/hash ./png_images ./src/hash

 あと、12bit12bitのハッシュ値の距離だが、“25"以下程度でほぼ同じ画像になるように見える。このパラメータは要調整。


ポーリングでWasmを呼び出しリアルタイムで判定

 あとは、普通にカメラを起動してある時点の画像を取り出して、wasmでハッシュ化、事前に用意された複数のハッシュ値との距離を計算。そしてハッシュ値の距離が"25"を下回ったら一致、下回らなければ再度カメラに表示されている画像を取り出して繰り返す処理を実装した。

 以下のようにWEBページで読み込んで使用する。

<meta name="viewport" content="width=device-width,initial-scale=1"><!--スマホ用にこの設定は必須 -->

<video id="video" autoplay playsinline></video>
<canvas id="frame" style="display: none;"></canvas>

<script type="text/javascript" src="./image-scanner.ja.js"></script>
<script type="text/javascript">
  imageScannerStart(
    200, // カメラのスキャン枠のwidth(px)
    200, // カメラのスキャン枠のheight(px)
    100, // カメラのスキャン枠のy軸pt(px)
    document.getElementById("video"), // video element
    document.getElementById("frame") // canvas element
  ).then(res => {
    console.log('成功!: id=' + res)
  }).catch(err => {
    console.log(err)
  })
</script>

 スキャンの枠をCSSで作成するとわかりやすくなるかも。

これはQRコードの代替になるかも

 今回作成したシステムでは、スキャンする対象がはっきりくっきりした対象の場合は、100%正確に識別できている。また、処理速度もポーリング1回の時間が速すぎるので、100msウエイトしている。つまり遅延もなくリアルタイムで識別が成功している。
 このシステム何に使うかというと、QRコードなどが印刷されていない出版物などで、その中に印刷されている内容でQRコードと同じような用途で使うという感じだ。もちろん全く同じことはできないかもしれないけど、QRコードを諸事情により印刷できない、または、すでに出版されている書籍の中からWebやアプリへ導線をはることができるのだ。

 また、通常印刷物にQRコードを印刷した場合、そのQRコードはひたすら世の中に流通し続け、その読み込み先へのアクセスは続いてしまう。しかし、今回のようなコードでもなんでもないマークをQRコードのように利用できるなら将来的なリスクを下げることができるかもしれない。つまり出版物とデジタルを疎結合で結ぶことができるのだ。

You May Also Like

RustでgRPCが最速か?1msを追い求める

RustでgRPCが最速か?1msを追い求める

 リクエストからレスポンスまでの時間はなるだけ短いほうがいい。ユーザー操作性的にも好ましいし、昨今ではリクエスト~レスポンスまでの時間で課金されるクラウドサービスも多い(GCPのCloudRunとか)。特にマイクロサービス設計の場合は、リクエスト~レスポンスまでの時間がより重要にな …

WindowsでもMacのUS配列キーボードが最強なわけ

WindowsでもMacのUS配列キーボードが最強なわけ

 コードを書く人間なら、キーボードにこだわりたくなるものだ。ノートPCをメインにしている場合はキーボードを変えることは難しいが、それでもJIS配列かUS配列など一度は考えたことがあるはずだ。 このサイトで何度も書いている通り、プログラミングのほとんどは試行錯誤の繰り返しで作り上げてい …