1. 程式人生 > >Unity3D Kinect 實時顯示物件的頭部影象

Unity3D Kinect 實時顯示物件的頭部影象

最近在做一個Unity+Kinect 的專案,因為涉及一些姿勢的識別,所以要鎖定一個識別的骨架,但是使用者怎麼知道我鎖定的骨架是誰呢?於是想到一個方法,那就是把當前的鎖定的骨架的物件的頭部圖片展示出來,那麼這樣使用者就知道當前檢測的是誰啦~
話不多說,先展示一下demo,最終的一個展示效果~
這裡寫圖片描述
有了這個想法,那麼我們就來開始動手做吧,查找了一些資料,但是一輸入關鍵詞頭部或者face什麼的都是一些Unity的面部檢測的相關內容,但是我只是想把頭部的彩色影象從ColorView中摳出來而已啊,而且,我的專案最終是需要戴Oculus的,所以面部檢測的話,估計最後帶上那麼大的Oculus也識別不出來。於是乎,突然想到Unity的開發者包裡面有一個GreenScreen的專案,但是那個匯入貌似有問題…於是乎去解決一下問題,然後再來研究一些那個專案是幹嘛的…
對於GreenScreen的匯入之後的錯誤,我們只要更改一下Shader的一處變數就好啦~
這裡寫圖片描述


然後,我們執行這個程式,你會發現它其實是把檢測到的人的部分顯示在螢幕上,而其他的部分全部用綠色填充,這也就是為什麼叫做GreenScreen吧…
我們來分析一下這個程式碼:CoordinateMapperManager和CoordinateMapperView。
瀏覽一遍程式碼之後,它的實現流程是這樣的,利用Kinect的MultiSourceFrameReader讀取Kinect的彩色影象,深度影象和BodyIdex,注意,這個BodyIndex不是Body,它不包含骨骼的資訊,它是一個和kinect 深度影象等大的一個數組,如果你用texture展示出來它的話,你會得到一個身體的輪廓
【如果你想檢視一下這個輪廓的話,可以在CoordinateMapperManager裡面更改一個texture的宣告:m_pColorRGBX = new Texture2D(cDepthWidth,cDeapthHeight,TextureFormat.BC4,false) ,然後在ProcessFrame函式裡面更改一下材質的填充語句為m_pColorRGBX.LoadRawTextureData(pBodyIndexBuffer),然後執行的話,你就應該可以看到一個紅色的人的輪廓影象】
額,說的有點多,我就不在這裡分析程式碼了,就是說一下實現,他這個例子的實現過程是:採集到影象的資料,然後把深度影象的資料以及BodyIndex的資料作為類似掩碼的一個功能,在Shader裡面片元著色器渲染的時候,判斷當前的畫素時候是需要渲染的狀態,是的話,就渲染,不是的話就渲染成綠色,大體上講有點類似於PS的模板的功能。
這裡寫圖片描述

這裡是例子Shader的片元著色器的部分程式碼:
這裡寫圖片描述
所以呢,我也就想到了,那我也做一個“模板”啊,於是呢,在做之前,我們要想一想具體的過程是怎麼樣的?
我這裡的流程是:
1. 選取Kinect最近骨架的Head和SpineShoulder兩個節點,作為計算展示的區域。
2. 把骨骼的Camera座標對映到彩色圖片的座標
3. 計算出末班陣列的內容(這裡0表示不渲染,1表示渲染)
4. Shader片元著色器渲染材質
嗯,好下面就開始編寫程式碼建立一個與BodySourceView類似的MineBodySourceView指令碼,首先根據判斷當前檢測到的骨架的遠近,拿出來距離最近的骨架的bodytrackid。

        //get the nearest body
        float near_Z = 5;
        foreach (var body in data) {
            if (body == null)
            {
                continue;
            }
            if (body.IsTracked) {
                if (body.Joints [Kinect.JointType.SpineMid].Position.Z < near_Z) {
                    nearest_body = body.TrackingId;
                    near_Z = body.Joints [Kinect.JointType.SpineMid].Position.Z;
                }
            }
        }

