Node.jsでもOpenCVしてみる

Node.jsでもOpenCVしてみる

少し前のエントリではPythonを使用してOpenCVで顔認識をしていたんですが、モノによってはnode.jsでも同様なことができるかなと思いますので 実験をやってみました。

node.jsでもopencvのモジュールがあります。ただ、OpenCVは2系であることに注意です。

www.npmjs.com

github.com

以前のエントリでRaspberryPiにインストールしたのは2系ですので問題なく動作します。(version 2.4.9)

uepon.hatenadiary.com

opencvモジュールのインストール

node.jsのインストールに関しては

uepon.hatenadiary.com

こちらで取り上げましたのでRaspbianのオリジナルのNode.jsではないことに注意です。npmopencvをインストールすればOKです。

$ npm install opencv
-
> opencv@6.0.0 install /home/pi/node_opencv/node_modules/opencv
> node-pre-gyp install --fallback-to-build

make: Entering directory '/home/pi/node_opencv/node_modules/opencv/build'
  CXX(target) Release/obj.target/opencv/src/init.o
  CXX(target) Release/obj.target/opencv/src/Matrix.o
  CXX(target) Release/obj.target/opencv/src/OpenCV.o
  CXX(target) Release/obj.target/opencv/src/CascadeClassifierWrap.o
  CXX(target) Release/obj.target/opencv/src/Contours.o
  CXX(target) Release/obj.target/opencv/src/Point.o
  CXX(target) Release/obj.target/opencv/src/VideoCaptureWrap.o
  CXX(target) Release/obj.target/opencv/src/CamShift.o
  CXX(target) Release/obj.target/opencv/src/HighGUI.o
  CXX(target) Release/obj.target/opencv/src/FaceRecognizer.o
  CXX(target) Release/obj.target/opencv/src/Features2d.o
  CXX(target) Release/obj.target/opencv/src/BackgroundSubtractor.o
  CXX(target) Release/obj.target/opencv/src/Constants.o
  CXX(target) Release/obj.target/opencv/src/Calib3D.o
  CXX(target) Release/obj.target/opencv/src/ImgProc.o
  CXX(target) Release/obj.target/opencv/src/Stereo.o
  CXX(target) Release/obj.target/opencv/src/LDAWrap.o
  SOLINK_MODULE(target) Release/obj.target/opencv.node
  COPY Release/opencv.node
  COPY /home/pi/node_opencv/node_modules/opencv/build/opencv/v6.0.0/Release/node-v46-linux-arm/opencv.node
  TOUCH Release/obj.target/action_after_build.stamp
  CXX(target) Release/obj.target/test_nativemat/test/nativemat.o
  SOLINK_MODULE(target) Release/obj.target/test_nativemat.node
  COPY Release/test_nativemat.node
make: Leaving directory '/home/pi/node_opencv/node_modules/opencv/build'
opencv@6.0.0 node_modules/opencv
tqq buffers@0.1.1
tqq nan@2.6.1
mqq istanbul@0.4.5 (abbrev@1.0.9, async@1.5.2, wordwrap@1.0.0, nopt@3.0.6, esprima@2.7.3, once@1.4.0, supports-color@3.2.3, which@1.2.14, mkdirp@0.5.1, resolve@1.1.7, glob@5.0.15, js-yaml@3.8.3, escodegen@1.8.1, handlebars@4.0.6)

比較的長い時間がかかります。(RaspberryPi2では10分ちょっとかかっています) これが終わるといよいよOpenCVの世界に入れます。

静止画像の顔認識

まずは静止画に含まれる顔認識を行ってみます。 認識をする画像はinput.jpg解析に成功した場合には、 顔と思われる部分に赤色の四角形を描画し、output.jpgとして画像を出力します。

若干書きぶりが異なりますが、なんとなくは同じ様です。oencvパッケージに含まれるサンプルを参考にしているので分類機の読み込みが異なるようです。

CascadeClassifierdetectMultiScaleでの呼び出しではなく、detectObjectで引数での呼び出しとなっています。

OpenCVサンプルフォルダ】 github.com

【face-detect.js】

var cv = require('opencv');

var RED = [0, 0, 255];
var THICKNESS = 2;

cv.readImage("./input.jpg", function(err, im){
  if (err) throw err;
  if (im.width() < 1 || im.height() < 1) throw new Error('error:画像が不正の様です。');

  im.detectObject("./haarcascade_frontalface_alt.xml", {}, function(err, faces){
    for (var i=0;i<faces.length; i++){
      var face = faces[i]
      im.rectangle([face.x, face.y], [face.width, face.height], RED, THICKNESS);
    }
    console.log('イメージを保存しました');
    im.save('./output.jpg');
  });
})

実行のためには、あらかじめプログラムと同じディレクトリにhaarcascade_frontalface_alt.xmlなど分類機設定のファイルがないとエラーになりますので注意してください。

