openCV.js を使ってスマホ上で画像処理をしてみた

はじめまして。
普段はWebサービスの開発とかしてます。

もはや最近ではよく聞く単語となった「画像処理」をJavaScriptでやってみました。


# openCV.jsとは
画像処理でよく使われるライブラリであるOpenCVのJavaScript版(opencvjs)。
これにより、jsファイルとブラウザさえあれば画像処理を実現できるので
面倒な環境設定をせずとも画像処理を行なえるようになりました。

そのOpenCV.jsを用いてスマホ上で画像処理を行う手順を備忘録として残しておきます。

# やったこと
以下の手順でスマホ上の画像処理を実現しました。

1. OpenCV.jsの作成
2. htmlでOpenCV.jsを読み込む
3. PythonでWebサーバを立てる
4. 3で立てたサーバにスマホでアクセスする

# 環境
MacOS 10.13.1
Python 2.7.11

本記事はMacOS環境を想定して書いていますが、
Ubuntuでも同じ手順でopencv.jsを作成出来ました。

パッケージマネージャーコマンドは
Linux, Ubuntuの場合は brew → apt-get に
CentOS, Fedoraの場合は brew → yum に適宜読み替えてください。


※Windows環境だと「ビルドしてopenCV.jsを作る」の部分が大変かも知れません
※Windows環境でもOpenCV.jsの作成を試みたのですが、色々とやった挙げ句、断念しました
※Windowsで試したい方は他記事をご参考にされるか、
※どこかから作成済みOpenCV.jsを持ってきて、2から試される方が良い気がします...


# 1. openCV.jsの作成(opencvjs)
手順は以下の通り

1. Emscriptenのインストール
2. OpenCVリポジトリのクローン
3. OpenCVリポジトリをEmscriptenでコンパイル

### 1. Emscriptenのインストール

#### ・準備
Emscriptenのインストールに必要なものをインストールする
既にインストールしている場合は不要です

```
# gitのインストール
sudo brew install git-core

# Python2.7のインストール
sudo brew install python2.7

# node.jsのインストール
sudo brew install nodejs

# CMakeのインストール
sudo brew install cmake
```


#### ・Emscripten のインストール

任意のフォルダで、以下のコマンドを実行する

```
# emsdkのリポジトリをclone
git clone https://github.com/juj/emsdk.git

# リポジトリに移動
cd emsdk

# 使用するツールの最新化
git pull

# 最新verをインストール
./emsdk install latest

# active
./emsdk activate latest

# 環境変数の設定
source ./emsdk_env.sh
```


### 2. OpenCVリポジトリのクローン

任意のフォルダ(※1)で以下のコマンドを実行する

```
# OpenCVのリポジトリをクローン
git clone https://github.com/opencv/opencv.git
```

### 3. OpenCVリポジトリをEmscriptenでコンパイル

※１のフォルダで以下のコマンドを実行

```
# リポジトリに移動
cd opencv

# コンパイルの実行
python ./platforms/js/build_js.py build_js　--emscripten /XXX/emsdk/emscripten/1.38.4
```

## 注意点：--emscripten
このオプションをつけないとbuild_js.pyの中で
emccの場所を見つけることが出来ずにエラーが発生する

--emscripten XXX
の XXX の部分は emcc が格納されているフォルダの絶対PATH
基本的には 1. で「emsdkのリポジトリをclone」したフォルダPATH



コンパイルが成功すると
以下のパスにopencv.jsが作られる(※コマンドの実行に3分ほどかかりました)

```
OpenCV.js location: ~/ws_openCV/opencv/build_js/bin/opencv.js
```

<br>
以下のようにtestが実行され
<img width="530" alt="スクリーンショット 2018-06-02 12.56.01.png" src="https://qiita-image-store.s3.amazonaws.com/0/259302/f713c2a0-640b-8876-9c42-4ea7d149a875.png">

<br>
以下のようにビルドが実行される
<img width="964" alt="スクリーンショット 2018-06-02 12.54.18.png" src="https://qiita-image-store.s3.amazonaws.com/0/259302/b43eb4e2-5206-974e-70fc-789175aa9cd4.png">