然後,根據對應骨架的頭部和肩膀中部的座標,計算顯示區域的大小。

    private void RefreshBodyObject(Kinect.Body body, GameObject bodyObject)
    {
        for (Kinect.JointType jt = Kinect.JointType.SpineBase; jt <= Kinect.JointType.ThumbRight; jt++)
        {
            Kinect.Joint sourceJoint = body.Joints[jt];
            Kinect.Joint? targetJoint = null;

            if(_BoneMap.ContainsKey(jt))
            {
                targetJoint = body.Joints[_BoneMap[jt]];
            }

            Transform jointObj = bodyObject.transform.FindChild(jt.ToString());
            jointObj.localPosition = GetVector3FromJoint(sourceJoint);
            if (body.TrackingId == nearest_body && jt == Kinect.JointType.Head) {
                headposition.X = jointObj.position.x;
                headposition.Y = jointObj.position.y;
                headposition.Z = jointObj.position.z;
                camerapoints [0] = headposition;
            }
            if (body.TrackingId == nearest_body && jt == Kinect.JointType.SpineShoulder) {
                neckposition.X = jointObj.position.x;
                neckposition.Y = jointObj.position.y;
                neckposition.Z = jointObj.position.z;
                camerapoints [1] = neckposition;
            }
            LineRenderer lr = jointObj.GetComponent<LineRenderer>();
            if(targetJoint.HasValue)
            {
                lr.SetPosition(0, jointObj.localPosition);
                lr.SetPosition(1, GetVector3FromJoint(targetJoint.Value));
                lr.SetColors(GetColorForState (sourceJoint.TrackingState), GetColorForState(targetJoint.Value.TrackingState));
            }
            else
            {
                lr.enabled = false;
            }
        }
    }

然後我們需要在HeadShowManager裡面獲得這個camerapoints的資料,建立一個MapCameraToImage函式:

    private void MapCameraToImage(){
        CameraSpacePoint[] CameraPoints = GameObject.Find ("BodyView").GetComponent<MineBodySourceView> ().getCameraPoints ();
        if (CameraPoints.Length > 0 && CameraPoints[0].X != 0 &&CameraPoints[0].Y != 0&&CameraPoints[0].Z != 0) {
//          Debug.Log ("head camera position is " + CameraPoints[0].X + ","+CameraPoints[0].Y+","+CameraPoints[0].Z);
            head = m_pCoordinateMapper.MapCameraPointToColorSpace (CameraPoints[0]);
            neck = m_pCoordinateMapper.MapCameraPointToColorSpace (CameraPoints[1]);
//          Debug.Log ("head image position is " + head.X + ","+head.Y);
            int distance = (int)Mathf.Ceil(Mathf.Abs ((head.X - neck.X) + (head.Y - neck.Y)));
//          Debug.Log ("distance is " + distance );
            int TX = (int)Mathf.Ceil(head.X+distance);
            int TY = (int)Mathf.Ceil(head.Y+distance);
            int BX = (int)Mathf.Ceil(head.X-distance);
            int BY = (int)Mathf.Ceil(head.Y-distance);
            _Key [0] = BX;
            _Key [1] = TX;
            _Key [2] = BY;
            _Key [3] = TY;
//              Debug.Log ("Top Right is " + TX+","+TY );
//              Debug.Log ("Bottom Left is " + BX+","+BY );
            for (int i = 0; i < cColorWidth * cColorHeight ; i++) {
                int X = i % cColorWidth;
                int Y = i / cColorWidth; 
                if (X > BX && X < TX && Y > BY && Y < TY) {
                    _Mask [i] = 1;
                } else {
                    _Mask [i] = 0;
                }
            }

        }
    }

注意上文把Camera空間座標對映到彩色圖片座標的是MapCameraPointToColorSpace函式。
因為Kinect獲得的彩色圖片的解析度是1920*1080,所以我們的末班也要是這個大小的陣列,於是上文中,我們給一個1920*1080 的陣列新增數字,根據我們獲得的head以及spineshould的資料,可以確定一個展示的區域(我這裡是以head為中心,2倍的head+spineshoulder的正方形)。
下面呢,就是把這個模板載入shader啦。
我們構造HeadColorShow指令碼Start裡面先建立一個computeBuffer用於給Shader傳值:

            mask = ColorSourceManager.GetComponent<HeadShowManager> ().GetMask ();
    //  Debug.Log ("mask length is "+mask.Length);
        if (mask != null) {
            maskbuffer = new ComputeBuffer (mask.Length, sizeof(int));
            gameObject.GetComponent<Renderer> ().material.SetBuffer ("_Mask", maskbuffer);
        }

