2025上半年,最新 AI实践都在这!20+ 应用案例,任听一场议题就值回票价 了解详情
写点什么

来看看机智的前端童鞋怎么防盗

  • 2019-08-22
  • 本文字数:5594 字

    阅读完需:约 18 分钟

来看看机智的前端童鞋怎么防盗

很多开发的童鞋都是只身混江湖、夜宿城中村,如果居住的地方安保欠缺,那么出门在外难免担心屋里的财产安全。

事实上世面上有很多高大上的防盗设备,但对于机智的前端童鞋来说,只要有一台附带摄像头的电脑,就可以简单地实现一个防盗监控系统~

纯 JS 的“防盗”能力很大程度借助于 H5 canvas 的力量,且非常有意思。

step1. 调用摄像头

我们需要先在浏览器上访问和调用摄像头,用来监控屋子里的一举一动。不同浏览器中调用摄像头的 API 都略有出入,在这里我们以 chrome 做示例:


<video width="640" height="480" autoplay></video>
<script> var video = document.querySelector('video');
navigator.webkitGetUserMedia({ video: true }, success, error); function success(stream) { video.src = window.webkitURL.createObjectURL(stream); video.play(); } function error(err) { alert('video error: ' + err) }
</script>
复制代码


运行页面后,浏览器出于安全性考虑,会询问是否允许当前页面访问你的摄像头设备,点击“允许”后便能直接在 video 上看到摄像头捕获到的画面了:


step2. 捕获 video 帧画面

光是开着摄像头监视房间可没有任何意义,浏览器不会帮你对监控画面进行分析。所以这里我们得手动用脚本捕获 video 上的帧画面,用于在后续进行数据分析。


从这里开始咱们就要借助 canvas 力量了。通过 ctx.drawImage() 方法可以捕获 video 帧画面并渲染到画布上。


我们需要创建一个画布,然后这么写:


