本範例要介紹如何使用 LinkIt Smart 7688 Duo(7688也可以)結合微軟認知服務下的Face API,您連到7688的影像串流IP之後會不斷偵測畫面中是否辨識到人臉,並將相關資訊(年齡)呈現在網頁上。本範例感謝台灣微軟同仁與 CAVEDU 講師袁佑緣協助。
例如前陣子幾乎人人都玩過的How-Old.net就是運用這類技術來判斷照片中是否有人臉以及年齡判斷等等。更多微軟認知服務的資訊與教學,請參考原廠網站:https://www.microsoft.com/cognitive-services/en-us/apis。
微軟認知服務首頁
目前可用的API
延伸閱讀
取得Face 與Emotion API金鑰
請登入您的Microsoft帳號(我的@msn.com還可用呢!):https://www.microsoft.com/cognitive-services/en-us/face-api
點選APIàFace API,找到頁面下方的Get started for free,如下圖
點選Get started for free
接著會列出可選用的API以及使用方案,以本範例的 Face 與Emotion API 來說,兩者的流量限制都是每個月執行30,000次,每分鐘20次。請勾選Face選項與Emotion選項,再點選頁面最下方的Subscribe即可。
勾選Face與Emotion選項
接著會進到以下頁面,您可在此看到這個產品所產生的Key,屆時就是要把這組Key輸入在本專案的 cognitive.js 與 index.html中。您也可點選 Show Quota 看一下已用掉幾次呼叫。
您目前啟用的服務列表
7688端設定
功能說明
本專案可將7688所連接之USB攝影機之影像串流到微軟認知服務進行辨識,在此用到了Face與Emotion兩種API。系統首先會偵測影像中的一或多張臉孔並以方框標示出臉孔的所在位置,以及該臉孔的相關資訊s which contain machine learning-based predictions of facial features. 偵測到臉孔之後,會將這張臉孔發送到Emotion API再次處理,影像中每一張偵測到的臉孔都會包含以下資訊:Age、 Gender、Pose、Smile等多種屬性。
您可以先從微軟網站來玩玩看,使用網站上現成的測試圖片或是上傳您喜歡的照片都可以。別擔心,微軟不會將您所上傳的照片用於其他用途。
硬體需求
- Linkit Smart 7688 Duo
- USB網路攝影機,在此使用 Logitech C170
- Micro / A type USB 轉接線,用於將網路攝影機接到7688 Duo
程式說明
- 請注意本範例須將7688連上微軟認知服務,因此您的7688一定要先連上外部網路才行。
- 請在 7688 上用 curl 指令取得本範例所有程式環境並解壓縮到指定資料夾:
- curl -L https://github.com/YuanYouYuan/7688-note/raw/master/ch3-ms/code/face-cognition.tar.gz
- tar zxv cd ms-face-cognition
- 請用以下指令確認7688所取得的IP位址之後,在cognitive.js 與 index.html 這兩個檔案修改。
- 請用vim或nano等您喜歡的文字編輯器建立以下兩個檔案,並直接貼入以下所有內容:
- 請修改js 與 index.html 這兩個檔案中的 7688 IP 與 Face API 金鑰(後續程式碼中標示紅字處)。
- 請輸入以下指令來執行程式:
- 開啟網路瀏覽器,輸入<7688IP>:3000 應該就能看到畫面了:
執行的網頁畫面
偵測到臉孔的console訊息
程式碼:
cognitive.js
'use strict';
var express = require("express");
var app = express();
var server = require('http').createServer(app);
var io = require('socket.io')(server);
var request = require('request');
var fs = require('fs');
var Fdata=[{faceId:'......',
faceAttributes:{gender:'....',age:0}}];
var exec = require('child_process').exec;
var exec2 = require('child_process').exec;
var hasOwnProperty = Object.prototype.hasOwnProperty;
app.use(express.static('static'));
//-----child_process-----//
exec('mjpg_streamer -i "input_uvc.so -f 20 -d /dev/video0" -o "output_http.so" ', function(error, stdout, stderr) {
console.log('stdout: ' + stdout);
console.log('stderr: ' + stderr);
if (error !== null) {
console.log('exec error: ' + error);
}
});
console.log('camera on!');
app.get('/',function(req,res){
res.sendFile(__dirname+'/static/index.html');
});
//-----socket on -----//
io.on('connection',function (socket) {
console.log("Linked");
});
//在此指定port編號為3000
server.listen(3000,function(){
console.log("Working on port 3000");
setInterval(function () {
//開啟影像串流,請在此修改 LinkIt Smart IP
console.log("New readFile...");
exec2('wget http://[linkit7688IP]:8080/?action=snapshot -O output.jpg', function(error, stdout, stderr) {
console.log('stdout: ' + stdout);
console.log('stderr: ' + stderr);
if (error !== null) {
console.log('exec error: ' + error);
}
});
//讀取jpg檔並發送到cognitive API,請在此填入Face API金鑰
fs.readFile("./output.jpg", function(err, data) {
request({
method: 'POST',
url: 'https://api.projectoxford.ai/face/v1.0/detect?returnFaceId=true&returnFaceAttributes=age,gender',
headers: {
'Content-Type': 'application/octet-stream',
'Ocp-Apim-Subscription-Key': 'your Face API key'
},
body:data
}, function (error, response, body) {
if (!error && response.statusCode == 200) {
Fdata =JSON.parse(body);
console.dir(Fdata, {depth: null, colors: true});
if (isEmpty(Fdata)) {
console.log("No face detect!");
io.emit('message',{'id':'No Face'});
}
else {
console.log('Face Detect'); io.emit('message',{'id':Fdata[0].faceId,'gender':Fdata[0].faceAttributes.gender,'age':Fdata[0].faceAttributes.age}); //由此解析該臉孔的性別與年齡
}
}
});
//--------emotion API-----------
request({
method: 'POST',
url: 'https://api.projectoxford.ai/emotion/v1.0/recognize',
headers: {
'Content-Type': 'application/octet-stream',
'Ocp-Apim-Subscription-Key': 'your Emotion API key'
},
body: data
}, function (error, response, body) {
if (!error && response.statusCode == 200) {
var object = JSON.parse(body);
console.dir(object, {depth: null, colors: true});
}
});
});
},3000)
});
function isEmpty(obj) {
// null and undefined are "empty"
if (obj == null) return true;
// Assume if it has a length property with a non-zero value that that property is correct.
if (obj.length > 0) return false;
if (obj.length === 0) return true;
// Otherwise, does it have any properties of its own?
// Note that this doesn't handle toString and valueOf enumeration bugs in IE < 9
for (var key in obj) {
if (hasOwnProperty.call(obj, key)) return false;
}
return true;
}
index.html
<!DOCTYPE HTML>
<html>
<head>
<style>
body {
margin: 0px;
padding: 0px;
}
</style>
<meta charset="UTF-8">
<title>Video</title>
<script src="/socket.io/socket.io.js"> </script>
</head>
<body>
<div style="position: relative; z-index: 1;">
//請在此修改 LinkIt Smart IP
<img src="http://[linkit7688IP]:8080/?action=stream" style="position: absolute; z-index: 2;" />
<canvas id="myCanvas" width="640" height="480" style="position: relative; top: -10px; z-index: 3;"></canvas>
</div>
<script>
var Xdata=0;
var socket=io.connect();
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
socket.on('message',function (data) {
console.log(data.id);
face_id=data.id;
face_gender=data.gender; //取得該臉孔的性別
face_age=data.age; //取得該臉孔的年齡
})
setInterval(function(){
context.clearRect(0, 0, canvas.width, canvas.height);
//繪製文字
context.font = 'italic 20pt Calibri'; //設定字體大小與字型
context.fillStyle = 'blue';
context.fillText("faceID:"+face_id,100, 100);
context.fillText("GENDER:"+face_gender,100, 300);
context.fillText("AGE:"+face_age,100, 400);
//顯示臉孔ID、性別與年齡
//繪製外框
context.beginPath();
context.rect(408, 50, 200, 300);
context.lineWidth = 7;
context.strokeStyle = 'red';
context.stroke();*/
},1000/15);
</script>
</body>
</html>