Slide 1

Slide 1 text

Wasm×C++で実現する フロントエンドAI画像処理 渡邉健 @フロントエンドカンファレンス関西2025

Slide 2

Slide 2 text

自己紹介 ● 渡邉 健 ● 大学時代にAIの受託&SaaSを開発するスタートアップを 共同で創業 ● KaminashiにM&Aして、引き続きAI周りのデリバリー

Slide 3

Slide 3 text

1. Wasmを活用して作ったAIと画像処理のシステム 2. Wasmのメモリ管理の難しさ 3. 運用時の難しさと解決のアプローチ 今回話すこと

Slide 4

Slide 4 text

1. Wasmを活用して作ったAIと画像処理のシステム 2. Wasmのメモリ管理の難しさ 3. 運用時の難しさと解決のアプローチ 今回話すこと

Slide 5

Slide 5 text

食品ラベルって色々書いてある

Slide 6

Slide 6 text

食品ラベルって色々書いてある 消費期限や成分を間違えて印刷 して出荷したら大惨事 🤯 なので、従来は目視で検査 これをAIによって検査して 楽にしたい

Slide 7

Slide 7 text

サーバ上で推論する方式 AIでの検査のよくあるパターン デメリット ● 結果が帰るまでどうしても遅い ● サーバの維持費がかなり高い ● オフラインなんて到底無理

Slide 8

Slide 8 text

  さっきの検査が スマホの中でローカルで完結したら!?

Slide 9

Slide 9 text

今からこいつを 検査します

Slide 10

Slide 10 text

デモの様子

Slide 11

Slide 11 text

AIや複雑な画像処理をどうブラウザで動かすのか Web Assembly (Wasm) の活用! Webカメラから 画像の取得 AIによる検査 結果のレンダリング

Slide 12

Slide 12 text

AIや複雑な画像処理をどうブラウザで動かすのか Web Assembly (Wasm) の活用! Webカメラから 画像の取得 AIによる検査 結果のレンダリング ここがWasmとして 実行される

Slide 13

Slide 13 text

● C++やRustなどの言語をコンパイルしたものをブラウザ上で実行できる ● 一般的にJSのみで記述するより、処理速度は速くなる傾向にある ● よく使われるOpenCV (画像処理用ライブラリ) とか ffmpeg (動画編集) などのライブラリは、事前にWasmにコンパイルされて、TSの型がつい た状態で呼び出せたりするものもある ○ OpenCV.js ○ ffmpeg.js Web Assembly (Wasm) とは?

Slide 14

Slide 14 text

Wasmのイメージ Wasmの世界 Webアプリケーションの世界 重い処理の依頼 結果を返す Wasm呼び出し 結果の取得 重い計算💦

Slide 15

Slide 15 text

Q. JSだけでできないの? A. できるにはできるが実行速度が遅くなりがち - Wasmだとコンパイラによる最適化がグッとかけられる - 重い演算を並列で実行できる手法がある (SIMD) - そもそもAIとか画像処理が動かせるようなライブラリはC++や Pythonに比べて著しく乏しい AIや複雑な画像処理をどうブラウザで動かすのか

Slide 16

Slide 16 text

1. Wasmを活用して作ったAIと画像処理のシステム 2. Wasmのメモリ管理の難しさ 3. 運用時の難しさと解決のアプローチ 今回話すこと

Slide 17

Slide 17 text

当初は、OpenCV.jsを活用 ● 画像処理ライブラリOpenCVをWasmにコンパイルしてブラウザ から利用できるライブラリ OpenCV.js https://docs.opencv.org/4.x/d5/d10/tutorial_js_root.html ● OpenCVでできることをWasmのことをあまり考えずに、Package をインストールするだけで使えて便利 Wasmのメモリ管理の難しさ

Slide 18

Slide 18 text

OpenCV.jsの例 let src = cv.imread('canvasInput'); let gray = new cv.Mat(); let dst = new cv.Mat(); // グレースケール化 cv.cvtColor(src, gray, cv.COLOR_RGBA2GRAY); // 二値化 cv.threshold(gray, dst, 110, 255, cv.THRESH_BINARY); cv.imshow('canvasOutput', dst); src.delete(); gray.delete(); dst.delete(); Wasmのメモリ管理の難しさ

Slide 19

Slide 19 text

Wasmでメモリを確保 let src = cv.imread('canvasInput'); let gray = new cv.Mat(); let dst = new cv.Mat(); // グレースケール化 cv.cvtColor(src, gray, cv.COLOR_RGBA2GRAY); // 二値化 cv.threshold(gray, dst, 110, 255, cv.THRESH_BINARY); cv.imshow('canvasOutput', dst); src.delete(); gray.delete(); dst.delete(); Wasmのメモリ管理の難しさ Wasmの世界 srcの画像のメモリ grayスケール後の 画像のメモリ dstの画像のメモリ

Slide 20

Slide 20 text

