p5.js Web Editor で opencv.js を動的にロードして利用する

以下のツイートで掲載していたように、以前、[p5.js Web Editor](https://editor.p5js.org/) で [opencv.js](https://docs.opencv.org/4.7.0/df/d0a/tutorial_js_intro.html) を利用する、ということをやったのですが、その時に行った内容の記事化です（[大晦日ハッカソン2022](https://omisoka-hackathon.connpass.com/event/269353/) 絡みで、この内容を活用いただけそうだったため、記事化し忘れていたのを書いて公開）。

https://twitter.com/youtoy/status/1554509591505141765

前に試した時からライブラリのバージョンがあがっていました。そのため、今回は以下の最新版にしてから動作を確認しています。

- p5.js 1.5.0
- opencv.js 4.7.0

## ソースコードの内容など
p5.js Web Editor で opencv.js を使うために過去に試行錯誤して、sketch.js のみに手を加えるやり方で動作をさせることができていました。

その時に作ったプログラムを、以下に掲載します。
ちなみに、index.html と style.css は、p5.js Web Editor が生成するデフォルトの内容そのままで OK です。

なお、画像処理部分は冒頭のツイートのものよりシンプルな内容にしていて、グレースケール変換のみ行っています。

```javascript:sketch.js
let canvas, pg, capture;
let cvFlag = false;

function setup() {
  canvas = createCanvas(560, 420);

  pg = createGraphics(width, height);

  capture = createCapture(VIDEO);
  capture.size(640, 480);
  capture.hide();
}

function draw() {
  background(120);
  pg.image(capture, 0, 0, width, height);

  if (cvFlag) {
    let src = cv.imread(pg.elt);
    let dst = new cv.Mat();
    cv.cvtColor(src, dst, cv.COLOR_RGBA2GRAY, 0);
    cv.imshow(canvas.elt, dst);
    src.delete();
    dst.delete();
  }
}

window.addEventListener("load", (event) => {
  console.log("The page has fully loaded");

  let script = document.createElement("script");
  script.addEventListener("load", (event) => {
    console.log("opencv.js file has been loaded");
    cv["onRuntimeInitialized"] = () => {
      console.log("onRuntimeInitialized");
      cvFlag = true;
    };
  });

  script.src = "https://docs.opencv.org/4.7.0/opencv.js";
  document.body.appendChild(script);
});
```

上記を実行して、opencv.js を使ってグレースケール化された画像が、p5.js Web Editor上のキャンバスに描画されたのを確認できました。

<blockquote class="twitter-tweet" data-conversation="none"><p lang="ja" dir="ltr">p5.js と opencv.js を当時使っていたバージョンから、最新に変更しただけで、当時作成していたソースコードはいじることなく無事に動いた<a href="https://twitter.com/hashtag/p5js?src=hash&amp;ref_src=twsrc%5Etfw">#p5js</a> <a href="https://twitter.com/hashtag/OpenCV?src=hash&amp;ref_src=twsrc%5Etfw">#OpenCV</a> <a href="https://twitter.com/hashtag/%E5%A4%A7%E6%99%A6%E6%97%A5%E3%83%8F%E3%83%83%E3%82%AB%E3%82%BD%E3%83%B3?src=hash&amp;ref_src=twsrc%5Etfw">#大晦日ハッカソン</a> <a href="https://t.co/ErUbnVWThm">pic.twitter.com/ErUbnVWThm</a></p>&mdash; you (@youtoy) <a href="https://twitter.com/youtoy/status/1609016941620137984?ref_src=twsrc%5Etfw">December 31, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

### プログラムに関する補足
上で掲載していたプログラムについて、少し補足をします。

#### cv["onRuntimeInitialized"] を利用する部分
過去に、 `cv["onRuntimeInitialized"]` を使った処理と、p5.js の処理を組み合わせてうまく動かすのに、試行錯誤していたのがあります（かなり、記憶が曖昧になってる...）。

ソースコード内に以下の記事のリンクをメモしていたので、こちらが、当時の試行錯誤で参照した記事の一つとなるようでした。

●OpenCV.jsとWebカメラ画像表示 - Qiita
　https://qiita.com/mktshhr/items/ac6351dd056e571715e0

`cv["onRuntimeInitialized"]` を利用する部分は、上記の記事内では、以下の WASM を使う分岐の処理で掲載されていたものです。
![cv["onRuntimeInitialized"] を利用する部分](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/50868/f6bc5470-3356-9b12-8cb5-f098e33911c6.png)

#### カメラ画像の利用と画像の入出力
カメラ画像は、p5.js の [createCapture()](https://p5js.org/reference/#/p5/createCapture) で取得したものを使っています。

そのカメラ画像を、 `createGraphics()` で作った `pg` に貼り付けてから、opencv.js に渡すようにしていて、メインのキャンバスとは分離して処理しています。その部分について、 `cv.imread()` には、HTML の要素を渡す形になるようだったので、[p5.Element](https://p5js.org/reference/#/p5.Element) の [elt](https://p5js.org/reference/#/p5.Element/elt) を渡す形にしています。

その処理の後、ページ上へ opencv.js での処理結果を描画する際にも、描画先は HTML要素を指定する形になるようでした。それで、メインのキャンバスの elt を指定した `cv.imshow(canvas.elt, dst);` という形にしています。

#### opencv.js の読み込み待ち
opencv.js の読み込みは、少し時間がかかるので、 `cvFlag` というフラグの変数を使い、読み込みが完了したかどうかを判定しています。
具体的には以下の部分です。p5.js の draw() が、opencv.js の読み込みを待たず動いていますが、draw() の中の opencv.js を使った処理は、以下のように opencv.js の読み込み完了後に行われるようにしています。

```javascript
  if (cvFlag) {
    let src = cv.imread(pg.elt);
    let dst = new cv.Mat();
    cv.cvtColor(src, dst, cv.COLOR_RGBA2GRAY, 0);
    cv.imshow(canvas.elt, dst);
    src.delete();
    dst.delete();
  }
```