JavaScriptの顔認識ライブラリをチューニングしたら実用レベルになったという話
2012/05/08 追記
再度検証して検証データを公開しました。
追記終わり。
JavaScriptの顔認識ライブラリにccv.jsという有名なものがある。
ただ、WebRTCで顔認識させようとすると遅くてしかたがなかった。
最初は速いこともあるが、10回ぐらい認識をさせるとすぐに遅くなる。
とりあえず、デモ。
最初は速いこともあるが、10回ぐらい認識をさせるとすぐに遅くなる。
とりあえず、デモ。
そこで、チューニングをしてみることにした。
まず、JavaScriptの定番の高速化を試してみた。
例えば、正の数で使える「Math.floor(x)」を「(x | 0)」に、整数で使える「x * Math.pow(2, y)」を「x << y」にする等。
これで、10~30%高速化できた。
次に、遅くなっている部分を調べたら、Web Workersで分散するための仕組みが遅くなる原因だとわかった。
これは、Web Workersを使わない場合にも影響が出ていた。
じゃあ、Web Workersを使えば速くなるのかといえばその逆で、20倍遅くなっていた。
詳しくは調べてないけど、多分Workerスレッドに処理データを渡す時にJSON化が走っているためっぽい。
(ちなみにccv.jsは、Worker数をひとつしか指定できない。)
この仕組みを全て外したのに加え、functionの呼び出しコスト等を考えて処理を色々弄ってみた。
これで、150~250%も高速化できた。
さすがにこれには驚いた。
(前述のデモと同時に開くと遅いので注意。)
まず、JavaScriptの定番の高速化を試してみた。
例えば、正の数で使える「Math.floor(x)」を「(x | 0)」に、整数で使える「x * Math.pow(2, y)」を「x << y」にする等。
これで、10~30%高速化できた。
次に、遅くなっている部分を調べたら、Web Workersで分散するための仕組みが遅くなる原因だとわかった。
これは、Web Workersを使わない場合にも影響が出ていた。
じゃあ、Web Workersを使えば速くなるのかといえばその逆で、20倍遅くなっていた。
詳しくは調べてないけど、多分Workerスレッドに処理データを渡す時にJSON化が走っているためっぽい。
(ちなみにccv.jsは、Worker数をひとつしか指定できない。)
この仕組みを全て外したのに加え、functionの呼び出しコスト等を考えて処理を色々弄ってみた。
これで、150~250%も高速化できた。
さすがにこれには驚いた。
(前述のデモと同時に開くと遅いので注意。)
手元の環境では、190ms~230msぐらいで処理できている。
これなら、実用でもなんとか使えるんじゃないかと。
あと、顔認識の知識がなかったので、gihyo.jpの記事を読んでからコード読むとかなり理解できたことを書いておく。
これなら、実用でもなんとか使えるんじゃないかと。
あと、顔認識の知識がなかったので、gihyo.jpの記事を読んでからコード読むとかなり理解できたことを書いておく。
2012/05/04 追記
ちょっと勘違いされそうな書き方だったので2点追記します。
1点目。
オリジナルのコードでは、同期/非同期を切り替えるためにparallableという関数を定義しています。
主観ですが、このparallableを使うと、(通常では問題ないレベルで)メモリを比較的大きめにとるようです。
しかしWebRTCを使ってリアルタイムで処理を行うと、10回ぐらい顔認識を行うとGCが実行され始めるようです。
勘でしかありませんが、GCの処理が邪魔をして、顔認識のリアルタイム性が落ちてしまっていると感じました。
2点目は、Web WorkersとJSON化について。
非同期で処理を行った場合、処理時間が20倍になってしまうのですが、気になったので確認しました。
Workerスレッドには、基本的なオブジェクトしか渡せません。
parallable内ではtryの中で一度postMessageを実行し、catchされた場合にJSON化して文字列としてpostMessageを実行していました。
ccv.jsがWorkerスレッドに渡そうとしていたのは、CanvasPixelArrayを含むオブジェクトのため、JSON化されてしまいます。
手元の環境ではこのJSON化の処理に2000ms以上かかっていました。
次に時間がかかっていたのは、WorkerスレッドがJSON化された文字列を受け取ったところです。
JSONをparseするのに、手元の環境で1500ms程かかっていました。
WorkerスレッドからメインスレッドへのpostMessageは、JSON化されていませんでした。
1点目。
オリジナルのコードでは、同期/非同期を切り替えるためにparallableという関数を定義しています。
主観ですが、このparallableを使うと、(通常では問題ないレベルで)メモリを比較的大きめにとるようです。
しかしWebRTCを使ってリアルタイムで処理を行うと、10回ぐらい顔認識を行うとGCが実行され始めるようです。
勘でしかありませんが、GCの処理が邪魔をして、顔認識のリアルタイム性が落ちてしまっていると感じました。
2点目は、Web WorkersとJSON化について。
非同期で処理を行った場合、処理時間が20倍になってしまうのですが、気になったので確認しました。
Workerスレッドには、基本的なオブジェクトしか渡せません。
parallable内ではtryの中で一度postMessageを実行し、catchされた場合にJSON化して文字列としてpostMessageを実行していました。
ccv.jsがWorkerスレッドに渡そうとしていたのは、CanvasPixelArrayを含むオブジェクトのため、JSON化されてしまいます。
手元の環境ではこのJSON化の処理に2000ms以上かかっていました。
次に時間がかかっていたのは、WorkerスレッドがJSON化された文字列を受け取ったところです。
JSONをparseするのに、手元の環境で1500ms程かかっていました。
WorkerスレッドからメインスレッドへのpostMessageは、JSON化されていませんでした。
2012/05/04 追記2
ccv.jsについてもう少し説明。
ccv.jsは、OpenCVの一部の機能をJavaScriptに移植したもののようです。
OpenCVはコンピュータビジョン(Computer Vision)のためのライブラリで、画像解析等でよく使われる処理をAPIとしてまとめたものです。
ccv.jsには、画像内のオブジェクト検出のAPIと、オブジェクト検出の前処理として必要なグレースケール化のAPIが含まれています。
オブジェクト検出には、機械学習の学習結果データが必要になるのですが、ccv.jsには顔検出の学習結果データが添付しています。
といっても、傾きなしの正面向きのデータしか含まれていないようで、現段階では用途は限られると思います。
このような理由のため、現バージョンのccv.jsは「顔検出のためのJavaScriptライブラリ」という認識がされています。
他のオブジェクト検出の学習結果データを準備すれば、他の色々な物を検出できるはずです。
ccv.jsは、OpenCVの一部の機能をJavaScriptに移植したもののようです。
OpenCVはコンピュータビジョン(Computer Vision)のためのライブラリで、画像解析等でよく使われる処理をAPIとしてまとめたものです。
ccv.jsには、画像内のオブジェクト検出のAPIと、オブジェクト検出の前処理として必要なグレースケール化のAPIが含まれています。
オブジェクト検出には、機械学習の学習結果データが必要になるのですが、ccv.jsには顔検出の学習結果データが添付しています。
といっても、傾きなしの正面向きのデータしか含まれていないようで、現段階では用途は限られると思います。
このような理由のため、現バージョンのccv.jsは「顔検出のためのJavaScriptライブラリ」という認識がされています。
他のオブジェクト検出の学習結果データを準備すれば、他の色々な物を検出できるはずです。
2012/05/04 追記3
このエントリーのタイトルに「顔認識」と入っていますが、実はこれは誤りでした。
正確には「顔検出」です。
顔検出は画像の中から顔を探し出すことを指し、顔認識はその顔が誰なのかを判断することを指すようです。
正確には「顔検出」です。
顔検出は画像の中から顔を探し出すことを指し、顔認識はその顔が誰なのかを判断することを指すようです。