Slide 1

Slide 1 text

はじめに質問 Wasmは何で書く︖ Go? Rust? AssembyScript? 1

Slide 2

Slide 2 text

やっぱりWasm は C++!!! 〜巨⼈の肩に乗ろう〜 WebAssembly Night #10 kounoike (@ko_noike) & OpenCV Advent Calendar 2020 9⽇⽬ ※現状での個⼈の⾒解です 2

Slide 3

Slide 3 text

はじめに ⾃⼰紹介 鴻池 祐輔(こうのいけ ゆうすけ) 株式会社スタジアム(Web⾯接サービス 「インタビューメーカー」を提供) 以前は産業⽤画像処理、IoT映像解析などの業務 ※本⽇の発表は個⼈としての発表で会社の意⾒表 明・開発した技術とは関係ありません。 注釈 この資料はWebAssembly Night #10の資料であり、 同時にOpenCV Advent Calendar 2020の9⽇⽬の記事 の素材でもあります。 ※資料には各社の商標などが含まれます。 3

Slide 4

Slide 4 text

ついでに 「Meet 最強 」記事を書いた⼈です (こうググれば出てくる)。 こういう記事を書けるくらいに画像処理の経 験と、ブラウザ上での画像処理への興味を持 ってます。 4

Slide 5

Slide 5 text

5

Slide 6

Slide 6 text

6

Slide 7

Slide 7 text

画像処理 on your Browserの時代 WebRTCインフラの充実 時⾬堂 WebRTC SFU Sora/NTT Com. SkyWay/Amazon Chime SDK/ Azure Communication Services/Zoom Customizable SDK... Web会議の実装がより簡単に →差別化要素は︖︓その⼀つとしてのブラウザ側での画像処理 Google Meet 背景ぼかし Meet 最強 記事に書いたように、Googleの技術を結集した成果 7

Slide 8

Slide 8 text

画像処理を⾃前で書く︖ まずは20年の歴史を持つOpenCVの⼒を借りよう GoやRustにも独⾃の画像処理ライブラリはあるが、 充実度で⾔えばやっぱりOpenCV Go/RustのOpenCVラッパー︖ 現状のwasmだとFFIは… 画像処理以外のマルチメディア・解析処理 動画: ffmpeg/gstreamer (話題のffmpeg.wasmはちょっと違う気がする…) ⾳声: Essentia(Essentia.jsあり) ⾃然⾔語処理: MeCab 推論処理: Tensorflow/Caffe/OpenVINO... 8

Slide 9

Slide 9 text

近い将来の変化 MediaStreamTrack Insertable Media Processing using Streams カメラからのストリームにフレーム単位で触れるようになる。 Wasm呼び出しとの相性も良いはず︖ →シンプルに画像処理に閉じたWasmの作成が求められる ※Web Audio APIの⽅が先⾏しててWasmで作成した処理ノード作るのが 流⾏ってるっぽい(Meetでも⾳声処理っぽいWasmを使ってる) 9

Slide 10

Slide 10 text

典型的ブラウザ画像処理 背景ぼかし TypeScript/tensorflow.jsで作られたBodyPixがよく使われる Webカメラ => getUserMedia() => videoタグ => BodyPix => canvasタグ => captureStream() => WebRTC 推論︓tensorflow.js(WebGLバックエンド/Wasmバックエンド) 後処理(ぼかし)︓BrowserJS/CSSでcanvasお絵描き頑張る WebRTCへの送信︓canvasの captureStream() バーチャル背景(背景差し替え)は︖ JSで頑張る︖ OpenCV使って楽をする︖ ⽬標処理時間(理想)︓30fps=33.3333ms 10

Slide 11

Slide 11 text

背景差し替え(ぼかし)処理 BodyPixの推論処理はマスク画像を得られる→マスクを使って合成(ぼかし)処理 元画像 →マスク画像 +背景画像 →合成画像 ※マスク画像だけはただのUint8Arrayで1チャンネル画像と看做せる。他はRGBA画像 「+」の処理(合成処理)はどうする︖ ※もうちょい複雑な話は「Meet 最強」記事にて解説 11

Slide 12

Slide 12 text

