【TensorFlow.js】Body Segmentationでセグメンテーションを試してみる

 今回は、TensorFlow.js の Body Segmentation を試していきたいと思います。コードは前回のものを引き続き使っていきます。

 完成しているコードは下記にあります。

Angular-sample/tensorflow at main · tsuneken5/Angular-sample
Contribute to tsuneken5/Angular-sample development by creating an account on GitHub.

セグメンテーションとは

 ピクセル単位で物体と物体とを分割する技術のことです。

 今回の Body Segmentation は自撮りなどが前提で、カメラから 2m 以内の人物を対象に検出します。人物と背景を分割できるのでビデオ会議システムの背景エフェクトなどに使用できます。

環境

  • node: 18.20.0
  • Angular CLI:17.0.0
  • TensorFlow.js:4.22.0
  • TensorFlow-model / Body Segmentation:1.0.2
GitHub - tensorflow/tfjs: A WebGL accelerated JavaScript library for training and deploying ML models.
A WebGL accelerated JavaScript library for training and deploying ML models. - tensorflow/tfjs
tfjs-models/body-segmentation at master · tensorflow/tfjs-models
Pretrained models for TensorFlow.js. Contribute to tensorflow/tfjs-models development by creating an account on GitHub.

パッケージのインストール

$ npm install @tensorflow/tfjs
$ npm install @tensorflow-models/body-segmentation

 tsconfig.json に "skipLibCheck": true を記載して型チェックをスキップさせるのを忘れないでください。

サービスの作成

$ ng generate service service/canvas
  public async uploadImage(imgStr: string, maxWidth: number, canvas: HTMLCanvasElement): Promise<void> {
    const img = new Image();
    const ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
    let scaleRaito: number = 1;

    img.src = imgStr;

    return new Promise((resolve, reject) => {
      img.onload = () => {
        let imageWidth = img.naturalWidth
        if (imageWidth > maxWidth) {
          imageWidth = maxWidth;
          scaleRaito = imageWidth / img.naturalWidth;
        }
        canvas.width = imageWidth;
        let imageHeight = img.naturalHeight * scaleRaito;
        canvas.height = imageHeight;

        resolve(ctx.drawImage(img, 0, 0, imageWidth, imageHeight));
      };
      img.onerror = (error) => {
        console.log(error);
        reject;
      }
    });
  }

uploadImage

 base64形式の画像を canvas に描画するためのメソッドです。

コンポーネントの作成

$ ng generate component component/page/body-segmentation
import { Component } from '@angular/core';

import * as tf from '@tensorflow/tfjs';
import * as bodySegmentation from '@tensorflow-models/body-segmentation';

import { ImageUploaderComponent } from '../../share/image-uploader/image-uploader.component';
import { LoadingSpinnerComponent } from '../../share/loading-spinner/loading-spinner.component';
import { LoadingSpinnerService } from '../../../service/loading-spinner.service';
import { CanvasService } from '../../../service/canvas.service';

@Component({
  selector: 'app-body-segmentation',
  standalone: true,
  imports: [ImageUploaderComponent, LoadingSpinnerComponent],
  templateUrl: './body-segmentation.component.html',
  styleUrl: './body-segmentation.component.css'
})
export class BodySegmentationComponent {
  private canvas!: HTMLCanvasElement;
  private SEGMENTER_CONFIG = {
    runtime: 'tfjs',
  } as bodySegmentation.MediaPipeSelfieSegmentationTfjsModelConfig;
  private segmenter!: bodySegmentation.BodySegmenter;

  constructor(
    private loadingSpinnerService: LoadingSpinnerService,
    private canvasService: CanvasService
  ) { }

  private async loadModel(): Promise<void> {
    this.loadingSpinnerService.show();
    await tf.ready();
    const model = bodySegmentation.SupportedModels.MediaPipeSelfieSegmentation;
    this.segmenter = await bodySegmentation.createSegmenter(model, this.SEGMENTER_CONFIG);
    this.loadingSpinnerService.hide();
  }

  private async detect(canvas: HTMLCanvasElement): Promise<any> {
    const predictions = await this.segmenter.segmentPeople(canvas);
    return predictions;
  }

  public async startDetected(image: string): Promise<void> {
    this.loadingSpinnerService.show();
    const parent = this.canvas.parentElement as HTMLElement;
    const width = parent.clientWidth;
    const canvas = document.createElement('canvas') as HTMLCanvasElement;
    await this.canvasService.uploadImage(image, width, canvas);

    const results = await this.detect(canvas);

    const foregroundColor = { r: 0, g: 0, b: 255, a: 255 };
    const backgroundColor = { r: 0, g: 0, b: 0, a: 0 };
    const backgroundDarkeningMask = await bodySegmentation.toBinaryMask(
      results, foregroundColor, backgroundColor);

    const opacity = 0.7;
    const maskBlurAmount = 3;
    const flipHorizontal = false;
    await bodySegmentation.drawMask(
      this.canvas, canvas, backgroundDarkeningMask, opacity, maskBlurAmount, flipHorizontal);
    this.loadingSpinnerService.hide();
  }

  async ngOnInit() {
    this.loadModel();
  }

  ngAfterViewInit() {
    this.canvas = document.getElementById('myCanvas') as HTMLCanvasElement;
  }
}

loadModel

 モデルをロードします。

detect

 セグメンテーションを実行します。

 Body Segmentation の場合は返ってきた結果をそのまま canvas に描画するメソッドが準備されているので、そのメソッドを使用してセグメンテーションの結果を描画します。

bodySegmentation.toBinaryMask(segmentation: Segmentation | Segmentation[], foreground: Color, background: Color)

 セグメンテーション結果の ImageData を取得します。

  • segmentation ・・・ segmentPeople で取得したセグメンテーションの結果を渡します。
  • foreground ・・・ マスク部分の色を rgba 形式で設定します。
  • background ・・・ マスク部分以外の色を rgba 形式で設定します。

bodySegmentation.drawMask(canvas: Canvas, image: ImageType, maskImage: ImageData | null, maskOpacity?: number, maskBlurAmount?: number, flipHorizontal?: boolean)

 canvasimagemaskImage を重ねて描画します。

  • canvas ・・・ 描画する canvas を渡します。
  • image ・・・ セグメンテーションに使用した元画像を渡します。
  • maskImage ・・・ bodySegmentation.toBinaryMask() などで作成したセグメンテーション結果の ImageData を渡します。
  • maskOpacity ・・・ maskImage の透過率を設定します。
  • maskBlurAmount ・・・ maskImage のぼかしのピクセル数を 0 ~ 20 で設定します。
  • flipHorizontal ・・・ 左右反転させるかを設定します。カメラの種類によっては左右反転が必要になります。

実行結果

元画像

https://pixabay.com/photos/beard-face-man-model-mustache-1845166/

実行結果

コメント

タイトルとURLをコピーしました