2015.12.09
- 人×技術
Node.jsで顔認識して画像を合成する
こんにちは、イシイです。
最近Node.jsに触れる機会が増えてきて、Node.jsで顔認識でもやってみようかと触り始めた矢先にCloud Vision APIの画像認識のニュースが出てきてしまい、ちょっと梯子を外された感じではありますが、せっかくなのでNode.jsでやってみました。
準備
顔認識にはnode-opencvを使ってみました。また、画像合成にはGraphicsMagickとImageMagickが使えるgmを使っています。
node-opencvの取得
まずは、GitHubからnode-opencvを持ってきます。
git clone https://github.com/peterbraden/node-opencv
ライブラリのインストール
次に、MacでOpenCVを使えるように、brewでインストールします。
brew tap homebrew/science
brew install opencv
インストールの際にエラーが出たのですが、
cd /usr/local/lib
sudo ln -s ../../lib/libSystem.B.dylib libgcc_s.10.5.dylib
こんな感じで動作するようです。次に先程GitHubからクローンしてきたnode-opencvのディレクトリに移動して
npm install
でモジュールをインストールします。
これで、node-opencv内のexamplesのものは大体動くのですが、画像合成用に追加で
brew install imagemagick
brew install graphicsmagick
をしておきます。
顔認識と画像合成プログラム
MacのWebカメラから取得した画像の顔認識を行い、認識した箇所にヨンマルマルキューのロゴを被せて画像をファイル出力する、ということをしています。今回はWebカメラから取得した画像を使っていますが、アップロードしてもらった画像の顔認識を行って画像合成するような仕組みにも転用できるようなイメージで作成しました。
app.js
キーイベントをフックにして顔認識処理をするようにしているので、プログラムが実行されてWebカメラの映像がスクリーンに表示されたら、Cキーを押して認識処理を開始します。Cキーを押したタイミングのフレーム画像に対して顔認識を行うようにしていて、スクリーン上では今どういうステータスなのかをメッセージとして表示するようにしています。画像のファイル出力まで終わったら、プログラムは自動で終了します。
const logger = require('./logger');
const cv = require('../lib/opencv');
const gm = require('gm').subClass({imageMagick: true});
var capture;
const fs = require('fs');
const compFile = './tmp/logo.png';
var compWidth, compHeight;
const dstFile = './tmp/composition.png';
const tmpFile = './tmp/detect_face.jpg';
gm(compFile)
.size((err, size) => {
if(!err) {
compWidth = size.width;
compHeight = size.height;
}
});
const async = require('async');
const keypress = require('keypress');
keypress(process.stdin);
process.stdin.setRawMode(true);
process.stdin.resume();
var detectStart = false;
var msg = 'Press C Key';
try {
const camera = new cv.VideoCapture(0);
const window = new cv.NamedWindow('Video', 0)
setInterval(() => {
camera.read((err, img) => {
if (err) throw err;
if (img.size()[0] > 0 && img.size()[1] > 0){
if (!detectStart) {
outMsg(img, msg);
} else {
capture = img.copy();
detectStart = false;
msg = 'Detecting...';
var opts = {};
//detect start
capture.detectObject(cv.FACE_CASCADE, opts, (err, faces) => {
//detect check
if (faces.length) {
//draw circle according to the number of face
faces.forEach ((item, index, array) => {
if (isTarget(item.width, capture.width())) {
capture.ellipse(item.x + item.width / 2, item.y + item.height / 2, item.width / 2, item.height / 2);
msg = 'Detected!';
logger.system.debug(array);
} else {
return;
}
});
//screen capture -> analyze capture -> composition -> file export
async.waterfall(
[
(callback) => {//screnn capture
capture.save(tmpFile);
callback(null);
},
(callback) => {//analyze capture
msg = 'Composing...';
var steps = gm();
steps.in('-page', '+0+0').in(tmpFile);
faces.forEach ((item, index) => {
var compCenterX = (item.x + item.width / 2 - compWidth / 2);
var compCenterY = (item.y + item.height / 2 - compHeight / 2);
var compPos = '+' + compCenterX + '+' + compCenterY;
steps.in('-page', compPos).in(compFile);
});
steps
.mosaic()
.toBuffer('PNG', (err, buffer) => {
callback(null, buffer);
});
},
(buffer, callback) => {//composition
msg = 'Writing...';
fs.writeFile(dstFile, buffer, 'binary', (err) => {
callback(null);
});
},
(callback) => {//file export
logger.system.debug('Composed file is ' + dstFile);
msg = 'Done.';
fs.unlink(tmpFile);
callback(null);
}
],
(err, result) => {//exit
if(err) {
throw err;
}
process.stdin.pause();
process.exit();
});
} else {
msg = 'Not detected. Retry. Press C Key.';
}
});
}
window.show(img);
window.blockingWaitKey(0, 50);
}
});
},50);
} catch (e){
logger.system.error("Camera Open Error", e)
}
//Status Message
function outMsg (img, msg) {
//Text, xPos, yPos, font, BGR, scale, bold
img.putText(msg, 100, 100, cv.FONT_HERSHEY_COMPLEX_SMALL, [0, 0, 255], 1, 2);
};
//Aptitude check
function isTarget (targetWidth, screenWidth) {
if ((targetWidth == 0) || (screenWidth == 0)) {
return false;
} else {
return true;
}
};
//KeyPress Event
process.stdin.on('keypress', (ch, key) => {
//Detect Start
if (key.name === 'c') {
detectStart = true;
}
//Process Exit
if (key.name === 'x') {
process.stdin.pause();
process.exit();
}
});
package.json
node-opencvのライブラリの他、今回のプログラム用に必要なパッケージはこんな感じです。log4jsについては必須ではないため、必要に応じて入れてください。
{
"name": "opencv_composite"
, "version": "0.1.0"
, "private": true
, "scripts": {
"start": "node app"
}
, "dependencies": {
"gm": "1.21.1",
"async": "1.5.0",
"keypress": "0.2.1",
"log4js": "0.6.29"
}
}
あとがき
合成するロゴ画像を顔のサイズに合わせてスケールさせる処理を入れていなかったり、顔認識から画像合成・出力の処理を子プロセスで処理するようにした方が良さそうだったりと、プログラムとしては実装すべきことはまだまだありそうですが、Node.jsだけでもそれなりに顔認識・画像合成はできそうです。