ヒキダスブログ

テック系や最近見たもの感じたことを書いて残す引き出しスペースです

Webブラウザで物体距離を測るサンプルを作ってみた

またもブログ更新がご無沙汰になってしまいました。。
仕事の兼ね合いからちょっとしたサンプルを作ってみました。

f:id:pujoru35:20180917011604p:plain

経緯

最近、フロントエンドでできる認識系のJSライブラリを色々と試しています。
認識系というのは、例えばよく使われるものだとQRコードの認識だったり、Webコンテンツで比較的取り上げられてきている顔認識から、物体、色、表情etcと色々とあります。色だけだとCanvasを使って静止画像から色を抽出したりできますが、顔や表情等は専用のライブラリやパーツを識別するデータとなる検出器も必要だったりします。

以下、私の方で調べてみたJSライブラリの一部を列記します。それ以外にもいろんなライブラリがありますし、バックエンドも含めると機械学習も関連して認識領域が広がりそうではあります。

認識系JSライブラリ

QRコード
- jsQR

バーコード:
- quaggaJS

顔認識:
- clmtrackr
- face-api.js
- tracking.js
- jquery.facedetection
- js-objectdetect

ポーズ認識(姿勢):
- PoseNet

ここまで調べてきて、「カメラに映った物体距離を識別できないか」と思いました。スマホアプリだと、ARKitやARCoreを使って良い精度で床の位置や2点間の距離まで認識できています。
ただ、通常のWebブラウザだとカメラの深度センサが備わってなかったりするのか、そこまでできていないのが難しいところです。。
そこで、現状のブラウザ仕様で擬似的にでも物体距離を測れないか探ってみました。

アイディア編

着想のヒントになったのが、「画像の2値化」でした。「画像のモノクロ化」と例えてみると良いでしょうか。閾値を超えたかどうかで色を白黒に二分する方法で、OCRによる文字認識や物体の輪郭検出と、認識実装でよく使われる前処理といえます。

カメラに近づくと画面が暗く、光源であれば逆に明るくなっていくでしょう。となると、カメラに映った画像を白黒化し、白の割合が多い(もしくは黒の割合が多い)かで物体が近づいている、離れているかが大雑把ながら識別できるのではと考えました。

実装編

まずは、画像の2値化処理を作ってみました。WebRTCでPC/スマホのカメラにアクセスし、取得したストリーム情報をVideoタグに流し、Canvasタグでその静止画を受け取って2値化処理を行っています。

Image Threshold with canvas | Computer Vision

2値化処理は、こちらの記事を参考にしました。

let src = ctx.getImageData(0, 0, canvas.width, canvas.height)
let dst = ctx.createImageData(canvas.width, canvas.height)

for(let i = 0; i < src.data.length; i = i + 4){
  let y = ~~(0.299 * src.data[i] + 0.587 * src.data[i + 1] + 0.114 * src.data[i + 2])
  let ret = (y > threshold.value) ? 255: 0
  dst.data[i] = dst.data[i + 1] = dst.data[i + 2] = ret
  dst.data[i + 3] = src.data[i + 3]
}

ctx.putImageData(dst, 0, 0)

「STOP」ボタンはWebカメラからのストリームが停止されもう一度押すと再開します。「FRONT」と「REAR」はスマホのみ対応で、フロント・リアカメラの切り替えボタンになります。「Threshold」のチェックを外すと2値化が解除され、横のレンジで2値化の閾値を変更することができます。

そして、先の「白黒の割合をみて近づいたか判定する」工程を以下のようにしてみます。近づいたかどうかをインタラクティブに表現したかったので、Web Audio APIを使って周波数を変更するようにしています(updateAudioFrequency関数のところです)。

let src = ctx.getImageData(0, 0, canvas.width, canvas.height)
let dst = ctx.createImageData(canvas.width, canvas.height)

let isWhite = 0 // 白の割合

/**
 * 画像の2値化
 */
function canvasBinarization(){
  for(let i = 0; i < src.data.length; i = i + 4){
    let y = ~~(0.299 * src.data[i] + 0.587 * src.data[i + 1] + 0.114 * src.data[i + 2])
    let ret = (y > threshold.value) ? 255: 0
    dst.data[i] = dst.data[i + 1] = dst.data[i + 2] = ret
    dst.data[i + 3] = src.data[i + 3]
    isWhite += ret === 255? 1: 0 
  }
}

/**
 * 周波数の更新(2値化した値を以って計算)
 */
function updateAudioFrequency(){
  const total = canvas.width * canvas.height;
  oscillator.frequency.value = BASE_FREQUENCY + ((isWhite / total) - 0.5) * 800
} 

デモ

そうして作ったのがこちらのサンプル。「AUDIO START」のテキストをクリックすると開始します。
カメラの前に手を近づけたり離したりすると音階が変わってきます。もしくは壁や床もありかも?
音の切り替わりを顕著にしようと思ったので、周波数調整の際係数を大きめにしています。この辺りも調整アリかと。

Example 01 | Image Threshold with canvas | Computer Vision

また、音に加えて画面の色味を動的に変えてみたのがこちら。「STOP」ボタンと「Threshold」のUIは割愛しました。

Example 02 | Image Threshold with canvas | Computer Vision

振り返り

作ってみた所感としては、高い精度でなく擬似的な形であれば出来なくはないといったところです。カメラの性能によるところか、近づけて暗くなっても自動的に補正がかかるみたいで、2値化された画像より一瞬明るくなり音域が低くなっていたのが一時的に戻ったりする部分が見受けられました。

また、スマホでの画像2値化は割と処理を食うみたいです。静止画ならそこまで気にならないもののカメラからのストリーム情報、かつ処理結果に応じてWeb Audioや色を動的に変えようとして、WebGLでなくCanvasでやっていたこともあってか、毎フレーム実行すると私の実機がカクカクでまともに動かなかったわけで。。 ひとまず、スマホでは毎フレームでなく30-60フレームに一回処理するという対応をとりました。なので、PCほどリアルタイムではないですが、割と動くようにはなりました。

それから、スマホでやるとジャイロセンサーと違う形のインタラクションが出来そうに感じました。壁や手に近づけるか離すと何かが見えるコンテンツとか、向き・加速度以外のアプローチができそう。

これを作って音まで出せた時、直接触れずに空間中の手の位置によって音の調整ができる、楽器のテルミンを連想しました。
なので、テルミンみたいなWebコンテンツ作りたい方いらっしゃいましたら、お声がけいただけると嬉しいです(笑)

参考