zoomのバーチャル背景がバズっていますが、WebRTCでも顔認識や体の認識ができるOSSがあるので試してみました。
今回は顔だけ認識させて、顔の部分だけ映像として生成するサンプルになります。
ブラウザで顔+体を認識させるには処理能力的に限界がありそうで、実質顔だけ認識させるのが現実的な実装のように思います。
利用するライブラリ
今回、顔の認識についてはpico.jsを利用しました。
その他にはclmtrackrもよく知られているようです。
完成イメージ
canvasの背景として画像をセットし、顔認識できた範囲に対して円でくり抜きをしています。
サンプルコードはこちらに公開しています。
コードの説明
主なポイントは4つです。
・Webカメラの映像をcanvasに描画
・カメラ映像を解析
・顔の範囲を円でくり抜き
・配信する映像ソースとして設定
Webカメラの映像をcanvasに描画
navigator.mediaDevices.getUserMedia({ auduo: false, video: { width: videoSource.width, height: videoSource.height } }).then(stream => {
videoSource.srcObject = stream;
videoSource.onloadedmetadata = function () {
drawLoop();
};
});
//中略
context.globalCompositeOperation = 'source-over';
context.drawImage(videoSource, 0, 0, videoSource.width, videoSource.height);
WebRTCではおなじみのgetUserMediaでカメラ映像を取得し、canvasに描画します。
カメラ映像を解析
dets = pico.run_cascade(image, facefinder_classify_region, params);
dets = update_memory(dets);
dets = pico.cluster_detections(dets, 0.2);
このあたりは本家のコードをそのまま利用しています。
顔の範囲を円でくり抜き
context.globalCompositeOperation = 'destination-in';
if(dets.length >= 1 && dets[0][3] > qthresh){
dx = dets[0][1];
dy = dets[0][0];
radius = dets[0][2]/2*0.8;//積極的に背景を隠す
context.beginPath();
context.arc(dx, dy, radius, 0, Math.PI*2, false);
context.fill();
}else{
context.beginPath();
context.arc(dx, dy, radius, 0, Math.PI*2, false);
context.fill();
}
destination-inは「重なった領域のみが描画される」という設定になります。
又、背景を隠すという意味で円の半径も解析値より0.8倍程度に補正をかけています。
配信する映像ソースとして設定
rtc.maskedStream = canvas.captureStream(15);
//中略
rtc.localStream = AgoraRTC.createStream({
streamID: rtc.params.uid,
audio: true,
video: true,
screen: false,
microphoneId: option.microphoneId,
videoSource: rtc.maskedStream.getVideoTracks()[0]
//cameraId: option.cameraId
})
こちらはagoa.io SDKのAPI部分になります。canvasのストリームを取得して、videoSourceのパラメータとして設定しています。
微妙に残った課題
今回の実装方法では背景画像について送信するストリームに乗ってきません。実際、もう少しcanvas部分に手を加えれば可能かもしれません。
その他の方法としては利用している背景画像のURLを相手にも通知してcssで表示させるというやり方になります。