AI x OpenCV x WebAR: SelfieSegmentationを使ってみよう

4.1K Views

September 25, 21

スライド概要

2021/09/25に開催したハンズオン資料

profile-image

可視化技術や人間計測/空間計測技術を活用した問題解決に関する研究開発。 ARコンテンツ作成勉強会(tryAR)を主催。

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

(ダウンロード不可)

関連スライド

各ページのテキスト
1.

AI x OpenCV x WebAR SelfieSegmentationを使ってみよう

2.

もろもろのダウンロード http://arfukuoka.lolipop.jp/selfie_segmen tation/sample.zip

3.

自己紹介 氏名:吉永崇(Takashi Yoshinaga) 専門:ARを用いた医療支援や運動計測 Volumetric Video コミュニティ:ARコンテンツ作成勉強会 主催

4.

ARコンテンツ作成勉強会の紹介  2013年5月に勉強会をスタート。  ARコンテンツの作り方をハンズオン形式で学ぶ  人数は5~10名程度の少人数で実施  参加条件はAR/VRに興味がある人(知識不要)  各地で開催 (福岡、熊本、宮崎、長崎、大分、 鹿児島、山口、広島、札幌、関東)

5.

Twitterと勉強会ページで情報を発信しています @AR_Fukuoka Googleで「AR勉強会」で検索

6.

ハッシュタグ #AR_Fukuoka

7.

本題に入ります

8.

本日のゴール https://youtu.be/Lj64eMZeYVE MediaPipeのSelfieSegmentationとOpenCVによる画像処理で遊ぶ

9.

テンプレートの複製 https://glitch.com/~selfie-seg-template GET STARTED

10.

テンプレートの複製 Remix Your Own

11.

テンプレートの確認 index.htmlをクリックし、コードが表示されることを確認 index.html

12.

テンプレートの確認 index.htmlをクリックし、コードが表示されることを確認 エディタ プレビュー

13.

テンプレートの確認 index.htmlをクリックし、コードが表示されることを確認 プレビューを閉じる

14.

ハンズオンの手順 1. テンプレートの概要を解説 2. SelfieSegmentation 3. OpenCVを使った画像処理 4. 結果の統合

15.

ハンズオンの手順 1. テンプレートの概要を解説 2. SelfieSegmentation 3. OpenCVを使った画像処理 4. 結果の統合

16.

テンプレートの確認 Lesson01

17.

テンプレートの確認 ライブラリの 読み込み MediaPipeや OpenCVでの 処理を記述 (今日のメイン) 描画領域等

18.

テンプレートの確認 描画領域等

19.
[beta]
HTMLの記述の解説
<!--Webカメラの映像を取得-->
<video id="input_video" style="position:absolute; "></video>
<!--最終結果の表示に使用-->
<canvas id="output_canvas" style="position:absolute;"></canvas>
<!--OpenCV用の画像作成や途中経過の表示に使用-->
<canvas id="opencv_canvas" style="position:absolute;"></canvas>
opencv_canvas

output_canvas

input_video

input_video

output_canvas

input_video

20.

テンプレートの確認 ライブラリの 読み込み

21.
[beta]
ライブラリ読み込みの解説
<!--① OpenCVの読み込み (Selfie Segmentationのみの利用なら不要)-->
<script src="https://docs.opencv.org/3.4.1/opencv.js"></script>
<!--② カメラをmediapipeで簡単に利用するためのツール-->
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/camera_utils/camera_utils.js"
crossorigin="anonymous"></script>
<!--③ selfie segmentationの読み込み-->
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/selfie_segmentation/
selfie_segmentation.js" crossorigin="anonymous"></script>

OpenCV

Camera Utils

Selfie Segmentation

22.

テンプレートの確認 MediaPipeや OpenCVでの 処理を記述 (今日のメイン)

23.

テンプレートの確認

24.

テンプレートの確認 変数宣言 初期化 描画領域/カメラ/ Segmentation 認識結果の利用

25.

テンプレートの確認 変数宣言

26.

テンプレートの確認 初期化 描画領域/カメラ/ Segmentation

