1
2

opencv.js入門。画像データの入力、出力、変換から

Last updated at Posted at 2024-09-17

はじめに

opencvは画像処理に使うライブラリーであり、本来主にC++とPythonで使われるためですが、実は「opencv.js」というJavaScriptで使えるopencvも、あまり知られていないようですが、存在しています。

opencvはPythonで書いた方が一番使いやすくて使い勝手がいいのですが、ウェブアプリを作る場合ブラウザで走らせる場合も多くて、その時JavaScriptで書く必要があります。opencv.jsを使うことでサーバーに画像データを送る必要せずに、ブラウザ上だけで画像処理することができて便利です。

ただしPythonのopencvと比べて書き方は色々違って複雑なところがあって使いやすいというわけではないようです。今までずっとPythonでopencvを使っていても、opencv.jsを使おうとしたら、色々勉強し直さなければならないことが意外と沢山あります。

問題は、今でもopencv.js関連の情報はまだ少なくて、検索してもやはPythonのopencvの方ばかり出てきます。

その中で一冊書籍があります。丁寧に説明してあるので、これを読んでとても参考になりました。基礎からきちんと勉強したい人にはおすすめです。

今回の記事はこの本で読んだ内容と更に自分で調べた知識を纏めて書いたopencv.js入門となります。

ただしopencvの機能を紹介するのではなく、「opencvがどうやってJavaScriptで導入する」がメインです。これがわかったら後はPython版やC++版の資料を読んでもopencv.jsでできるはずです。

opencv.jsを使う時一番Pythonと違うのは、入力、出力、変換など、基本的な画像データの取り扱いです。だからこの入門はこれをメインとして説明します。

導入と前提

導入し方それぞれ

opencv.jsを導入する方法は色んな方法があります。基本的に他のJavaScriptと同じです。ただしファイルサイズは10MB以上も大きいので、軽量化でビルトする方法を使う人もいます。でも私は普通にそのまま全部読み込んでも特に重いと感じないのでそこまで必要ないと思います。だから簡単にライブラリー全体を読み込んですぐ使います。

コードはopencvの公式サイトに載っているので、これを直接読み込んで

今この記事を書いた時点一番新しいバージョンは4.10.0です。URLは:

https://docs.opencv.org/4.10.0/opencv.js

これを<script>タグで読み込んだらすぐ使えます。勿論実際に使う時はこのファイルをダウンロードして自分のサイトに置いた方がいいですが、この記事のコードはすぐ簡単に使えるようにこのままこれを使います。

又、npmやyarnを使う場合、@techstark/opencv-jsというパッケージがあって、これでインストールできます。TypeScriptで使うこともできます。

npm i @techstark/opencv-js

opencv.jsスクリプトを読み込み

ではまず試してみます。このようにコードを書いてブラウザで開けます。

index.html
<script src="https://docs.opencv.org/4.10.0/opencv.js"></script>
<script>
console.log(cv);
</script>

そしてコンソールにこのように表示されたら読み込み成功でしょう。

Object { HEAP8: Int8Array(134217728), HEAP16: Int16Array(67108864), HEAP32: Int32Array(33554432), HEAPU8: Uint8Array(134217728), HEAPU16: Uint16Array(67108864), HEAPU32: Uint32Array(33554432), HEAPF32: Float32Array(33554432), HEAPF64: Float64Array(16777216), preloadedImages: {}, preloadedAudios: {},  }

ただし普段読み込んですぐその中の機能が呼び出せるようになるわけではないので、その時点でまだ使えません。

全部の機能の準備が整って使えるようになったらすぐ実行するコードがあれば、このようにonRuntimeInitialized関数として定義するという方法もあります。

<script async src="https://docs.opencv.org/4.10.0/opencv.js"></script>
<script>
let Module = {
  onRuntimeInitialized() {
    let mat = new cv.Mat(); // 行列オブジェクトを作成
    console.log(mat.size()); // サイズを出力してみる
  },
};
</script>

コンソールでの出力。

Object { width: 0, height: 0 }

cv.Matはopencv.jsにおいて行列として画像データを扱うためのオブジェクトです。.size()は行列のサイズを返すメソッドですが、ここでただの空っぽな行列を作ったので0、0になります。

