RustでgRPCが最速か?1msを追い求める
リクエストからレスポンスまでの時間はなるだけ短いほうがいい。ユーザー操作性的にも好ましいし、昨今ではリクエスト~レスポンスまでの時間で課金されるクラウドサービスも多い(GCPのCloudRunとか)。特にマイクロサービス設計の場合は、リクエスト~レスポンスまでの時間がより重要にな …
ブラウザ上でカメラを起動して映っている映像を操作することができる。そして映しているものがあらかじめ用意されたどの画像と一致するのかというシステムを作成した。よいライブラリが存在しないか探してみたんだが見当たらなかったので、自分用に作成したものを公開してみた。正直汎用性はないけど参考にしてみて。(Rust,Node必須)
画像が近似しているかどうかの判定ロジックで手軽なものに画像のhash値の距離で測定するpHashという方法がある。今回これをを使用するが、手軽といっても画像のピクセル値をハッシュ値計算するのは結構なマシンパワーを使用するようだ。なので処理効率が良さそうなRustで画像ハッシュ化するモジュールを作成し、WasmとしてJsから読み込む形をとる。
また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でハッシュ化、事前に用意された複数のハッシュ値との距離を計算。そしてハッシュ値の距離が"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で作成するとわかりやすくなるかも。
今回作成したシステムでは、スキャンする対象がはっきりくっきりした対象の場合は、100%正確に識別できている。また、処理速度もポーリング1回の時間が速すぎるので、100msウエイトしている。つまり遅延もなくリアルタイムで識別が成功している。
このシステム何に使うかというと、QRコードなどが印刷されていない出版物などで、その中に印刷されている内容でQRコードと同じような用途で使うという感じだ。もちろん全く同じことはできないかもしれないけど、QRコードを諸事情により印刷できない、または、すでに出版されている書籍の中からWebやアプリへ導線をはることができるのだ。
また、通常印刷物にQRコードを印刷した場合、そのQRコードはひたすら世の中に流通し続け、その読み込み先へのアクセスは続いてしまう。しかし、今回のようなコードでもなんでもないマークをQRコードのように利用できるなら将来的なリスクを下げることができるかもしれない。つまり出版物とデジタルを疎結合で結ぶことができるのだ。
リクエストからレスポンスまでの時間はなるだけ短いほうがいい。ユーザー操作性的にも好ましいし、昨今ではリクエスト~レスポンスまでの時間で課金されるクラウドサービスも多い(GCPのCloudRunとか)。特にマイクロサービス設計の場合は、リクエスト~レスポンスまでの時間がより重要にな …
コードを書く人間なら、キーボードにこだわりたくなるものだ。ノートPCをメインにしている場合はキーボードを変えることは難しいが、それでもJIS配列かUS配列など一度は考えたことがあるはずだ。 このサイトで何度も書いている通り、プログラミングのほとんどは試行錯誤の繰り返しで作り上げてい …