├── .gitignore
├── server
├── lib
│ ├── config
│ │ └── server.js
│ └── routes
│ │ ├── index.js
│ │ └── socket.js
├── package.json
└── server.js
├── client
├── styles.css
├── index.html
├── receiver.js
└── sender.js
├── README.md
└── LICENSE.md
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/server/lib/config/server.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 |
3 | module.exports = {
4 | httpPort: 8080,
5 | staticFolder: path.join(__dirname + '/../../../client')
6 | };
--------------------------------------------------------------------------------
/client/styles.css:
--------------------------------------------------------------------------------
1 | .container {
2 | padding-top: 50px;
3 | }
4 |
5 | .center {
6 | text-align: center;
7 | }
8 |
9 | #canvas-video {
10 | width: 640px;
11 | height: 480px;
12 | border: 1px solid #ccc;
13 | }
--------------------------------------------------------------------------------
/server/lib/routes/index.js:
--------------------------------------------------------------------------------
1 | // Load the page
2 | // NB: This needs to be the last route added
3 | exports.serveIndex = function (app, staticFolder) {
4 | app.get('*', function (req, res) {
5 | res.sendFile('index.html', { root: staticFolder });
6 | });
7 | };
--------------------------------------------------------------------------------
/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "edi-cam",
3 | "private": true,
4 | "scripts": {
5 | "start": "node server.js"
6 | },
7 | "dependencies": {
8 | "async": "^1.0.0",
9 | "express": "4.12.4",
10 | "morgan": "1.4.1",
11 | "opencv": "3.0.0",
12 | "socket.io": "1.3.2"
13 | }
14 | }
--------------------------------------------------------------------------------
/client/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | face-detection-node-opencv
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/server/server.js:
--------------------------------------------------------------------------------
1 | // modules
2 | var express = require('express')
3 | , http = require('http')
4 | , morgan = require('morgan');
5 |
6 | // configuration files
7 | var configServer = require('./lib/config/server');
8 |
9 | // app parameters
10 | var app = express();
11 | app.set('port', configServer.httpPort);
12 | app.use(express.static(configServer.staticFolder));
13 | app.use(morgan('dev'));
14 |
15 | // serve index
16 | require('./lib/routes').serveIndex(app, configServer.staticFolder);
17 |
18 | // HTTP server
19 | var server = http.createServer(app);
20 | server.listen(app.get('port'), function () {
21 | console.log('HTTP server listening on port ' + app.get('port'));
22 | });
23 |
24 | // WebSocket server
25 | var io = require('socket.io')(server);
26 | io.on('connection', require('./lib/routes/socket'));
27 |
28 | module.exports.app = app;
--------------------------------------------------------------------------------
/client/receiver.js:
--------------------------------------------------------------------------------
1 | // MODIFY THIS TO THE APPROPRIATE URL IF IT IS NOT BEING RUN LOCALLY
2 | var socket = io.connect('http://localhost:8080');
3 |
4 | var canvasFace = document.getElementById('canvas-face');
5 | var context = canvasFace.getContext('2d');
6 | var img = new Image();
7 |
8 | // show loading notice
9 | context.fillStyle = '#333';
10 | context.fillText('Loading...', canvasFace.width / 2 - 30, canvasFace.height / 3);
11 |
12 | socket.on('frame', function (data) {
13 | // Reference: http://stackoverflow.com/questions/24107378/socket-io-began-to-support-binary-stream-from-1-0-is-there-a-complete-example-e/24124966#24124966
14 | var uint8Arr = new Uint8Array(data.buffer);
15 | var str = String.fromCharCode.apply(null, uint8Arr);
16 | var base64String = btoa(str);
17 |
18 | img.onload = function () {
19 | context.drawImage(this, 0, 0, canvasFace.width, canvasFace.height);
20 | };
21 | img.src = 'data:image/png;base64,' + base64String;
22 | });
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # face-detection-browser-opencv
2 |
3 | Real-time face detection using WebRTC, OpenCV, Node.js, and WebSockets.
4 |
5 | Similar to [face-detection-node-opencv](https://github.com/claudiopetrini/face-detection-node-opencv) but this time:
6 |
7 | - camera acquisition is made with WebRTC on the browser
8 | - the image is sent to the server via web socket for processing
9 | - the processed image is rendered on the client
10 |
11 | ## Requirements
12 |
13 | * [Node.js](http://nodejs.org/)
14 | * [OpenCV 2.4.9 - 2.4.11](http://opencv.org/)
15 | * A webcam, e.g. laptop-integrated webcam, USB webcam
16 |
17 | ## Installing Node.js packages
18 |
19 | * Navigate to the `server` directory
20 | * To install the packages: `npm install`
21 |
22 | ## Running the demo
23 |
24 | * Make sure you are still in the `server` directory
25 | * To run the server: `node server.js`
26 | * To run the demo locally, open a browser and go to `localhost:8080`
27 |
28 | The app should be up and running!
29 |
--------------------------------------------------------------------------------
/client/sender.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | var canvas = document.getElementById('canvas');
3 | var width = 640;
4 | var height = 480;
5 |
6 | function takepicture(video) {
7 | return function () {
8 |
9 | var context = canvas.getContext('2d');
10 | if (width && height) {
11 | canvas.width = width;
12 | canvas.height = height;
13 | context.drawImage(video, 0, 0, width, height);
14 | var jpgQuality = 0.6;
15 | var theDataURL = canvas.toDataURL('image/jpeg', jpgQuality);
16 | socket.emit('img', theDataURL);
17 | }
18 | }
19 | }
20 |
21 | navigator.getMedia = (navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia);
22 |
23 | navigator.getMedia(
24 | // constraints
25 | {
26 | video: true,
27 | audio: false
28 | },
29 |
30 | // success callback
31 | function (mediaStream) {
32 | var video = document.getElementsByTagName('video')[0];
33 | video.src = window.URL.createObjectURL(mediaStream);
34 | video.play();
35 | setInterval(takepicture(video), 1000 / 10)
36 |
37 | },
38 | //handle error
39 | function (error) {
40 | console.log(error);
41 | })
42 | })();
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Esther Jun Kim
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/server/lib/routes/socket.js:
--------------------------------------------------------------------------------
1 | var cv = require('opencv'),
2 | async = require('async');
3 |
4 | module.exports = function (socket) {
5 | socket.on('img', function (base64) {
6 | var output = base64.replace(/^data:image\/(png|jpeg);base64,/, "");
7 | var buffer = new Buffer(output, 'base64');
8 |
9 | async.auto({
10 | readFromSocket: readFromSocket(buffer),
11 | face: ['readFromSocket', detect(cv.FACE_CASCADE)],
12 | eyes: ['readFromSocket', detect('./node_modules/opencv/data/haarcascade_mcs_eyepair_small.xml')]
13 | }, emitFrame(socket));
14 | })
15 | }
16 |
17 | var readFromSocket = function (buffer) {
18 | return function (callback) {
19 | cv.readImage(buffer, function (err, mat) {
20 | callback(err, mat);
21 | });
22 | }
23 | }
24 |
25 | var detect = function (haarfile) {
26 | return function (callback, results) {
27 | var im = results['readFromSocket'];
28 | im.detectObject(haarfile, {}, function (err, faces) {
29 | if (err) callback(err);
30 |
31 | for (var i = 0; i < faces.length; i++) {
32 | face = faces[i];
33 | im.ellipse(face.x + face.width / 2, face.y + face.height / 2, face.width / 2, face.height / 2);
34 | }
35 | callback(null, im);
36 | });
37 | }
38 | }
39 |
40 | var emitFrame = function (socket) {
41 | return function (err, results) {
42 | if (err) {
43 |
44 | } else {
45 | var im = results['eyes'];
46 | socket.emit('frame', {
47 | buffer: im.toBuffer()
48 | });
49 | }
50 | }
51 | }
--------------------------------------------------------------------------------