onRuntimeInitializedに関してはemscriptenの仕様です。詳しく説明は割愛します。

これがわかりにくいので、今回の記事ではこのように書くことにします。

<button onclick="jikkou()">実行</button>
<script async src="https://docs.opencv.org/4.10.0/opencv.js"></script>
<script>
function jikkou() {
  let mat = new cv.Mat();
  console.log(mat.size());
}
</script>

単に関数を作って、ボタンを押して起動するという形です。

jikkou_botan.png

CORS問題の対策のためサーバーを立てる

ブラウザ上で画像処理をするためにはもう一つ課題があります。これはopencv.jsだけの問題ではないのですが、普段画像データを読み込む時によくCORS(Cross-origin resource sharing)問題にぶつかります。他のウェブサイトからデータを読み込む時だけでなく、たとえ自分のパソコンにあるデータを読み込もうとする場合も駄目です。面倒ですが、これはセキュリティのためですね。

だから画像を読み込むためには、自分でサーバーを立てて画像もウェブページもその中に置く必要があります。サーバーで起動したウェブページならそのサーバーに置いた画像などのファイルを読み込むことができます。

方法サーバーを簡単に立てる方法は色々ありますが、私がいつも使っているのはFastAPIです。他の方法を使っている人やPythonを使っていない人は他の方法を使ってもいいです。ここでは一応FastAPIで使う方法を手短に説明します。

まずライブラリーのインストールです。

pip install fastapi uvicorn

そして、あるフォルダを作って、その中で例えばこのようにファイルを入れます。

web.py
index.html
gazou.jpg

gazou.jpgは処理したい画像ファイルで、index.htmlは今回opencv.jsコードを書くウェブページです。

そしてweb.pyはサーバーを起動するPythonコードで、こう書きます。

web.py
from fastapi import FastAPI,staticfiles

app = FastAPI()
app.mount('/',staticfiles.StaticFiles(directory='.',html=True))

そしてコマンドラインでこれを実行したらすぐ使えます。

uvicorn web:app

これでindex.htmlで書いたJavaScriptのコードはgazou.jpgや他にこのフォルダに置いたファイルを読み込むことができるようになります。

関わるオブジェクトの種類

ブラウザ上で画像を扱う時に関わる可能性があるデータやオブジェクトの種類は色々ありますので、ここでひとまず纏めておきます。詳しくは後で説明していきます。

オブジェクト JavaScriptクラス
imgタグ HTMLImageElement
canvasタグ HTMLCanvasElement
canvas2Dコンテキスト CanvasRenderingContext2D
canvas上の画像データ ImageData
データURL string
blob Blob、File
opencv.jsの行列 cv.Mat

その中でcv.Mat以外はJavaScriptの既存のクラスで、opencv.jsを使わなくても普段使えます。

行列オブジェクトMatに読み込む方法

画像データは色んな形で現れますが、opencv.jsで画像データを扱う際まず行列として読み込む必要があります。だからここではまず既存の画像を行列に読み込む色んな方法について説明します。

imgタグから画像読み込み

opencv.jsにはimread()という関数があって、これを使ったら簡単にimgタグの中の画像を読み込めます。(ただし上述のCORS問題対策は忘れてはならない)

例えばウェブページの中にgazou.jpgという画像があって、隣に実行するためのボタンがある例です。

<img id="img" src="gazou.jpg">
<button onclick="jikkou()">実行</button>
<script src="https://docs.opencv.org/4.10.0/opencv.js"></script>
<script>
function jikkou() {
  let imgelm = document.getElementById("img");
  let mat = cv.imread(imgelm);
  console.log(mat.size());
}
</script>

gazou01.jpg
(因みにこの画像はFLUX.1で生成したもの)

実行結果

Object { width: 150, height: 200 }

今回の例ではただ読み込めることを確認するためだけなので、まずサイズ(解像度)を測るだけでまだ特に何もしません。以下の例もそうします。

仮のimgタグで画像読み込み

予めウェブページに置いたimgタグではなく、その場で作ったimgタグもimshowで読み込むことができます。

