콜백함수 사용법..


(Ju Young Lee) #1

var cv = require(‘opencv’);
var fs = require(‘fs’)
, path = require(‘path’)
, async = require(‘async’)
, Controller = require(‘node-pid-controller’)
, events = require(‘events’)
, EventEmitter = new events.EventEmitter()
;

var DT = 150; // time between faces detection

var client, io, lastPng;
var tracking = false;
var debug = true;
var processingImage = false;
var face_cascade = new cv.CascadeClassifier(path.join(__dirname,'node_modules','opencv','data','haarcascade_frontalface_alt2.xml'));


/** 
 *  Controllers initialization.
 */
var ver_ctrl = new Controller(0.3, 0.01, 0.1)
  , hor_ctrl = new Controller(0.4, 0.01, 0.1)
  ;

function log(string) {
	if (debug) {
		console.log(string);
	}
}

var times = [];

function detectFaces() {
  if(tracking && (!processingImage) && lastPng) {
	processingImage = true;

	async.waterfall([
	  function(cb) {
		// 1. Stop the Drone before taking picture
		client.stop();
		setTimeout(function() { // wait the drone stabilization for a new image
		  EventEmitter.once('newPng', function() {
			cb();
		  });
		}, 200);
	  },
	  function(cb) {
		// 2. Read picture (takes between 60 and 100 ms)
		cv.readImage( lastPng, function(err, im) {
		  cb(err,im);
		});
	  },
	  function(im, cb) {
		// 3. Detect faces (takes between 200 and 250 ms)
		var opts = {};
		face_cascade.detectMultiScale(im, function(err, faces) {
		  cb(err, faces, im);
		}, opts.scale, opts.neighbors
		 , opts.min && opts.min[0], opts.min && opts.min[1]);
	  },
	  function(faces, im, cb) {
		// 4. Analyze faces
		var face;
		var biggestFace;
		var dt = DT; // minimum time for the next detection

		for(var k = 0; k < faces.length; k++) {
		  face = faces[k];
		  if( !biggestFace || biggestFace.width < face.width ) biggestFace = face;
		}

		if( biggestFace ) {
		  face = biggestFace;
		  io.sockets.emit('face', { x: face.x, y: face.y, w: face.width, h: face.height, iw: im.width(), ih: im.height() });

		  face.centerX = face.x + face.width * 0.5;
		  face.centerY = face.y + face.height * 0.5;

		  var centerX = im.width() * 0.5;
		  var centerY = im.height() * 0.5;

		  var heightAmount = -( face.centerY - centerY ) / centerY;
		  var turnAmount = -( face.centerX - centerX ) / centerX;

		  heightAmount = ver_ctrl.update(-heightAmount); // pid
		  turnAmount   = hor_ctrl.update(-turnAmount);   // pid

		  var lim = 0.1;
		  if( Math.abs( turnAmount ) > lim || Math.abs( heightAmount ) > lim ){
			log( "  turning " + turnAmount );
			if (debug) io.sockets.emit('/message', 'turnAmount : ' + turnAmount);
			if( turnAmount < 0 ) client.clockwise( Math.abs( turnAmount ) );
			else client.counterClockwise( turnAmount );

			log( "  going vertical " + heightAmount );
			if (debug) io.sockets.emit('/message', 'heightAmount : ' + heightAmount);
			if(  heightAmount < 0 ) client.down( Math.abs(heightAmount) );
			else client.up( heightAmount );
		  }
		  else {
			if (debug) io.sockets.emit('/message', 'pause!');
			client.stop();
		  }

		  // to determine how much time the drone will move, we use the lower of the changes [-1,1], and multiply by a reference time.
		  dt = Math.min(Math.abs(turnAmount), Math.abs(heightAmount));
		  dt = dt * 2000;
		}
		
		processingImage = false;
		cb(null, dt);
	  }
	], function(err, dt) {
	  dt = Math.max(dt, DT);
	  setTimeout(detectFaces, dt);
	});
  } else {
	if (tracking) setTimeout(detectFaces, DT);
  };
};

function copterface(name, deps) {
	debug = deps.debug || false;
	io = deps.io;
	io.sockets.on('connection', function (socket) {
		socket.on('/copterface', function (cmd) {
			console.log("copterface", cmd);
			if (cmd == "toggle") {
			  client.stop(); // make sure to stop the helicopter if stop copterface
			  tracking = tracking ? false : true;
			  if (tracking) detectFaces();
			} 
		});
	});

	client = deps.client;
	client.createPngStream()
	  .on('error', console.log)
	  .on('data', function(pngBuffer) {
	  lastPng = pngBuffer;
	  EventEmitter.emit('newPng');
	});

}

module.exports = copterface;