<video width="640" height="480" autoplay></video><canvas width="640" height="480"></canvas>
<script> var video = document.querySelector('video');
var canvas = document.querySelector('canvas'); // video捕获摄像头画面(略)
//canvas var context = canvas.getContext('2d');
setTimeout(function(){ //把当前视频帧内容渲染到画布上 context.drawImage(video, 0, 0, 640, 480); }, 5000);</script>
复制代码


如上代码所示,5 秒后把视频帧内容渲染到画布上(下方右图):


step3. 对捕获的两个帧画面执行差异混合

在上面我们提到过,要有效地识别某个场景,需要对视频画面进行数据分析。


那么要怎么识别咱们的房子是否有人突然闯入了呢?答案很简单 —— 定时地捕获 video 画面,然后对比前后两帧内容是否存在较大变化。


我们先简单地写一个定时捕获的方法,并将捕获到的帧数据存起来:


    //canvas    var context = canvas.getContext('2d');        var preFrame,   //前一帧        curFrame;   //当前帧
//捕获并保存帧内容 function captureAndSaveFrame(){ console.log(context); preFrame = curFrame; context.drawImage(video, 0, 0, 640, 480); curFrame = canvas.toDataURL; //转为base64并保存 } //定时捕获 function timer(delta){ setTimeout(function(){ captureAndSaveFrame(); timer(delta) }, delta || 500); }
timer();
复制代码


如上代码所示,画布会每隔 500 毫秒捕获并渲染一次 video 的帧内容:



留意这里我们使用了 canvas.toDataURL 方法来保存帧画面。


接着就是数据分析处理了,我们可以通过对比前后捕获的帧画面来判断摄像头是否监控到变化,那么怎么做呢?


熟悉设计的同学肯定常常使用一个图层功能 —— 混合模式:



当有两个图层时,对顶层图层设置“差值/Difference”的混合模式,可以一目了然地看到两个图层的差异:



“图 A”是我去年在公司楼下拍的照片,然后我把它稍微调亮了一点点,并在上面画了一个 X 和 O 得到“图 B”。接着我把它们以“差值”模式混合在一起,得到了最右的这张图。


“差值”模式原理:要混合图层双方的 RGB 值中每个值分别进行比较,用高值减去低值作为合成后的颜色,通常用白色图层合成一图像时,可以得到负片效果的反相图像。用黑色的话不发生任何变化(黑色亮度最低,下层颜色减去最小颜色值 0,结果和原来一样),而用白色会得到反相效果(下层颜色被减去,得到补值),其它颜色则基于它们的亮度水平


在 CSS3 中,已经有 blend-mode 特性来支持这个有趣的混合模式,不过我们发现,在主流浏览器上,canvas 的 globalCompositeOperation 接口也已经良好支持了图像混合模式。


于是我们再建多一个画布来展示前后两帧差异:


<video width="640" height="480" autoplay></video><canvas width="640" height="480"></canvas><canvas width="640" height="480"></canvas>
<script> var video = document.querySelector('video'); var canvas = document.querySelectorAll('canvas')[0]; var canvasForDiff = document.querySelectorAll('canvas')[1]; // video捕获摄像头画面(略)
//canvas var context = canvas.getContext('2d'), diffCtx = canvasForDiff.getContext('2d'); //将第二个画布混合模式设为“差异” diffCtx.globalCompositeOperation = 'difference';
var preFrame, //前一帧 curFrame; //当前帧
//捕获并保存帧内容 function captureAndSaveFrame(){ preFrame = curFrame; context.drawImage(video, 0, 0, 640, 480); curFrame = canvas.toDataURL(); //转为base64并保存 } //绘制base64图像到画布上 function drawImg(src, ctx){ ctx = ctx || diffCtx; var img = new Image(); img.src = src; ctx.drawImage(img, 0, 0, 640, 480); } //渲染前后两帧差异 function renderDiff(){ if(!preFrame || !curFrame) return; diffCtx.clearRect(0, 0, 640, 480); drawImg(preFrame); drawImg(curFrame); } //定时捕获 function timer(delta){ setTimeout(function(){ captureAndSaveFrame(); renderDiff(); timer(delta) }, delta || 500); }
timer();</script>
复制代码


效果如下:



可以看到,当前后两帧差异不大时,第三个画布几乎是黑乎乎的一片,只有当摄像头捕获到动作了,第三个画布才有明显的高亮内容出现。


因此,我们只需要对第三个画布渲染后的图像进行像素分析——判断其高亮阈值是否达到某个指定预期:


    var diffFrame;  //存放差异帧的imageData
//渲染前后两帧差异 function renderDiff(){ if(!preFrame || !curFrame) return; diffCtx.clearRect(0, 0, 640, 480); drawImg(preFrame); drawImg(curFrame); diffFrame = diffCtx.getImageData( 0, 0, 640, 480 ); //捕获差异帧的imageData对象 } //计算差异 function calcDiff(){ if(!diffFrame) return 0; var cache = arguments.callee, count = 0; cache.total = cache.total || 0; //整个画布都是白色时所有像素的值的总和 for (var i = 0, l = diffFrame.width * diffFrame.height * 4; i < l; i += 4) { count += diffFrame.data[i] + diffFrame.data[i + 1] + diffFrame.data[i + 2]; if(!cache.isLoopEver){ //只需在第一次循环里执行 cache.total += 255 * 3; //单个白色像素值 } } cache.isLoopEver = true; count *= 3; //亮度放大 //返回“差异画布高亮部分像素总值”占“画布全亮情况像素总值”的比例 return Number(count/cache.total).toFixed(2); } //定时捕获 function timer(delta){ setTimeout(function(){ captureAndSaveFrame(); renderDiff(); setTimeout(function(){ console.log(calcDiff()); }, 10);
timer(delta) }, delta || 500); }
timer();
复制代码