# 2. openCV.jsをhtml中でloadし、cv関数を使用する

~/ws_openCV/opencv/build_js/bin/にある
opencv.js と opencv_js.wasm を htmlファイルと同じ階層に置いて
次のように書くと、openCV.jsをloadできる

```html:
<script src="opencv.jsのpath" type="text/javascript"></script>
```

基本的にはOpenCVの関数をそのまま使える

例えば、画像の読み込み、グレースケール変換する処理であれば次のように記述する

```js:グレースケール変換
let src= cv.imread(画像);
let dst = new cv.Mat();
cv.cvtColor(src, dst, cv.COLOR_RGBA2GRAY, 0);
```

# サンプルコード(html)
以下のように書くと、opencv.jsをloadして画像処理ができるようになる

```html:sample.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Hello OpenCV.js</title>
</head>
<body>
<h2>OpenCV sample</h2>
<p id="status">OpenCV.js is loading...</p>
<div>
  <div class="inputoutput">
    <img id="imageSrc" alt="No Image" />
    <div class="caption">imageSrc <input type="file" id="fileInput" name="file" /></div>
  </div>
  <br>
  <form name="test">
    <input type="button" value="grayscale" onClick="grayscale()">
    <br>
  </form>
  <br>
  <div class="inputoutput">
    <canvas id="canvasOutput" ></canvas>
    <div class="caption">canvasOutput</div>
  </div>
</div>
<script type="text/javascript">
let imgElement = document.getElementById('imageSrc');
let inputElement = document.getElementById('fileInput');
inputElement.addEventListener('change', (e) => {
  imgElement.src = URL.createObjectURL(e.target.files[0]);
}, false);
function grayscale(){
  let mat = cv.imread(imgElement);
  var dst = new cv.Mat();
  cv.cvtColor(mat, dst, cv.COLOR_RGBA2GRAY, 0);
  cv.imshow('canvasOutput', dst);
  mat.delete();
  dst.delete();
}
function onOpenCvReady() {
  document.getElementById('status').innerHTML = 'OpenCV.js is ready.';
}
</script>
<script async src=".\opencv.js" onload="onOpenCvReady();" type="text/javascript"></script>
</body>
</html>
```

## 注意点：cv.Mat()
openCVの処理を行うときは、Mat形式で扱う必要がある

例えば、グレースケール変換の処理の場合、
処理の前に変換先の変数をMatクラスで定義する

```js
// 画像の読み込み
let mat = cv.imread(imgElement);

// 変換先(グレースケール処理をかけた画像)の定義
let dst = new cv.Mat();

// グレースケール処理
cv.cvtColor(mat, dst, cv.COLOR_RGBA2GRAY, 0);
```

## 注意点：mat.delete()
EmscriptenがC++オブジェクトのdestructorを自動で呼び出せないので、
利用したMatオブジェクトは以下のように手動で削除する必要がある

```js
mat.delete();
dst.delete(); 
```

# 3.サーバの起動
ターミナルでhtmlファイルを置いている階層まで移動し、
以下のコマンドを実行するとサーバが立ち上がる

```
python -m SimpleHTTPServer
```

この状態でPCのブラウザで次のURLにアクセス

```
http://localhost:8000
```

作成したhtml名のリンクをクリックするとhtmlが表示される


# 4.スマホからPCで立ち上げたサーバにアクセスする

PCとスマホを同じwifiに接続し、
ターミナルでifconfigコマンド(Windowsならipconfig)を実行してIPアドレスを調べる
<br>

3.と同じようにhtmlを置いている階層まで移動して以下のコマンドを入力

```
python -m SimpleHTTPServer
```

<br>
スマホのブラウザで次のURLにアクセスすると同じようにhtmlが表示される

```
http://XXX.XXX.XXX.XXX/ファイル名
# XXXX は先に調べたIPアドレス
```

# まとめ

私はOpenCV.jsの作成の部分で少しハマったのですが、
一度、OpenCV.jsを作ってしまえば、jsを使うのは簡単でした。
これで画像処理のハードルが一段下がったような気がしますね。