Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

Wasm×C++で実現するフロントエンドAI画像処理 / Frontend AI Image ...

Avatar for 株式会社カミナシ 株式会社カミナシ
November 29, 2025
21

Wasm×C++で実現するフロントエンドAI画像処理 / Frontend AI Image Processing with Wasm and C++

2025/11/30
フロントエンドカンファレンス関西2025
https://fortee.jp/fec-kansai-2025

Wasm×C++で実現するフロントエンドAI画像処理

渡邉 健
ソフトウェアエンジニア

Avatar for 株式会社カミナシ

株式会社カミナシ

November 29, 2025
Tweet

More Decks by 株式会社カミナシ

Transcript

  1. • C++やRustなどの言語をコンパイルしたものをブラウザ上で実行できる • 一般的にJSのみで記述するより、処理速度は速くなる傾向にある • よく使われるOpenCV (画像処理用ライブラリ) とか ffmpeg (動画編集)

    などのライブラリは、事前にWasmにコンパイルされて、TSの型がつい た状態で呼び出せたりするものもある ◦ OpenCV.js ◦ ffmpeg.js Web Assembly (Wasm) とは?
  2. Q. JSだけでできないの? A. できるにはできるが実行速度が遅くなりがち - Wasmだとコンパイラによる最適化がグッとかけられる - 重い演算を並列で実行できる手法がある (SIMD) -

    そもそもAIとか画像処理が動かせるようなライブラリはC++や Pythonに比べて著しく乏しい AIや複雑な画像処理をどうブラウザで動かすのか
  3. 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のメモリ管理の難しさ
  4. 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の画像のメモリ
  5. 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の画像のメモリ
  6. 解放せず何回も呼び出すと 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スケール後の 画像のメモリ
  7. 解放せず何回も呼び出すと 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スケール後の 画像のメモリ ここでクラッシュ→
  8. 実装の流れ Wasmのメモリ管理の難しさ #include <emscripten/bind.h> class Calculator { public: int add(int

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

    a, int b) { return a + b; } }; EMSCRIPTEN_BINDINGS(calculator_module) { emscripten::class_<Calculator>("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(); }
  10. 値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>("Person") .field("name", &Person::name) .field("age", &Person::age); C++ JavaScript
  11. スマートポインタを使う Wasmのメモリ管理の難しさ function foo() { const obj = new Module.MyObject();

    // fooのスコープが外れたら自動的にGCされ る } #include <emscripten/bind.h> class MyObject { public: MyObject() { } ~MyObject() { } Result doSomething() { return new Result(); } }; EMSCRIPTEN_BINDINGS(my_module) { emscripten::class_<MyObject>("MyObject") .smart_ptr_constructor("MyObject", &std::make_shared<MyObject>); } C++ JavaScript
  12. 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
  13. 開発時の余談 • AI開発とフロントエンド開発を完全に分業していたため、 局所最適化された実装がまとまってしまった (≠全体最適) ◦ Wasmとそれを呼び出す処理の開発 ◦ カメラ動画の繋ぎ込み •

    複数コンポーネントにまたがって、検査ロジックがいろんなファイルに散 らばる • useEffectの大量発生 • キャッシュ戦略の無考慮・重めのwasmファイルを何度もfetchしてたり Wasmのメモリ管理の難しさ
  14. 結果的に • 並列化できるところは並列に • ロジックの凝集性を高くもつ • 複数のカスタムフックごとに責務を小さ く   ➡ カメラの起動時間

    & 検査速度の向上 (全体最適に近づく) リリース時にこれはやばいので超リファクタリング 実行のフローの整理
  15. 1. Wasmの品質を確保する Wasmのレポジトリ フロントエンドのレ ポジトリ Wasmの変更 GitHub Actions Build Wasmの生成

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

    AWS Code Artifact NPM Package NPM Package NPM Package Wasmファイル NPM Package ブラウザで動作させ、メモリ・速度に問題がないか Playwrightで メモリ・検査速度の確認