27.
[beta]
javascriptを用いた初期化
window.onload = function() {
videoElm = document.getElementById('input_video'); //ビデオ要素の取得
canvasElm = document.getElementById('output_canvas'); //表示用のCanvasを取得
canvasCtx = canvasElm.getContext('2d’); //Canvas描画に関する情報にアクセス
//Segmentationを使用するための関連ファイルの取得と初期化
let selfieSegmentation = new SelfieSegmentation({locateFile: (file) => {
return `https://cdn.jsdelivr.net/npm/@mediapipe/selfie_segmentation/${file}`;
}});
//Segmentationで使う学習モデルを選択
selfieSegmentation.setOptions({ modelSelection: 0, });
//Segmentation結果を処理する関数を登録
selfieSegmentation.onResults(onResults);
//カメラの初期化
let camera= new Camera(videoElm, {
onFrame: async () => {
await selfieSegmentation.send({image: videoElm});
},
width: 640, height: 360
});
//カメラ動作開始
camera.start();
};
function onResults(results) {/*Segmentationの結果を利用する*/ };

28.
[beta]
javascriptを用いた初期化
window.onload = function() {
videoElm = document.getElementById('input_video'); //ビデオ要素の取得
canvasElm = document.getElementById('output_canvas'); //表示用のCanvasを取得
canvasCtx = canvasElm.getContext('2d’); //Canvas描画に関する情報にアクセス
//Segmentationを使用するための関連ファイルの取得と初期化
let selfieSegmentation = new SelfieSegmentation({locateFile: (file) => {
return `https://cdn.jsdelivr.net/npm/@mediapipe/selfie_segmentation/${file}`;
}});
//Segmentationで使う学習モデルを選択
selfieSegmentation.setOptions({ modelSelection: 0, });
//Segmentation結果を処理する関数を登録
selfieSegmentation.onResults(onResults);
//カメラの初期化
let camera= new Camera(videoElm, {
onFrame: async () => {
await selfieSegmentation.send({image: videoElm});
},
width: 640, height: 360
});
//カメラ動作開始
camera.start();
};
function onResults(results) {/*Segmentationの結果を利用する*/ };

29.
[beta]
javascriptを用いた初期化
window.onload = function() {
videoElm = document.getElementById('input_video'); //ビデオ要素の取得
canvasElm = document.getElementById('output_canvas'); //表示用のCanvasを取得
canvasCtx = canvasElm.getContext('2d’); //Canvas描画に関する情報にアクセス
//Segmentationを使用するための関連ファイルの取得と初期化
let selfieSegmentation = new SelfieSegmentation({locateFile: (file) => {
return `https://cdn.jsdelivr.net/npm/@mediapipe/selfie_segmentation/${file}`;
}});
//Segmentationで使う学習モデルを選択
selfieSegmentation.setOptions({ modelSelection: 0, });
//Segmentation結果を処理する関数を登録
selfieSegmentation.onResults(onResults);
//カメラの初期化
これの実現に使用
let
camera= new Camera(videoElm, {
onFrame: async () => {
await selfieSegmentation.send({image: videoElm});
詳細は後ほど実装
},
width: 640, height: 360
});
//カメラ動作開始
camera.start();
};
function onResults(results) {/*Segmentationの結果を利用する*/ };

30.
[beta]
javascriptを用いた初期化
window.onload = function() {
videoElm = document.getElementById('input_video'); //ビデオ要素の取得
canvasElm = document.getElementById('output_canvas'); //表示用のCanvasを取得
canvasCtx = canvasElm.getContext('2d’); //Canvas描画に関する情報にアクセス
//Segmentationを使用するための関連ファイルの取得と初期化
let selfieSegmentation = new SelfieSegmentation({locateFile: (file) => {
return `https://cdn.jsdelivr.net/npm/@mediapipe/selfie_segmentation/${file}`;
}});
//Segmentationで使う学習モデルを選択
selfieSegmentation.setOptions({ modelSelection: 0, });
//Segmentation結果を処理する関数を登録
videoElmの映像を
selfieSegmentation.onResults(onResults);
selfieSegmentationに渡す
//カメラの初期化
let camera= new Camera(videoElm, {
onFrame: async () => {
await selfieSegmentation.send({image: videoElm});
},
width: 640, height: 360
});
画像サイズはあまり大きくない
//カメラ動作開始
ように設定(for OpenCV)
camera.start();
};
function onResults(results) {/*Segmentationの結果を利用する*/ };

31.

ハンズオンの手順 1. テンプレートの概要を解説 2. SelfieSegmentation 3. OpenCVを使った画像処理 4. 結果の統合

32.

テンプレートの確認 認識結果の利用

33.

結果画像の表示 //Segmentationの結果を利用する function onResults(results) { //canvasのサイズを設定 if(!initialized){ initialized=true; //canvasのサイズは入力画像の2倍 (お好きなサイズでどうぞ) canvasElm.width=results.image.width*2; canvasElm.height=results.image.height*2; } canvasCtx.save(); //描画内容をクリア canvasCtx.clearRect(0, 0, canvasElm.width, canvasElm.height); //カメラ画像(results.image)をcanvasのサイズに引き伸ばして描画 canvasCtx.drawImage(results.image, 0, 0, canvasElm.width, canvasElm.height); canvasCtx.restore(); }; Lesson02

34.

動作確認 ①Show ②In a New Window

35.

動作確認

36.

Segmentation結果の表示 //Segmentationの結果を利用する function onResults(results) { //canvasのサイズを設定 if(!initialized){ initialized=true; //canvasのサイズは入力画像の2倍 (お好きなサイズでどうぞ) canvasElm.width=results.image.width*2; canvasElm.height=results.image.height*2; } imageを canvasCtx.save(); segmentationMaskに変更 //描画内容をクリア canvasCtx.clearRect(0, 0, canvasElm.width, canvasElm.height); //マスク画像をcanvasのサイズに引き伸ばして描画 canvasCtx.drawImage(results.segmentationMask, 0, 0, canvasElm.width, canvasElm.height); canvasCtx.restore(); }; Lesson03

37.

動作確認 人物領域は色がつく 背景領域は透過

38.
[beta]
人物領域にカメラ画像を描画
//canvasのサイズを設定
if(!initialized){
initialized=true;
canvasElm.width=results.image.width*2;
canvasElm.height=results.image.height*2;
}
canvasCtx.save();
//描画内容をクリア
canvasCtx.clearRect(0, 0, canvasElm.width, canvasElm.height);
//マスク画像をcanvasのサイズに引き伸ばして描画
canvasCtx.drawImage(results.segmentationMask, 0, 0,
canvasElm.width, canvasElm.height);
//不透明な領域に書き込み許可
canvasCtx.globalCompositeOperation = 'source-in’;
//カメラ画像を描画
canvasCtx.drawImage(results.image, 0, 0,
canvasElm.width, canvasElm.height);
canvasCtx.restore();
Lesson04

39.

動作確認 人物領域のみ書き込まれる 背景領域は透過

40.

背景の塗りつぶし canvasCtx.save(); //描画内容をクリア canvasCtx.clearRect(0, 0, canvasElm.width, canvasElm.height); //マスク画像をcanvasのサイズに引き伸ばして描画 canvasCtx.drawImage(results.segmentationMask, 0, 0, canvasElm.width, canvasElm.height); //不透明な領域に書き込み許可 canvasCtx.globalCompositeOperation = 'source-in'; //カメラ画像を描画 canvasCtx.drawImage(results.image, 0, 0, canvasElm.width, canvasElm.height); //透明な領域に書き込み許可 canvasCtx.globalCompositeOperation = 'destination-atop'; //背景色を緑(#00FF00)に設定し、canvasと同サイズの長方形(Rectangle)を描画 canvasCtx.fillStyle = '#00FF00'; canvasCtx.fillRect(0, 0, canvasElm.width, canvasElm.height); canvasCtx.restore(); Lesson05

41.

動作確認

42.

塗りつぶしとカメラ画像の入れ替え canvasCtx.save(); //描画内容をクリア canvasCtx.clearRect(0, 0, canvasElm.width, canvasElm.height); //マスク画像をcanvasのサイズに引き伸ばして描画 canvasCtx.drawImage(results.segmentationMask, 0, 0, canvasElm.width, canvasElm.height); //不透明な領域に書き込み許可 canvasCtx.globalCompositeOperation = 'source-in'; //カメラ画像を描画 canvasCtx.drawImage(results.image, 0, 0, canvasElm.width, canvasElm.height); //透明な領域に書き込み許可 canvasCtx.globalCompositeOperation = 'destination-atop'; //背景色を緑(#00FF00)に設定し、canvasと同サイズの長方形(Rectangle)を描画 canvasCtx.fillStyle = '#00FF00'; canvasCtx.fillRect(0, 0, canvasElm.width, canvasElm.height); canvasCtx.restore(); Lesson06

43.

塗りつぶしとカメラ画像の入れ替え canvasCtx.save(); //描画内容をクリア canvasCtx.clearRect(0, 0, canvasElm.width, canvasElm.height); //マスク画像をcanvasのサイズに引き伸ばして描画 canvasCtx.drawImage(results.segmentationMask, 0, 0, canvasElm.width, canvasElm.height); //不透明な領域に書き込み許可 canvasCtx.globalCompositeOperation = 'source-in'; //背景色を緑(#00FF00)に設定し、canvasと同サイズの長方形(Rectangle)を描画 canvasCtx.fillStyle = '#00FF00'; canvasCtx.fillRect(0, 0, canvasElm.width, canvasElm.height); //透明な領域に書き込み許可 canvasCtx.globalCompositeOperation = 'destination-atop'; //カメラ画像を描画 canvasCtx.drawImage(results.image, 0, 0, canvasElm.width, canvasElm.height); canvasCtx.restore(); Lesson06

44.

動作確認

45.

次のステップ ここに画像処理結果を描画

46.

ハンズオンの手順 1. テンプレートの概要を解説 2. SelfieSegmentation 3. OpenCVを使った画像処理 4. 結果の統合

47.

変数の追加

48.

変数の追加 let videoElm ; let canvasElm; let canvasCtx; let cvCanvasElm; //OpenCVで使う画像を表示 let cvCanvasCtx; //cVCanvasElmの描画に関する情報 let initialized=false; //初期化 window.onload = function() { //ビデオ要素の取得 videoElm = document.getElementById('input_video'); //表示用のCanvasを取得 canvasElm = document.getElementById('output_canvas'); //Canvas描画に関する情報にアクセス canvasCtx = canvasElm.getContext('2d'); //opencv処理結果表示用のCanvasと描画に関する情報を取得 cvCanvasElm = document.getElementById('opencv_canvas'); cvCanvasCtx= cvCanvasElm.getContext('2d’); /*スペーつの都合により省略*/ } Lesson07

49.

OpenCVと連携する準備 認識結果の利用

50.

OpenCVと連携する準備 //Segmentationの結果を利用する function onResults(results) { if(!initialized){ initialized=true; //canvasのサイズは入力画像の2倍 (お好きなサイズでどうぞ) canvasElm.width=results.image.width*2; canvasElm.height=results.image.height*2; //入力画像と同じサイズのcanvasを用意 cvCanvasElm.width=results.image.width; cvCanvasElm.height=results.image.height; } /*スペースの都合により省略*/ }; Lesson08

51.

OpenCVと連携する準備 function onResults(results) { if(!initialized){ initialized=true; //canvasのサイズは入力画像の2倍 (お好きなサイズでどうぞ) canvasElm.width=results.image.width*2; canvasElm.height=results.image.height*2; //入力画像と同じサイズのcanvasを用意 cvCanvasElm.width=results.image.width; cvCanvasElm.height=results.image.height; } //OpenCVを使った画像処理を行う関数 cvFilter(results); canvasCtx.save(); /*スペースの都合により省略*/ canvasCtx.restore(); }; //OpenCVを使った画像処理を行う関数の実装 function cvFilter(results){ }; Lesson09

52.

画像の表示 function cvFilter(results){ //入力画像をOpenCV用のcanvasに表示 cvCanvasCtx.drawImage(results.image, 0, 0); } Lesson10

53.

OpenCVの処理結果を表示 function cvFilter(results){ //入力画像をOpenCV用のcanvasに表示 cvCanvasCtx.drawImage(results.image, 0, 0); //cvCanvasElmでの表示画像をOpenCVに渡す(Mat形式) let src = cv.imread(cvCanvasElm); //処理結果を格納するMatを作成 let dst = new cv.Mat(); //カラー画像をグレーススケール画像に変換 cv.cvtColor(src, dst, cv.COLOR_RGB2GRAY, 0); //canvasでの表示のためカラー形式に戻す(見た目はグレーのまま) cv.cvtColor(dst, dst, cv.COLOR_GRAY2RGBA); //Matをcanvasで描画できるフォーマットに変換 let imgData = new ImageData( new Uint8ClampedArray(dst.data, dst.cols, dst.rows), dst.cols ,dst.rows); //画像を描画 cvCanvasCtx.putImageData(imgData,0,0); //Matのメモリを解放 src.delete(); dst.delete(); } Lesson11

54.

結果 グレーに変換された画像が表示される

55.

エッジ検出 //入力画像をOpenCV用のcanvasに表示 cvCanvasCtx.drawImage(results.image, 0, 0); //cvCanvasElmでの表示画像をOpenCVに渡す(Mat形式) let src = cv.imread(cvCanvasElm); //処理結果を格納するMatを作成 let dst = new cv.Mat(); //カラー画像をグレーススケール画像に変換 cv.cvtColor(src, dst, cv.COLOR_RGB2GRAY, 0); //Cannyフィルタを用いたエッジ検出 cv.Canny(dst, dst, 50, 90, 3, false); //canvasでの表示のためカラー形式に戻す(見た目はグレーのまま) cv.cvtColor(dst, dst, cv.COLOR_GRAY2RGBA); //Matをcanvasで描画できるフォーマットに変換 let imgData = new ImageData( new Uint8ClampedArray(dst.data, dst.cols, dst.rows), dst.cols ,dst.rows); //画像を描画 cvCanvasCtx.putImageData(imgData,0,0); //Matのメモリを解放 src.delete(); dst.delete(); Lesson12

56.

動作確認 線画が表示される

57.

Canny Filter cv.Canny(source, result, minVal, maxVal, 3, false); 隣接ピクセルとの濃淡差をチェックし、ある値(=maxVal)以上ならエッジとみなす。 ただし、少しでも下回ったらエッジと判定されなくなると連続性が低下するので、 隣接するピクセルがエッジと判断され、かつminVal以上なら引き続きエッジとする。 隣のピクセルとの濃淡差 255 0 https://docs.opencv.org/3.4.15/d7/de1/tutorial_js_canny.html

58.

白黒反転 //cvCanvasElmでの表示画像をOpenCVに渡す(Mat形式) let src = cv.imread(cvCanvasElm); //処理結果を格納するMatを作成 let dst = new cv.Mat(); //カラー画像をグレーススケール画像に変換 cv.cvtColor(src, dst, cv.COLOR_RGB2GRAY, 0); //Cannyフィルタを用いたエッジ検出 cv.Canny(dst, dst, 50, 90, 3, false); //白黒反転 cv.bitwise_not(dst,dst); //canvasでの表示のためカラー形式に戻す(見た目はグレーのまま) cv.cvtColor(dst, dst, cv.COLOR_GRAY2RGBA); //Matをcanvasで描画できるフォーマットに変換 let imgData = new ImageData( new Uint8ClampedArray(dst.data, dst.cols, dst.rows), dst.cols ,dst.rows); //画像を描画 cvCanvasCtx.putImageData(imgData,0,0); //Matのメモリを解放 src.delete(); dst.delete(); Lesson13

59.

動作確認 白黒反転した線画が表示される

60.

ハンズオンの手順 1. テンプレートの概要を解説 2. SelfieSegmentation 3. OpenCVを使った画像処理 4. 結果の統合

61.
[beta]
画像処理結果を人物領域に描き込む
function onResults(results) {
if(!initialized){
initialized=true;
canvasElm.width=results.image.width*2;
canvasElm.height=results.image.height*2;
cvCanvasElm.width=results.image.width;
cvCanvasElm.height=results.image.height;
}
cvFilter(results);
canvasCtx.save();
canvasCtx.clearRect(0, 0, canvasElm.width, canvasElm.height);
canvasCtx.drawImage(results.segmentationMask, 0, 0,
canvasElm.width, canvasElm.height);
//不透明な領域に書き込み許可
canvasCtx.globalCompositeOperation = 'source-in';
//canvasCtx.fillStyle = '#00FF00';
//canvasCtx.fillRect(0, 0, canvasElm.width, canvasElm.height);

塗りつぶしに関する2行をコメントアウト

62.
[beta]
結果画像の表示
function onResults(results) {
if(!initialized){
initialized=true;
canvasElm.width=results.image.width*2;
canvasElm.height=results.image.height*2;
cvCanvasElm.width=results.image.width;
cvCanvasElm.height=results.image.height;
}
cvFilter(results);
canvasCtx.save();
canvasCtx.clearRect(0, 0, canvasElm.width, canvasElm.height);
canvasCtx.drawImage(results.segmentationMask, 0, 0,
canvasElm.width, canvasElm.height);
//不透明な領域に書き込み許可
canvasCtx.globalCompositeOperation = 'source-in';
//canvasCtx.fillStyle = '#00FF00’;
//canvasCtx.fillRect(0, 0, canvasElm.width, canvasElm.height);
//OpenCV用のcanvasの表示内容を書き込む
canvasCtx.drawImage(cvCanvasElm, 0, 0,
canvasElm.width, canvasElm.height);

Lesson14

63.

動作確認 白黒反転した線画が表示される

64.

OpenCVの領域の不可視化 <canvas id="opencv_canvas" style="position:absolute;display:none;"></canvas> Lesson15

65.

参考 公式ページ https://google.github.io/mediapipe/solutions/selfie_s egmentation globalCompositeOperation https://developer.mozilla.org/enUS/docs/Web/API/CanvasRenderingContext2D/global CompositeOperation drawImage https://developer.mozilla.org/enUS/docs/Web/API/CanvasRenderingContext2D/drawI mage