四、实例演示 说什么都不如给我一个例子! 如下图所示,我们演示如何获取人体骨骼,并标识脊椎中段及手势:
- npm install kinect2
- var Kinect2 = require('../../lib/kinect2'),
- express = require('express'),
- app = express(),
- server = require('http').createServer(app),
- io = require('socket.io').listen(server);
- var kinect = new Kinect2();
- // 打开kinect
- if (kinect.open()) {
- // 监听8000端口
- server.listen(8000);
- // 指定请求指向根目录
- app.get('/',
- function(req, res) {
- res.sendFile(__dirname + '/public/index.html');
- });
- // 将骨骼数据发送给浏览器端
- kinect.on('bodyFrame',
- function(bodyFrame) {
- io.sockets.emit('bodyFrame', bodyFrame);
- });
- // 开始读取骨骼数据
- kinect.openBodyReader();
- }
2、浏览器端
浏览器端获取骨骼数据,并用 canvas 描绘出来,关键代码如下:
- var socket = io.connect('/');
- var ctx = canvas.getContext('2d');
- socket.on('bodyFrame', function(bodyFrame){
- ctx.clearRect(0, 0, canvas.width, canvas.height);
- var index = 0;
- // 遍历所有骨骼数据
- bodyFrame.bodies.forEach(function(body){
- if(body.tracked) {
- for(var jointType in body.joints) {
- var joint = body.joints[jointType];
- ctx.fillStyle = colors[index];
- // 如果骨骼节点为脊椎中点
- if(jointType == 1) {
- ctx.fillStyle = colors[2];
- }
- ctx.fillRect(joint.depthX * 512, joint.depthY * 424, 10, 10);
- }
- // 识别左右手手势
- updateHandState(body.leftHandState, body.joints[7]);
- updateHandState(body.rightHandState, body.joints[11]);
- index++;
- }
- });
- });
很简单的几行代码,我们便完成了玩家骨骼捕获,有一定 javascript 基础的同学应该很容易能看明白,但不明白的是我们能获取哪些数据?如何获取?骨骼节点名称分别是什么?而 node-kienct2 并没有文档告诉我们这些。
五、开发文档 Node-Kinect2 并没有提供文档,我将我测试总结的文档整理如下:1、服务器端能提供的数据类型;
- kinect.on('bodyFrame',
- function(bodyFrame) {}); //还有哪些数据类型呢?
bodyFrame | 骨骼数据 |
infraredFrame | 红外数据 |
longExposureInfraredFrame | 类似 infraredFrame,貌似精度更高,优化后的数据 |
rawDepthFrame | 未经处理的景深数据 |
depthFrame | 景深数据 |
colorFrame | 彩色图像 |
multiSourceFrame | 所有数据 |
audio | 音频数据,未测试 |
- body.joints[11] // joints包括哪些呢?
节点类型 | JointType | 节点名称 |
0 | spineBase | 脊椎基部 |
1 | spineMid | 脊椎中部 |
2 | neck | 颈部 |
3 | head | 头部 |
4 | shoulderLeft | 左肩 |
5 | elbowLeft | 左肘 |
6 | wristLeft | 左腕 |
7 | handLeft | 左手掌 |
8 | shoulderRight | 右肩 |
9 | elbowRight | 右肘 |
10 | wristRight | 右腕 |
11 | handRight | 右手掌 |
12 | hipLeft | 左屁 |
13 | kneeLeft | 左膝 |
14 | ankleLeft | 左踝 |
15 | footLeft | 左脚 |
16 | hipRight | 右屁 |
17 | kneeRight | 右膝 |
18 | ankleRight | 右踝 |
19 | footRight | 右脚 |
20 | spineShoulder | 颈下脊椎 |
21 | handTipLeft | 左手指(食中无小) |
22 | thumbLeft | 左拇指 |
23 | handTipRight | 右手指 |
24 | thumbRight | 右拇指 |
0 | unknown | 不能识别 |
1 | notTracked | 未能检测到 |
2 | open | 手掌 |
3 | closed | 握拳 |
4 | lasso | 剪刀手,并合并中食指 |
on | 监听数据 |
open | 打开 Kinect |
close | 关闭 |
openBodyReader | 读取骨骼数据 |
open**Reader | 类似如上方法,读取其它类型数据 |
1.1、通过手势触发开始游戏 |
1.2、玩家化身四代,左右跑动躲避九尾攻击 |
1.3、摆出手势 "奥义",触发四代大招 |
1.4、用户扫描二维码获取自己现场照片 |
- var emitColorFrame = false;
- io.sockets.on('connection',
- function(socket) {
- socket.on('startColorFrame',
- function(data) {
- emitColorFrame = true;
- });
- });
- kinect.on('multiSourceFrame',
- function(frame) {
- // 发送玩家骨骼数据
- io.sockets.emit('bodyFrame', frame.body);
- // 玩家拍照
- if (emitColorFrame) {
- var compression = 1;
- var origWidth = 1920;
- var origHeight = 1080;
- var origLength = 4 * origWidth * origHeight;
- var compressedWidth = origWidth / compression;
- var compressedHeight = origHeight / compression;
- var resizedLength = 4 * compressedWidth * compressedHeight;
- var resizedBuffer = new Buffer(resizedLength);
- // ...
- // 照片数据过大,需要压缩提高传输性能
- zlib.deflate(resizedBuffer,
- function(err, result) {
- if (!err) {
- var buffer = result.toString('base64');
- io.sockets.emit('colorFrame', buffer);
- }
- });
- emitColorFrame = false;
- }
- });
- kinect.openMultiSourceReader({
- frameTypes: Kinect2.FrameType.body | Kinect2.FrameType.color
- });
3、客户端
客户端业务逻辑较复杂,我们提取关键步骤进行讲解。
3.1、用户拍照时,由于处理的数据比较大,为防止页面出现卡顿,我们需要使用 web worker
- (function() {
- importScripts('pako.inflate.min.js');
- var imageData;
- function init() {
- addEventListener('message',
- function(event) {
- switch (event.data.message) {
- case "setImageData":
- imageData = event.data.imageData;
- break;
- case "processImageData":
- processImageData(event.data.imageBuffer);
- break;
- }
- });
- }
- function processImageData(compressedData) {
- var imageBuffer = pako.inflate(atob(compressedData));
- var pixelArray = imageData.data;
- var newPixelData = new Uint8Array(imageBuffer);
- var imageDataSize = imageData.data.length;
- for (var i = 0; i < imageDataSize; i++) {
- imageData.data[i] = newPixelData[i];
- }
- for (var x = 0; x < 1920; x++) {
- for (var y = 0; y < 1080; y++) {
- var idx = (x + y * 1920) * 4;
- var r = imageData.data[idx + 0];
- var g = imageData.data[idx + 1];
- var b = imageData.data[idx + 2];
- }
- }
- self.postMessage({
- "message": "imageReady",
- "imageData": imageData
- });
- }
- init();
- })();
3.2、接投影仪后,如果渲染面积比较大,会出现白屏,需要关闭浏览器硬件加速。
3.3、现场光线较暗,其它玩家干扰,在追踪玩家运动轨迹的过程中,可能会出现抖动的情况,我们需要去除干扰数据。(当突然出现很大位移时,需要将数据移除)
- var tracks = this.tracks;
- var len = tracks.length;
- // 数据过滤
- if(tracks[len-1] !== window.undefined) {
- if(Math.abs(n - tracks[len-1]) > 0.2) {
- return;
- }
- }
- this.tracks.push(n);
3.4、当玩家站立,只是左右少量晃动时,我们认为玩家是站立状态。
七、展望 1、使用 HTML5 开发 Kinect 体感游戏,降低了技术门槛,前端工程师可以轻松的开发体感游戏;
- // 保留5个数据
- if(this.tracks.length > 5) {
- this.tracks.shift();
- } else {
- return;
- }
- // 位移总量
- var dis = 0;
- for(var i = 1; i < this.tracks.length; i++) {
- dis += this.tracks[i] - this.tracks[i-1];
- }
- if(Math.abs(dis) < 0.01) {
- this.stand();
- } else {
- if(this.tracks[4] > this.tracks[3]) {
- this.turnRight();
- } else {
- this.turnLeft();
- }
- this.run();
- }
来源: