幀繪玩=>使用video與canvas玩耍的僞「人體感應」

寫在前面

感謝各位在前端一直努力奮鬥的程序猿們,但願之後的HelloWorld會更美好。css

感謝來看看機智的前端童鞋怎麼防盜做者的分享html

感謝停了一天的網終於恢復前端

乾貨

先來分享2張圖
1)王的憤怒
64380cd7912397dd0b00143f5182b2b7d0a28794.jpg
2) 程序猿的憤怒
clipboard.pnggit

原理

經過啓用瀏覽器攝像的方式,把每一幀的圖映射到canvas上,經過比較上一幀與當前幀的差別,算出來的差別佔的百分比,超過某個百分比就觸發函數。github

栗子:我搖動個人雙手,兩個幀的手的位置不同,從而差別佔的比例就挺大的,就觸發了寫好的回調函數{web

給水杯添加一個動畫
給body更換一個背景顏色

}
程序猿的憤怒是經過在來看看機智的前端童鞋怎麼防盜借鑑的代碼實現的,
原做者項目GitHub地址
clipboard.png
同時,在不破壞原做者源碼的基礎下,憤怒的程序猿代碼以下:chrome

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>憤怒的程序猿</title>
        
        <style type="text/css">
        
            .room{    
                text-align: center;
                margin: 200px 10px;
                position: relative;
                height: 240px;
                width: 320px;
                display: inline-block;
                
            }
            .room>*{
                float: left;
                position: absolute;
                bottom: 0;
                left: 100px;
            }
            
            .room .beizi{
                width: 50px;
                height: 100px;
                border-radius: 10px;
                background-color: darkolivegreen;
                color: white;
                line-height: 100px;
                    
            }
            .room>span{
                width: 100%;
                left: 80px; color: white;
            }
        
            canvas{
                /*display: none;*/
            }
            
            
        

            @keyframes tiao{0%{transform: translateY(0);}50%{transform: translateY(-100%);}100%{transform: translateY(0);}}
            @-webkit-keyframes tiao{0%{transform: translateY(0);}50%{transform: translateY(-100%);}100%{transform: translateY(0);}}
            
            .tiao{
                animation: tiao 1s ease-in-out .1s;
                -webkit-animation: tiao 1s ease-in-out .1s;
            }

        </style>
    </head>
    <body>
        <div class="room">
            <video autoplay width="320" height="240"></video>
            <div class="beizi">水杯</div>    
            <span>您能夠嘗試敲打桌面</span>
        </div>
        
        <div class="room" style="display: none;">
            <canvas width="320" height="240"></canvas>
            <span>每幀截取</span>
        </div>
        
        <div class="room">
            <canvas width="320" height="240"></canvas>
            <span>上一幀與這一幀的對比差別</span>
        </div>
        
        <div class="room">
            <canvas width="320" height="240"></canvas>
            <span>檢測到動了必定幅度的截圖</span>
        </div>
        
        <script>
            var w = 320,
                h = 240;
            
            var video = document.querySelector('video'),
                beizi = document.querySelector(".beizi"),
                canvas = document.querySelectorAll('canvas'),
                canvasForDiff = canvas[1];
                canvasPhoto = canvas[2];
                canvas = canvas[0];
            
            
            
            navigator.getUserMedia || navigator.webkitGetUserMedia// || navigator.mozGetUserMedia//ie chrome firefox
            ({video:true}, function(stream) {
                video.src = window.URL.createObjectURL(stream);
                video.play();
            }, function(err) {
                alert('出錯: ' + err)
            });
            
            //canvas
            var context = canvas.getContext('2d'),
                diffCtx = canvasForDiff.getContext('2d'),
                photo = canvasPhoto.getContext('2d');
            //將第二個畫布混合模式設爲「差別」
            diffCtx.globalCompositeOperation = 'difference';
            
            var preFrame,   //前一幀
                curFrame;   //當前幀
        
            var diffFrame;  //存放差別幀的imageData
        
            //捕獲並保存幀內容
            function captureAndSaveFrame(){
                preFrame = curFrame;
                context.drawImage(video, 0, 0, w, h);
                curFrame = canvas.toDataURL();  //轉爲base64並保存
            }
        
            //繪製base64圖像到畫布上
            function drawImg(src, ctx){
                ctx = ctx || diffCtx;
                var img = new Image();
                img.src = src;
                ctx.drawImage(img, 0, 0, w, h);
            }
        
            //渲染先後兩幀差別
            function renderDiff(){
                if(!preFrame || !curFrame) return;
                diffCtx.clearRect(0, 0, w, h);
                drawImg(preFrame);
                drawImg(curFrame);
                diffFrame = diffCtx.getImageData( 0, 0, w, h );  //捕獲差別幀的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);
            }
            
            
            
            
            var t,d;
            //定時捕獲
            function timer(delta){
                d = 0;
                setTimeout(function(){
                    captureAndSaveFrame();
                    renderDiff();
                    setTimeout(function(){
                        if((d=calcDiff())>=0.13){
                            
                            handel();
                        }
                    }, 16.7);
        
                    timer(delta)
                }, delta || 500);
                
                
                
            }
            
            
            function handel(){
                if(t){return}
                photo.drawImage(video, 0, 0, w, h);
                beizi.classList.add('tiao');
                canvasForDiff.classList.add('tiao');
                document.body.style.backgroundColor = (function(){
                    return ['red','yellow','black','blue','green','white'][Math.floor(Math.random()*6)];
                }());
                t = setTimeout(function(){
                    canvasForDiff.classList.remove('tiao');
                    beizi.classList.remove('tiao');
                    t = null;
                },1000);
                
            }
        
            timer();
            
        </script>
    </body>
