我的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这个第三方库之后我突然心情很愉快,这将节省我很多时间. 过了这个项目之后,就再也 ...
随机推荐
- 201521123098 《Java程序设计》第7周学习总结
1. 本周学习总结 以你喜欢的方式(思维导图或其他)归纳总结集合相关内容. 2. 书面作业 1. ArrayList代码分析 1.1 解释ArrayList的contains源代码 该方法调用了ind ...
- 201521123109《java程序设计》第五周学习总结
1. 本周学习总结 1.1 尝试使用思维导图总结有关多态与接口的知识点. 1.2 可选:使用常规方法总结其他上课内容. 2. 书面作业 作业参考文件下载 1.代码阅读:Child压缩包内源代码 1.1 ...
- 201521123047 《Java程序设计》第4周学习总结
1. 本周学习总结 1.1 尝试使用思维导图总结有关继承的知识点. 1.2 使用常规方法总结其他上课内容. 答: - 只能有一个父类,即单继承,子类继承父类的全部成员(属性和方法),并可能有自己特有的 ...
- 201521123013 《Java程序设计》第3周学习总结
1. 本章学习总结 2. 书面作业 Q1.代码阅读 public class Test1 { private int i = 1;//这行不能修改 private static int j = 2; ...
- 201521123065《Java程序设计》第1周学习总结
1. 本周学习总结 java是门语言较为简单,并且可以在多种平台运行编译的语言. JDK是java开发工具,可以将源程序编译成字节码. JRE:java运行环境. JVM:虚拟机,是java实现多平台 ...
- sublime列显示控制
Shift+右键拖拽或者Ctrl+左键单击选择多个位置
- Oracle总结第一篇【基本SQL操作】
前言 在之前已经大概了解过Mysql数据库和学过相关的Oracle知识点,但是太久没用过Oracle了,就基本忘了-印象中就只有基本的SQL语句和相关一些概念-.写下本博文的原因就是记载着Oracle ...
- 关于使用lombok遇到的问题
在官网上下载了lombok.jar包以后,有两种安装方式 : 1. 双击下载下来的 JAR 包安装 lombok 我选择这种方式安装的时候提示没有发现任何 IDE,所以我没安装成功,我是手动安装 ...
- Java学习笔记四---打包成双击可运行的jar文件
写笔记四前的脑回路是这样的: 前面的学习笔记二,提到3个环境变量,其中java_home好理解,就是jdk安装路径:classpath指向类文件的搜索路径:path指向可执行程序的搜索路径.这里的类文 ...
- Servlet 3.0 使用注解配置URl提示404错误
我的环境是 Eclipse oxygen + Servlet 3.0 因为3.0已经开始使用注解了 之前我都是配置listenner 还有Servlet mapping 在 web.xml 中 就 ...