ただし画像の読み込みは非同期処理で行われるので、書き方は複雑になりま。例えばasyncawaitを使う方法です。

<button onclick="jikkou()">実行</button>
<script src="https://docs.opencv.org/4.10.0/opencv.js"></script>
<script>
async function jikkou() {
  let imgelm = new Image();
  imgelm.src = "gazou.jpg";
  await imgelm.decode();
  let mat = cv.imread(imgelm);
  URL.revokeObjectURL(imgelm);
  console.log(mat.size());
}
</script>

ここで.decode()メソッドは画像をその場でimgタグに読み込むためです。awaitを使うことで読み込みが終わるまで待つことになります。そうしないと後でimread()を書いても画像を行列に読み込むことはできません。

行列に読み込まれた後imgタグに残る画像データはもう必要ないので、URL.revokeObjectURLで削除しておいた方がいいです。そうしなければずっとメモリーを占めたままで自動的に消えません。

もう一つは.onloadメソッドを定義する方法です。asyncawaitが必要ないですが、書き方は更に複雑に見えます。

<button onclick="jikkou()">実行</button>
<script src="https://docs.opencv.org/4.10.0/opencv.js"></script>
<script>
function jikkou() {
  let imgelm = new Image();
  imgelm.onload = () => {
    let mat = cv.imread(imgelm);
    URL.revokeObjectURL(imgelm);
    console.log(mat.size());
  }
  imgelm.src = "gazou.jpg";
}
</script>

.onloadは画像がimgタグに読み込み終わった後に発動する関数です。コードの順番が書いたのと違って可読性が低くなるので、decode()を使った方法の方がいいと思います。

ファイルinputタグから画像読み込み

次はファイルを選択するためのinputタグを使って、選択されたファイルを読み込む方法です。この方法はCORS問題の対策が必要ないです。

inputタグによって読み込んだファイルはblobという形になるが、これを直接行列に読み込むことはできません。まずblobをimgタグにしてから又imread読み込みます。

blobをimgタグに入れるにはデータURLに変換する必要があります。そのためにはURL.createObjectURL関数を使うという方法と、FileReaderクラスを使うという方法があります。

違いに関しては。

https://scrapbox.io/mrsekut-p/URL.createObjectURL()%E3%81%A8FileReader.readAsDataURL()

違いを見ると、URL.createObjectURLの方がいいと思いますが、ここではとりあえず両方の方法を載せます。

まずはURL.createObjectURLを使う方法。

<input id="finput" type="file"><br><br>
<button onclick="jikkou()">実行</button>
<script src="https://docs.opencv.org/4.10.0/opencv.js"></script>
<script>
async function jikkou() {
  let file = document.getElementById("finput").files[0];
  let imgelm = new Image();
  imgelm.src = URL.createObjectURL(file);
  await imgelm.decode();
  let mat = cv.imread(imgelm);
  URL.revokeObjectURL(imgelm);
  console.log(mat.size());
}
</script>

upload_botan.png

FileReaderを使う方法。

<input id="finput" type="file"><br><br>
<button onclick="jikkou()">実行</button>
<script src="https://docs.opencv.org/4.10.0/opencv.js"></script>
<script>
function jikkou() {
  let reader = new FileReader();
  let file = document.getElementById("finput").files[0];
  reader.readAsDataURL(file);
  reader.onload = async () => {
    let imgelm = new Image();
    imgelm.src = reader.result;
    await imgelm.decode();
    let mat = cv.imread(imgelm);
    URL.revokeObjectURL(imgelm);
    console.log(mat.size());
  }
}
</script>

FileReaderは非同期処理なのでonloadを使うのです。更にもしimgelmonloadを使ったらもっとわかりにくくなりそうです。

canvasから画像読み込み

画像を表示する時にimgタグの他にcanvasタグもよく使われています。canvasタグの中で書かれた画像を直接行列に読み込むことはできなくて、まずimgタグ又はcanvasの中の2d画像コンテキストCanvasRenderingContext2Dを経由する必要があります。

CanvasRenderingContext2D経由

canvasの中で画像はCanvasRenderingContext2Dというクラスのオブジェクトの中で描かれるのです。.getImageDataメソッドで画像データを取得してopencv.jsのmatFromImageData()関数で読み込めます。

