LoginSignup
34
12

More than 3 years have passed since last update.

WebAssembly版OpenCV(OpenCV.js)について

Last updated at Posted at 2020-12-22

NRI OpenStandia Advent Calendar 2020の 23日目は、WebAssembly版OpenCV(OpenCV.js)についてです。

WebAssemblyとは

本題に入る前に、軽くWebAssemblyについて説明です。
WebAssemblyとは、ブラウザから機械語を実行できるようにした技術であり、下記のような特徴があります。

  • JavaScriptよりも高速
  • C、C++、Rust、Go等幅広い言語に対応
  • Chrome、Firefox、Edge、Safari等幅広いブラウザに対応

利便性が高いため、公式からも幅広い使い方が提案されています。
- https://github.com/WebAssembly/design/blob/master/UseCases.md

WebAssemblyは高速

JavaScriptは高度な処理を行おうとすると、挙動が遅くなります。理由は主に二つありますが、WebAssemblyはその二点を解消しています。

JavaScriptが遅い理由1:インタプリタ方式だから

インタプリタ方式は、ソースコードを実行する際、機械語に都度変換する作業が発生してしまいます。
しかし、コンパイラ方式はあらかじめ中間言語に変換されているため、実行速度が速くなります。

image.png

JavaScriptが遅い理由2:動的型付けだから

動的型付けは型が未決定のため、実行するたびに変数の型を決める作業が発生します。WebAssemblyは静的型付けであるため、この作業が存在せず、実行速度が速くなります。

image.png

WebAssemblyの仕組み

WebAssemblyではwasmファイルを作成し、それをブラウザにて実行します。
このwasmファイルには、人間が読みやすいように設計されているテキスト形式(.wat)と機械用のバイナリ形式(.wasm)の二つの側面があります。

テキスト形式(.wat)は、S式と呼ばれるツリー構造の形式で成り立っています。

テキスト形式(.wat)例
(module
  (func (param $lhs i32) (param $rhs i32) (result i32)
    local.get $lhs
    local.get $rhs
    i32.add))

上記のファイルをバイナリ形式(.wasm)に変換すると、下記のようになります。

バイナリ形式(.wasm)例
0000000: 0061 736d                                 ; WASM_BINARY_MAGIC
0000004: 0100 0000                                 ; WASM_BINARY_VERSION
; section "Type" (1)
0000008: 01                                        ; section code
0000009: 00                                        ; section size (guess)
000000a: 01                                        ; num types
; func type 0
000000b: 60                                        ; func
000000c: 02                                        ; num params
000000d: 7f                                        ; i32
000000e: 7f                                        ; i32
000000f: 01                                        ; num results
0000010: 7f                                        ; i32
0000009: 07                                        ; FIXUP section size
; section "Function" (3)
0000011: 03                                        ; section code
0000012: 00                                        ; section size (guess)
0000013: 01                                        ; num functions
0000014: 00                                        ; function 0 signature index
0000012: 02                                        ; FIXUP section size
; section "Code" (10)
0000015: 0a                                        ; section code
0000016: 00                                        ; section size (guess)
0000017: 01                                        ; num functions
; function body 0
0000018: 00                                        ; func body size (guess)
0000019: 00                                        ; local decl count
000001a: 20                                        ; local.get
000001b: 00                                        ; local index
000001c: 20                                        ; local.get
000001d: 01                                        ; local index
000001e: 6a                                        ; i32.add
000001f: 0b                                        ; end
0000018: 07                                        ; FIXUP func body size
0000016: 09                                        ; FIXUP section size
; section "name"
0000020: 00                                        ; section code
0000021: 00                                        ; section size (guess)
0000022: 04                                        ; string length
0000023: 6e61 6d65                                name  ; custom section name
0000027: 02                                        ; local name type
0000028: 00                                        ; subsection size (guess)
0000029: 01                                        ; num functions
000002a: 00                                        ; function index
000002b: 02                                        ; num locals
000002c: 00                                        ; local index
000002d: 03                                        ; string length
000002e: 6c68 73                                  lhs  ; local name 0
0000031: 01                                        ; local index
0000032: 03                                        ; string length
0000033: 7268 73                                  rhs  ; local name 1
0000028: 0d                                        ; FIXUP subsection size
0000021: 14                                        ; FIXUP section size

ちなみにWebAssemblyは、正確にはアセンブリではございません。

アセンブリとは、機械語に近く、人間が読める文字列のことです。
このアセンブリというものは、実はプロセッサごとに微妙に異なります。