然後再update裡面,更新每一幀的“模板”的值:

        int[] buffer = new int[1920 * 1080];
        for (int i = 0; i < mask.Length; i++)
        {
            buffer[i] = (int)mask[i];
        }
        maskbuffer.SetData(buffer);
        buffer = null;

最後呢,我們需要根據GreenScreen裡面的shader更改一個我們自己的shader:
其實其他地方不需要做太多的更改,只需要更改一下片元著色器就好啦~

float4 frag (ps_input i, in uint id : SV_InstanceID) : COLOR
{
    float4 o;

    int colorWidth = (int)(i.tex.x * (float)1920);
    int colorHeight = (int)(i.tex.y * (float)1080);
    int colorIndex = (int)(colorWidth + colorHeight * (float)1920);

    o = float4(0, 0, 0, 1);
    if (_Mask[colorIndex] == 1)
    {
        o = _MainTex.Sample(sampler_MainTex, i.tex);
    }

    return o;
}

哦,對了,在setbuffer那裡需要填寫一個name,那個name必須和shader裡面宣告的變數名稱一樣,如我這裡:

HeadColorShow.cs:
    gameObject.GetComponent<Renderer> ().material.SetBuffer ("_Mask", maskbuffer);
trackhead.shader:
    StructuredBuffer<int> _Mask;

看到了嗎,你只有在setbuffer哪裡宣告我們的資料放在“_Mask”的變數裡面,那麼,在shader裡面我們才可以使用_Mask變數。
到此為止,我們已經完成了一個類似頭部追蹤的demo。
這裡寫圖片描述
但是,更多的時候,我們需要只顯示我們頭部的影象,其他地方既然已經被模板過濾了,那麼我們就不想顯示了。
那怎麼解決這個問題呢?
如果你熟悉Shader的話,其實,shader在渲染材質的紋理的時候,是每個畫素每個畫素去取樣的,然後再畫在紋理上。所以呢,我們只要保證shader的取樣範圍恰好就是我們展示的範圍,那麼所渲染的紋理就是我們期望的啦。
我們可以使用資料縮放來把一個大的集合對映到一個小的範圍:
比如吧[1,100]的資料縮放到[50,90]的範圍,我們可以根據二元一次的方程,很容易的求出來這個對映的公式:
這裡寫圖片描述
所以就可以得到這個轉換公式是Y= (90 - 50)/100 * X + 50;
同理,我們可以分析一下我麼畫素點的橫向對映:原圖的寬度是1920即X的原本變化範圍是[0,1920],而顯示的X的範圍是:
[head.X - distance(head,spineshoulder),head.X+distance(head,spaneshoulder)]
所以呢,關於X的對映就可以輕鬆的算出來啦。同理可得Y的對映公式。
使用這種方法,我們反而不需要傳遞一個很大的陣列到shader,而是傳遞四個值就可以啦,也就是X,Y對應的對映函式的引數。
所以呢,在HeadColorView.cs裡面的Update函式做如下更改:

        computekeys[0] = (float)(keys[1]-keys[0])/1920;
        computekeys [1] = (float)keys [0] / 1920;
        computekeys [2] = (float)(keys [3] - keys [2]) / 1080;
        computekeys [3] = (float)keys [2] / 1080;
        if (computekeys [0] < 0 || computekeys [1] < 0 || computekeys [2] < 0 || computekeys [3] < 0) {
            computekeys [0] = 0f;
            computekeys [1] = 0f;
            computekeys [2] = 0f;
            computekeys [3] = 0f;
        }
    //  Debug.Log ("area is "+ computekeys[0]+","+computekeys[1]+","+computekeys[2]+","+computekeys[3]);
        keybuffer.SetData(computekeys);

這裡要注意的是,在Start裡面生命buffer的時候要設定成float型別,而且,對應的ShowHead.shader的引數也要是float型別。