実行させると…

$ node.js face-detect.js

【input画像】 f:id:ueponx:20170410232450j:plain ライセンス: CC0 Public DomainCreative Commons — CC0 1.0 Universal

【output画像】 f:id:ueponx:20170410232511j:plain

Webカメラの画像の取り込み

カメラ画像の取り込みに関しては基本的にpythonと違いはありません。 OpenCVGUIを使えますが、node.js経由でも同様にGUIを使用することができます。

【camera.js】

var cv = require('opencv');

try {
  var camera = new cv.VideoCapture(0);
  var window = new cv.NamedWindow('Video', 0)

  setInterval(function() {
    camera.read(function(err, im) {
      if (err) throw err;
      console.log(im.size())
      if (im.size()[0] > 0 && im.size()[1] > 0){
        window.show(im);
      }
      window.blockingWaitKey(0, 50);
    });
  }, 20);
  
} catch (e){
  console.log("Couldn't start camera:", e)
}

実行させると…

$ node.js camera.js

【動作画像】 f:id:ueponx:20170410232605j:plain

多少のアラームは発生するのですが問題なく動作しました。使用したWebカメラMicrosoftのLifeCamになります。(以前の動作までに少し時間がかかる挙動に関係するのだと思います)あとpythonからの呼び出しとは異なってデフォルト解像度は320*240ぽいですね。

Webカメラで顔認識を行う

あとはこれら2つを組み合わせていきます。

【face-detect-cam.js】

var cv = require('opencv');

var RED = [0, 0, 255];
var THICKNESS = 2;

try {
  var camera = new cv.VideoCapture(0);
  var window = new cv.NamedWindow('Video', 0);

  camera.setWidth(480);
  camera.setHeight(320);

  setInterval(function() {
    camera.read(function(err, im) {
      if (err) throw err;

      if (im.size()[0] > 0 && im.size()[1] > 0){
        im.detectObject("./haarcascade_frontalface_alt.xml", {}, function(err, faces){
          for (var i=0;i<faces.length; i++){
            var face = faces[i]
            im.rectangle([face.x, face.y], [face.width, face.height], RED, THICKNESS);
          }
          window.show(im);
        });
        window.blockingWaitKey(0, 50);
      }
    });
  }, 500);

} catch (e){
  console.log("Couldn't start camera:", e)
}

実行させると…

$ node.js face-detect-cam.js

【動作画像】

f:id:ueponx:20170411094557j:plain

このコードでは動作に関してはかなり遅いと思います。遅延が3秒ほどありそうでした。

今回はsetInterval()を使って周期処理を行っているのですが、直感的には whileでループを回せばいいのかなと思ったんですが、うまくいきません。 javascriptsleep()とかwait()が無いことに起因してるかなと思います。

あと、setInterval()の周期のパラメータを小さくすると結構な確率でSegmentation faultが発生するので500msec以上にしたほうがいいかなと思います。

終わりに

一応、node.jsでOpenCVを使ったプログラムを組んでみました。今後そういうこともあるかなとは思います。RaspberryPi2では少しパワー不足という印象がありますが、ホストが一般的なPCならもっといいパフォーマンスをだせるのかなと思います。

これが限界なのかなと色々おもっていたのですが、Xを使ってリモートで接続していることに気が付きました。

ダイレクトにモニタに接続するとsetInterval()の周期のパラメータを250程度まで小さくしてもSegmentation faultもほとんど発生せず、遅延も2秒程度まで追い込めました。

ちなみにファイルに一度保存したほうが早いのではないかと思って、以下のコードを書いてみましたが、全然早くはならず、逆に処理が追いつかずSegmentation faultの確率が上がったように感じます。こちらはディスプレイの直接接続でもSegmentation faultが発生するようでした。

var cv = require('opencv');

var RED = [0, 0, 255];
var THICKNESS = 2;

try {
  var camera = new cv.VideoCapture(0);
  var window = new cv.NamedWindow('Video', 0);

  camera.setWidth(480);
  camera.setHeight(320);

  setInterval(function() {
    camera.read(function(err, im) {
      if (err) throw err;

      if (im.size()[0] > 0 && im.size()[1] > 0){
        im.detectObject("./haarcascade_frontalface_alt.xml", {}, function(err, faces){
          for (var i=0;i<faces.length; i++){
            var face = faces[i]
            im.rectangle([face.x, face.y], [face.width, face.height], RED, THICKNESS);
          }
          im.save('./tmp.jpg');
        });
        cv.readImage("./tmp.jpg", function(err2, im2){
          if (err2) throw err2;
          
          if (im2.size()[0] > 0 && im2.size()[1] > 0){
            window.show(im2);
          }
        });
        window.blockingWaitKey(0, 50);
      }
    });
  }, 500);

} catch (e){
  console.log("Couldn't start camera:", e)
}