Deleteするとメモリが解放 let src = cv.imread('canvasInput'); let gray = new cv.Mat(); let dst = new cv.Mat(); // グレースケール化 cv.cvtColor(src, gray, cv.COLOR_RGBA2GRAY); // 二値化 cv.threshold(gray, dst, 110, 255, cv.THRESH_BINARY); cv.imshow('canvasOutput', dst); src.delete(); gray.delete(); dst.delete(); Wasmのメモリ管理の難しさ Wasmの世界 srcの画像のメモリ grayスケール後の 画像のメモリ dstの画像のメモリ

Slide 21

Slide 21 text

解放せず何回も呼び出すと let src = cv.imread('canvasInput'); let gray = new cv.Mat(); let dst = new cv.Mat(); // グレースケール化 cv.cvtColor(src, gray, cv.COLOR_RGBA2GRAY); // 二値化 cv.threshold(gray, dst, 110, 255, cv.THRESH_BINARY); cv.imshow('canvasOutput', dst); src.delete(); gray.delete(); dst.delete(); Wasmのメモリ管理の難しさ Wasmの世界 srcの画像のメモリ grayスケール後の 画像のメモリ dstの画像のメモリ srcの画像のメモリ grayスケール後の 画像のメモリ

Slide 22

Slide 22 text

解放せず何回も呼び出すと let src = cv.imread('canvasInput'); let gray = new cv.Mat(); let dst = new cv.Mat(); // グレースケール化 cv.cvtColor(src, gray, cv.COLOR_RGBA2GRAY); // 二値化 cv.threshold(gray, dst, 110, 255, cv.THRESH_BINARY); cv.imshow('canvasOutput', dst); src.delete(); gray.delete(); dst.delete(); Wasmのメモリ管理の難しさ Wasmの世界 srcの画像のメモリ grayスケール後の 画像のメモリ dstの画像のメモリ srcの画像のメモリ grayスケール後の 画像のメモリ ここでクラッシュ→

Slide 23

Slide 23 text

このメモリリークが乱発 ● ラベル検査では非常に多くの処理を施す過程で、たくさんのメモ リを確保する ● その上で検査を繰り返し続けるので、一つの変数でも解放し損ね ると致命的な問題を引き起こす Wasmのメモリ管理の難しさ

Slide 24

Slide 24 text

OpenCV.jsを活用するのをやめて Wasmの検査処理を我々が C++で全て実装 Wasmのメモリ管理の難しさ

Slide 25

Slide 25 text

実装の流れ Wasmのメモリ管理の難しさ #include class Calculator { public: int add(int a, int b) { return a + b; } }; EMSCRIPTEN_BINDINGS(calculator_module) { emscripten::class_("Calculator") .constructor<>() .function("add", &Calculator::add); } 自動生成される Wasmを呼び出すJS Wasmファイル emcc /src/src/calc.cpp -o /src/calc.js

Slide 26

Slide 26 text

実装の流れ Wasmのメモリ管理の難しさ #include class Calculator { public: int add(int a, int b) { return a + b; } }; EMSCRIPTEN_BINDINGS(calculator_module) { emscripten::class_("Calculator") .constructor<>() .function("add", &Calculator::add); } 自動生成される Wasmを呼び出すJS Wasmファイル emcc /src/src/calc.cpp -o /src/calc.js const Module = await WasmModule(); const obj = new Module.Calculator(); try { obj.add(); } finally { obj.delete(); }

Slide 27

Slide 27 text

Wasmの世界 Webアプリケーションの 世界 検査処理を集約 C++ 写真撮影 JavaScript Wasmのメモリ管理の難しさ メモリ解放 メモリ解放を検査のたびに一回に減らせる

Slide 28

Slide 28 text

deleteをしなくてもいい方法はあるのか? ● 基本的には、明示的にdeleteをした方が安全ではあるが、 deleteをしなくてもGCが効く手法がいくつかあるにはある Wasmのメモリ管理の難しさ

Slide 29

Slide 29 text

値Objectを使う場合 Wasmのメモリ管理の難しさ const person = new Module.Person(); person.name = "Taro"; person.age = 20; struct Person {. std::string name; int age; }; // 構造体 Person を値オブジェクトとして JavaScript から呼び出せるように設定 EMSCRIPTEN_BINDINGS(my_module) { value_object("Person") .field("name", &Person::name) .field("age", &Person::age); C++ JavaScript

Slide 30

Slide 30 text

