使用JavaScript实现Motion Detection

标签: 技术与实践 资源与规范 JavaScript, | 发表时间:2012-05-05 17:33 | 作者:Lisober
出处:http://www.webapptrend.com

Romuald Quantin 本篇文章作者 Romuald Quantin,他成为一个前端开发者已经13年有余,在过去的13年中,他一直尝试动画、视频等方面的开发。Romuald在音乐、艺术欣赏、声音设计和音乐方面都颇有造诣,这些使他在开发过程当中受益匪浅,下面我们就来看一下他是如何使用JavaScript实现Motion Detection的吧。

一、 知识必备

初、中级水平的JavaScript

初、中级水平的HTML5

对jQuery有基本了解

由于使用getUserMdedia和Audio API,Demo需要使用Chrome Canary,并通过本机Web服务器运行。

第三方 必备产品

Chrome Canary

本机Web服务器,例:Xampp(Mac,Windows,Linux)Mamp(Mac)

Webm格式文件的视频解码器,例:

Online ConVert Video converter to convert to the WebM format (VP8)

Mp4格式文件的视频解码器,例:

·  Adobe Media Encoder

·  Adobe After Effects

·  HandBrake

Ogg格式文件的视频解码器,例:

Easy HTML5 Video

二、 示例文件

javascript_motion_detection_soundstep.zip

 

我会在本篇文章中,跟大家讨论一下如何在JavaScript中使用webcam stream追踪用户的操作。Demo中展示了从用户的网络摄像头中搜集到的一段视频,用户可以实时地在使用HTML开发的木琴上弹奏,就像弹奏真正的乐器一样。这个Demo是使用HTML5中的两个“仍需改进”的接口开发的,它们是:getUserMedia API,用来调用用户的网络摄像头,和Audio API,用来弹奏木琴。

同时,我也给大家展示了如何使用混合模式来捕获用户的操作。几乎所有提供了图片处理接口的语言都提供了混合模式。这里引用Wikipedia评论混合模式的一句话,“数字图像编辑中的混合模式通常用来判断两个图层是否能够融合在一起。”JavaScript本身不支持混合模式,而混合模式不过是对像素进行的数学运算,因此我创建了一个混合模式“difference”。

我制作了一个Demo,用来展示web技术的的发展方向。JavaScript和HTML5将会为新型的交互式Web应用提供接口。这是多么激动人心的一件事啊!

  HTML5 getUserMedia API

一直到我写这篇文章的时候,getUserMedia API仍在完善中。W3C第5次发布的HTML,新增了相当多的接口,用于访问本机硬件。navigator.getUserMediaAPI()接口提供了一些工具,使网站能够捕获音频和视频。

一些技术博客中有很多关于如何使用这些接口的文章,所以,这里我不再赘述。如果你想学习更多相关的内容,文章的最后我给大家提供了一些有关getUserMedia的链接。下面,我们一起来看一下我是如何使用该接口做出示例中的内容的。

目前,getUserMedia API只能在Opera 12和Chrome Canary中使用,并且两款浏览器都没有公开发布。针对该示例,你只能使用Chrome Canary,因为该浏览器允许使用AudioContext弹奏木琴。Opera暂还不支持AudioContext。

Chrome Canary 完成安装并启动,然后启用API。在地址栏中输入:about:flags。在启用Media

Figure 1. Enable MediaStream.

图1. 启用Media Stream

最后,由于安全限制,摄像机不允许以本地文件:///的形式进行访问,你必须使用自己的本机web服务器运行示例文件。

现在万事俱备,只需添加一个视频标签用来播放摄像头流媒体文件。使用JavaScript把接收到的流媒体文件添加到视频标签的src属性中。

<video id=”webcam” autoplay width=”640″ height=”480″></video>

在JavaSript代码段中,首先要做两项准备工作,第一项是确保用户的浏览器能够使用getUserMedia API。

function hasGetUserMedia() { return !!(navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia); }

第二项工作就是尝试从用户的网络摄像头中获取流媒体文件。