↑ index.js

	(function (window, undefined) {
  'use strict';

  var CF;

  CF = function CopterFace(cockpit) {
	console.log("Loading Copterface plugin.");

	// Instance variables
	this.cockpit = cockpit;
	this.tracking = false;

	// Add required UI elements
	$("#cockpit").append('<canvas id="copterface" width="640" height="360"></canvas>');
	$("#cockpit").append('<div id="copterface-label" style="display:none;">Face Tracking ON</div>');
	this.ctx = $('#copterface').get(0).getContext('2d');

	// Bind to navdata events on websockets
	var self = this;
	this.cockpit.socket.on('face', function(data) {
	  if (self.tracking && !jQuery.isEmptyObject(data)) {
		requestAnimationFrame(function() {
		  self.render(data);
		});
	  }
	});

	// Bind on window events to resize
	$(window).resize(function(event) {
	  self.draw();
	});

	$(document).keypress(function(ev) {
	  self.keyPress(ev);
	});
  };

  CF.prototype.keyPress = function(ev) {
	console.log("Keypress: " + ev.keyCode);
	if (ev.keyCode != 102) {
	  return;
	}

	ev.preventDefault();
	this.tracking = this.tracking ? false : true;
	this.cockpit.socket.emit("/copterface", "toggle");
	this.clear();
	if (this.tracking) {
		$("#copterface-label").show();
	} else {
		$("#copterface-label").hide();
	}
  }

  CF.prototype.render = function(data) {
	this.ctx.canvas.width = $('#cockpit').innerWidth();
	this.ctx.canvas.height = $('#cockpit').innerHeight();
	  
	var cw = this.ctx.canvas.width;
	var ch = this.ctx.canvas.height;

	var x = (data.x/data.iw) * cw;
	var y = (data.y/data.ih) * ch;
	var w = (data.w/data.iw) * cw;
	var h = (data.h/data.ih) * ch;
	
	this.ctx.clearRect(0, 0, cw, ch);
	this.ctx.save();
	this.ctx.strokeStyle = 'green';
	this.ctx.lineWidth = 2;
	
	this.ctx.strokeRect(x,y,w,h);
	this.ctx.restore();
  }

  CF.prototype.clear = function() {
	this.ctx.canvas.width = $('#cockpit').innerWidth();
	this.ctx.canvas.height = $('#cockpit').innerHeight();
	  
	var cw = this.ctx.canvas.width;
	var ch = this.ctx.canvas.height;
	
	this.ctx.clearRect(0, 0, cw, ch);
  }

  window.Cockpit.plugins.push(CF);

}(window, undefined));

↑ copterface.js

↓ 디렉토리 구조
image

  1. index.js의 copterface 함수 안에 있는
    socket.on(’/copterface’, function (cmd)의 동작 과정을 잘 모르겠습니다.

  2. 저의 예상으로는 디렉토리 구조에서 노란색 형광펜으로 표시한
    copterface.js를 호출하는 것 같은데,
    (function (window, undefined) 안에 있는 console.log(“Loading Copterface plugin.”);이 출력이 안됩니다.

  3. copterface.js파일을 index.js에서 호출하는 방법이 있을까요…?


(InGrowth) #2

음… 주어진 코드만 보면 index.js에서 detectFaces를 실행하고 있는데요.
detectFaces는 정의된적이 없으니 찾을수 없는 상태입니다.

그리고 copterface.js를 require하는 구문 자체가 없어서 copterface.js를 어떻게 실행한다는 것인지 알 수가 없어요.


(Ju Young Lee) #3

detectFaces는 index.js에 정의되어있는데 너무 길어서 제가 생략했었습니다…
index.js에서 require(“copterface”)하니까
copterface.js의 copterface안에서
if(typeof faceCallback !== ‘function’) throw new Error(‘missing callback, expected as second argument to copterface’);
이 부분에서 에러가 납니다…


(InGrowth) #4

모듈을 호출하는 부분에서 코드가 잘못되었겠지요.

require한 copterface가 function(pngStream,options,faceCallback) {} 이니,

인자 3개를 주어서 실행시켜주셔야 하는데 그 부분 코드가 없네요.


(Ju Young Lee) #5

질문 수정했는데, 죄송하지만 혹시 다시 한번 봐주실수있나요?!


(InGrowth) #6

API를 보시면 사용법이 socket.on(eventName, callback) 입니다.

socket.on(’/copterface’, function (cmd)의 ’/copterface’는 require가 아니라 그냥 이벤트 네임일 뿐이에요.

connection 이벤트를 수신한 다음에, 앞으로 /copterface 이벤트를 수신한다는 의미의 API 입니다.

socket.io를 공부하셔야 겠어요.