OpenCV 5.0.0
Open Source Computer Vision
読み込み中...
検索中...
見つかりません
🤖 AIによる機械翻訳(非公式) — これは OpenCV 5.0.0 公式リファレンス(英語)を AI (Claude) で自動翻訳したものです。訳に誤りを含む場合があります。正確な情報は 公式英語版(原文) を参照してください。
Node.js で OpenCV.js を使う

目的

このチュートリアルでは、次のことを学ぶ:

  • Node.js アプリケーションで OpenCV.js を使用する。
  • OpenCV.js で使用するために、jimp で画像を読み込む。
  • cv.imread()cv.imshow() をサポートするために、jsdomnode-canvas を使用する。
  • OpenCV.js が基盤とする emscripten API の基礎。たとえば ModuleFile System など。
  • Node.js の基礎を学ぶ。このチュートリアルではユーザーが JavaScript を知っていることを前提とするが、Node.js の経験は必須ではない。
覚え書き
OpenCV.js を Node.js で実行する手順を示すことに加えて、このチュートリアルのもう1つの目的は、emscripten API の基礎 (たとえば ModuleFile System など) と Node.js をユーザーに紹介することである。

最小限の例

次の内容で example1.js というファイルを作成する:

// Define a global variable 'Module' with a method 'onRuntimeInitialized':
Module = {
onRuntimeInitialized() {
// this is our application:
console.log(cv.getBuildInformation())
}
}
// Load 'opencv.js' assigning the value to the global variable 'cv'
cv = require('./opencv.js')

実行する

  • ファイルを example1.js として保存する。
  • opencv.js ファイルが同じフォルダにあることを確認する。
  • Node.js がシステムにインストールされていることを確認する。

次のコマンドを実行すると、OpenCV のビルド情報が表示されるはずである:

node example1.js

いま何が起きたのか?

  • 最初の文では:、'Module' という名前のグローバル変数を定義することで、ライブラリが使用可能になったときに emscripten が Module.onRuntimeInitialized() を呼び出す。我々のプログラムはそのメソッド内にあり、ブラウザの場合と同じようにグローバル変数 cv を使用する。
  • "cv = require('./opencv.js')" という文は opencv.js ファイルを require し、その戻り値をグローバル変数 cv に代入する。Node.js の API である require() は、モジュールやファイルを読み込むために使用される。ここではカレントフォルダから opencv.js ファイルを読み込み、前述のとおり emscripten は準備が整うと Module.onRuntimeInitialized() を呼び出す。
  • 詳細は emscripten Module API を参照。

画像を扱う

OpenCV.js は画像フォーマットをサポートしないため、png や jpeg 画像を直接読み込むことはできない。ブラウザでは HTML DOM (HTMLCanvasElement や HTMLImageElement など) を使用して画像のデコードを行う。node.js ではこのためにライブラリを使用する必要がある。

この例では jimp を使用する。これは一般的な画像フォーマットをサポートし、非常に使いやすい。

例のセットアップ

次のコマンドを実行して、新しい node.js パッケージを作成し、jimp 依存関係をインストールする:

mkdir project1
cd project1
npm init -y
npm install jimp

const Jimp = require('jimp');
async function onRuntimeInitialized(){
// load local image file with jimp. It supports jpg, png, bmp, tiff and gif:
var jimpSrc = await Jimp.read('./lena.jpg');
// `jimpImage.bitmap` property has the decoded ImageData that we can use to create a cv:Mat
var src = cv.matFromImageData(jimpSrc.bitmap);
// following lines is copy&paste of opencv.js dilate tutorial:
let dst = new cv.Mat();
let M = cv.Mat.ones(5, 5, cv.CV_8U);
let anchor = new cv.Point(-1, -1);
cv.dilate(src, dst, M, anchor, 1, cv.BORDER_CONSTANT, cv.morphologyDefaultBorderValue());
// Now that we are finish, we want to write `dst` to file `output.png`. For this we create a `Jimp`
// image which accepts the image data as a [`Buffer`](https://nodejs.org/docs/latest-v10.x/api/buffer.html).
// `write('output.png')` will write it to disk and Jimp infers the output format from given file name:
new Jimp({
width: dst.cols,
height: dst.rows,
data: Buffer.from(dst.data)
})
.write('output.png');
src.delete();
dst.delete();
}
// Finally, load the open.js as before. The function `onRuntimeInitialized` contains our program.
Module = {
onRuntimeInitialized
};
cv = require('./opencv.js');

