Kinect V2 開發專題(3)
1、Kinect裝置資訊擷取
2、音頻功能探索
1、Kinect裝置資訊擷取
由於Kinect支援多台裝置同連,所以,理論上,我們應該可以擷取所有的裝置資訊,這裡我們探索一下。
通過官方的AudioBasics-D2D這個Demo發現,擷取Kinect裝置只需要一步:
IKinectSensor* m_pKinectSensor; GetDefaultKinectSensor(&m_pKinectSensor);
我們在介面列表中(https://msdn.microsoft.com/en-us/library/dn791996.aspx)尋找,發現一個叫做 IKinectSensorCollection 的類,很顯眼的是關於Sensor收集的類,我們的腳步就從這裡開始。
建立一個工程,按照第二節的內容,設定屬性,加入標頭檔。好啦,我們開始:
額,不好意思。為什麼呢。因為坑x的微軟在去年八月份的時候把這個功能給Cut了,所以...你是不可能用的,將來可能還會加上。我們瞭解下就好了,下面我找了一份舊代碼大家看看就可以了。
IKinectSensorCollection* pKinectCollection = nullptr; IEnumKinectSensor* pEnumKinect = nullptr; IKinectSensor* pKinect = nullptr; // 擷取Kinect集合 HRESULT hr = ::GetKinectSensorCollection(&pKinectCollection); // 擷取Kinect列舉程式 if (SUCCEEDED(hr)){ hr = pKinectCollection->get_Enumerator(&pEnumKinect); } // 枚舉Kinect if (SUCCEEDED(hr)){ BOOLEAN available = false; while (true){ // 擷取下一個 if (SUCCEEDED(pEnumKinect->GetNext(&pKinect))){ // 判斷有效性 pKinect->get_IsAvailable(&available); if (available && YourJudgmentFunc(pKinect)){ break; } SafeRelease(pKinect); } else break; } } SafeRelease(pEnumKinect); SafeRelease(pKinectCollection);
不要在意這些細節,這都是小事。我們可以通過 GetDefaultKinectSensor(_COM_Outptr_ IKinectSensor** defaultKinectSensor); 這個函數擷取預設的Kinect裝置,哈哈哈。
下面是一段測試的小程式:
#include "stdafx.h"#include "kinect.h"int _tmain(int argc, _TCHAR* argv[]){printf("Hello, Wellcome to kinect world!\n");//IKinectSensorCollection aa;IKinectSensor* bb;HRESULT hr = GetDefaultKinectSensor(&bb);if ( FAILED(hr) ){printf("No Kinect connect to your pc!\n");goto endstop;}BOOLEAN bAvaliable = 0;bb->get_IsAvailable(&bAvaliable);printf("bAvaliable: %d\n", bAvaliable);BOOLEAN bIsOpen = 0;bb->get_IsOpen(&bIsOpen);printf("bIsOpen: %d\n", bIsOpen);DWORD dwCapability = 0; bb->get_KinectCapabilities(&dwCapability);printf("dwCapability: %d\n", dwCapability);TCHAR bbuid[256] = { 0 };bb->get_UniqueKinectId(256, bbuid);printf("UID: %s\n",bbuid);endstop:system("pause");return 0;}
在沒有插上Kinect時的結果:
這裡很容易發現一個奇怪的問題,擷取預設Kinect裝置成功,但是這個裝置是停用,什麼資訊都擷取不到,看來 GetDefaultKinectSensor 很不靠譜啊,需要通過 isAvaliable 判斷一下才能使用。
下面,我們插上Kinect裝置繼續測試:
暈啊,傻眼了,為什麼會這樣子呢。我調試啊調試,然後拿著SDK中的Demo調試,甚至加上了bb->Open()調試,還是那個樣子。經多方折騰,最終發現了Kinect不得不說的秘密啊。
Kinect預設是關閉狀態,你必須Open之後才能判斷是否有效等等內容。但是呢,Kinect的Open是需要時間的,在我的電腦上需要3S左右,否則即便isOpen為1,但是avaliable可能還是0。
再繼續研究Kinect的標頭檔發現,它使用了rpc技術,這樣導致函數的狀態的回複不可能像本地機器碼跑得那麼快,所以你需要一定的時間等待Kinect更新自身的狀態。
新的代碼如下:
#include "stdafx.h"#include "kinect.h"int _tmain(int argc, _TCHAR* argv[]){printf("Hello, Wellcome to kinect world!\n");IKinectSensor* bb; //申請一個Sensor指標HRESULT hr = GetDefaultKinectSensor(&bb); // 擷取一個預設的Sensorif ( FAILED(hr) ){printf("No Kinect connect to your pc!\n");goto endstop;}BOOLEAN bIsOpen = 0;bb->get_IsOpen(&bIsOpen); // 查看下是否已經開啟printf("bIsOpen: %d\n", bIsOpen);if ( !bIsOpen ) // 沒開啟,則嘗試開啟{hr = bb->Open();if ( FAILED(hr) ){printf("Kinect Open Failed!\n");goto endstop;}printf("Kinect opened! But it need sometime to work!\n");// 這裡一定要多等會,否則下面的判斷都是錯誤的printf("Wait For 3000 ms...\n");Sleep(3000);}bIsOpen = 0;bb->get_IsOpen(&bIsOpen); // 是否已經開啟printf("bIsOpen: %d\n", bIsOpen);BOOLEAN bAvaliable = 0;bb->get_IsAvailable(&bAvaliable); // 是否可用printf("bAvaliable: %d\n", bAvaliable);DWORD dwCapability = 0; bb->get_KinectCapabilities(&dwCapability); // 擷取容量printf("dwCapability: %d\n", dwCapability);TCHAR bbuid[256] = { 0 };bb->get_UniqueKinectId(256, bbuid); // 擷取唯一IDprintf("UID: %s\n",bbuid);bb->Close();endstop:system("pause");return 0;}
接上Kinect結果如下:
哈哈,圓滿完成任務。只不過,實際情況應該輪詢檢查Kinect的狀態,不應該使用Sleep()草草替代了。
2、Kinect音頻功能探索
由於Kinect音頻是一個麥克風矩陣,所以可以進行聲音方向的探測,這個功能比較喜人。當然,你也可以使用它結合微軟的語音辨識引擎,進行語音辨識和控制,這個是個比較大的專題,這裡不做討論。
我們先來進行聲源的判斷。接著上面的內容,通過GetDefaultKinectSensor 擷取預設Kinect裝置,然後Open,接著就可以通過它的成員函數 get_AudioSource 擷取音頻源,然後根據音頻源的成員函數 get_AudioBeams 擷取波束列表,然後 OpenAudioBeam 擷取第一個波束,為什麼是第一個呢。因為現在只支援第一個。最後波束beam的成員函數get_BeamAngle 擷取角度,get_BeamAngleConfidence 擷取對應的可信度。
#include "stdafx.h"#include "kinect.h"int _tmain(int argc, _TCHAR* argv[]){printf("Hello, Wellcome to kinect world!\n");IKinectSensor* bb; //申請一個Sensor指標HRESULT hr = GetDefaultKinectSensor(&bb); // 擷取一個預設的Sensorif ( FAILED(hr) ){printf("No Kinect connect to your pc!\n");goto endstop;}BOOLEAN bIsOpen = 0;bb->get_IsOpen(&bIsOpen); // 查看下是否已經開啟printf("bIsOpen: %d\n", bIsOpen);if ( !bIsOpen ) // 沒開啟,則嘗試開啟{hr = bb->Open();if ( FAILED(hr) ){printf("Kinect Open Failed!\n");goto endstop;}printf("Kinect opened! But it need sometime to work!\n");// 這裡一定要多等會,否則下面的判斷都是錯誤的printf("Wait For 3000 ms...\n");Sleep(3000);}bIsOpen = 0;bb->get_IsOpen(&bIsOpen); // 是否已經開啟printf("bIsOpen: %d\n", bIsOpen);BOOLEAN bAvaliable = 0;bb->get_IsAvailable(&bAvaliable); // 是否可用printf("bAvaliable: %d\n", bAvaliable);DWORD dwCapability = 0; bb->get_KinectCapabilities(&dwCapability); // 擷取容量printf("dwCapability: %d\n", dwCapability);TCHAR bbuid[256] = { 0 };bb->get_UniqueKinectId(256, bbuid); // 擷取唯一IDprintf("UID: %s\n",bbuid);// 音頻資料擷取IAudioSource* audios = nullptr;UINT nAudioCount = 0;hr = bb->get_AudioSource(&audios);if ( FAILED(hr) ){printf("Audio Source get failed!\n");goto endclose;}IAudioBeam* audiobm = nullptr;IAudioBeamList* audiobml = nullptr;audios->get_AudioBeams(&audiobml);audiobml->OpenAudioBeam(0, &audiobm); // 目前只支援第一個float fAngle = 0.0f;float fAngleConfidence = 0.0f;while (true){fAngle = 0.0f;fAngleConfidence = 0.0f;audiobm->get_BeamAngle(&fAngle); // 擷取音訊角度,[ -0.872665f, 0.8726665f ]audiobm->get_BeamAngleConfidence(&fAngleConfidence); // 擷取音訊可信度(0 - 1)printf("Angle: %3.2f (%1.2f)\n", (fAngle/3.1415926f)*180.0f, fAngleConfidence);Sleep(200);}endclose:bb->Close();endstop:system("pause");return 0;}
下面進行音頻資料的擷取。
我們可以用一般擷取錄音一樣擷取音頻流,請注意,從這裡擷取的音頻流是未經處理資料:麥克風列陣擷取的多頻道音訊,並且沒有利用麥克風列陣進行降噪處理。代碼可以查看SDK內建的擷取未經處理資料的例子,因為與通用裝置打交道,很麻煩,這裡不做說明。
這裡說的是利用內建的方法,擷取經處理的音頻資料。經過處理的資料資訊如下:
l 編碼: 32位標準浮點(IEEE FLOAT)
l 聲道: 1
l 採樣率: 16000Hz
SDK中擷取處理後的音頻流有兩種方法,一種是音訊框架,和之前的各種幀差不多:
// 擷取音頻源(AudioSource) if (SUCCEEDED(hr)){ hr = m_pKinect->get_AudioSource(&pAudioSource); } // 再擷取音訊框架讀取器 if (SUCCEEDED(hr)){ hr = pAudioSource->OpenReader(&m_pAudioBeamFrameReader); } // 註冊臨幀事件 if (SUCCEEDED(hr)){ m_pAudioBeamFrameReader->SubscribeFrameArrived(&m_hAudioBeamFrameArrived); }
這樣初始化。使用後,像之前那樣,根據事件擷取 AudioBeamFrameArrivedEventArgs, 再擷取 AudioBeamFrameReference 音訊框架引用,再擷取 AudioBeamFrameList 音訊框架鏈表,目前鏈表只有一個元素,直接擷取 AudioBeamFrame音訊框架。音訊框架可能包含複數 AudioBeamSubFrame 音頻副幀(比如本人這裡包含2個),這個東西才能擷取音頻流的真正資訊。
還有就是IStream,前面的這不是指C++標準庫的輸入資料流,而是COM組件的“流介面”,可讀可寫。初始化代碼如下:
if (SUCCEEDED(hr)) { hr = m_pKinectSensor->get_AudioSource(&pAudioSource); } if (SUCCEEDED(hr)) { hr = pAudioSource->get_AudioBeams(&pAudioBeamList); } if (SUCCEEDED(hr)) { hr = pAudioBeamList->OpenAudioBeam(0, &m_pAudioBeam); } if (SUCCEEDED(hr)) { hr = m_pAudioBeam->OpenInputStream(&m_pAudioStream); }
m_pAudioSteam就是Steam對象,使用時ISteam::Read(void*, ULONG, ULONG*)主動擷取音頻資料。相比而言,使用音訊框架既可以主動擷取,又能使用事件機制,而Stream只能主動擷取。
我們這裡使用第二種流的方式,編寫一個Demo,代碼如下:
#include "stdafx.h"#include "kinect.h"#define _USE_MATH_DEFINES#include <math.h>int _tmain(int argc, _TCHAR* argv[]){printf("Hello, Wellcome to kinect world!\n");IKinectSensor* bb; //申請一個Sensor指標HRESULT hr = GetDefaultKinectSensor(&bb); // 擷取一個預設的Sensorif ( FAILED(hr) ){printf("No Kinect connect to your pc!\n");goto endstop;}BOOLEAN bIsOpen = 0;bb->get_IsOpen(&bIsOpen); // 查看下是否已經開啟printf("bIsOpen: %d\n", bIsOpen);if ( !bIsOpen ) // 沒開啟,則嘗試開啟{hr = bb->Open();if ( FAILED(hr) ){printf("Kinect Open Failed!\n");goto endstop;}printf("Kinect opened! But it need sometime to work!\n");// 這裡一定要多等會,否則下面的判斷都是錯誤的printf("Wait For 3000 ms...\n");Sleep(3000);}bIsOpen = 0;bb->get_IsOpen(&bIsOpen); // 是否已經開啟printf("bIsOpen: %d\n", bIsOpen);BOOLEAN bAvaliable = 0;bb->get_IsAvailable(&bAvaliable); // 是否可用printf("bAvaliable: %d\n", bAvaliable);DWORD dwCapability = 0; bb->get_KinectCapabilities(&dwCapability); // 擷取容量printf("dwCapability: %d\n", dwCapability);TCHAR bbuid[256] = { 0 };bb->get_UniqueKinectId(256, bbuid); // 擷取唯一IDprintf("UID: %s\n",bbuid);// 音頻資料擷取IAudioSource* audios = nullptr;UINT nAudioCount = 0;hr = bb->get_AudioSource(&audios);if ( FAILED(hr) ){printf("Audio Source get failed!\n");goto endclose;}IAudioBeam* audiobm = nullptr;IAudioBeamList* audiobml = nullptr;audios->get_AudioBeams(&audiobml);audiobml->OpenAudioBeam(0, &audiobm); // 目前只支援第一個IStream* stm = nullptr;audiobm->OpenInputStream(&stm);audios->Release();audiobm->Release();float fAngle = 0.0f;float fAngleConfidence = 0.0f;ULONG lRead = 0;const ULONG lBufferSize =3200;float* fDataArr = new float[lBufferSize];while (true){fAngle = 0.0f;fAngleConfidence = 0.0f;audiobm->get_BeamAngle(&fAngle); // 擷取音訊角度,[ -0.872665f, 0.8726665f ]audiobm->get_BeamAngleConfidence(&fAngleConfidence); // 擷取音訊可信度(0 - 1)if ( fAngleConfidence > 0.5f ) printf("Angle: %3.2f (%1.2f)\n", (fAngle)*180.0f/static_cast<float>(M_PI), fAngleConfidence);// audio datalRead = 0;memset(fDataArr, 0, lBufferSize);stm->Read(fDataArr, lBufferSize, &lRead);if ( lRead > 0 ){printf("Audio Buffer: %d\n", lRead);}Sleep(200);}endclose:bb->Close();endstop:system("pause");return 0;}
完畢