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


動機

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

言語選定

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

落とし穴

opencv4nodejsはopencvの型やメソッドと全く同じ名称ではなく、JavaScriptの言語仕様に合わせて引数なども異なります。→ Contribution Guide
従って、Pythonのコードをそのまま置き換えればOK!といった形では実装できません。
自分はここを安易に考えていて若干ハマりました…

環境構築

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

公式の手順はこちら

事前注意

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

cmakeインストール

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

  1. cmake をインストールする
  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で書いてます

顔検出

サンプルソースをそのまま使いました。opencv4nodejsではサンプルソースが充実していてます。

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だと形式が異なったため、ちょっと悩みました。

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で画像にモザイク処理(全面、一部、顔など)より引用

issueを検索して見つけました。


// 対象の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;
}

実行

この画像に対して実行すると

こうなります

画像取得元

まとめと気づき

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

今回は、Haar Cascade識別器(分類器)のhaarcascade_frontalface_alt2.xml → コード中では cv.HAAR_FRONTALFACE_ALT2 を使いました(サンプルそのまま)。

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

参考