実行する

  • ファイルを exampleNodeJimp.js として保存する。
  • サンプル画像 lena.jpg がカレントディレクトリに存在することを確認する。

次のコマンドを実行すると、output.png ファイルが生成されるはずである:

node exampleNodeJimp.js

HTML DOM と canvas のエミュレート

すでに見たかもしれないが、残りの例では cv.imread()cv.imshow() といった関数を使って画像の読み書きを行う。残念ながら前述のとおり、Node.js には HTML DOM が存在しないため、これらは動作しない。

このセクションでは、jsdomnode-canvas を使用して Node.js 上で HTML DOM をエミュレートし、それらの関数を動作させる方法を学ぶ。

例のセットアップ

前と同様に、Node.js プロジェクトを作成し、必要な依存関係をインストールする:

mkdir project2
cd project2
npm init -y
npm install canvas jsdom

const { Canvas, createCanvas, Image, ImageData, loadImage } = require('canvas');
const { JSDOM } = require('jsdom');
const { writeFileSync, existsSync, mkdirSync } = require("fs");
// This is our program. This time we use JavaScript async / await and promises to handle asynchronicity.
(async () => {
// before loading opencv.js we emulate a minimal HTML DOM. See the function declaration below.
installDOM();
await loadOpenCV();
// using node-canvas, we an image file to an object compatible with HTML DOM Image and therefore with cv.imread()
const image = await loadImage('./lena.jpg');
const src = cv.imread(image);
const dst = new cv.Mat();
const M = cv.Mat.ones(5, 5, cv.CV_8U);
const anchor = new cv.Point(-1, -1);
cv.dilate(src, dst, M, anchor, 1, cv.BORDER_CONSTANT, cv.morphologyDefaultBorderValue());
// we create an object compatible HTMLCanvasElement
const canvas = createCanvas(300, 300);
cv.imshow(canvas, dst);
writeFileSync('output.jpg', canvas.toBuffer('image/jpeg'));
src.delete();
dst.delete();
})();
// Load opencv.js just like before but using Promise instead of callbacks:
function loadOpenCV() {
return new Promise(resolve => {
global.Module = {
onRuntimeInitialized: resolve
};
global.cv = require('./opencv.js');
});
}
// Using jsdom and node-canvas we define some global variables to emulate HTML DOM.
// Although a complete emulation can be archived, here we only define those globals used
// by cv.imread() and cv.imshow().
function installDOM() {
const dom = new JSDOM();
global.document = dom.window.document;
// The rest enables DOM image and canvas and is provided by node-canvas
global.Image = Image;
global.HTMLCanvasElement = Canvas;
global.ImageData = ImageData;
global.HTMLImageElement = Image;
}

実行する

  • ファイルを exampleNodeCanvas.js として保存する。
  • サンプル画像 lena.jpg がカレントディレクトリに存在することを確認する。

次のコマンドでファイル output.jpg が生成されるはずである:

node exampleNodeCanvas.js

ファイルを扱う

このチュートリアルでは、ファイル操作にメモリではなくローカルファイルシステムを使うように emscripten を設定する方法を学ぶ。また、emscripten アプリケーションがどのようにファイルをサポートするかについても説明を試みる。

OpenCV アプリケーションでは、たとえば ONNX フレームワークのモデルを読み込むブラウザでディープネットワークを実行する方法 で使われるような機械学習モデルを読み込むために、emscripten のファイルシステムへのアクセスがしばしば必要になる。

例のセットアップ

例に入る前に、まず OpenCV.js のような emscripten アプリケーションでファイルがどのように扱われるかを考えておく価値がある。OpenCV ライブラリは C++ で書かれており、opencv.js ファイルはその C++ コードが emscripten の C++ コンパイラによって JavaScript または WebAssembly に変換されたものに過ぎないことを思い出してほしい。

これらの C++ ソースは標準 API を使ってファイルシステムにアクセスし、その実装は最終的にハードドライブ上のファイルを読むシステムコールに行き着くことが多い。ブラウザ内の JavaScript アプリケーションはローカルファイルシステムにアクセスできないため、emscripten は標準ファイルシステムをエミュレートすることで、コンパイルされた C++ コードがそのまま動作するようにしている。

