立即领取|华润集团、宁德核电、东风岚图等 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:247194

评论

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

降本增效: 蚂蚁在 Sidecarless 的探索和实践

SOFAStack

阿里巴巴技术官墙裂推荐,鸟哥的Linux私房菜,你是否吃过?

钟奕礼

Java 程序员 java面试 java编程

[1.2.0新功能系列:一] Apache Doris 1.2.0 版本 Light Schema Change

张家锋

[1.2.0新功能系列:三]Apache doris 1.2.0 Java UDF 函数开发及使用

张家锋

软件测试丨单元测试框架怎么搭?新版的Junit5有哪些神奇之处?

测试人

软件测试 单元测试 自动化测试 测试开发 junit5

如何快速构建企业级数据湖仓?

字节跳动数据平台

大数据 湖仓一体 12 月 PK 榜

精彩纷呈!亚信科技深度参与2022中国移动全球合作伙伴大会

亚信AntDB数据库

AntDB 国产数据库 AntDB数据库

Zebec正在以流支付的方式,推动加密资产支付走上正轨

西柚子

如何配置悬浮层滑动进入动画?

Towify

微信小程序 编辑器 无代码

[1.2.0新功能系列:二] Apache Doris 1.2.0 JDBC外表 及 Mutil Catalog

张家锋

kali linux忘记密码如何重置|在kali上重置密码

Sher10ck

kali Linux

直播 | 新一代极速云原生湖仓的技术内核,StarRocks PMC 今天下午为你揭秘!

StarRocks

#数据库

WeLink蒲公英表单,一款用了都说好的信息收集工具

科技怪授

华为云

星河璀璨 | 2022大数据“星河”案例入选公示

亚信AntDB数据库

AntDB 国产数据库 AntDB数据库

2023年上海等保测评机构名单看这里!

行云管家

等保 等级保护 等保测评 上海

LED显示屏行业演变趋势

Dylan

LED显示屏 全彩LED显示屏 led显示屏厂家

全新的 React 组件设计理念 Headless UI

阿里巴巴终端技术

前端 React

详解视频中动作识别模型与代码实践

华为云开发者联盟

人工智能 图像识别 华为云 12 月 PK 榜

超赞!阿里大牛熬夜纯手写的微服务架构笔记,肝完面试犹如开挂

钟奕礼

Java 程序员 java面试 java编程

云原生时代下,如何落地企业可观测体系?

嘉为蓝鲸

可观测性 自动化运维 嘉为蓝鲸 云原生时代

ArkUI框架,更懂程序员的UI信息语法

HarmonyOS开发者

HarmonyOS

Chaos 测试下的若干 NebulaGraph Raft 问题分析

NebulaGraph

分布式 分布式系统 raft 分布式图数据库

网站等保三级怎么做?手续是怎样的?

行云管家

等保 等级保护 过等保 等保三级

中台是企业架构的又一次实践吗?

涛哥 数字产品和业务架构

中台 企业架构

不愧是阿里资深架构师,这本“分布式架构笔记”写得如此透彻明了

钟奕礼

程序员 java面试 java编程 #java

一个随手就能画出小程序的工具

Towify

微信小程序 编辑器 无代码

YOLOv5全面解析教程①:网络结构逐行代码解读

OneFlow

人工智能 深度学习 代码

华为云WeLink协作文档,助您开启职场高效办公

i生活i科技

华为云

您有一封来神州云科冬季发布会的邀请函,请查收!

通明湖

裸辞美团花两月吃透这Java岗798道真题解析,定级阿里P7

钟奕礼

Java 程序员 java面试 java编程

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