注意这里我们使用了 count *= 3 来放大差异高亮像素的亮度值,不然得出的数值实在太小了。我们运行下页面:



经过试(xia)验(bai),个人觉得如果 calcDiff() 返回的比值如果大于 0.20,那么就可以定性为“一间空屋子,突然有人闯进来”的情况了。

step4. 上报异常图片

当上述的计算发现有状况时,需要有某种途径通知我们。有钱有精力的话可以部署个邮件服务器,直接发邮件甚至短信通知到自己,but 本文走的吃吐少年路线,就不搞的那么高端了。


那么要如何简单地实现异常图片的上报呢?我暂且想到的是 —— 直接把问题图片发送到某个站点中去。


这里我们选择博客园的“日记”功能,它可以随意上传相关内容。


我们在管理后台创建日记时,通过 Fiddler 抓包可以看到其请求参数非常简单:



从而可以直接构造一个请求:


    //异常图片上传处理    function submit(){        //ajax 提交form        $.ajax({            url : 'http://i.cnblogs.com/EditDiary.aspx?opt=1',            type : "POST",            data : {                '__VIEWSTATE': '',                '__VIEWSTATEGENERATOR': '4773056F',                'Editor$Edit$txbTitle': '告警' + Date.now(),                'Editor$Edit$EditorBody': '<img src="' + curFrame + '" />',                                'Editor$Edit$lkbPost': '保存'            },            success: function(){                                console.log('submit done')            }        });    }
复制代码


当然如果请求页面跟博客园域名不同,是无法发送 cookie 导致请求跨域而失效,不过这个很好解决,直接修改 host 即可(怎么修改就不介绍了,自行百度吧)。


我这边改完 host,通过 http://i.cnblogs.com/h5monitor/final.html 的地址访问页面,发现摄像头竟然失效了~


通过谷歌的文档可以得知,这是为了安全性考虑,非 HTTPS 的服务端请求都不能接入摄像头。不过解决办法也是有的,以 window 系统为例,打开 cmd 命令行面板并定位到 chrome 安装文件夹下,然后执行:


chrome --unsafely-treat-insecure-origin-as-secure="http://i.cnblogs.com/h5monitor/final.html"  --user-data-dir=C:\testprofile
复制代码


此举将以沙箱模式打开一个独立的 chrome 进程,并对指定的站点去掉安全限制。注意咱们在新开的 chrome 中得重新登录博客园。


这时候便能正常访问摄像头了,我们对代码做下处理,当差异检测发现异常时,创建一份日记,最小间隔时间为 5 秒(不过后来发现没必要,因为博客园已经有做了时间限制,差不多 10 秒后才能发布新的日记):


  //定时捕获    function timer(delta){        setTimeout(function(){            captureAndSaveFrame();            renderDiff();                        if(calcDiff() > 0.2){  //监控到异常,发日志                submit()            }
timer(delta) }, delta || 500); }
setTimeout(timer, 60000 * 10); //设定打开页面十分钟后才开始监控

//异常图片上传处理 function submit(){ var cache = arguments.callee, now = Date.now(); if(cache.reqTime && (now - cache.reqTime < 5000)) return; //日记创建最小间隔为5秒
cache.reqTime = now; //ajax 提交form $.ajax({ url : 'http://i.cnblogs.com/EditDiary.aspx?opt=1', type : "POST", timeout : 5000, data : { '__VIEWSTATE': '', '__VIEWSTATEGENERATOR': '4773056F', 'Editor$Edit$txbTitle': '告警' + Date.now(), 'Editor$Edit$EditorBody': '<img src="' + curFrame + '" />', 'Editor$Edit$lkbPost': '保存' }, success: function(){ console.log('submit done') }, error: function(err){ cache.reqTime = 0; console.log('error: ' + err) } }); }

复制代码


执行效果:



日记也是妥妥的出来了:



点开就能看到异常的那张图片了:



不过这种形式仅能上报异常图片,暂时无法让我们及时收悉告警,有兴趣的童鞋可以试着再写个 chrome 插件,定时去拉取日记列表做判断,如果有新增日记则触发页面 alert。


另外我们当然希望能直接对闯入者进行警告,这块比较好办 —— 搞个警示的音频,在异常的时候触发播放即可:


    //播放音频    function fireAlarm(){        audio.play()    }    //定时捕获    function timer(delta){        setTimeout(function(){            captureAndSaveFrame();                        if(preFrame && curFrame){                renderDiff();                                if(calcDiff() > 0.2){  //监控到异常                    //发日记                    submit();                                        //播放音频告警                    fireAlarm();                }            }            timer(delta)        }, delta || 500);    }
复制代码


setTimeout(timer, 60000 * 10); //设定打开页面十分钟后才开始监控


最后说一下,本文代码均挂在 github 上:https://github.com/VaJoy/h5monitor/ ,有兴趣的童鞋可以自助下载。共勉~


本文转载自公众号小时光茶舍(ID:gh_7322a0f167b5)。


原文链接:


https://mp.weixin.qq.com/s/q_vr5m3FqeDvdIguG1z2aQ


2019-08-22 10:247565

评论

发布
暂无评论
发现更多内容

你学会如何将项目部署到Linux系统上了吗?要不我带你耍耍。

Java学术趴

7月月更

linux之realpath命令

入门小站

Linux

现场可程式化逻辑闸阵列 FPGA

贾献华

7月月更

排序子序列与倒置字符串

未见花闻

7月月更

在线摇骰子色子工具

入门小站

工具

Android gradle常用

沃德

android Gradle 7月月更

群里的初级工程师求助说,要采集采招数据,必须给他安排上

梦想橡皮擦

Python 爬虫 7月月更

【Docker 那些事儿】容器网络(下篇)

Albert Edison

Docker Kubernetes 容器 云原生 7月月更

Dockerfile中的保留字指令讲解

宁在春

Docker Dockerfile 7月月更

鲲鹏代码迁移工具基础知识

乌龟哥哥

7月月更

云原生(六) | Docker篇之实战Dockerfile

Lansonli

Docker 云原生 7月月更

王者荣耀商城异地多活架构设计

地下地上

架构实战营

长安链学习研究-存储分析wal机制

长安链

ArkUI开发框架组件的生命周期详解

坚果

HarmonyOS OpenHarmony Open Harmony 7月月更

Zabbix 6.0 源码安装以及 HA 配置

耳东@Erdong

zabbix ha 7月月更 zabbix 6.0

python文件操作知多少

迷彩

Python基础 文件操作 7月月更

Spring cloud 之限流

Damon

7月月更

在线SQL转文本工具

入门小站

工具

Vscode 搭建 C / C++ 开发环境

攻城狮杰森

c c++ vscode 开发环境 7月月更

使用kitti数据集实现自动驾驶——发布照片、点云、IMU、GPS、显示2D和3D侦测框

秃头小苏

7月月更 kitti

Python 迭代器介绍及其作用

宇宙之一粟

Python 迭代器 7月月更

如何在Linux中比较多个文件?这12个优秀工具了解一下!

wljslmz

Linux 7月月更 文件比较

Qt | 控件之QComboBox

YOLO.

qt 7月月更

NFT市场格局仍未变化,Okaleido能否掀起新一轮波澜?

股市老人

uni-app进阶之自定义【day13】

恒山其若陋兮

7月月更

zookeeper-watcher的javaApi相关使用

zarmnosaj

7月月更

Mysql 温故知新系列「触发器详解」

安逸的咸鱼

MySQL 7月月更

认识区块链和比特币

沃德

程序员 7月月更

一款强大的mock数据生成工具

Xd

来看看机智的前端童鞋怎么防盗_文化 & 方法_蓝邦珏_InfoQ精选文章