<canvas id="canvas" width="100" height="200" style="background-color: #DD9;"></canvas>
<button onclick="jikkou()">実行</button>
<script src="https://docs.opencv.org/4.10.0/opencv.js"></script>
<script>
let canvaselm = document.getElementById("canvas");
let ctx = canvaselm.getContext("2d");
ctx.fillStyle = "#44F";
ctx.arc(0, 100, 100, -Math.PI/2, Math.PI/2);
ctx.fill();

function jikkou() {
  let imgdata = ctx.getImageData(0, 0, canvaselm.width, canvaselm.height); 
  let mat = cv.matFromImageData(imgdata);
  console.log(mat.size()); // Object { width: 100, height: 200 }
  console.log(imgdata); // ImageData { width: 100, height: 200, data: Uint8ClampedArray(80000) }
  console.log(imgdata.data); // Uint8ClampedArray(80000) [ 68, 68, 255, 223, 68, 68, 255, 223, 67, 67, … ]
  console.log(mat.data); // Uint8Array(80000) [ 68, 68, 255, 223, 68, 68, 255, 223, 67, 67, … ]
}
</script>

gazou02.png

.getImageDataで取得する画像の範囲を指定する必要があるので書き方は面倒ですが、もしただ一部だけ欲しい場合は簡単にできて便利かもしれません。

因みに.getImageDataの返り値はImageDataというクラスのオブジェクトで、その中でUint8ClampedArrayというクラスの配列で画像データを格納しています。これはopencv.jsで主に扱うUint8Arrayと大体同じで、自動的に変換されます。

imgタグ経由

blobの場合と同じように、imgタグになったら簡単にimshow()関数で読み込むことができます。canvasには.toDataURL()というメソッドがあります。これを使ってcanvasの中の画像をデータURLにしてimgタグに入れることができます。

<canvas id="canvas" width="100" height="200" style="background-color: #DD9;"></canvas>
<button onclick="jikkou()">実行</button>
<script src="https://docs.opencv.org/4.10.0/opencv.js"></script>
<script>
let canvaselm = document.getElementById("canvas");
let ctx = canvaselm.getContext("2d");
ctx.fillStyle = "#44F";
ctx.arc(0, 100, 100, -Math.PI/2, Math.PI/2);
ctx.fill();

async function jikkou() {
  let imgelm = new Image();
  imgelm.src = canvaselm.toDataURL();
  await imgelm.decode();
  let mat = cv.imread(imgelm);
  URL.revokeObjectURL(imgelm);
  console.log(mat.size());
}
</script>

外から取得した画像データを読み込み

fetchやaxiosなどでAPIや外部のウェブサイトから画像データを取得する時も、普段inputタグと同じようにblobという形なので、URL.createObjectURLでimgタグに画像を入れたら読み込むことになります。

今回は例普通にgetメソッドで同じサイトの静的ファイルょ読み込むこと例を挙げます。こんな場合は実は直接imgタグを使ってもいいのですが、postを使う時など条件によって返ってくるデータが違う場合はimgタグだけで対応できません。

asyncawaitを使う例。

<button onclick="jikkou()">実行</button>
<script async src="https://docs.opencv.org/4.10.0/opencv.js"></script>
<script>
async function jikkou() {
  let blob = await (await fetch("/gazou.jpg")).blob();
  let imgelm = new Image();
  imgelm.src = URL.createObjectURL(blob);
  await imgelm.decode();
  let mat = cv.imread(imgelm);
  console.log(mat.size());
}
</script>

onloadthenを使う例。

<button onclick="jikkou()">実行</button>
<script async src="https://docs.opencv.org/4.10.0/opencv.js"></script>
<script>
function jikkou() {
  let imgelm = new Image();
  imgelm.onload = () => {
    let mat = cv.imread(imgelm);
    console.log(mat.size());
    URL.revokeObjectURL(imgelm);
  }
  fetch("/gazou.jpg")
    .then((res) => res.blob())
    .then((blob) => {
      imgelm.src = URL.createObjectURL(blob);
    });
}
</script>

opencvの行列オブジェクトMatから出力