</html>
  • Q:在谷歌瀏覽器上運行的,表示頁面一片空白,啥都沒有,請問這是爲何?
  • A:經過谷歌的文檔能夠得知,這是爲了安全性考慮,非 HTTPS 的服務端請求都不能接入攝像頭,簡單來講,chrome經過文件的方式打開一個html文件,是沒法開啓攝像頭的,能夠在本地服務端打開哦!如:localhost
  • 還要注意寫兼容哦!樓主只寫了chrome的

嗨起來

程序猿們怎麼可能就地止步,讓咱們運用代碼,本身來寫一個自動拍照吧!canvas

  • AV.htmlclipboard.png
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>
    <style>
        div{
            text-align: center;
            width: 250px;
            margin: 10px 0;
        }
    </style>
    <body>
        <video></video>
        
        <div>
            <button onclick="paipaipai()">手動拍照</button>
            <button onclick="auto()">自動拍照</button>    
            <button onclick="debug()">調試模式</button>    
        </div>
        
        <img/>
        
        <script src="av.js"></script>
        <script>
            var img = document.querySelector("img");
            var av = Av({
                //style: {
                //    width: 320,//視頻 畫布 寬
                //    height: 240//高
                //},
                //deg: 0.12,//靈敏度 觸發動做幅度
                //die: 500,//dwon機時間,觸發事件後500ms內不觸發
                //delta:300,//取幀間隔 300ms 獲取一次視頻幀
                //sw:true//開關,默認爲開
                fn:function(data){//觸發函數
                    img.src = data;
                }
            });
            
            function paipaipai(){
                av.switchAV(false);
                img.src = av.getCurFrame();
            }
            
            function auto(){
                av.switchAV(true);
            }
            
            function debug(){
                [].map.call(document.querySelectorAll("canvas"),function(c){
                    c.style.display = 'inline';
                });
            }
            
        </script>
    </body>
</html>
  • AV.jsclipboard.png
