今回はWebカメラやラップトップパソコンのカメラを使って映像を取得し、適当な画像処理フィルターをかけ、ブラウザに表示する方法をまとめる。(筆者環境はMBP)
基本: カメラの映像をブラウザに表示する
単純に表示するだけの場合は、video要素(HTMLMediaElement)とMediaStreamを使う。
<video id="camera"></video>
async function main() {
const video = document.querySelector("video#camera");
// MediaStreamを取得する(Promiseが返る)
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
video.srcObject = stream;
// MediaStreamが取得できたら再生する
video.onloadedmetadata = () => video.play();
}
main();
今回は映像だけを使いたいので、mediaDevices.getUserMedia
に{video: true}
を指定したが、音声も使いたい場合は{video: true, audio: true}
のように指定する。詳細はMediaDevices.getUserMediaを参照してください。
※ 執筆時点では日本語ドキュメントが古いため、英語ドキュメントを読んだほうが良い。
カメラの映像にリアルタイムにフィルターをかける
画像処理フィルターをかけるために、video要素に直接表示するのではなくCanvasを使う。
詳しく説明すると以下のような流れになる。
- MediaStreamを使ってカメラから映像を取得する
- 一定間隔(60fpsなど)で映像をCanvasに描画する
- このときに直接ブラウザに表示するCanvasに描画するのではなくオフスクリーンCanvasを使うと高速化が図れる
- オフスクリーンを使わないと1フレームに2回描画されるため
- オフスクリーンCanvasに画像処理フィルターをかける
- ブラウザ表示用のCanvasに描画する
以降、具体的な実装方法を説明する。
Canvasを使ってカメラの映像を表示する
<canvas id="canvas"></canvas>
async function main() {
// 表示用のCanvas
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
// 画像処理用のオフスクリーンCanvas
const offscreen = document.createElement("canvas");
const offscreenCtx = offscreen.getContext("2d");
// カメラから映像を取得するためのvideo要素
const video = document.createElement("video");
const stream = await navigator.mediaDevices.getUserMedia({
video: true
});
video.srcObject = stream;
// streamの読み込み完了
video.onloadedmetadata = () => {
video.play();
// Canvasのサイズを映像に合わせる
canvas.width = offscreen.width = video.videoWidth;
canvas.height = offscreen.height = video.videoHeight;
tick();
};
// 1フレームごとに呼び出される処理
function tick() {
// カメラの映像をCanvasに描画する
offscreenCtx.drawImage(video, 0, 0);
// イメージデータを取得する([r,g,b,a,r,g,b,a,...]のように1次元配列で取得できる)
const imageData = offscreenCtx.getImageData(0, 0, offscreen.width, offscreen.height);
// imageData.dataはreadonlyなのでfilterメソッドで直接書き換える
filter(imageData.data);
// オフスクリーンCanvasを更新する
offscreenCtx.putImageData(imageData, 0, 0);
// 表示用Canvasに描画する
ctx.drawImage(offscreen, 0, 0);
// 次フレームを処理する
window.requestAnimationFrame(tick);
}
function filter(data) {
// 画像処理を行う
}
}
main();
動画や音声のメタデータの読み込みが終わったら、
onloadedmetadata
イベントでvideo.play()
で再生する。このとき、Canvasのサイズを設定する。注意事項としてwidth
、height
はvideo要素のサイズなので、映像自体のサイズを取得するときは、videoWidth
、videoHeight
プロパティを見る必要がある。Canvasのサイズ指定が終わったら
tick
メソッドを呼び出す。tickメソッド内では、毎フレーム、オフスクリーンCanvasに映像を描画→画像処理フィルタ→表示用Canvasに描画をする。このとき
requestAnimationFrame
を使って約60fpsで実行する。getImageData
メソッドを使って画像情報を取得する。中身は[r,g,b,a,r,g,b,a,....]
と画像の左上から1ピクセルずつRGBA(Red, Green, Blue, Alpha:透過)がフラットに一次元配列に格納されている。この値を書き換えて、再度Canvasに描画し直すことで画像処理フィルターをかけることができる。オフスクリーンCanvasの具体的な使い方は、以下の記事を参照してほしい。
filterメソッド内で画像処理を行うのだが、具体的な実装方法やアルゴリズムは以下の記事にまとめているので参照してほしい。
そんなこんなで実際にfilter部分をいい感じに実装すると以下のようなことができる。
昨日作った映像をリアルタイムにピクセル化するやつを改良してみた!ちなみにこのgifは8色に減色してる。 pic.twitter.com/3EgBnn6052— ダーシノ (@bc_rikko) 2018年12月28日
以上
written by @bc_rikko
0 件のコメント :
コメントを投稿