AI x OpenCV x WebAR
SelfieSegmentationを使ってみよう
もろもろのダウンロード
http://arfukuoka.lolipop.jp/selfie_segmen
tation/sample.zip
自己紹介
氏名:吉永崇(Takashi Yoshinaga)
専門:ARを用いた医療支援や運動計測
Volumetric Video
コミュニティ:ARコンテンツ作成勉強会 主催
ARコンテンツ作成勉強会の紹介
 2013年5月に勉強会をスタート。
 ARコンテンツの作り方をハンズオン形式で学ぶ
 人数は5~10名程度の少人数で実施
 参加条件はAR/VRに興味がある人(知識不要)
 各地で開催 (福岡、熊本、宮崎、長崎、大分、 鹿児島、山口、広島、札幌、関東)
Twitterと勉強会ページで情報を発信しています
@AR_Fukuoka Googleで「AR勉強会」で検索
#AR_Fukuoka
ハッシュタグ
本題に入ります
本日のゴール
MediaPipeのSelfieSegmentationとOpenCVによる画像処理で遊ぶ
https://youtu.be/Lj64eMZeYVE
テンプレートの複製
https://glitch.com/~selfie-seg-template
GET STARTED
テンプレートの複製
Remix Your Own
テンプレートの確認
index.htmlをクリックし、コードが表示されることを確認
index.html
index.htmlをクリックし、コードが表示されることを確認
テンプレートの確認
エディタ プレビュー
index.htmlをクリックし、コードが表示されることを確認
テンプレートの確認
プレビューを閉じる
ハンズオンの手順
1. テンプレートの概要を解説 2. SelfieSegmentation
3. OpenCVを使った画像処理 4. 結果の統合
ハンズオンの手順
2. SelfieSegmentation
3. OpenCVを使った画像処理 4. 結果の統合
1. テンプレートの概要を解説
テンプレートの確認
Lesson01
テンプレートの確認
ライブラリの
読み込み
MediaPipeや
OpenCVでの
処理を記述
(今日のメイン)
描画領域等
テンプレートの確認
描画領域等
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>
input_video input_video
output_canvas output_canvas
input_video
opencv_canvas
テンプレートの確認
ライブラリの
読み込み
ライブラリ読み込みの解説
<!--① 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
テンプレートの確認
MediaPipeや
OpenCVでの
処理を記述
(今日のメイン)
テンプレートの確認
テンプレートの確認
変数宣言
初期化
描画領域/カメラ/
Segmentation
認識結果の利用
テンプレートの確認
変数宣言
テンプレートの確認
初期化
描画領域/カメラ/
Segmentation
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の結果を利用する*/ };
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の結果を利用する*/ };
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の結果を利用する*/ };
これの実現に使用
詳細は後ほど実装
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の結果を利用する*/ };
videoElmの映像を
selfieSegmentationに渡す
画像サイズはあまり大きくない
ように設定(for OpenCV)
ハンズオンの手順
1. テンプレートの概要を解説
3. OpenCVを使った画像処理 4. 結果の統合
2. SelfieSegmentation
テンプレートの確認
認識結果の利用
結果画像の表示
//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
動作確認
①Show
②In a New Window
動作確認
Segmentation結果の表示
Lesson03
//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);
//マスク画像をcanvasのサイズに引き伸ばして描画
canvasCtx.drawImage(results.segmentationMask, 0, 0,
canvasElm.width, canvasElm.height);
canvasCtx.restore();
};
imageを
segmentationMaskに変更
動作確認
人物領域は色がつく
背景領域は透過
人物領域にカメラ画像を描画
//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
動作確認
人物領域のみ書き込まれる
背景領域は透過
背景の塗りつぶし
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
動作確認
塗りつぶしとカメラ画像の入れ替え
Lesson06
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
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();
動作確認
次のステップ
ここに画像処理結果を描画
ハンズオンの手順
1. テンプレートの概要を解説 2. SelfieSegmentation
4. 結果の統合
3. OpenCVを使った画像処理
変数の追加
変数の追加
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
OpenCVと連携する準備
認識結果の利用
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
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
画像の表示
function cvFilter(results){
//入力画像をOpenCV用のcanvasに表示
cvCanvasCtx.drawImage(results.image, 0, 0);
}
Lesson10
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
結果
グレーに変換された画像が表示される
エッジ検出
Lesson12
//入力画像を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();
動作確認
線画が表示される
Canny Filter
cv.Canny(source, result, minVal, maxVal, 3, false);
隣のピクセルとの濃淡差
https://docs.opencv.org/3.4.15/d7/de1/tutorial_js_canny.html
隣接ピクセルとの濃淡差をチェックし、ある値(=maxVal)以上ならエッジとみなす。
ただし、少しでも下回ったらエッジと判定されなくなると連続性が低下するので、
隣接するピクセルがエッジと判断され、かつminVal以上なら引き続きエッジとする。
0
255
白黒反転
Lesson13
//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();
動作確認
白黒反転した線画が表示される
ハンズオンの手順
1. テンプレートの概要を解説 2. SelfieSegmentation
3. OpenCVを使った画像処理 4. 結果の統合
画像処理結果を人物領域に描き込む
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行をコメントアウト
結果画像の表示
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
動作確認
白黒反転した線画が表示される
OpenCVの領域の不可視化
<canvas id="opencv_canvas" style="position:absolute;display:none;"></canvas>
Lesson15
参考
公式ページ
https://google.github.io/mediapipe/solutions/selfie_s
egmentation
globalCompositeOperation
https://developer.mozilla.org/en-
US/docs/Web/API/CanvasRenderingContext2D/global
CompositeOperation
drawImage
https://developer.mozilla.org/en-
US/docs/Web/API/CanvasRenderingContext2D/drawI
mage
AI x OpenCV x WebAR: Selfie Segmentationを使ってみよう

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