(function() {
    var av = function(option) {
        
        option = option || {};
        this.op = (function(o) {
            for(var i in option) {
                o[i] = option[i];
            }
            return o;
        }({
            style: {
                width: 320,//視頻 畫布 寬
                height: 240//高
            },
            deg: 0.12,//靈敏度 觸發動做幅度
            die: 500,//dwon機時間,觸發事件後500ms內不觸發
            delta:300,//取幀間隔 300ms 獲取一次視頻幀
            sw:true//開關,默認爲開
        }));
        this.initEm();
        this.initMedia();
        this.initCanvas();
        this.switchAV();
    }
    var avpro = av.prototype;

    //初始化基礎元素
    avpro.initEm = function() {
        //video元素
        this.video = document.querySelector(this.op.el || 'video');
        this.video.setAttribute('autoplay', '');
        this.video.style.objectFit = 'fill';
        //初始化canvas元素
        var canvas = document.createElement("canvas");
        canvas.style.display = 'none';
        canvas.style.backgroundColor = this.video.style.backgroundColor = 'grey';
        canvas.style.width = this.video.style.width = (this.w = canvas.width = this.op.style.width) + 'px';
        canvas.style.height = this.video.style.height = (this.h = canvas.height = this.op.style.height) + 'px';
        
        //動做畫布克隆,映射視頻  ac 動做Action 便於記憶
        var acCanvas = canvas.cloneNode(true);
        //對比畫布克隆,比較差別 bw 表示黑白 便於記憶 
        var bwCanvas = canvas.cloneNode(true);
        //清除原體釋放資源
        canvas = null;
        //添加至頁面
        document.body.appendChild(acCanvas);
        document.body.appendChild(bwCanvas);
        
        this.canvas = acCanvas;
        this.acCanvas = acCanvas.getContext('2d');
        this.bwCanvas = bwCanvas.getContext('2d');
    }

    //初始化攝像頭
    avpro.initMedia = function() {
        var tv = this.video;
        navigator.getUserMedia || navigator.webkitGetUserMedia //ie chrome
            ({
                video: true
            }, function(se) {
                tv.src = window.URL.createObjectURL(se);
                tv.play();
            }, function(err) {
                console.log('err:' + err);
            });
    }

    //初始化畫布,幀數
    avpro.initCanvas = function() {
        //將第二個畫布混合模式設爲「差別」
        this.bwCanvas.globalCompositeOperation = 'difference';
        //   前一幀                          當前幀                         差別幀
        this.preFrame = this.curFrame = this.diffFrame = null;
    }

    //開始AV
    avpro.startAv = function() {
        var tv = this;
        var call = arguments.callee;
        tv.zt = setTimeout(function() {
            tv.avSaveFrame();
            tv.renderDiff();
            setTimeout(function() {
                if(tv.calcDiff() >= tv.op.deg) {
                    //觸發事件
                    tv.handel();
                }
            }, 16.7);
            call.call(tv);
        },tv.op.delta);

    }
    
    //設置開關 和 回調函數
    avpro.switchAV = function(sw,fn){
        if(sw == undefined ? this.op.sw:sw){
            this.startAv();
        }else{
            this.stopAv();
        }
        fn && (this.op.fn = fn);
    }
    
    avpro.stopAv = function(){
        this.zt && clearTimeout(this.zt);
    }
    
    //觸發事件
    avpro.handel = function() {
        var tv = this;
        if(tv.t) {
            return;
        }
        console.log(tv.fn);
        tv.op.fn && tv.fn(tv.curFrame);
        tv.t = setTimeout(function() {
            tv.t = null;
        },tv.op.die);
        
    }
    
    avpro.getCurFrame = function(){
        return this.curFrame;
    }

    //捕獲並保存幀
    avpro.avSaveFrame = function() {
        //幀替換
        this.preFrame = this.curFrame;
        this.acCanvas.drawImage(this.video, 0, 0, this.w, this.h);
        //轉爲base64並保存當前幀
        this.curFrame = this.canvas.toDataURL();
    }

    //繪製base64圖像到畫布上
    avpro.drawImg = function(src, ctx) {
        ctx = ctx || this.bwCanvas;
        var img = new Image();
        img.src = src;
        ctx.drawImage(img, 0, 0 ,this.w, this.h);
        
    }

    //渲染先後兩幀差別
    avpro.renderDiff = function() {
        if(!this.preFrame || !this.curFrame) return;
        this.bwCanvas.clearRect(0, 0, this.w, this.h);
        this.drawImg(this.preFrame);
        this.drawImg(this.curFrame);
        //捕獲差別幀的imageData對象
        this.diffFrame = this.bwCanvas.getImageData(0, 0, this.w, this.h);
    }

    //計算差別
    avpro.calcDiff = function() {
        if(!this.diffFrame) return 0;
        var cache = arguments.callee,
            count = 0;
        cache.total = cache.total || 0; //整個畫布都是白色時全部像素的值的總和
        for(var i = 0, l = this.diffFrame.width * this.diffFrame.height * 4; i < l; i += 4) {
            count += this.diffFrame.data[i] + this.diffFrame.data[i + 1] + this.diffFrame.data[i + 2];
            if(!cache.isLoopEver) { //只需在第一次循環裏執行
                cache.total += 255 * 3; //單個白色像素值
            }
        }
        cache.isLoopEver = true;
        count *= 3; //亮度放大
        //返回「差別畫布高亮部分像素總值」佔「畫布全亮狀況像素總值」的比例
        return Number(count / cache.total).toFixed(2);
    }

    var nav = null;
    window.Av = function(op) {
        return nav || (nav = new av(op));
    };

}())

遺言

寫前聖成佛:感受本身立刻要寫出一篇無與倫比的文章。
寫後呆成魔:寫的跟流水帳樣的。瀏覽器

最後但願寶強可以好起來。安全

相關文章
相關標籤/搜索