opencv.jsの中で行列という形で画像データを処理した後、表示や保存のために次は行列から変換し返す必要があります。

Matからcanvasへ

行列からの出力は、一番基本的なのはimshow()関数を使うという方法です。これを使ってcanvasタグで画像表示できます。

例えばimgタグの中の画像を読み込んで何等かの処理をしてcanvasに表示するとします。

<img id="img" src="gazou.jpg">
<button onclick="jikkou()">実行</button>
<canvas id="canvas"></canvas>
<script src="https://docs.opencv.org/4.10.0/opencv.js"></script>
<script>
function jikkou() {
  let mat = cv.imread(document.getElementById("img"));
  cv.convertScaleAbs(mat, mat, 0.7); // 何等かの処理
  cv.imshow("canvas", mat);
  mat.delete();
}
</script>

gazou03.jpg

ここではボタンの左側は元画像のimgタグでボタンの右側は編集した後のcanvasです。

又、普段出力に使ったMatはもう使わないなら.delete()メソッドを使ってメモリーから削除する必要があります。そうしないと、自動的に消されるわけでなく、ページが閉じるまでメモリーの中に残るからです。

第1引数のcanvasの指定はcanvasのIDを入れることもできますが、その他にもそのcanvasのオブジェクトを入れることもできます。

上の例をこのように書き換えられます。

<img id="img" src="gazou.jpg">
<button onclick="jikkou()">実行</button>
<canvas id="canvas"></canvas>
<script src="https://docs.opencv.org/4.10.0/opencv.js"></script>
<script>
function jikkou() {
  let mat = cv.imread(document.getElementById("img"));
  cv.convertScaleAbs(mat, mat, 0.7); // 何等かの処理
  let canvaselm = document.getElementById("canvas");
  cv.imshow(canvaselm, mat);
  mat.delete();
}
</script>

Matからimgへ

imshow()関数はcanvasだけに使えて、imgタグに使えるわけではないので、imgタグに画像を入れたい場合は一旦canvasにしてから又変換するという過程になります。

使う例。

<img id="img" src="gazou.jpg">
<button onclick="jikkou()">実行</button>
<img id="img2">
<script src="https://docs.opencv.org/4.10.0/opencv.js"></script>
<script>
function jikkou() {
  let mat = cv.imread(document.getElementById("img"));
  let canvaselm = document.createElement("canvas");
  cv.convertScaleAbs(mat, mat, 0.7);
  cv.imshow(canvaselm, mat);
  mat.delete();
  let imgelm2 = document.getElementById("img2");
  imgelm2.src = canvaselm.toDataURL();
}
</script>

上の例と同じ見た目の結果ですが、今回出力はcanvasではなく、もう一つのimgタグにします。結果も見た目は完全に同じです。

Matからblobへ。そしてファイル保存

Matから直接blobに出力する方法はなく、まず一旦canvasにする必要があります。canvasには.toBlobメソッドを持って、これわ使ってblobが作れます。blobになったファイルとして送ったり、保存したりすることができます。

imgタグから読み込んで処理したMatをblobにして出力して保存する例です。

<img id="img" src="gazou.jpg">
<button onclick="jikkou()">実行</button>
<script src="https://docs.opencv.org/4.10.0/opencv.js"></script>
<script>
function jikkou() {
  let imgelm = document.getElementById("img");
  let mat = cv.imread(imgelm);
  let canvaselm = document.createElement("canvas");
  cv.convertScaleAbs(mat, mat, 0.7);
  cv.imshow(canvaselm, mat);
  mat.delete();
  
  canvaselm.toBlob((blob) => {
    let a = document.createElement("a");
    a.href = URL.createObjectURL(blob);
    a.download = "gazou.jpg";
    a.click();
    URL.revokeObjectURL(a.href);
    canvaselm.remove();
  }, "image/jpeg");
}
</script>

尚、出力画像ファイルのタイプを指定することができます。ここでは"image/jpeg"にしていますが、指定しない場合の既定値はpngとなります。

データ型とチャンネル数

今までの説明ではデータの種類について触れていませんが、実はopencv.jsでは色んなデータ種類が使われています。今はこれについて説明します。