HeadColorView.cs:
        keys = ColorSourceManager.GetComponent<HeadShowManager> ().GetKey();
        if (keys != null) {
            keybuffer = new ComputeBuffer (keys.Length, sizeof(float));
            gameObject.GetComponent<Renderer> ().material.SetBuffer ("key", keybuffer);
            //gameObject.GetComponent<Renderer> ().material.SetFloatArray("key",computekeys);
        }
ShowHead.shader:
    StructuredBuffer<float> key;

對於,只顯示頭部圖畫的話,我們只要實時的在shader的定點著色器裡面更改取樣區域就好啦~

ps_input vert (vs_input v)
{
    ps_input o;
    o.pos = mul (UNITY_MATRIX_MVP, v.pos);
    o.tex = v.tex;
    // Flip x texture coordinate to mimic mirror.
    o.tex.x = key[0]* (1-v.tex.x) + key[1];
    o.tex.y = key[2] * v.tex.y+ key[3];
    return o;
}

那麼,你就可以獲得,如下的效果啦:
這裡寫圖片描述
如果,你想兩個效果都實現的話,需要注意的是,我們只需要而且必須使用一個ColorManger,經過我的實驗,我發現,我發同時呼叫兩個_Sensor.ColorFrameSource.OpenReader(),所以,我們只要把兩個不同的功能合成一個ColorManager即可。
如果大家需要原始碼和原工程玩玩的話,可以從這裡下載:
https://github.com/ArlexDu/UnityKinectMLTest

=======================================================
更新:
最近把我這些指令碼轉移到一個專案裡面時候,發現原本展示頭部的紋理一直是空的,後來發現,原來在HeadColorView.cs的start函式執行的比HeadShowManager.cs的Start早,於是導致獲得的texture一直是空的。
所以這就涉及到一個unity鍾指令碼執行順序的問題,這裡有一個文章很好的說明了這個問題:http://www.jb51.net/article/57309.htm
所以,我們需要自定義指令碼的執行順徐,把我們的manager指令碼放在其他指令碼執行之前,就可以啦~
這裡寫圖片描述

相關推薦

Unity3D Kinect 實時顯示物件頭部影象

最近在做一個Unity+Kinect 的專案,因為涉及一些姿勢的識別,所以要鎖定一個識別的骨架,但是使用者怎麼知道我鎖定的骨架是誰呢?於是想到一個方法,那就是把當前的鎖定的骨架的物件的頭部圖片展示出來,那麼這樣使用者就知道當前檢測的是誰啦~ 話不多說,先展示一

Kinect與PCL搭配實現RGBD點雲的實時顯示

剛剛學會了將彩色影象與深度影象的對齊方法。利用Kinect SDK裡面的對映函式,可以將深度圖的畫素和彩色圖對應,同時將深度圖對映到相機空間上。在利用PCL將深度影象的的每一個畫素和與其對應的顏色存到點雲中,再進行顯示。 程式碼如下:   #include <Kine

Unity3D中如何記錄並實時顯示物體運動的軌跡?希望各位大神幫忙解答,十分感謝。

歡迎使用Markdown編輯器 你好! 這是你第一次使用 Markdown編輯器 所展示的歡迎頁。如果你想學習如何使用Markdown編輯器, 可以仔細閱讀這篇文章,瞭解一下Markdown的基本語法知識。 新的改變 我們對Markdown編輯器進行了一些功能拓

MFC編個對話方塊,能夠實時顯示攝像頭捕捉的鏡頭, 點選確定,儲存當前影象