WebAssemblyはどのプロセッサにも対応できるよう、最小公倍数のような仕組みとなっております。
これにより、WebAssemblyはどのプロセッサやOS対しても同じwasmファイルを適用することができる(つまり、移植性が高い)という素晴らしい特徴があります。

OpenCV.jsとは

image.png

OpenCVとは画像処理のためのオープンソースであり、画像編集や物体認識を実現することができます。
OpenCVはC++やPythonで書かれているオープンソースですが、WebAssemblyを利用することで、
JavaScriptでも利用可能です。

このWebAssembly版OpenCVのことをOpenCV.jsと呼びます。

OpenCV.jsのつくりかた

WebAssemblyではC++言語のプログラムをwasmファイルにコンパイルする際、Emscriptenが主に用いられます。
そのため、本稿ではEmscriptenを用いてコンパイルを実行します。

まず、Windows10で実施する場合は、下記を事前に用意します。

  • Ubuntu
  • Git
  • CMake
  • Visual Studio
  • Python

次に、Emscriptenをダウンロードします。EmscriptenはLLVMをベースとしたものです。

git clone https://github.com/juj/emsdk.git
cd emsdk
./emsdk install latest
./emsdk activate latest

Emscriptenのダウンロードが完了したら、環境変数もセットします。

source ./emsdk_env.sh --build=Release

ここまで完了したら、コンパイル作業に移ります。
まずOpenCVをgitからcloneします。

git clone https://github.com/opencv/opencv.git

次に、OpenCVの中のスクリプトであるbuild_js.pyを実行します。
なおこのスクリプトを実行するためにはPythonとcmakeが必要です。

これはbuild_wasmディレクトリ内のソースをビルドするスクリプトとなっております。

cd opencv
python ./platforms/js/build_js.py build_wasm --build_wasm

この際、--build_wasmオプションがないとasm.jsでビルドされてしまうので注意してください。

OpenCV.jsのつかいかた

OpenCV.jsを手に入れたら、通常のOpenCVと同じように画像編集や物体認識が可能です。
ここでは公式ドキュメントで紹介されていた例を記載します。

画像の切り取り
let src = cv.imread('canvasInput');
let dst = new cv.Mat();
// You can try more different parameters
let rect = new cv.Rect(100, 100, 200, 200);
dst = src.roi(rect);
cv.imshow('canvasOutput', dst);
src.delete();
dst.delete();

切り取り結果
image.png

顔特徴量の検出

let src = cv.imread('canvasInput');
let gray = new cv.Mat();
cv.cvtColor(src, gray, cv.COLOR_RGBA2GRAY, 0);
let faces = new cv.RectVector();
let eyes = new cv.RectVector();
let faceCascade = new cv.CascadeClassifier();
let eyeCascade = new cv.CascadeClassifier();
// load pre-trained classifiers
faceCascade.load('haarcascade_frontalface_default.xml');
eyeCascade.load('haarcascade_eye.xml');
// detect faces
let msize = new cv.Size(0, 0);
faceCascade.detectMultiScale(gray, faces, 1.1, 3, 0, msize, msize);
for (let i = 0; i < faces.size(); ++i) {
    let roiGray = gray.roi(faces.get(i));
    let roiSrc = src.roi(faces.get(i));
    let point1 = new cv.Point(faces.get(i).x, faces.get(i).y);
    let point2 = new cv.Point(faces.get(i).x + faces.get(i).width,
                              faces.get(i).y + faces.get(i).height);
    cv.rectangle(src, point1, point2, [255, 0, 0, 255]);
    // detect eyes in face ROI
    eyeCascade.detectMultiScale(roiGray, eyes);
    for (let j = 0; j < eyes.size(); ++j) {
        let point1 = new cv.Point(eyes.get(j).x, eyes.get(j).y);
        let point2 = new cv.Point(eyes.get(j).x + eyes.get(j).width,
                                  eyes.get(j).y + eyes.get(j).height);
        cv.rectangle(roiSrc, point1, point2, [0, 0, 255, 255]);
    }
    roiGray.delete(); roiSrc.delete();
}
cv.imshow('canvasOutput', src);
src.delete(); gray.delete(); faceCascade.delete();
eyeCascade.delete(); faces.delete(); eyes.delete();

検出結果
image.png

もちろん動画像処理やDNNによる画像処理といった重い作業もOpenCV.jsで可能です。

ちなみに、codepenにもOpenCV.jsが存在していたので、WebAssemblyの環境構築を
行わなくてもOpenCV.js自体は試すことができます。興味ある方は、ぜひこちらでお試しください。
- https://codepen.io/puku0x/pen/aKPgVE

image.png

参考

34
12
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
34
12