ブラウザではこのファイルシステムはメモリ上でエミュレートされるが、Node.js ではローカルファイルシステムを直接利用する選択肢もある。ファイルの内容をメモリにコピーする必要がないため、この方法がしばしば望ましい。本節では、まさにそれを行う方法、すなわちファイルがローカルファイルシステムから直接アクセスされ、相対パスが現在のローカルディレクトリからの相対ファイルに期待どおり一致するように emscripten を設定する方法を説明する。

const { Canvas, createCanvas, Image, ImageData, loadImage } = require('canvas');
const { JSDOM } = require('jsdom');
const { writeFileSync, existsSync, mkdirSync } = require('fs');
const https = require('https');
(async () => {
const createFileFromUrl = function (path, url, maxRedirects = 10) {
console.log('Downloading ' + url + '...');
return new Promise((resolve, reject) => {
const download = (url, redirectCount) => {
if (redirectCount > maxRedirects) {
reject(new Error('Too many redirects'));
} else {
let connection = https.get(url, (response) => {
if (response.statusCode === 200) {
let data = [];
response.on('data', (chunk) => {
data.push(chunk);
});
response.on('end', () => {
try {
writeFileSync(path, Buffer.concat(data));
resolve();
} catch (err) {
reject(new Error('Failed to write file ' + path));
}
});
} else if (response.statusCode === 302 || response.statusCode === 301) {
connection.abort();
download(response.headers.location, redirectCount + 1);
} else {
reject(new Error('Failed to load ' + url + ' status: ' + response.statusCode));
}
}).on('error', (err) => {
reject(new Error('Network Error: ' + err.message));
});
}
};
download(url, 0);
});
};
if (!existsSync('./face_detection_yunet_2023mar.onnx')) {
await createFileFromUrl('./face_detection_yunet_2023mar.onnx', 'https://media.githubusercontent.com/media/opencv/opencv_zoo/main/models/face_detection_yunet/face_detection_yunet_2023mar.onnx')
}
if (!existsSync('./opencv.js')) {
await createFileFromUrl('./opencv.js', 'https://docs.opencv.org/5.x/opencv.js')
}
if (!existsSync('./lena.jpg')) {
await createFileFromUrl('./lena.jpg', 'https://docs.opencv.org/5.x/lena.jpg')
}
await loadOpenCV();
const image = await loadImage('./lena.jpg');
const src = cv.imread(image);
let srcBGR = new cv.Mat();
cv.cvtColor(src, srcBGR, cv.COLOR_RGBA2BGR);
// Load the deep learning model file. Notice how we reference local files using relative paths just
// like we normally would do
let netDet = new cv.FaceDetectorYN("./face_detection_yunet_2023mar.onnx", "", new cv.Size(320, 320), 0.9, 0.3, 5000);
netDet.setInputSize(new cv.Size(src.cols, src.rows));
let out = new cv.Mat();
netDet.detect(srcBGR, out);
let faces = [];
for (let i = 0, n = out.data32F.length; i < n; i += 15) {
let left = out.data32F[i];
let top = out.data32F[i + 1];
let right = (out.data32F[i] + out.data32F[i + 2]);
let bottom = (out.data32F[i + 1] + out.data32F[i + 3]);
left = Math.min(Math.max(0, left), src.cols - 1);
top = Math.min(Math.max(0, top), src.rows - 1);
right = Math.min(Math.max(0, right), src.cols - 1);
bottom = Math.min(Math.max(0, bottom), src.rows - 1);
if (left < right && top < bottom) {
faces.push({
x: left,
y: top,
width: right - left,
height: bottom - top,
x1: out.data32F[i + 4] < 0 || out.data32F[i + 4] > src.cols - 1 ? -1 : out.data32F[i + 4],
y1: out.data32F[i + 5] < 0 || out.data32F[i + 5] > src.rows - 1 ? -1 : out.data32F[i + 5],
x2: out.data32F[i + 6] < 0 || out.data32F[i + 6] > src.cols - 1 ? -1 : out.data32F[i + 6],
y2: out.data32F[i + 7] < 0 || out.data32F[i + 7] > src.rows - 1 ? -1 : out.data32F[i + 7],
x3: out.data32F[i + 8] < 0 || out.data32F[i + 8] > src.cols - 1 ? -1 : out.data32F[i + 8],
y3: out.data32F[i + 9] < 0 || out.data32F[i + 9] > src.rows - 1 ? -1 : out.data32F[i + 9],
x4: out.data32F[i + 10] < 0 || out.data32F[i + 10] > src.cols - 1 ? -1 : out.data32F[i + 10],
y4: out.data32F[i + 11] < 0 || out.data32F[i + 11] > src.rows - 1 ? -1 : out.data32F[i + 11],
x5: out.data32F[i + 12] < 0 || out.data32F[i + 12] > src.cols - 1 ? -1 : out.data32F[i + 12],
y5: out.data32F[i + 13] < 0 || out.data32F[i + 13] > src.rows - 1 ? -1 : out.data32F[i + 13],
confidence: out.data32F[i + 14]
})
}
}
out.delete();
faces.forEach(function(rect) {
cv.rectangle(src, {x: rect.x, y: rect.y}, {x: rect.x + rect.width, y: rect.y + rect.height}, [0, 255, 0, 255]);
if(rect.x1>0 && rect.y1>0)
cv.circle(src, {x: rect.x1, y: rect.y1}, 2, [255, 0, 0, 255], 2)
if(rect.x2>0 && rect.y2>0)
cv.circle(src, {x: rect.x2, y: rect.y2}, 2, [0, 0, 255, 255], 2)
if(rect.x3>0 && rect.y3>0)
cv.circle(src, {x: rect.x3, y: rect.y3}, 2, [0, 255, 0, 255], 2)
if(rect.x4>0 && rect.y4>0)
cv.circle(src, {x: rect.x4, y: rect.y4}, 2, [255, 0, 255, 255], 2)
if(rect.x5>0 && rect.y5>0)
cv.circle(src, {x: rect.x5, y: rect.y5}, 2, [0, 255, 255, 255], 2)
});
const canvas = createCanvas(image.width, image.height);
cv.imshow(canvas, src);
writeFileSync('output3.jpg', canvas.toBuffer('image/jpeg'));
console.log('The result is saved.')
src.delete(); srcBGR.delete();
})();
/**
* Loads opencv.js.
*
* Installs HTML Canvas emulation to support `cv.imread()` and `cv.imshow`
*
* Mounts given local folder `localRootDir` in emscripten filesystem folder `rootDir`. By default it will mount the local current directory in emscripten `/work` directory. This means that `/work/foo.txt` will be resolved to the local file `./foo.txt`
* @param {string} rootDir The directory in emscripten filesystem in which the local filesystem will be mount.
* @param {string} localRootDir The local directory to mount in emscripten filesystem.
* @returns {Promise} resolved when the library is ready to use.
*/
function loadOpenCV(rootDir = '/work', localRootDir = process.cwd()) {
if(global.Module && global.Module.onRuntimeInitialized && global.cv && global.cv.imread) {
Promise.resolve()
}
return new Promise(resolve => {
installDOM()
global.Module = {
onRuntimeInitialized() {
// We change emscripten current work directory to 'rootDir' so relative paths are resolved
// relative to the current local folder, as expected
cv.FS.chdir(rootDir)
resolve()
},
preRun() {
// preRun() is another callback like onRuntimeInitialized() but is called just before the
// library code runs. Here we mount a local folder in emscripten filesystem and we want to
// do this before the library is executed so the filesystem is accessible from the start
const FS = global.Module.FS
// create rootDir if it doesn't exists
if(!FS.analyzePath(rootDir).exists) {
FS.mkdir(rootDir);
}
// create localRootFolder if it doesn't exists
if(!existsSync(localRootDir)) {
mkdirSync(localRootDir, { recursive: true});
}
// FS.mount() is similar to Linux/POSIX mount operation. It basically mounts an external
// filesystem with given format, in given current filesystem directory.
FS.mount(FS.filesystems.NODEFS, { root: localRootDir}, rootDir);
}
};
global.cv = require('./opencv.js')
});
}
function installDOM(){
const dom = new JSDOM();
global.document = dom.window.document;
global.Image = Image;
global.HTMLCanvasElement = Canvas;
global.ImageData = ImageData;
global.HTMLImageElement = Image;
}

実行する

  • ファイルを exampleNodeCanvasData.js として保存する。
  • face_detection_yunet_2023mar.onnxlena.jpgopencv.js の各ファイルは、プロジェクトのディレクトリに存在しない場合はダウンロードされる。

次のコマンドにより、ファイル output3.jpg が生成されるはずである。下の画像を参照:

node exampleNodeCanvasData.js
image