現在在vc上採集視訊常用的方法有三:vfw,directshow,opencv  你是要進行影象處理的話推薦opencv(具體參考:於仕琪,opencv教程基礎篇中的例3-6,稍作修改,估計就能用於你的工程)  下面貼出我自己編的一個小工程:如有疑問,E-mail:[email protected

OpenCV入門學習之讀取usb攝像頭影象實時顯示

首先,新建一個.cpp的源程式檔案,例如,gedit test.cpp 在該檔案中新增以下程式: #include <opencv2/core/core.hpp> #include

JS中使用Date物件實時顯示系統時間小示例

JS中包含Date物件,其提供了一些方法獲取系統日期,直接上程式碼: <!DOCTYPE html> <html> <head> <meta char

Ubuntu標題欄實時顯示資源管理器

實時 -a get span pos 安裝 運行 smon 添加 添加安裝資源包sudo add-apt-repository ppa:fossfreedom/indicator-sysmonitorsudo apt-get update安裝更新sudo apt-get i

基於Jquery插件Uploadify實現實時顯示進度條上傳圖片

準備 深入學習 pla 回調 true bar put and 分割 網址:http://www.jb51.net/article/83811.htm 這篇文章主要介紹了基於Jquery插件Uploadify實現實時顯示進度條上傳圖片的相關資料,感興趣的小夥伴們可

PHP實時顯示輸出

index sleep 顯示 dex 關閉 bsp flush set mit //實時顯示輸出 ob_end_flush();//關閉緩存 //echo str_repeat(" ",256); //ie下 需要先發送256個字節 set_time_limit(0); f

實時顯示從file輸入框中打開的圖片C:fakepath路徑問題

webkit ins alt container dom clas ref 元素 filters html代碼: <input id="file_upload" type="file" /> <div class="image_container"

用JS實現實時顯示系統時間

class mon 星期 itl utf sso inner watermark cti 下面為大家附上代碼: <!DOCTYPE html> <html lang="en"> <head> <meta charset=

js實現圖片上傳實時顯示

input res window splay ack 顯示 style rip 是否 在開發的時候經常遇到這樣的需求,用戶在上傳圖片的時候,想要看到自己上傳的圖片是否正確,這時候需要把用戶上傳的圖片及時顯示出來,然後等他點擊上傳的時候,程序再執行上傳到服務器。 <!

linux 實時顯示文件的內容

desc end 運行 lin 參數 實時監控 output inux 內容 1. watch -n 1 aa.txt #每個1秒顯示aa.txt的內容 2. tail -f ***.log Linux shell中有一個tail命令,常用來顯示一個文件的最後n

上傳圖片實時顯示 兼容 ie11

IT chan nsf btn obj files text this BE html: <style type="text/css">   .imgOnloadWrap{ width: 150px; height: 200px;}   #img1{ wid

軍事實時顯示圖形工作站介紹

有限元 科技 動態 設計 網絡遊戲 png jpg 建築 顯示 現代戰爭強調C4ISR技術,指揮中心在千裏萬裏之外,要通過信息化技術對整個海、陸、空、天、電磁戰場進行全面的了解、掌握和指揮控制,那麽傳統指揮部裏的行軍地圖、模型沙盤就已經不敷使用了。扁平化的戰場

頁面時間日期星期實時顯示

TE doc 顯示 tel interval h+ 小時 num 定時 //定時器每秒調用一次fnDate() setInterval(function(){   fnDate(); },1000); //js 獲取當前時間 function fnDate

WebSocket實現數據庫更新前臺實時顯示

throwable implement 技術 exception endpoint CA 建立 round 新頁面 通過一個小實例來實現數據庫更新後,推送消息給前臺,讓前臺進行相應操作。 需求 數據庫更新之後服務器推送消息給前臺,讓前臺做操作。(數據庫的數據不是由服務器寫入

在python3下使用OpenCV 抓取攝像頭圖像並實時顯示3色直方圖

rom vid mar inline oat ima tps ble log 以下代碼為在Python3環境下利用OpenCV 抓取攝像頭的實時圖像, 通過OpenCV的 calHist函數計算直方圖, 並顯示在3個不同窗口中.import cv2 import numpy

報表實時顯示時間

time 效果 統計數據 大屏幕 修改 jpg ner 循環調用 表設計 報表,除了相對靜態地展現匯總統計數據以及分布、趨勢等數據內容外,也可以用於顯示和時間相關的即時信息,包括實時顯示時間。例如,下面這個設備監控應用統系中,首頁除了顯示實時監控數據外,還需要在右上角顯示實

HSmartWindowControl 之 攝像頭實時顯示( 使用 WPF )

防止 stat 連接 結束 設置 取圖 ssa spa dispose 1、添加Halcon控件,創建WPF項目在VS2013中創建一個WPF工程,然後添加halcon的控件和工具包,參見:HSmartWindowControl之安裝篇 (Visual Studio 201