opencv.jsの画像データは主に2つの分け方で分けられています。

  • データ型
  • チャンネル数

データ型は7つあります。以下の表で表します。

変数 定数 意味 JavaScriptのデータ型
CV_8U 0 8ビット符号なし整数 Uint8Array
CV_8S 1 8ビット符号あり整数 Int8Array
CV_16U 2 16ビット符号なし整数 Uint16Array
CV_16S 3 16ビット符号あり整数 Int16Array
CV_32S 4 32ビット符号あり整数 Int32Array
CV_32F 5 32ビット浮動小数点数 Float32Array
CV_64F 6 64ビット浮動小数点数 Float64Array

ここに書いた変数と定数は.convertTo()などデータ型を変えるための関数やメソッドに使われます。例えば「32ビット浮動小数点数」に変換したい時cv.CV_32Fを入れても5を入れても同じになります。

チャンネル数は1から4です。1は普段グレースケール、3はRGB3色で、4はアルファも加えるRGBAです。

matFromArray()関数などで行列を作る時にデータ型とチャンネル数の組み合わせで種類を指定する必要があります。その組み合わせは7×4=28あります。

このように表で表します。

変数 定数 データ型 チャンネル数
CV_8UC1 0 8ビット符号なし整数 1
CV_8SC1 1 8ビット符号あり整数
CV_16UC1 2 16ビット符号なし整数
CV_16SC1 3 16ビット符号あり整数
CV_32SC1 4 32ビット符号あり整数
CV_32FC1 5 32ビット浮動小数点数
CV_64FC1 6 64ビット浮動小数点数
CV_8UC2 8 8ビット符号なし整数 2
CV_8SC2 9 8ビット符号あり整数
CV_16UC2 10 16ビット符号なし整数
CV_16SC2 11 16ビット符号あり整数
CV_32SC2 12 32ビット符号あり整数
CV_32FC2 13 32ビット浮動小数点数
CV_64FC2 14 64ビット浮動小数点数
CV_8UC3 16 8ビット符号なし整数 3
CV_8SC3 17 8ビット符号あり整数
CV_16UC3 18 16ビット符号なし整数
CV_16SC3 19 16ビット符号あり整数
CV_32SC3 20 32ビット符号あり整数
CV_32FC3 21 32ビット浮動小数点数
CV_64FC3 22 64ビット浮動小数点数
CV_8UC4 24 8ビット符号なし整数 4
CV_8SC4 25 8ビット符号あり整数
CV_16UC4 26 16ビット符号なし整数
CV_16SC4 27 16ビット符号あり整数
CV_32SC4 28 32ビット符号あり整数
CV_32FC4 29 32ビット浮動小数点数
CV_64FC4 30 64ビット浮動小数点数

こんなにも沢山ありますが、普段いつも使われるのは主にCV_8U(8ビット符号なし整数)系です。その他にCV_32FC2(32ビット浮動小数点数)もよく使われています。

imread()関数で読み込んだ場合はCV_8UC4、つまり「4チャンネル8ビット符号なし整数」となります。たとえ無色の画像を読み込んでもそうなります。

Matの画像データの種類は.type()メソッドで調べられます。返してくるのはただの数値でこれだけ見てもわかりにくいので、上の表を参考にしてください。

色空間を変換するcvtColor()関数を使ったらチャンネル数は変わりますが、データ型はそのままです。

<img id="img" src="gazou.jpg">
<button onclick="jikkou()">実行</button>
<script src="https://docs.opencv.org/4.10.0/opencv.js"></script>
<script>
function jikkou() {
  let imgelm = document.getElementById("img");
  let mat = cv.imread(imgelm);
  console.log(mat.type()); // 24
  console.log(mat.data); // Uint8Array(120000) [ 49, 75, 76, 255, 69, 95, 92, 255, 85, 108, … ]
  cv.cvtColor(mat, mat, cv.COLOR_RGBA2RGB);
  console.log(mat.type()); // 16
  console.log(mat.data); // Uint8Array(90000) [ 49, 75, 76, 69, 95, 92, 85, 108, 100, 44, … ]
  cv.cvtColor(mat, mat, cv.COLOR_RGB2GRAY);
  console.log(mat.type()); // 0
  console.log(mat.data); // Uint8Array(30000) [ 67, 87, 100, 57, 37, 54, 45, 47, 42, 59, … ]
}
</script>