合成処理(単純版) JSだと for(row=0; row

Slide 13

Slide 13 text

OpenCV.jsの実態 C++ライブラリをEmscriptenビルドしたもの jsライブラリのドキュメントはそこまで充実してない C++のドキュメントを⾒ながら書く ラッパーの都合上、メモリ開放( img.delete() など)を⾃前で⾯倒みる必要がある 13

Slide 14

Slide 14 text

簡単な処理を書いて⽐較してみる 画像の明るさを-100〜+100でフレームごとに変化させてみる (ちょっとサボってRGBで-100〜+100にしておく) 配布されているopencv.js版 ⾃分でビルドしたopencv.js版 C++で書いたフルWasm版 GitHub kounoike/webassembly-night-sampleで公開 Docker/docker-compose があればOK 14

Slide 15

Slide 15 text

opencv.js版(配布/⾃分でビルドともにソースは同じ) let cnt = 0; const updateCanvas = () => { const srcMat = new cv.Mat(video.height, video.width, cv.CV_8UC4); const rgbMat = new cv.Mat(); const rgbOutMat = new cv.Mat(); const outMat = new cv.Mat(); cap.read(srcMat); // videoタグから画像読み込み cv.cvtColor(srcMat, rgbMat, cv.COLOR_RGBA2RGB); // RGBA->RGB rgbMat.convertTo(rgbOutMat, -1, 1, cnt++ % 200 - 100); // -100〜+100 cv.cvtColor(rgbOutMat, outMat, cv.COLOR_RGB2RGBA); // RGBAに戻す cv.imshow("canvas", outMat); // canvasに描画 srcMat.delete(); // メモリ解放処理 rgbMat.delete(); rgbOutMat.delete(); outMat.delete(); requestAnimationFrame(updateCanvas); // rAFでループ }; updateCanvas(); ※実際は変数のスコープを持ち上げたりして解放処理サボったりする 15

Slide 16

Slide 16 text

C++版(C++処理関数) void doOpenCvTask(size_t addr, int width, int height, int cnt) { auto data = reinterpret_cast(addr); cv::Mat rgbaMat(height, width, CV_8UC4, data); cv::Mat rgbMat; cv::Mat rgbOutMat; cv::Mat outMat; cv::cvtColor(rgbaMat, rgbMat, cv::COLOR_RGBA2RGB); // RGBA->RGB rgbMat.convertTo(rgbOutMat, -1, 1.0, cnt % 200 - 100.0); // -100〜+100 cv::cvtColor(rgbOutMat, outMat, cv::COLOR_RGB2RGBA); // RGBAに戻す // メモリ解放処理いらない if (SDL_MUSTLOCK(screen)) // 描画処理はちょっと⾯倒 SDL_LockSurface(screen); cv::Mat dstRGBAImage(height, width, CV_8UC4, screen->pixels); outMat.copyTo(dstRGBAImage); if (SDL_MUSTLOCK(screen)) SDL_UnlockSurface(screen); SDL_Flip(screen); } 16

Slide 17

Slide 17 text

C++版呼び出しJS const updateCanvas = () => { context.drawImage(video, 0, 0); const data = context.getImageData(0, 0, width, height); const buffer = Module._malloc(data.data.length); Module.HEAPU8.set(data.data, buffer); Module.doOpenCvTask(buffer, width, height, cnt++); Module._free(buffer); requestAnimationFrame(updateCanvas); }; updateCanvas(); ※JS側でヒープメモリを管理している (確保・解放をループの外側にしてもいいけど) 17

Slide 18

Slide 18 text

以上のように・・・ OpenCV関数の呼び出し⽅はC++/JSで⼤して変わらない OpenCV.js専⽤ドキュメントはないのでC++版を⾒ながら書く JavaScriptはメモリ管理しなければならなくて⾯倒 C++はメモリ管理がいらなくて簡単 「C++はJavaScriptと違ってメモリ管理がいらなくて簡単」 18

Slide 19

Slide 19 text

その他もろもろ 実⾏速度 画像サイズや処理内容(OpenCVの関数呼び出し回数)などにもよるが若⼲C++版の⽅ が速い、かな︖ というくらい サイズ OpenCV.jsは配布版・フルビルド版ともに8MBほど C++版は数百KB リンカで必要な関数だけになってる︖ 19

Slide 20

Slide 20 text

もうちょっと複雑なことをやってみる DNNモジュールで顔検出の実装 C++での実装記事を参考にごにょごにょっと実装 結果 0.5fpsくらい ⾼速化 →SIMD対応でWasmビルド& chrome://flags で有効化 →8fpsくらい 16倍⾼速化 ソースは先のリポジトリのfacedetect-full-wasm 20

Slide 21

Slide 21 text

まとめ&補⾜ C++でWasm書くのは便利 各種マルチメディア・⾃然⾔語処理などのライブラリ活⽤ 「C++はJavaScriptと違ってメモリ管理がいらなくて簡単」 やりたいことと各⾔語の現状次第 単機能Wasmを組み合わせて使えばそれぞれで⾔語選択が可能 なぜ WebAssembly ⽣成を Go にしたのか(WebRTC E2EEの話) 「Pure Goで信頼できる暗号化ライブラリがある」など Apache TVM(Rust:推論ランタイム on Wasm)とかも頑張ってる Wasmそのものはまだまだ遅いのでSIMD/Thread/WebGPUなどの発展に期待 21