p5.js の処理で既存の videoタグに表示されたカメラ画像を取り込む【drawingContext を利用】


タイトルの通りの内容の記事で、p5.js をお使いの方なら「createCapture() を使えば良いのでは?」と感じる話かと思います。

これをやるモチベーションは、「videoタグを使ったカメラ入力の処理 + それに関わる色々な処理」を持っている既存の Webアプリや仕組みを流用し、それと p5.js とを組み合わせたいという時のものです。
もう少し補足すると、既存の仕組みの「videoタグを使ったカメラ入力の処理」を、p5.js の createCapture() に置きかえるのが難しそうな場合に、入力は既存の仕組みのものを使う構成にするのを想定したものです。例えば、既存の仕組みがカメラ画像を扱う部分を独自のライブラリの処理に内包していて、videoタグに出力される部分であれば容易に取得できる、という場合などです。

将来の自分に向けてのメモ、という感じで記事にしています。

試す環境

今回の内容は、自分が以前書いた p5.js関連の記事と同様に「p5.js Web Editor」を使います。

カメラ画像をキャンバスに表示する

p5.js でシンプルに実装した場合

冒頭にも書いていた「createCapture() を使った、p5.js における一般的なカメラ画像取得の処理をまずは書いてみます。

let capture;

function setup() {
  createCanvas(500, 400);
  capture = createCapture(VIDEO);
  capture.size(width, height);
  capture.hide();
}

function draw() {
  image(capture, 0, 0, width, height);
}

MediaDevices.getUserMedia() を使った形(p5.js の処理は空)

今回のお試しをするため、上記の「videoタグを使ったカメラ入力の処理」に相当するもので、カメラ画像処理周りを簡単化したものを p5.js Web Editor上に準備します。
カメラ入力の処理に使うのは、MediaDevices.getUserMedia() を使ったものになります。

p5.js Web Editor のデフォルトの HTMLファイルには videoタグがないので、HTMLファイル内の bodyタグで sketch.js を読み込んでいる部分に、以下のように videoタグを追加します(id も付与しました)。

  <body>
    <video id="input_video"></video>
    <script src="sketch.js"></script>
  </body>

そして、sketch.js の側では以下のような処理を書きます。
ひとまず p5.js の処理は何も行わず、p5.js の外の部分でカメラ画像の取得と表示を行います。

const video = document.getElementById("input_video"),
  constraints = {
    audio: false,
    video: true,
  };

navigator.mediaDevices
  .getUserMedia(constraints)
  .then(function (stream) {
    video.srcObject = stream;
    video.onloadedmetadata = function (e) {
      video.play();
    };
  })
  .catch(function (err) {
    console.log(err.name + ": " + err.message);
  });

function setup() {
  noCanvas();
  noLoop();
}

function draw() {
}

p5.js Web Editor上に、videoタグを用いたカメラ画像表示が行えるのが確認できました。

videoタグの映像と p5.js のキャンバスに取り込む

ここが今回のゴールにあたる部分です。

ソースコードの内容

上の「MediaDevices.getUserMedia() を使った形」の時には videoタグにカメラ画像を表示させていたのを、 style.display = "none" で表示させないようにして、カメラ画像は p5.js のキャンバスに表示させるようにします。

HTMLファイルは、上で用いていたものと同じ、videoタグを追加したものを用います。
そして sketch.js の内容は以下の通りです。

const video = document.getElementById("input_video"),
  constraints = {
    audio: false,
    video: true,
  };
video.style.display = "none";

navigator.mediaDevices
  .getUserMedia(constraints)
  .then(function (stream) {
    video.srcObject = stream;
    video.onloadedmetadata = function (e) {
      video.play();
    };
  })
  .catch(function (err) {
    console.log(err.name + ": " + err.message);
  });

let videoImage;

function setup() {
  createCanvas(500, 400);
  videoImage = createGraphics(width, height);
}

function draw() {
  background(220);

  videoImage.drawingContext.drawImage(video, 0, 0);
  image(videoImage, 0, 0);
}

videoタグ周りの処理は、 video.style.display = "none" の部分を加えたくらいで、先ほどの事例と違ってくるのは、p5.js の処理周りの部分です。

ポイントになるのは drawingContext.drawImage(video, 0, 0) の部分になります。

●reference | drawingContext
 https://p5js.org/reference/#/p5/drawingContext

createCapture() を使った実装であれば、 image() の 1つ目のパラメータに capture = createCapture(VIDEO) などとした中の capture を指定すれば OK ですが、既存の videoタグが使われている場合に似たような書き方をすることはできません。

これは image() のパラメータの 1つ目に用いることができるのが「p5.Image か p5.Element」になるためです。

●reference | image()
 https://p5js.org/reference/#/p5/image

そこで、キャンバスへの出力に CanvasRenderingContext2D.drawImage() を使います。
こちらは 1つ目のパラメータに「HTMLVideoElement」を指定することができます。

●CanvasRenderingContext2D.drawImage() - Web APIs | MDN
 https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage

そして、p5.js のキャンバスで CanvasRenderingContext2D.drawImage() が直接は扱えないので、先ほど登場した drawingContext を介して処理を行います。
ちなみに、取り込んだ画像を処理してからキャンバスに出力する、というような将来的な処理の流れを想定して createGraphics() を使っていますが、ここは createCanvas() で作ったキャンバスに直接出力するする形にしても OK です。

おわりに

冒頭に書いていた、「videoタグを使ったカメラ入力の処理 + それに関わる色々な処理」を持っている既存の Webアプリや仕組みを流用し、それと p5.js とを組み合わせたいという時、という話については、別で記事にしようと思います。

【追記】 やりたかったこと

上記の流用したかった仕組みは、Googleさんの MediaPipe の JavaScript版でした。
それについて、以下の記事を書きました。

●【Processing 2021】 #p5js で #MediaPipe (JavaScript版)を使った高精度な認識を利用する - Qiita
 https://qiita.com/youtoy/items/70571c7066d41729c2bd