.dataの中で中身を見る時にチャンネル数によってサイズが変わりますね。サイズは「縦×横×チャンネル数」だから。

cvtColor()に関しては使い方は殆どPythonの時と同じなので説明は割愛します。

データ型の変換はMatオブジェクトにある.convertTo()メソッドでできます。ただし色の表現は整数の場合と浮動小数点数の場合とは違うので注意です。整数では0~255で、浮動小数点数では0~1です。変換する時この値の範囲が自動的に計算されるわけではないが、.convertTo()メソッドではデータ型変換と同時にスケールを指定することもできます。

例えばCV_8UCからCV_32FC4に変換する時に1/255を入れます。

使う例。

<img id="img" src="gazou.jpg">
<button onclick="jikkou()">実行</button>
<canvas id="canvas"></canvas>
<script src="https://docs.opencv.org/4.10.0/opencv.js"></script>
<script>
function jikkou() {
  let imgelm = document.getElementById("img");
  let mat = cv.imread(imgelm);
  console.log(mat.type()); // 24
  console.log(mat.data); // Uint8Array(120000) [ 49, 75, 76, 255, 69, 95, 92, 255, 85, 108, … ]
  mat.convertTo(mat, cv.CV_32F, 1 / 255);
  console.log(mat.type()); // 29
  console.log(mat.data); // Uint8Array(480000) [ 198, 196, 68, 62, 151, 150, 150, 62, 153, 152, … ]
  mat.convertTo(mat, cv.CV_64F);
  console.log(mat.type()); // 30
  console.log(mat.data); // Uint8Array(960000) [ 0, 0, 0, 192, 152, 152, 200, 63, 0, 0, … ]
  cv.imshow("canvas", mat);
}
</script>

Matで扱った時のデータ型が変わってもcanvasに出力したら同じです。この例では変換した後処理せずに出力したので、左側のimgタグの元画像と、右側の出力canvasは全く同じですね。でももし変換の時にスケールを1/255に指定していなければ真っ白になるでしょう。

gazou04.jpg

ただ、.dataで中身を見たらどれもUint8Arrayで、変わったのは配列のサイズです。中で全てUint8から合成するという仕組みですね。CV_32FC4では4つの「8ビット符号なし整数」が1つの「32ビット浮動小数点数」として解釈されるのです。

例えばmatFromArray関数で配列から行列を作成してみます。

<button onclick="jikkou()">実行</button>
<script src="https://docs.opencv.org/4.10.0/opencv.js"></script>
<script>
function jikkou() {
  let arr = [205, 49, 71, 74, 116, 126, 161, 95, 216, 88, 44, 178, 198, 17, 90, 195, 104, 66];
  let mat = cv.matFromArray(2, 3, cv.CV_8UC3, arr);
  console.log(mat.data); // Uint8Array(18) [ 205, 49, 71, 74, 116, 126, 161, 95, 216, 88, … ]
  console.log(mat.size()); // Object { width: 3, height: 2 }
  mat = cv.matFromArray(2, 3, cv.CV_32FC3, arr);
  console.log(mat.data); // Uint8Array(72) [ 0, 0, 77, 67, 0, 0, 68, 66, 0, 0, … ]
  console.log(mat.size()); // Object { width: 3, height: 2 }
}
</script>

中身のデータはCV_8UC3の場合は入れたままの数値ですが、CV_32FC3では全然違う4つの数値に変換されます。

終わりに

以上opencv.jsの入門です。とはいっても、まだデータの入力、出力、変換やデータ型のことしか説明していなくて、肝心の画像処理は殆ど触れていません。

でもopencvによる画像処理は既に沢山記事が書いてあります。ただ殆どはPythonとC++なだけ。JavaScript特有の入力、出力、変換がわかればPythonのopencvの資料を読んでもopencv.jsで行うためにどうやって書き換えるかわかるでしょう。

参考

JavaScriptにおけるファイルとデータ処理

opencv.js

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2