我的three.js学习记录(三)
此次的亮点不是three.js的3d部分,而是通过调用摄像头然后通过摄像头的图像变化进行简单的判断后进行一些操作。上篇中我通过简单的示例分析来学习three.js,这次是通过上一篇的一些代码来与摄像头判断部分的代码相互结合,弄一个新的东西,可以看下图
说明
这次的示例是我们可以通过一个摄像头隔空控制我们屏幕中的视频的播放。
原理其实也是很简单,我们看到的摄像头图像其实是通过获取到的图像数据然后再通过canvas
画上去的,这里有两层canvas
一层是我们的正常的摄像头输出,一层是我们的播放按钮和暂停按钮。然后还有一层canvas
被我们隐藏了,这层隐藏的就是将我们的上次的图像输出记录下来作为缓存的作用。这层隐藏的canvas
和图像输出的进行特定区域(按钮区域)的RGB值的判断,如果判断到波动了一定的范围,那么我们就进行特定的操作。
相关
我们这里除了需要用到我们three.js的相关知识还需要用到canvas
和js调用摄像头的工作,所以这里我给出一些链接,希望有用
https://developer.mozilla.org/zh-CN/docs/Web/API/Navigator/getUserMedia
http://blog.csdn.net/qq_16559905/article/details/51743588
http://www.w3school.com.cn/tags/html_ref_canvas.asp
http://www.w3school.com.cn/tags/tag_canvas.asp
准备工作
通过以上的说明,我们现在开始工作
在准备工作中我们需要用到我的three.js学习记录(一)和我的three.js学习记录(二)的一些东西,这里就不一一列举出来了。
首先,我们先来看一下html代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>webcam_demo</title>
<style>
body {
background-color: #000;
color: #fff;
margin: 0;
overflow: hidden;
}
</style>
<script src="js/three.js"></script>
<script src="js/stats.min.js"></script>
<script src="js/dat.gui.js"></script>
<script src="js/Detector.js"></script>
<script src="js/DDSLoader.js"></script>
<script src="js/day1020.js"></script>
<script src="js/OrbitControls.js"></script>
</head>
<body>
<!--加载我们要看的视频-->
<video id="video" src="video/sintel.ogv" style="display: none; left: 15px; top: 75px;"></video>
<!--加载我们的摄像机的图像-->
<video id="webcam" autoplay style="display: none; width: 320px; height: 240px;"></video>
<div id="canvasLayers" style="position: relative; left: 0; top: 0;">
<!--画摄像头输出图像-->
<canvas id="videoCanvas" width="320" height="240" style="z-index: 1; position: absolute; left:0; top:0; opacity:0.5;"></canvas>
<!--画出按钮-->
<canvas id="layer2" width="320" height="240" style="z-index: 2; position: absolute; left:0; top:0; opacity:0.5;"></canvas>
</div>
<canvas id="blendCanvas" style="display: none; position: relative; left: 320px; top: 240px; width: 320px; height: 240px;"></canvas>
<!--加载摄像机,主要是将我们的摄像机获取的图像数据放入video#webcam中-->
<script>
navigator.getUserMedia = navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia;
if (navigator.getUserMedia) {
navigator.getUserMedia({ audio: false, video: { width: 1280, height: 720 } },
function(stream) {
var video = document.querySelector('#webcam');
video.srcObject = stream;
video.onloadedmetadata = function(e) {
video.play();
};
},
function(err) {
console.log("当前错误:" + err.name);
}
);
} else {
console.log("设备不支持");
}
</script>
<!--用作渲染器的容器-->
<div id="webgl" style="position: absolute; left: 0; top: 0;"></div>
<!--这里的js文件是操作我们图像处理部分的-->
<script src="js/webcam.js"></script>
<script type="text/javascript">
threeStart();
</script>
</body>
</html>
上面的html代码主要是布局还有将我们需要的视频以及我们的摄像机需要的图像数据加载进来,其他的处理部分则是调用了threeStart()
来实现
开发
现在我们已经搭建好了一切我们需要的(上两篇博客中已经有了,只是沿用上次的进行开发),现在我们来处理我们获取到的图像数据,然后进行判断图像的变化,如果变化的量超过了一定的值我们就进行特定的操作。
这里不同于上一篇博客我的three.js学习记录(二)的地方除了减少了一些东西外,我们的arimate()
函数也做了一些变化,如下:
/**
* 回调函数,重画整个场景
*/
var isPlayTv = false;
function arimate() {
if (isPlayTv && video.readyState === video.HAVE_ENOUGH_DATA) {
if (texture) texture.needsUpdate = true;
// video.play();
}
//将我们的摄像头的图像和按钮图片分别放入两层canvas中
renderWebcam();
blender();
checkUpdate(function (msg) {
if (msg === 'play') {
isPlayTv = true;
video.play();
} else {
isPlayTv = false;
video.pause();
}
});
//渲染
renderer.render(scene, camera);
//fps状态更新
stats.update();
//重新调用arimate
requestAnimationFrame(arimate);
}
接下来我们来看看上面的三个函数,分别是renderWebcam()
,blender()
,checkUpdate(func)
这三个函数的代码如下:
//我们从摄像机获取的图像数据就存放于video#webcam
var webcam = document.getElementById('webcam');
//画出我们的摄像机图像
var videoCanvas = document.getElementById('videoCanvas');
var videoContext = videoCanvas.getContext('2d');
//专门用于画出按钮(播放和暂停)
var layer2Canvas = document.getElementById('layer2');
var layer2Context = layer2Canvas.getContext('2d');
var blendCanvas = document.getElementById('blendCanvas');
//这里主要是用于缓冲,储存上一个视频图像与下一个视频图像之间的变化
var blendContext = blendCanvas.getContext('2d');
//这里是加入我们的两个按钮(分别都是图片)
var buttons = [];
var button1 = new Image();
button1.src ="img/play.png";
var buttonData2 = { name:"play", image:button1, x:320 - 64 - 20, y:10, w:32, h:32 };
buttons.push( buttonData2 );
var button2 = new Image();
button2.src ="img/pause.png";
var buttonData3 = { name:"pause", image:button2, x:320 - 32 - 10, y:10, w:32, h:32 };
buttons.push( buttonData3 );
// 这里能将视频反转,缺一不可,是一个搭配
videoContext.translate(320, 0);
videoContext.scale(-1, 1);
// 设置背景颜色,如果没有视频输出显示该颜色
videoContext.fillStyle = '#005337';
videoContext.fillRect( 0, 0, videoCanvas.width, videoCanvas.height );
var lastImage;
/**
* 功能:主要是拿我们当前的canvas#videoCanvas上下文中的图像数据,与我们上次的数据lastImage作比较
* 然后将我们比较后的数据的结果放入canvas#blendCanvas的上下文,当我们调用checkUpdate(func)
* 就能调用canvas#blendCanvas的上下文的数据做判断是否按钮区域的rgb是否变化然后调用func
*/
function blender() {
var width = videoCanvas.width;
var height = videoCanvas.height;
//获取摄像机视频中的图像资源信息,包括了rgba,是一个数组,数组大小是像素的4倍(rgba)
//r = temp[0]; g = temp[1]; b = temp[2]; a = temp[3];
var source = videoContext.getImageData(0, 0, width, height);
//创建一个跟视频cavas一样大小的图像数据区
var blend = videoContext.createImageData(width, height);
//如果没有上次的数据则置入本次的图像数据
if (!lastImage)lastImage = videoContext.getImageData(0, 0, width, height);
//判断我们rgb的值有没有变化
differenceAccuracy(blend.data, source.data, lastImage.data);
//将我们判断后的数据blend放入blendCanvas上下文
blendContext.putImageData(blend, 0, 0);
//将我们上次的数据置为本次
lastImage = source;
/**
* 混合源rgb值和前rgb值,得到当前像素点是否发生改变 改变用1表示,不改变用0表示
* @param targetData 转换的目标rgba数组
* @param sourceData 源,即当前的视频图像rgba数组
* @param lastData 上一个图像数组
*/
function differenceAccuracy(targetData, sourceData, lastData) {
if (sourceData.length !== lastData.length) return null;
var i = 0;
//这里sourceData.length * 0.25只是获取图像的1/4
//这里用一维数组获取数据是因为整个图像rbga二维值都使用一维数组
while (i < (sourceData.length * 0.25))
{
//这里每隔4个像素点获取一个像素rgba值
var average1 = (sourceData[4*i] + sourceData[4*i+1] + sourceData[4*i+2]) / 3;
var average2 = (lastData[4*i] + lastData[4*i+1] + lastData[4*i+2]) / 3;
//算出我们的上一个和当前的图像数据的值是否超过一个规定的值(可以理解为对变化的敏感度)
//如果是则将diff置为0xFF,否0
var diff = threshold(Math.abs(average1 - average2));
//将算出的值放入targetData
targetData[4*i] = diff;
targetData[4*i+1] = diff;
targetData[4*i+2] = diff;
targetData[4*i+3] = 0xFF;
++i;
}
function threshold(value)
{
return (value > 0x15) ? 0xFF : 0;
}
}
}
function renderWebcam() {
if ( webcam.readyState === webcam.HAVE_ENOUGH_DATA ){
//将我们video#webcam的图像数据使用videoCanvas画出来
videoContext.drawImage(webcam, 0, 0, videoCanvas.width, videoCanvas.height);
//画出我们的图片按钮播放和暂停
for ( var i = 0; i < buttons.length; i++ )
layer2Context.drawImage( buttons[i].image, buttons[i].x, buttons[i].y, buttons[i].w, buttons[i].h );
}
}
/**
* 判断canvas#blendCanvas的上下文数据总体上是否变化,如果是则调用func
* @param func 回调
*/
function checkUpdate(func) {
//我们这里是循环按钮的个数,我们这里有两个按钮,有可能两个按钮都有变化
for (var i = 0; i < buttons.length; i++){
var data = blendContext.getImageData(buttons[i].x, buttons[i].y, buttons[i].w, buttons[i].h).data;
//储存当前区域的countPixels数量的rgb相加的总值
var sum = 0;
//countPixels是我们区域中所有的像素点的1/4
var countPixels = data.length * 0.25;
for (var j = 0; j < countPixels; j++){
//因为我们countPixels所有的像素点的1/4,所以每一次需要*4
sum += data[4*j] + data[4*j+1] + data[4*j+2];
}
//做出平均值
var average = Math.round((sum / (3 * countPixels)));
//如果平均值大于某个值则判断为变化了,就调用func将我们按钮区域的名字传过去
if (average > 50){
func(buttons[i].name);
}
}
}
上面的代码就是我们处理图像的核心,通过以上的代码可以判断我们的按钮区域的rgb值是否在总体上变化,如果变化就进行调用func
,这里也就进入尾声了
总结
本次的调用摄像头来进行隔空的操作需要感谢http://stemkoski.github.io/本链接提供的东西,里面有很多操作,可以供我们学习,这篇博客个人感觉写的不是很好,毕竟思路还是没有理清晰,可能是因为对于代码的不完全理解吧,希望海涵
以上代码已经上传Github
我的three.js学习记录(三)的更多相关文章
- vue.js学习记录
vue.js学习记录 文章已同步我的github笔记https://github.com/ymblog/blog,欢迎大家加star~~ vue实例 生命周期 beforeCreate:不能访问thi ...
- 我的three.js学习记录(二)
通过上一篇文章我的three.js学习记录(一)基本上是入门了three.js,但是这不够3D,这次我希望能把之前做的demo弄出来,然后通过例子来分析操作步骤. 1. 示例 上图是之前做的一个dem ...
- JavaScript学习记录三
title: JavaScript学习记录三 toc: true date: 2018-09-14 23:51:22 --<JavaScript高级程序设计(第2版)>学习笔记 要多查阅M ...
- 3.VUE前端框架学习记录三:Vue组件化编码1
VUE前端框架学习记录三:Vue组件化编码1文字信息没办法描述清楚,主要看编码Demo里面,有附带完整的代码下载地址,有需要的同学到脑图里面自取.脑图地址http://naotu.baidu.com/ ...
- D3.js学习记录【转】【新】
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- leveldb 学习记录(三) MemTable 与 Immutable Memtable
前文: leveldb 学习记录(一) skiplist leveldb 学习记录(二) Slice 存储格式: leveldb数据在内存中以 Memtable存储(核心结构是skiplist 已介绍 ...
- node.js学习(三)简单的node程序&&模块简单使用&&commonJS规范&&深入理解模块原理
一.一个简单的node程序 1.新建一个txt文件 2.修改后缀 修改之后会弹出这个,点击"是" 3.运行test.js 源文件 使用node.js运行之后的. 如果该路径下没有该 ...
- webrtc学习———记录三:mediaStreamTrack
参考: https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack 转自http://c.tieba.baidu.com/p/3 ...
- 我的three.js学习记录(一)
在之前因为项目需要使用WebGL技术做网页应用,但是苦于自己没有接触,只是使用过OpenGL.然后接触到了thre.js这个第三方库之后我突然心情很愉快,这将节省我很多时间. 过了这个项目之后,就再也 ...
随机推荐
- 英语APP体验
第一部分 1.下载并使用,描述最简单直观的个人第一次上手体验. 感觉不是很好用,可能是个人习惯吧,之前用的都是扇贝单词和有道词典,所以不是特别顺手. 2.找出几个功能性的比较严重的 bug 在口语挑战 ...
- 201521123028《Java程序设计》第4周学习总结
1. 本周学习总结 2. 书面作业 Q1.注释的应用 使用类的注释与方法的注释为前面编写的类与方法进行注释,并在Eclipse中查看. 对上周PTA的实验5-3中的矩形和圆形类做注释. Q2.面向对象 ...
- 201521123080《Java程序设计》第2周学习总结
1.本周学习总结 a.学习了如何建立远程仓库和本地仓库并建立连接. b.学习了一些基础语法. 2.书面作业 Q1.使用Eclipse关联jdk源代码,并查看String对象的源代码(截图)?分析Str ...
- 201521123022 《Java程序设计》 第十周学习总结
1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结异常与多线程相关内容. 2. 书面作业 本次PTA作业题集异常.多线程 Q1.finally 题目4-2 Q1.1 截图你的提交结果 ...
- 201521123089 《Java程序设计》第11周学习总结
1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多线程相关内容. 2. 书面作业 本次PTA作业题集多线程 Q1.互斥访问与同步访问 1.1 除了使用synchronized修饰方 ...
- Ubuntu Server 17.04安装GNOME指令
短短几行指令,从安装到打开: apt-get install gnome-shell apt-get install xinit xshell 开启 关闭: Ctrl+Alt+F1 Ctrl+A ...
- python新增nonlocal关键字
def fa(a): b = 2 def fb(): nonlocal b print(b) return fbc = 2fa(22)()# python作用域:LEGB
- 如何查看maven plugin所包含的goal
maven项目的构建生命周期(build lifecycle)由很多阶段组成:从validate到deploy. maven插件中包含goal.这些goal可以被绑定到不同的maven的构建阶段上.g ...
- C#中的两把双刃剑:抽象类和接口
问题出现: 这也是我在学习抽象类和接口的时候遇到的问题,从我归纳的这三个问题,不难看出这也许是我们大多数程序员遇到问题的三个阶段, 第一阶段(基础概念):就象问题1一样,这部分人首先需要扫清基础概念的 ...
- 关于ng-options
在实际使用过程中对angular的ng-options指令有点不解,有的时候觉得很容易理解和上手,但其实等到遇到问题时,发现它很是生疏,(key,value)键值对获取,as关键词,track by ...