TF.js 実践開発レシピ

TensorFlow.jsでブラウザ上で画像セグメンテーションを実装する

Tags: TensorFlow.js, 画像認識, 画像セグメンテーション, JavaScript, ブラウザAI

はじめに:ブラウザ上で画像セグメンテーションを実行する意義

画像認識技術の一つである画像セグメンテーションは、画像中の各ピクセルがどのカテゴリ(例: 人、背景、道路など)に属するかを識別するタスクです。これにより、オブジェクトの輪郭を正確に検出したり、特定の領域を切り出したりすることが可能になります。

Python環境では、TensorFlowやPyTorchといったフレームワークを使用して画像セグメンテーションモデルを構築・実行することが一般的です。しかし、これらのモデルをWebブラウザ上で直接実行できれば、サーバーサイドの処理負荷を軽減し、ユーザーデバイス上でのリアルタイムな処理やインタラクティブな体験を提供できるようになります。

TensorFlow.jsは、このようなニーズに応えるJavaScriptライブラリです。本記事では、TensorFlow.jsを用いてWebブラウザ上で画像セグメンテーションを実行するための具体的な方法を、コード例を交えながら詳細に解説します。Pythonでの機械学習開発経験をお持ちの読者の方々が、これまでの知識をTensorFlow.js環境に応用するためのヒントも含まれています。

画像セグメンテーションの基本とTensorFlow.jsでのアプローチ

画像セグメンテーションにはいくつかの種類がありますが、主に以下の二つに分けられます。

PythonのTensorFlowやPyTorchでは、DeepLab、UNet、Mask R-CNNといった様々なセグメンテーションモデルが利用可能です。これらのモデルは通常、畳み込みニューラルネットワーク(CNN)をベースとしており、入力画像と同じ解像度のセグメンテーションマスクを出力することが一般的です。

TensorFlow.jsでは、これらのセグメンテーションモデルをWebブラウザやNode.js環境で実行するために、主に以下の二つのアプローチが考えられます。

  1. TensorFlow.js Layers APIやCore APIを用いてモデルをJavaScriptで直接構築・学習する: ゼロからモデルを構築する場合や、比較的小規模なモデルに適しています。
  2. Pythonで学習・保存したモデルをTensorFlow.js形式に変換して利用する: Pythonで開発された高性能なモデル資産を活用する場合に最も一般的です。TensorFlow SavedModelやKerasモデルは、tensorflowjs_converterツールを用いてTensorFlow.js形式に変換できます。
  3. 公式やコミュニティが提供する事前学習済みセグメンテーションモデルを利用する: BodyPix(人物セグメンテーション)、DeepLabV3(様々な物体セグメンテーション)など、TF.jsチームやコミュニティがTF.js向けに最適化して提供しているモデルがあります。手軽に始めたい場合に適しています。

本記事では、主に3のアプローチ(事前学習済みモデルの利用)を中心に解説を進めます。これは、実践的なアプリケーション開発において、多くのケースで高性能な事前学習済みモデルをベースに開発を開始することが多いためです。また、Pythonでモデルを開発してきた方々にとって、変換されたモデルをどのようにTF.jsで扱うかを理解する上でも共通する部分が多いからです。

TensorFlow.jsでの画像セグメンテーション実装ステップ

ブラウザ上で画像セグメンテーションを実行するための基本的なステップは以下のようになります。

  1. TensorFlow.jsライブラリと使用するセグメンテーションモデルライブラリを読み込む
  2. HTML上に画像を表示する要素(<img>タグなど)と、結果を描画するためのCanvas要素を準備する
  3. TensorFlow.jsモデルを非同期でロードする
  4. 表示された画像を取得し、モデルが期待する形式に前処理する
  5. 前処理された画像をモデルに入力し、セグメンテーション結果を取得する
  6. 取得したセグメンテーション結果をCanvas要素に描画する

以下に、TensorFlow.jsが提供するBodyPixモデル(人物のセマンティック/インスタンスセグメンテーションに特化)を用いた具体的な実装例を示します。

具体的なコード例(BodyPixを使用)

この例では、Webページ上に配置した画像要素から画像を取得し、BodyPixモデルで人物のセグメンテーションを実行、その結果をCanvasに描画します。

<!DOCTYPE html>
<html>
<head>
<title>TensorFlow.js BodyPix Segmentation Example</title>
<style>
  /* 任意でスタイルを調整 */
  #input-image {
    max-width: 100%; /* レスポンシブ対応 */
    height: auto;
  }
  canvas {
    border: 1px solid #000; /* 描画範囲を確認用 */
    display: block;
    margin-top: 10px;
  }
