我的three.js學習記錄(三)

来源:http://www.cnblogs.com/lger/archive/2017/10/20/7700405.html
-Advertisement-
Play Games

此次的亮點不是three.js的3d部分,而是通過調用攝像頭然後通過攝像頭的圖像變化進行簡單的判斷後進行一些操作。上篇中我通過簡單的示例分析來學習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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 這幾天在知乎看到了許多關於濫用三層的討論,很多觀點都暗指三層架構這東西太Low了。Low嗎?其實不然。我們覺得三層Low主要是因為三層太簡單了,人們往往不理解三層的思想和目的,濫用三層,加之一些劣質程式員為其加上些自認牛逼的更改搞個五六七八層,代碼寫得又爛,結果看起來就很Low,於是人們就把鍋扣到三 ...
  • 進入工作室也有一段時間了,期間也進行過多次授課,使我對c#這門新的編程語言有了一定的理解。 c#是一種純面向對象的程式設計語言,設計一個程式就是設計一個或多個類,這與之前我所學習的c和c++有些不一樣,它們都是通過函數來實驗目標,而c#是通過方法來完成的。c#中的方法是在類或結構中定義的,有點類似於 ...
  • dubbo支持多種遠程調用方式,例如dubbo RPC(二進位序列化 + tcp協議)、http invoker(二進位序列化 + http協議,至少在開源版本沒發現對文本序列化的支持)、hessian(二進位序列化 + http協議)、WebServices (文本序列化 + http協議)等等,... ...
  • 什麼是狀態者模式? 每個對象都有其對應的狀態,而每個狀態又對應一些相應的行為,如果某個對象有多個狀態時,那麼就會對應很多的行為。那麼對這些狀態的判斷和根據狀態完成的行為,就會導致多重條件語句,並且如果添加一種新的狀態時,需要更改之前現有的代碼。這樣的設計顯然違背了開閉原則。狀態模式正是用來解決這樣的 ...
  • mybatis實際上是一個更多關註sql語句的框架,他的出現是想讓開發者更簡單的去操作資料庫。 與hibernate相比較,hibernate更多的是去sql化,雖然hibernate也可以本地sql執行,hibernate更多的是關註與hql的編寫和對象映射的配置, 只要配置完了,hibernat ...
  • { let list = new Set(); list.add(5); list.add(7); console.log('size', list, list.size); //{5, 7} 2 } { let arr = [1, 2, 3, 4, 5]; let list = new Set(a... ...
  • 這個問題是我在公司需求的時候遇到的,QQ推廣工具網站獲取的鏈接在蘋果自帶瀏覽器沒法打開到聊天界面,是因為safair在打開到app store的時候把參數給丟了,app store再打開到QQ的時候就無法打開到鏈接所屬人的聊天界面。 在瀏覽器中可以通過JS代碼打開QQ並彈出聊天界面,一般作為客服QQ ...
  • 移動端前端開發較PC端開發最大的不同之一就是需要適配各種大小不同的屏幕尺寸,如何實現辛苦編寫的 H5 頁面在各個機型屏幕上都能得到最佳展示?設計師設計的視覺稿為什麼都是2倍稿? ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...