画像に対して顔検出を行いマスクする(opencv/opencv4nodejs/Node.js)

# 動機
インカメラで人物や顔写真入り証明書を撮影したエビデンス画像に対して、顔部分のマスクを行うツールを作成したかった。

# 言語選定
画像処理ライブラリ [opencv](https://opencv.org/) の対応言語は `C/C++` `Java` `Python` です。
今回の動機であるツール的に用いるならば、Pythonが適していると思います。サンプルコードもたくさんあります。しかし、自分がPythonに対する知識が少なく、時間がかかりそうで逡巡していました。
しかし、opencv を Node.js 環境から使えるライブラリ[opencv4nodejs](https://www.npmjs.com/package/opencv4nodejs)を見つけたので、試してみることにしました。

## 落とし穴
opencv4nodejsはopencvの型やメソッドと全く同じ名称ではなく、JavaScriptの言語仕様に合わせて引数なども異なります。→ [Contribution Guide](https://github.com/justadudewhohacks/opencv4nodejs/blob/master/CONTRIBUTING.md)
従って、Pythonのコードをそのまま置き換えればOK！といった形では実装できません。
自分はここを安易に考えていて若干ハマりました…

# 環境構築

Windows10(64bit) 
Nodist(0.9.1) Node.js 11.13.0 上に
opencv4nodejs(5.5.0)をインストール

公式の手順は[こちら](https://github.com/justadudewhohacks/opencv4nodejs#how-to-install)

## 事前注意
- スペースを含むパスにインストールしない → node-gyp でパスが読めない
- 日本語を含むパスにインストールしない → opencvでパスが読めない

## cmakeインストール
opencv4nodejs のインストール時に要求され、これが無いとエラーで進みません。

1. [cmake](https://cmake.org/download/) をインストールする
2. 実行ファイルがあるフォルダにPATHを通す 例：`G:\Program Files\CMake\bin`

## git

git ロングファイルネーム許可の設定(opencv4nodejs 内でのC++コンパイル用)
```git config --system core.longpaths true```


## npm

1. node-gypのインストール
```npm install --global node-gyp```

2. windows-build-tools インストール
```npm install --global windows-build-tools```
※時間かかります

3. opencv4nodejs のインストール
```npm install opencv4nodejs```
※とても時間かかります

# 実装
TypeScriptで書いてます

## 顔検出

[サンプルソース](https://github.com/justadudewhohacks/opencv4nodejs/blob/master/examples/faceDetect/faceDetection.js)をそのまま使いました。opencv4nodejsではサンプルソースが充実していてます。

```TypeScript
import * as cv from 'opencv4nodejs';

export const feceMaskBlur = (imagePath:string) => {
  
  // 対象画像読込
  const image = cv.imread(imagePath);
  if (!image) {
    throw new Error(`No file ${imagePath}`);
  }

  const classifier = new cv.CascadeClassifier(cv.HAAR_FRONTALFACE_ALT2);
  // detect faces
  const { objects, numDetections } = classifier.detectMultiScale(image.bgrToGray());
  

  if (!objects.length) {
    throw new Error(`No faces detected!  ${imagePath}`);
  }
  console.log('顔検出した領域:', objects);
  console.log('確度:', numDetections);
  
  // draw detection
  let blurImage:cv.Mat;
  blurImage = image.copy();
  objects.forEach((rect, i) => {
    // 顔検出した部分に対してマスクを実施
    blurImage = drawBlurRect(blurImage, rect, numDetections[i]);
  });

  // file 保存
  cv.imwrite(imagePath, blurImage);

}

```

検出した領域は、以下のような座標と大きさで取得できます

```
顔検出した領域: [ Rect { height: 525, width: 525, y: 1188, x: 1923 },
  Rect { height: 262, width: 262, y: 3214, x: 2298 },
  Rect { height: 584, width: 584, y: 878, x: 2714 } ]
確度: [ 8, 4, 11 ]
```

## 画像の一部上書き

上記で取得した領域に対して、マスクをかけます。
python だと、画像のデータは二次元配列として読み込まれるため、座標位置を指定して代入する形で記述できるようなのですが、opencv4nodejsだと形式が異なったため、ちょっと悩みました。

```Python
def mosaic_area(src, x, y, width, height, ratio=0.1):
    dst = src.copy()
    dst[y:y + height, x:x + width] = mosaic(dst[y:y + height, x:x + width], ratio)
    return dst

dst_area = mosaic_area(src, 100, 50, 100, 150)
cv2.imwrite('data/dst/opencv_mosaic_area.jpg', dst_area)

```
[Python, OpenCVで画像にモザイク処理（全面、一部、顔など）](https://note.nkmk.me/python-opencv-mosaic/)より引用

[issue](https://github.com/justadudewhohacks/opencv4nodejs/issues/297)を検索して見つけました。

```TypeScript

// 対象のRectを塗りつぶし
const drawBlurRect = (image: cv.Mat, rect: cv.Rect, numDetection: number):cv.Mat  => {
  // 領域の切り出し
  const srcRoi:cv.Mat = image.getRegion(rect);
  // 切り出した部分にマスクをかける
  const masked = cv.blur(srcRoi, new cv.Size(rect.width,rect.height));
  // 切り出した部分を元画像に合成
  masked.copyTo(image.getRegion(rect));
  return image;
}

```
# 実行

この画像に対して実行すると
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4608/e4f2c9b9-4675-d762-4214-b7a19d2f44e3.jpeg" width=70%>


こうなります
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4608/2a0ca576-e0d4-aa8c-fe05-3b224c33e6ec.jpeg" width=70%>
[画像取得元](https://www.pakutaso.com/20140912244post-4510.html)

# まとめと気づき

画像から何かを識別する場合には識別器を用い、OpenCVにはこの識別に使う学習済みファイルが準備されています。

今回は、Haar Cascade識別器（分類器）の[haarcascade_frontalface_alt2.xml](https://github.com/opencv/opencv/tree/master/data/haarcascades) →　コード中では `cv.HAAR_FRONTALFACE_ALT2` を使いました(サンプルそのまま)。

今回はインカメラを使った正面を向いた写真が基本なので顔検出がマッチしたのですが、横向きの顔は検出されにくい点については把握しておいた方がよさそうです。

# 参考

- [【入門者向け解説】openCV顔検出の仕組と実践(detectMultiScale)](https://qiita.com/FukuharaYohei/items/ec6dce7cc5ea21a51a82#%E5%88%A4%E5%AE%9A%E6%96%B9%E6%B3%95%E3%81%AE%E5%BC%B1%E7%82%B9)

- [OpenCV-CookBook 画像処理](http://opencv.jp/cookbook/opencv_img.html)