スマートポインタを使う Wasmのメモリ管理の難しさ function foo() { const obj = new Module.MyObject(); // fooのスコープが外れたら自動的にGCされ る } #include class MyObject { public: MyObject() { } ~MyObject() { } Result doSomething() { return new Result(); } }; EMSCRIPTEN_BINDINGS(my_module) { emscripten::class_("MyObject") .smart_ptr_constructor("MyObject", &std::make_shared); } C++ JavaScript

Slide 31

Slide 31 text

using宣言を使う Wasmのメモリ管理の難しさ using obj = new Module.MyObject(); obj.method(); // 処理がスコープから外れると自動で obj.delete() が呼ばれる Wasmを利用する 開発者が記述するJavaScript // Support `using ...` from https://github.com/tc39/proposal-explicit-res ource-management. const symbolDispose = Symbol.dispose; if (symbolDispose) { proto[symbolDispose] = proto['delete']; } emccが自動生成した Wasmの呼び出しJS

Slide 32

Slide 32 text

あくまでdeleteをしておいた方がいい Wasmのメモリ管理の難しさ https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.h tml#automatic-memory-management

Slide 33

Slide 33 text

開発時の余談 ● AI開発とフロントエンド開発を完全に分業していたため、 局所最適化された実装がまとまってしまった (≠全体最適) ○ Wasmとそれを呼び出す処理の開発 ○ カメラ動画の繋ぎ込み ● 複数コンポーネントにまたがって、検査ロジックがいろんなファイルに散 らばる ● useEffectの大量発生 ● キャッシュ戦略の無考慮・重めのwasmファイルを何度もfetchしてたり Wasmのメモリ管理の難しさ

Slide 34

Slide 34 text

結果的に ● 並列化できるところは並列に ● ロジックの凝集性を高くもつ ● 複数のカスタムフックごとに責務を小さ く   ➡ カメラの起動時間 & 検査速度の向上 (全体最適に近づく) リリース時にこれはやばいので超リファクタリング 実行のフローの整理

Slide 35

Slide 35 text

1. Wasmを活用して作ったAIと画像処理のシステム 2. Wasmのメモリ管理の難しさ 3. 運用時の難しさと解決のアプローチ 今回話すこと

Slide 36

Slide 36 text

1. 「メモリリーク起きていないか」「速度が落ちていない か」の品質保証 2. 導入時に、Wasmが動かないことがあったトラブル 3. ユーザ環境で何が行われているかモニタリングの手段 運用時におけるクライアントでAIを処理する ならでは問題

Slide 37

Slide 37 text

1. Wasmの品質を確保する ● Wasmでの機能追加にともなって、 検査速度が落ちたり、実装ミスによって、メモリリーク が起きたら困るため、 一定ラインの品質を確かめてデプロイしたい

Slide 38

Slide 38 text

1. Wasmの品質を確保する Wasmのレポジトリ フロントエンドのレ ポジトリ Wasmの変更 GitHub Actions Build Wasmの生成 AWS Code Artifact NPM Package NPM Package NPM Package Wasmファイル NPM Package Wasmのデリバリー

Slide 39

Slide 39 text

1. Wasmの品質を確保する Wasmのレポジトリ フロントエンドのレ ポジトリ Wasmの変更 GitHub Actions Build Wasmの生成 AWS Code Artifact NPM Package NPM Package NPM Package Wasmファイル NPM Package ブラウザで動作させ、メモリ・速度に問題がないか Playwrightで メモリ・検査速度の確認

Slide 40

Slide 40 text

2. 検査機能導入時の課題 ● OSやブラウザのバージョン差で動作しないケース ● 導入後に速度不足などの問題が発生

Slide 41

Slide 41 text

● 数百回の検査をユーザの端末で実行し メモリ遷移・検査時間の収集 ● 導入可否の判断に活用でき、 導入後のトラブル削減 2. 検査機能導入時の課題 導入したいユーザ環境でベンチマークを測定する アプリの配布

Slide 42

Slide 42 text

3. ユーザ環境で何が行われているかモニタリング サーバ環境と違い、クライアントの検査なので、そもそもモ ニタリングがしづらい 1. 検査がクラッシュすることがたまにあり、それを検知し て調査したい 2. 検査の快適さを測定したい

Slide 43

Slide 43 text

3. ユーザ環境で何が行われているかモニタリング ● サーバ推論と違い、クライアント推論では運用データを直接取得しづらい ○ クラッシュしてしまった場合はSentryでアラートを出して分析をする ● MLOps的に必要な項目を定義し、検査完了時に送信できるようにする 1. 検査一回あたりの時間 2. 検査完了までの時間 3. ユーザ環境で何が行われているかモニタリングの手段

Slide 44

Slide 44 text

定量的な分析だけだと難しい ● 検査完了時間などは、ユーザの利用の仕方に依存して、 大きく揺れる ● 検査精度は、事前にテンプレートで設定したラベルの設 定の仕方の問題によって精度が揺れる 3. ユーザ環境で何が行われているかモニタリングの手段

Slide 45

Slide 45 text

3. ユーザ環境で何が行われているかモニタリングの手段 内製のダッシュボードを作って、 なぜ検査に失敗するか分析もする

Slide 46

Slide 46 text

最後に ● 今回Wasmでの画像処理を主に説明したが、 もっと様々な箇所で活用できる ● RustやC++を始め様々な言語のコードをコンパイルでき るし、ぜひ活用してみてください

Slide 47

Slide 47 text

ご清聴ありがとうございました!