注:在这篇文章和示例中,我使用的是jQuery框架,但是你可以根据自己的需要选择其它框架,例如,本地文件.querySelector

var webcamError = function(e) {

alert(‘Webcam error!’, e);

};

var video = $(‘#webcam’)[0];

if (navigator.getUserMedia) {

navigator.getUserMedia({audio: true, video: true}, function(stream) { video.src = stream;

}, webcamError); }

else if (navigator.webkitGetUserMedia) { navigator.webkitGetUserMedia(‘audio, video’, function(stream) { video.src = window.webkitURL.createObjectURL(stream);

}, webcamError);

} else {

//video.src = ’video.webm’; // fallback.

}

现在你已经可以在HTML页面中展示网络摄像头流媒体文件了。在下一部分中,我将为大家介绍总体结构以及示例中用到的一些资源。

如果你希望在自己的程序中使用getUserMedia,我推荐以下Addy Osmani的作品。Addy使用Flash fallback创建了getUserMedia API的shim。

 Blend mode difference

接下来,我给大家展示的是使用blend mode difference来检测用户的操作。

把两张图片融合在一起就是对像素值进行加减计算。在Wikipedia的文章中有关混合模式的部分对difference进行了描述。“两个图层相减取其绝对值作为Difference,任何图片和黑片进行融合,所有颜色的值都为0.”

为了实现这个操作,首先需要两张图片。代码反复计算图片的每一个像素,第一张图片的色彩通道值减去第二上的色彩通道值。

比如说,两张红色的图片的每个像素中的色彩通道值为:

-红色:255(0xFF)

-绿色:0

-蓝色:0

下面的操作表示从这些图片减去这些颜色的值:

-红色:255-255=0

-绿色:0-0=0

-蓝色:0-0=0

换句话说,把blend mode difference应用于两张相同的图片会产生一张黑色的图片。下面我们来讨论一下blend mode difference的实用性和图片的获取途径。

我们逐步地完成这个过程,首先设定一个时间间隔,使用canvas元素画出webcam stream中的一张图片,如示例中所示,我设定为1分钟画60张图片,这个数量远远超过你所需要的。当前显示在webcam stream中的图片是你要融合的图片中的第一张。

第二张图片是从webcam中捕获的另外一张图片。现在就有了两张图片,要做的就是对它们的像素值进行差值计算。这意味着,如果两张照片是相同的—换句话说,如果用户没有任何操作—运算结果会是一张黑色的图片。

当用户开始操作,奇妙的事情发生了。当前时间间隔获取到的图片与在之前的时间间隔中获取到图片相比,有细微的变化。如果对不同的像素值进行差值计算,一些颜色出现了。这意味着这两帧图画之间发生了变化。

Figure 2. Example of a blended image from a webcam stream.

图2.融合后的两张图片

Motion detection这个过程就完成了。最后一步就是反复计算融合后的图片中的所有像素,是看否存在不为黑色的像素。   

  必备资源

为了运行这个Demo,还需要一般网站的必备元素。首先,你需要一个包含了canvas和video tags 元素的HTML页面,还需要JavaScript来运行这个应用程序。

然后,还需要一张木琴的图片,最好是透明背景(png格式)。另外,还需要木琴每个按键的图片,每一张图片都需要有轻微的变化。这样使用rollover 效果,这些图片就可以给用户视觉上的反馈,弹奏的琴键会高亮显示。

我们还需要一个音频文件,包含了每一个音符的声音,mp3文件即可。如果弹奏木琴没有声音就会没有任何乐趣可言了。

最后,这时候,需要新创建一个视频,把它解码成MP4格式,下一节,我将为大家介绍一些用来解码视频的工具,你可以在Demo的zip文件中找到所有的资源。

  解码HTML5视频文件

把视频Demo后备文件解码成用来展示HTML5视频文件常见的三种格式。

在把文件解码成Webm格式的过程中,我遇到了点麻烦,我选择的解码工具不允许选择码率,而码率恰恰决定了视频的大小和质量,通常被称作kbps(千比特每秒)。我不再使用Online ConVert Video转换器来转换WebM格式(VP8),这个问题迎刃而解:

MP4格式:你可以使用Adobe Creative Suite中的工具,例如Adobe Media Encoder或者After Effects。或者你可以使用免费的视频转换软件,如Handbrake。

Ogg格式:我通常使用一些工具一次性转换所有格式。我对解码后生产webm和MP4格式视频的质量感觉不是很满意,虽然我没办法更改输出的质量,而Ogg格式的视频的质量比较乐观。你可以使用Easy HTNL5 Video或者Miro等转换器。

Demo中用到的HTML代码段

这是开始JavaScript编码之前的最后一步。添加HTML标签,下面是需要用到的HTML标签(我不会列出我用到的所有内容,比如说视频Demo中的fallback代码,你可以在资源中下载得到。)

这里需要一个简单的视频标签,用来接收用户的webcam feed。不要忘记设计自动播放属性,如果忘记了,stream会暂停在接收到的第一帧画面上。

<video id=”webcam” autoplay width=”640″ height=”480″></video>

视频标签不会显示出来,它的CSS显示样式被设置成none,另外,添加一个canvas元素,用来绘制webcam stream。

<canvas id=”canvas-source” width=”640″ height=”480″></canvas>

这时还需要另外添加一个canvas用来显示motion detection中实时的变动。

<canvas id=”canvas-blended” width=”640″ height=”480″></canvas>

下面添加一个DIV标签,把木琴的图片放入其中。把木琴放到webcam的上部:用户可以身入其境地弹奏木琴。把音符放在木琴的上方,将其属性设置为隐藏,当音符被触发的时候,将会显示在rollover中。

<div id=”xylo”>

<div id=”back”><img id=”xyloback” src=”images/xylo.png”/></div>   <div id=”front”> <img id=”note0″ src=”images/note1.png”/> <img id=”note1″ src=”images/note2.png”/> <img id=”note2″ src=”images/note3.png”/> <img id=”note3″ src=”images/note4.png”/> <img id=”note4″ src=”images/note5.png”/> <img id=”note5″ src=”images/note6.png”/> <img id=”note6″ src=”images/note7.png”/> <img id=”note7″ src=”images/note8.png”/> </div> </div>

  JavaScript motion detection

使用以下JavaScript代码段完成Demo

确认应用是否可以使用getUserMedia API。

确认已接收webcam stream

载入木琴琴谱的音频

启动时间间隔,并调用Update函数

在每一个时间间隔中,在canvas中绘制一张webcam feed。

在每一个时间间隔中,把当前的webcam中的图片和之前的一张融合在一起。

在每一个时间间隔中,在canvas中绘制出融合后的图片。

在每一个时间间隔中,在木琴的琴谱中检查像素的颜色值。

在每一个时间间隔中,如果发现用户进行操作,弹奏出特定的木琴音符。

 第一步:声明变量

声明一些用于存储drawing和motion detection过程中使用到的变量。首先,需要两个对canvas元素的引用,一个用来存储每个canvas上下文

的变量,一个用来存储已绘制的webcam stream。也需要存储每个音符x轴线的位置,弹奏的乐谱,和一些与声音相关的变量。

var notesPos = [0, 82, 159, 238, 313, 390, 468, 544];

var timeOut, lastImageData;

var canvasSource = $(“#canvas-source”)[0];

var canvasBlended = $(“#canvas-blended”)[0];

var contextSource = canvasSource.getContext(’2d’);

var contextBlended = canvasBlended.getContext(’2d’);

var soundContext, bufferLoader;

var notes = [];

同时还需要倒置webcam stream的轴线,让用户感觉自己是在镜子前。这使得他们的弹奏更顺畅。下面是完成该功能需要的代码段: 

contextSource.translate(canvasSource.width, 0);          contextSource.scale(-1, 1);

  第二步:升级和视频绘制

创建一个名为Update的函数,并设定一秒钟执行60次,其中调用其它一些函数。

function update() {

drawVideo();

blend();

checkAreas();

timeOut = setTimeout(update, 1000/60);

}

把视频绘制到canvas中非常简单,只有一行代码

function drawVideo() {

contextSource.drawImage(video, 0, 0, video.width, video.height); }

 第三步:创建blend mode difference

创建一个帮助函数,确保像素相减得到的值务必为正值。你可以使用内置函数Math.abs,不过我用二进制运算符写了一个差不多的函数.大部分情况下,使用二进制运算结构能够提升性能,你不一定要了解其中的细节,只要使用以下的代码即可:

function fastAbs(value) {

// equivalent to Math.abs(); return (value ^ (value >> 31)) - (value >> 31);

}

下面我们写一个blend mode difference函数,这个函数包含三个形参:

一个二维数组用来存储减法得到的结果

一个二维数组存储当前webcam stream中图片的像素

一个二维数组存储之前webcam stream中图片的像素

存储像素的数组是二维的,并且还包含了Red,Green,Blue和Alpha等通道值.

· pixels[0] = red value

· pixels[1] = green value

· pixels[2] = blue value

· pixels[3] = alpha value

· pixels[4] = red value

· pixels[5] = green value

等等……

如示例中所示,webcam stream的宽为640像素,高为480像素。因此,数组的大小为640*480*4=1228000.

循环处理这些像素数组的最好方式是把值扩大4倍(红、绿、蓝、Alpha),这意味着现在只需要处理 307,200 次–情况更加乐观一些。

function difference(target, data1, data2) {

var i = 0;

while (i < (data1.length / 4)) {

var red = data1[i*4];

var green = data1[i*4+1];

var blue = data1[i*4+2]; var alpha = data1[i*4+3]; ++i;

}

}

现在你可以对图片中的像素进行减法运算—从效果上来看,会有大不同—如果颜色通道值已经为0,停止减法运行,并自动设置Alpha的值为255(0xFF)。下面是已经完成的blend mode difference函数(可以进行自主优化):

function difference(target, data1, data2) { // blend mode difference if (data1.length != data2.length) return null; var i = 0; while (i < (data1.length * 0.25)) { target[4*i] = data1[4*i] == 0 ? 0 : fastAbs(data1[4*i] - data2[4*i]); target[4*i+1] = data1[4*i+1] == 0 ? 0 : fastAbs(data1[4*i+1] - data2[4*i+1]); target[4*i+2] = data1[4*i+2] == 0 ? 0 : fastAbs(data1[4*i+2] - data2[4*i+2]); target[4*i+3] = 0xFF; ++i; } }

我在Demo中使用的是一个稍微不同的版本,以取得更好的准确性。我写了一个threshold函数,应用于颜色值。当颜色值低于临界值的时候,该方法把颜色像素值更改为黑色,而高于临界值时,将颜色像素值更改为白色。你可以使用以下的代码段完成该功能。

function threshold(value) {

return (value > 0×15) ? 0xFF : 0;

}

同样我也计算了三种颜色通道的平均值,它们在图片中的像素为黑色或白色 。

function differenceAccuracy(target, data1, data2)

{ if (data1.length != data2.length) return null;

var i = 0;

while (i < (data1.length * 0.25)) {

var average1 = (data1[4*i] + data1[4*i+1] + data1[4*i+2]) / 3;

var average2 = (data2[4*i] + data2[4*i+1] + data2[4*i+2]) / 3;

var diff = threshold(fastAbs(average1 - average2)); target[4*i] = diff;

target[4*i+1] = diff;

target[4*i+2] = diff; target[4*i+3] = 0xFF; ++i; }

}

结果如下面的黑白图片所示:

 

Figure 3. The blend mode difference results in a black and white image.

图3.blend mode difference的结果以黑白图片显示

  第4步:blend canvas

下面我们创建一个函数,用来融合图片,你只需要把正确的值传给它:像素数组。

JavaScript的绘图API提供了一个方法,。这个对象包含了很多有用的属性,比如宽度和高度,同样还有数据属性,正是你需要用到的像素数组。同样,创建一个空的ImageData示例,用来存储结果,并把当前的webcam图片存储下来,以便重复使用。下面是在canvas中融合并绘制结果所用到的代码段:

function blend() {

var width = canvasSource.width;

var height = canvasSource.height;

// get webcam image data

var sourceData = contextSource.getImageData(0, 0, width, height);

// create an image if the previous image doesn’t exist 

if (!lastImageData) lastImageData = contextSource.getImageData(0, 0, width, height);

// create a ImageData instance to receive the blended result

var blendedData = contextSource.createImageData(width, height);

// blend the 2 images

differenceAccuracy(blendedData.data, sourceData.data, lastImageData.data);

// draw the result in a canvas contextBlended.putImageData(blendedData, 0, 0);

// store the current webcam image lastImageData = sourceData;

}

第5步:search for pixel

这是完成Demo的最后一步,这就是motion detection,这里用到上一节我们创建的融合的图片。

在准备阶段,放入了8张木琴音符的图片。用这些音符的位置和大小作融合后图像的像素。然后,重复找出白色的像素。

在重复的过程中,计算出颜色通道的平均值,使用变量存储计算结果。重复过程完成之后,计算出整张图片的像素的总平均值。

通过设定临界值10来避免噪音和一些细微的操作,如果你发现大于10的数值,则应当考虑到在最后一张图像之后,用户进行了操作。这就是运动追踪。

然后,播放出相对应的音符,并展示出note rollover。此处用到的函数如下所示:

function checkAreas() {

// loop over the note areas

for (var r=0; r<8; ++r)

{

// get the pixels in a note area from the blended image

var blendedData = contextBlended.getImageData

( notes[r].area.x,

notes[r].area.y,

notes[r].area.width,

notes[r].area.height);

var i = 0;

var average = 0;

// loop over the pixels

while (i < (blendedData.data.length / 4)) {

// make an average between the color channel

average += (blendedData.data[i*4] + blendedData.data[i*4+1] + blendedData.data[i*4+2]) / 3;

++i; }

// calculate an average between of the color values of the note area

average = Math.round(average / (blendedData.data.length / 4));

if (average > 10) {

// over a small limit, consider that a movement is detected

// play a note and show a visual feedback to the user

playSound(notes[r]);

notes[r].visual.style.display = ”block”;

$(notes[r].visual).fadeOut(); } }

相关链接

我希望这篇文章可以带给你更多的灵感,开发出基于视频的交互程序,

通过以下的Chrome和Opera浏览器的开发和W3C网站上有关API的草案,你可以学到更多有关getUserMedia接口的知识。

如果你想找到更多关于motion detection或者基于视频的应用的灵感,我推荐你遵照以下一些Flash开发人员和motion设计者使用After Effects得出的意见。现在Java Script和HTML势头正劲,开发出了很多新的功能。要学习的内容有很多,你可以参考以下资源:

·  Soundstep blog

·  Blend modes

·  GetUserMedia API

·  GetUserMedia article to get started

·  GetUserMedia shim

· 优秀的开发者博客,如何找到灵感以及写出更好的代码:

·  Grant Skinner’s blog

·  Keith Peter’s blog

· Motion 设计者的博客, Video Copilot

·  ProVideo Coalition

·  Motionographer

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

您可能也喜欢:

Javascript不可用的问题探究

可汗学院试行新教育方式,JavaScript将成为计算机学习第一语言

2011回顾:20个将JavaScript推到极致的网站

新兴中间语言的抉择 JavaScript大受青睐
无觅

相关 [javascript motion detection] 推荐:

使用JavaScript实现Motion Detection

- - Web App Trend
本篇文章作者 Romuald Quantin,他成为一个前端开发者已经13年有余,在过去的13年中,他一直尝试动画、视频等方面的开发. Romuald在音乐、艺术欣赏、声音设计和音乐方面都颇有造诣,这些使他在开发过程当中受益匪浅,下面我们就来看一下他是如何使用JavaScript实现Motion Detection的吧.

改变世界的Leap Motion

- - 望月的博客
当Wii、PlayStation Move和Kinect无比自豪的把体感技术应用于游戏中,让人觉得很牛逼时,Leap Motion一定在一旁冷笑,因为,这款革命性的体感交互设备,有可能改变整个世界.. 所谓体感技术,简单来说,就是可以让人使用肢体动作,而无需任何复杂的控制设备,就可以身历其境地与内容做互动.

贾岳杭:leap motion 技术原理

- - 雷锋网
本文来源 贾岳杭的博客, 原文地址. 我搞体感开发两年,从去年知道了leapmotion这款产品,开始不以为然,以为是基于空气动力学的扰流分析产品,春节后看到某位po主的拆机图,不禁惊叹:平台类的好创意都让外国开发者抢先了. leapmotion的官方售价低于500元人民币,利润不高. 既然制作相同产品为时已晚,就让我详细地分析它的技术原理,抛砖引玉,激励我们国内开发者也能有如此优秀的创意迸发出来吧.

时间序列分段算法 [Time series Breakout Detection]

- - ITeye博客
在时间序列分析中,断点检测(breakout detection)是一个很基本的问题. 通过捕捉时序数据中的断点(breakout),来发现时序数据所表示的系统在过去是否发生了某种事件(event),进而为系统诊断提供必要的数据支持. 为了实现对时序断点的检测,我们首先需要对时序的整体时序做拟合. 这里我们通过一条直线来拟合一段时序,如果时序的趋势发生了变化,则用多条直线来拟合整条时序数据.

Community Detection 社群发现算法 - CSDN博客

- -
        社区发现(Community Detection)算法用来发现网络中的社区结构,也可以视为一种广义的. 以下是我的一个 PPT 报告,分享给大家.         从上述定义可以看出:社区是一个比较含糊的概念,只给出了一个定性的刻画. 另外需要注意的是,社区是一个子图,包含顶点和边.

Joint Face Detection and Alignment using Multi-task Cascaded Convolutional Neural Networks | 邹进屹的博客

- -
第三个网络叫ONet,对第二个CNN获得的人脸区域进行再次训练获得是否是人脸,人脸坐标以及五个特征点. 以下项目时MTCNN的具体代码实现. 项目地址:https://github.com/pangyupo/mxnet_mtcnn_face_detection. // MTCNN_VS2015.cpp : 定义控制台应用程序的入口点.

“妈妈再也不用担心我的数学了”,Motion Math用游戏帮助儿童轻松学习数学

- - PingWest
相信数学对于很多小学的孩子来说是最痛苦的事情之一,那些抽象的排序、分数、小数、乘法、还有简单的几何学,恐怕让老师和家长们头疼怎么说清楚,孩子们也不能很好的理解. 总部位于旧金山的Motion Math团队就想要结合iPad游戏来解决这个问题. 他们团队中的8个人都都有过当老师的经历,然后他们一起在斯坦福教育学院做一个教学项目时开发了第一款游戏,后来就一发不可收拾,干脆成立公司一连推出7款游戏.

Javascript诞生记

- Milido - 阮一峰的网络日志
二周前,我谈了一点Javascript的历史. 今天把这部分补全,从历史的角度,说明Javascript到底是如何设计出来的. 只有了解这段历史,才能明白Javascript为什么是现在的样子. 我依据的资料,主要是Brendan Eich的自述. "1994年,网景公司(Netscape)发布了Navigator浏览器0.9版.

JavaScript,你懂的

- dylan - keakon的涂鸦馆
经常有人问我,JavaScript应该怎么学. 先学基本语法,如果曾学过C等语言,应该1小时内就能掌握了. 再去使用内置的函数、方法和DOM API,熟悉它能干什么;而在学习DOM API的过程中,你还不得不与HTML和CSS打交道. 然后弄懂匿名函数和闭包,学会至少一个常用的JavaScript库(例如jQuery).

Javascript 里跑Linux

- rockmaple - Shellex&#39;s Blog
牛逼到暴的大拿 Fabrice Bellard,用Javascript实现了一个x86 PC 模拟器,然后成功在这个模拟器里面跑Linux(请用Firefox 4 / Google Chrome 11打开,Chome 12有BUG). 关于这个东西… 伊说 “I did it for fun“,大大啊大大啊….