</style>
</head>
<body>

  <h1>BodyPixによる画像セグメンテーション</h1>

  <!-- 入力画像要素 -->
  <img id="input-image" src="your_image.jpg" crossorigin="anonymous" alt="入力画像">

  <!-- セグメンテーション結果を描画するCanvas -->
  <canvas id="segmentation-canvas"></canvas>

  <!-- TensorFlow.js ライブラリ -->
  <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs"></script>
  <!-- BodyPix モデルライブラリ -->
  <script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/body-pix"></script>

  <script>
    let net; // ロードしたBodyPixモデルを保持する変数

    // モデルのロードとセグメンテーション処理を実行する非同期関数
    async function runSegmentation() {
      // 1. モデルのロード (一度だけ実行)
      if (!net) {
        console.log('Loading BodyPix model...');
        // bodyPix.load() 関数でモデルをロードします。
        // オプションでモデルのサイズや量子化を指定できます。
        net = await bodyPix.load({
          architecture: 'MobileNetV1', // モデルアーキテクチャを指定
          outputStride: 16,           // 出力ストライドを指定 (高速化 vs 精度)
          multiplier: 0.75,           // MobileNetの場合、モデルのサイズを調整 (精度 vs 速度/サイズ)
          quantBytes: 2               // モデルの量子化 (サイズ/速度 vs 精度)。1, 2, 4 バイトが選択可能。
        });
        console.log('Model loaded.');
      }

      // 2. 入力画像要素を取得
      const imageElement = document.getElementById('input-image');
      if (!imageElement) {
        console.error('Input image element not found.');
        return;
      }

      // 3. 画像に対してセグメンテーションを実行
      console.log('Running segmentation...');
      // estimatePersonSegmentation() 関数で人物のセマンティックセグメンテーションを実行します。
      // estimatePersonMask() はバイナリマスク、estimateMultiPersonSegmentation() は複数人物のインスタンスセグメンテーションです。
      const segmentation = await net.estimatePersonSegmentation(imageElement, {
        flipHorizontal: false, // 水平反転 (Webカメラの場合にtrueにすることが多い)
        internalResolution: 'medium', // 内部解像度 ('low', 'medium', 'high', 'full')
        segmentationThreshold: 0.7    // セグメンテーションの閾値
      });
      console.log('Segmentation complete.');
      // segmentation オブジェクトは、各ピクセルが人物の一部であるかを示す情報や、
      // スコア、透明度などが含まれます。詳細はBodyPixのドキュメントを参照してください。

      // 4. 結果をCanvasに描画
      const canvas = document.getElementById('segmentation-canvas');
      if (!canvas) {
         console.error('Canvas element not found.');
         return;
      }

      // Canvasのサイズを入力画像に合わせる
      canvas.width = imageElement.width;
      canvas.height = imageElement.height;

      // drawBokehEffect や drawMask などのヘルパー関数を使って結果を簡単に描画できます。
      // ここでは、セグメンテーションマスクを直接Canvasに描画します。
      // bodyPix.drawMask() 関数は、セグメンテーション結果を使いCanvasにマスクを描画します。
      // drawMask(canvas, image, mask, forecolor, backcolor, isBackground)
      const foregroundColor = {r: 0, g: 0, b: 0, a: 0}; // 前景は透明
      const backgroundColor = {r: 0, g: 255, b: 0, a: 255}; // 背景を緑色に塗る例

      // 背景部分を緑色で塗りつぶす
      bodyPix.drawMask(
          canvas,        // 描画先Canvas
          imageElement,  // 元画像 (描画には使わないが、Helper関数が必要とする場合がある)
          segmentation,  // セグメンテーション結果
          foregroundColor, // 前景の色(人物領域)
          backgroundColor, // 背景の色(人物以外の領域)
          true           // isBackground: trueを指定すると、背景部分が指定色で塗られる
      );

      console.log('Segmentation result drawn on canvas.');
    }

    // ページ読み込み完了後、および画像要素がロードされた後にセグメンテーションを実行
    window.onload = () => {
        const imageElement = document.getElementById('input-image');
        if (imageElement.complete) {
            // 画像が既にロードされている場合
            runSegmentation();
        } else {
            // 画像のロード完了を待つ場合
            imageElement.onload = runSegmentation;
        }
    };

    // エラーハンドリングの例
    // bodyPix.load() や estimatePersonSegmentation() はPromiseを返すので、
    // .catch() メソッドでエラーを捕捉できます。
    // runSegmentation().catch(error => console.error("An error occurred:", error));

  </script>

</body>
</html>

上記のコードでは、まずTensorFlow.js本体とBodyPixモデルのスクリプトをCDNから読み込んでいます。<img>タグで表示された画像をBodyPixモデルに入力し、estimatePersonSegmentation関数で人物のセグメンテーションマスクを取得します。取得したマスク情報は、bodyPix.drawMaskといったヘルパー関数を使うことで、容易にCanvas要素に描画できます。この例では背景部分を緑色に塗りつぶしていますが、マスク情報を使って人物だけを切り出したり、背景をぼかしたりといった様々な応用が可能です。

画像要素には crossorigin="anonymous" 属性を指定しています。これは、異なるオリジンにある画像をCanvasに描画したり、ピクセルデータを操作したりする場合に必要です。指定しない場合、CORSポリシーによりエラーが発生する可能性があります。

実装上の考慮事項

ブラウザ上で画像セグメンテーションを実装する際には、いくつかの考慮事項があります。

Pythonで画像セグメンテーションモデルを扱った経験がある場合、TensorFlow.jsでの実装は、Pythonでの画像読み込み、前処理ライブラリ(Pillow, OpenCV, NumPyなど)の操作が、JavaScriptのDOM API (<img>, <canvas>) やTensorFlow.js API (tf.browser.fromPixels, model.predict) に置き換わるものと理解するとスムーズです。基本的なテンソル操作やモデルの概念は共通しています。

まとめ

本記事では、TensorFlow.jsを用いてWebブラウザ上で画像セグメンテーションを実装する方法を解説しました。BodyPixモデルを例に、HTML要素の準備からモデルのロード、画像処理、セグメンテーション実行、結果の描画に至るまでの具体的なコード例を示しました。

ブラウザ上で画像認識AIを実行することは、Python環境とは異なる考慮事項(特にパフォーマンスとJavaScript/DOM操作)が必要ですが、TensorFlow.jsを活用することで、Pythonで培った機械学習の知識をWebアプリケーション開発に活かすことが可能です。

画像セグメンテーションは、画像編集ツールの開発、バーチャル背景、自動運転における環境認識など、様々なアプリケーションに応用できます。本記事の実装例を基に、さらに高度な画像認識機能をブラウザ上で実現するための一歩を踏み出していただければ幸いです。