C ++ एप्लिकेशन में Microsoft Kinect के साथ कार्य करना

परिचय


हाल ही में, Microsoft ने Kinect टूलकिट, Microsoft रिसर्च Kinect SDK का बीटा संस्करण जारी किया। टूल्स में हेडर फाइल्स, एक लाइब्रेरी और साथ ही C ++ एप्लिकेशन में उपयोग के उदाहरण शामिल हैं। लेकिन एसडीके की मौजूदगी से ही समझदारी भरे उदाहरणों और प्रलेखन की कमी से समस्या का समाधान नहीं होता है। यह ध्यान देने योग्य है कि Microsoft .NET डेवलपर्स पर अधिक केंद्रित है, इसलिए, उदाहरण के लिए, आधिकारिक मंच में विषयों के विशाल बहुमत C # से संबंधित हैं, और Google खोज जब Kinect के लिए एपीआई के किसी भी विवरण को खोजने की कोशिश कर रहा है केवल कुछ लिंक देता है, और वे - आधिकारिक दस्तावेज के लिए।

यह आलेख Microsoft Kinect, साथ ही C ++ अनुप्रयोगों में ऊपर वर्णित सॉफ़्टवेयर टूल और wxWidgets लाइब्रेरी के साथ संयोजन के लिए विकल्पों पर चर्चा करता है।



शुरुआत हो रही है


आरंभ करने के लिए, आपको कम से कम, विकास उपकरण डाउनलोड करना होगा। आप इसे Microsoft अनुसंधान Kinect SDK पृष्ठ पर कर सकते हैं।
हमें wxWidgets लाइब्रेरी की भी आवश्यकता है। आप इसे (v2.9.1) आधिकारिक वेबसाइट से या एसवीएन रिपॉजिटरी से डाउनलोड कर सकते हैं (मैं एसवीएन हेड पसंद करता हूं - इसमें अक्सर कई नई उपयोगी चीजें शामिल होती हैं जो आधिकारिक रिलीज में नहीं होती हैं, लेकिन बग अक्सर दिखाई देते हैं)।
उन लोगों के लिए जो "उज्ज्वल" पक्ष पसंद करते हैं और केवल मुफ्त टूल का उपयोग करके एप्लिकेशन विकसित करने के लिए उत्सुक हैं, यह विज़ुअल सी ++ 2010 एक्सप्रेस , साथ ही विंडोज 7 (या बाद में) के लिए माइक्रोसॉफ्ट विंडोज एसडीके डाउनलोड करने के लिए समझ में आता है, इसके बिना विजुअल सी ++ एक्सप्रेस में wxWidgets का निर्माण करें। सबसे अधिक संभावना असफल हो जाएगी।

WxWidgets एकत्रित करना


हम विजुअल स्टूडियो और किनेक्ट एसडीके के लिए इंस्टॉलेशन प्रक्रिया पर विचार नहीं करेंगे, यह सब इंस्टॉलेशन विज़ार्ड में नेक्स्ट बटन के कुछ क्लिकों के लिए नीचे आता है, लेकिन हम wxWidgets बिल्ड प्रक्रिया को और अधिक विस्तार से देखेंगे, क्योंकि अनुप्रयोग विकास के अगले चरण इस पर निर्भर करते हैं।
WxWidgets स्रोत कोड को डाउनलोड करने और एक अलग फ़ोल्डर में अनपैक करने के बाद, आपको WXWIN पर्यावरण चर को जोड़ने की आवश्यकता है, जिसके मूल्य में आपको wxWidgets स्रोत कोड फ़ोल्डर में पथ निर्दिष्ट करने की आवश्यकता है।
SVN से स्रोत कोड का उपयोग करते समय, आपको % WXWIN% / / / msw / setup0.h फ़ाइल को % WXWIN% / शामिल / msw / setup.h में कॉपी करना होगा
डिफ़ॉल्ट रूप से, wxWidgets में कई कॉन्फ़िगरेशन उपलब्ध हैं (चित्र 1):

पहले दो कॉन्फ़िगरेशन आपको स्थिर पुस्तकालयों के रूप में wxWidgets बनाने की अनुमति देते हैं, बाद वाले - कई गतिशील रूप से लोड किए गए मॉड्यूल के रूप में।



स्थैतिक पुस्तकालयों का निर्माण


स्थैतिक पुस्तकालयों (डिबग और रिलीज़ कॉन्फ़िगरेशन) को इकट्ठा करने से पहले, समाधान में सभी परियोजनाओं के गुणों में, C / C ++ -> कोड जेनरेशन -> रनटाइम लाइब्रेरी पैरामीटर को बहु-थ्रेड डीबग और मल्टी-थ्रेडेड, क्रमशः (छवि 2) पर सेट करें।



इन संकलन विकल्पों को सेट करने से हमारे एप्लिकेशन के साथ-साथ एंड-यूज़र मशीनों पर Visual C ++ Redistributable स्थापित करने की आवश्यकता समाप्त हो जाएगी। संकलन विकल्प सेट करने के बाद, आप एक समाधान बना सकते हैं। परिणामस्वरूप, कई .lib फाइलें lib / vc_lib उपनिर्देशिका में बनाई जानी चाहिए, जो बाद में हमारे एप्लिकेशन में उपयोग की जाएंगी।

गतिशील पुस्तकालयों का निर्माण


गतिशील पुस्तकालयों के निर्माण के लिए, संकलक सेटिंग्स में कुछ भी बदलने की आवश्यकता नहीं है। लेकिन एक और समस्या है - समाधान में कोई निर्भरता नहीं हैं, इसलिए निर्माण प्रक्रिया को कई बार फिर से शुरू करने की आवश्यकता होगी, क्योंकि कुछ पुस्तकालयों को जोड़ने से त्रुटियां होंगी। असेंबली के बाद, कई .DLL और .LIB फ़ाइलों को lib / vc_dll उपनिर्देशिका में बनाया जाना चाहिए।
मैं यह नोट करना चाहूंगा कि पुस्तकालयों के डिबगिंग और डिबगिंग और अनुकूलित (रिलीज़) संस्करणों को इकट्ठा करना आवश्यक है।

टेस्ट एप्लिकेशन बनाएं


इस समय, हमारे पास:

आप एप्लिकेशन बनाना शुरू कर सकते हैं।
परीक्षण आवेदन में हमारे पास होगा:

KinectTestApp.h
#ifndef _KINECTTESTAPP_H_
#define _KINECTTESTAPP_H_

#include "wx/image.h"
#include "KinectTestMainFrame.h"

class KinectTestApp: public wxApp
{
DECLARE_CLASS( KinectTestApp )
DECLARE_EVENT_TABLE()
public :
KinectTestApp();
void Init();
virtual bool OnInit();
virtual int OnExit();
};

DECLARE_APP(KinectTestApp)

#endif


KinectTestApp.cpp
...
bool KinectTestApp::OnInit()
{
#if wxUSE_LIBPNG
wxImage::AddHandler( new wxPNGHandler);
#endif
#if wxUSE_LIBJPEG
wxImage::AddHandler( new wxJPEGHandler);
#endif
KinectTestMainFrame* mainWindow = new KinectTestMainFrame( NULL );
mainWindow->Show( true );
return true ;
}


KinectTestMainFrame.h
class KinectTestMainFrame: public wxFrame, public wxThreadHelper
{
DECLARE_CLASS( KinectTestMainFrame )
DECLARE_EVENT_TABLE()
public :
KinectTestMainFrame();
KinectTestMainFrame( wxWindow* parent,
wxWindowID id = SYMBOL_KINECTTESTMAINFRAME_IDNAME,
const wxString& caption = SYMBOL_KINECTTESTMAINFRAME_TITLE,
const wxPoint& pos = SYMBOL_KINECTTESTMAINFRAME_POSITION,
const wxSize& size = SYMBOL_KINECTTESTMAINFRAME_SIZE,
long style = SYMBOL_KINECTTESTMAINFRAME_STYLE );
bool Create( wxWindow* parent,
wxWindowID id = SYMBOL_KINECTTESTMAINFRAME_IDNAME,
const wxString& caption = SYMBOL_KINECTTESTMAINFRAME_TITLE,
const wxPoint& pos = SYMBOL_KINECTTESTMAINFRAME_POSITION,
const wxSize& size = SYMBOL_KINECTTESTMAINFRAME_SIZE,
long style = SYMBOL_KINECTTESTMAINFRAME_STYLE );
~KinectTestMainFrame();
void Init();
void CreateControls();
wxBitmap GetBitmapResource( const wxString& name );
wxIcon GetIconResource( const wxString& name );
virtual wxThread::ExitCode Entry();

wxGridBagSizer* m_MainSizer;
wxListBox* m_DeviceListBox;
KinectCanvas* m_Canvas;
};

#endif


KinectTestMainFrame.cpp
...
void KinectTestMainFrame::CreateControls()
{
KinectTestMainFrame* itemFrame1 = this ;

m_MainSizer = new wxGridBagSizer(0, 0);
m_MainSizer->SetEmptyCellSize(wxSize(10, 20));
itemFrame1->SetSizer(m_MainSizer);

wxArrayString m_DeviceListBoxStrings;
m_DeviceListBox = new wxListBox( itemFrame1,
ID_DEVICE_LISTBOX, wxDefaultPosition,
wxDefaultSize, m_DeviceListBoxStrings,
wxLB_SINGLE );
m_MainSizer->Add(m_DeviceListBox,
wxGBPosition(0, 0), wxGBSpan(1, 1),
wxGROW|wxGROW|wxALL, 5);

m_Canvas = new KinectCanvas( itemFrame1,
ID_KINECT_CANVAS, wxDefaultPosition,
wxSize(320, 240), wxSIMPLE_BORDER );
m_MainSizer->Add(m_Canvas, wxGBPosition(0, 1),
wxGBSpan(1, 1), wxALIGN_CENTER_HORIZONTAL|
wxALIGN_CENTER_VERTICAL|wxALL, 5);
m_MainSizer->AddGrowableCol(1);
m_MainSizer->AddGrowableRow(0);
}
...
wxThread::ExitCode KinectTestMainFrame::Entry()
{
return NULL;
}


KinectCanvas.h
...
class KinectCanvas: public wxWindow
{
DECLARE_DYNAMIC_CLASS( KinectCanvas )
DECLARE_EVENT_TABLE()

public :
KinectCanvas();
KinectCanvas(wxWindow* parent,
wxWindowID id = ID_KINECTCANVAS,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxSize(100, 100),
long style = wxSIMPLE_BORDER);

bool Create(wxWindow* parent,
wxWindowID id = ID_KINECTCANVAS,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxSize(100, 100),
long style = wxSIMPLE_BORDER);
~KinectCanvas();

void Init();
void CreateControls();
void OnPaint( wxPaintEvent& event );

wxImage * GetCurrentImage() const { return m_CurrentImage ; }
void SetCurrentImage(wxImage * value ) { m_CurrentImage = value ; }

wxBitmap GetBitmapResource( const wxString& name );
wxIcon GetIconResource( const wxString& name );

wxImage * m_CurrentImage;
};

#endif



KinectCanvas.cpp
...
IMPLEMENT_DYNAMIC_CLASS( KinectCanvas, wxWindow )
BEGIN_EVENT_TABLE( KinectCanvas, wxWindow )
EVT_PAINT( KinectCanvas::OnPaint )
END_EVENT_TABLE()
...
void KinectCanvas::OnPaint( wxPaintEvent& event )
{
wxAutoBufferedPaintDC dc( this );
if (!m_CurrentImage)
{
dc.SetBackground(wxBrush(GetBackgroundColour(), wxSOLID));
dc.Clear();
dc.DrawLabel(_( "No image" ), wxRect(dc.GetSize()),
wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL);
}
else
{
wxBitmap bmp(*m_CurrentImage);
dc.DrawBitmap(bmp,
(dc.GetSize().GetWidth()-bmp.GetWidth())/2,
(dc.GetSize().GetHeight()-bmp.GetHeight())/2);
}
}



उपरोक्त स्रोत कोड में, कई खाली विधियां हैं, साथ ही कई विधियां स्पष्ट नहीं हैं (उदाहरण के लिए, GetIconResource() , GetBitmapResource() , Init() )। यह सब क्योंकि डायलॉगब्लॉक फॉर्म डिज़ाइनर का उपयोग एप्लिकेशन फ्रेमवर्क बनाने के लिए किया गया था। यह एक पेड टूल है, लेकिन ट्रायल वर्जन की कार्यक्षमता हमारे एप्लिकेशन को बनाने के लिए पर्याप्त है।
एप्लिकेशन बनाने का प्रयास करने से पहले, आपको प्रोजेक्ट सेटिंग्स बदलने की आवश्यकता होती है ताकि वे wxWidgets बिल्ड विकल्पों से मेल खाएं। इसका अर्थ है कि यदि हम स्थिर wxWidgets लाइब्रेरीज़ का उपयोग करना चाहते हैं, तो हमें प्रोजेक्ट गुण C / C ++ -> कोड जेनरेशन -> रनटाइम लाइब्रेरी पैरामीटर में डीबग और रिलीज़ कॉन्फ़िगरेशन के लिए समान मान सेट करने की आवश्यकता है। यदि हमें wxWidgets डायनेमिक लाइब्रेरीज़ का उपयोग करने की आवश्यकता है, तो C / C ++ -> प्रीप्रोसेसर -> प्रीप्रोसेसर परिभाषाएँ पैरामीटर में प्रोजेक्ट सेटिंग्स में, आपको WXUSINGDLL मैक्रो को जोड़ना होगा। इस मैक्रो का उपयोग डायनेमिक wxWidgets लाइब्रेरीज़ को बनाने के लिए भी किया जाता है, और इसके परिणामस्वरूप, हमारे प्रोजेक्ट और wxWidgets की सेटिंग का मिलान होगा (चित्र 3)।



इसके अलावा, एप्लिकेशन के डिबग संस्करण के लिए, आपको संसाधन कंपाइलर सेटिंग में प्रीप्रोसेसर निर्देशों में मैक्रो wxUSE_NO_MANIFEST=1 को जोड़ना होगा। यह सुनिश्चित करने के लिए है कि wxWidgets संसाधन फ़ाइल ( % WXWIN% / शामिल / msw / wx.rc ) में निर्दिष्ट मैनिफ़ेस्ट के साथ कोई विरोध नहीं है और यह प्रकट होता है कि विज़ुअल स्टूडियो स्वचालित रूप से एप्लिकेशन में जुड़ जाता है।
उपरोक्त चरणों को करने के बाद, आप एप्लिकेशन का निर्माण कर सकते हैं। नतीजतन, हमें कुछ ऐसा मिलता है (चित्र 4):



Microsoft अनुसंधान Kinect SDK का उपयोग करना


Kinect SDK को स्थापित करने के बाद, पर्यावरण चर% MSRKINECTSDK% सिस्टम में दिखाई देगा और उस फ़ोल्डर में पथ समाहित करेगा जिसमें SDK इंस्टॉल किया गया था। इस फ़ोल्डर में एक inc सबडायरेक्टरी है - हेडर फाइल के साथ, और लाइबरी - एक लाइब्रेरी के साथ। हेडर फ़ाइलों के पथ को हमारे परीक्षण एप्लिकेशन में कंपाइलर सेटिंग्स में जोड़ा जाना चाहिए, लाइब्रेरी से लिंकर सेटिंग्स तक का रास्ता।

उपकरणों की एक सूची प्राप्त करना


फिलहाल, हमारे पास सभी एकत्रित निर्भरताएं और एप्लिकेशन टेम्पलेट हैं। अब आप सीधे Kinect SDK का उपयोग करके कोड लिखना शुरू कर सकते हैं।
Kinect SDK में कार्यक्षमता है जो आपको एक कंप्यूटर से जुड़े कई Kinect उपकरणों के साथ काम करने की अनुमति देती है। आवेदन विकसित करते समय यह एक अधिक सार्वभौमिक समाधान है यह पहले से ज्ञात नहीं है कि हमें कितने उपकरणों की आवश्यकता होगी। इसलिए, इस विशेष एपीआई का उपयोग करना हमारे लिए अधिक बेहतर होगा।
उपकरणों की एक सूची प्राप्त करने के लिए, MSR_NuiGetDeviceCount() फ़ंक्शन का उपयोग किया जाता है, जो एक पूर्णांक चर के लिए एक संकेतक के रूप में पैरामीटर लेता है, जिसमें, यदि सफल होता है, तो उपलब्ध सेंसर की संख्या लिखी जाएगी:

NUIAPI HRESULT MSR_NuiGetDeviceCount(
int * pCount
);

प्रत्येक Kinect डिवाइस का अपना विशिष्ट पहचानकर्ता होता है, जिसे INuiInstance::MSR_NuiGetPropsBlob() विधि का उपयोग करके प्राप्त किया जा सकता है। यह विधि मापदंडों के रूप में लेती है:

virtual bool MSR_NuiGetPropsBlob(
MsrNui::NUI_PROPSINDEX Index,
void * pBlob,
DWORD * pdwInOutSize
);


नए अधिग्रहीत ज्ञान के साथ सशस्त्र, हम अपने आवेदन में उपकरणों की एक सूची प्राप्त कर सकते हैं।

wxKinectHelper.h
#pragma once

#include <vector>

interface INuiInstance;

class KinectHelper
{
protected :
typedef std::pair<INuiInstance *, HANDLE> InstanceInfo;
typedef std::vector<InstanceInfo> InstanceVector;
public :
KinectHelper();
virtual ~KinectHelper();

size_t GetDeviceCount();
wxString GetDeviceName(size_t index);

bool IsDeviceOK(size_t deviceIndex);
protected :
InstanceVector m_Instances;

void Finalize();
InstanceInfo * GetInstanceByIndex(size_t index);
};

wxKinectHelper.cpp
#include <wx/wx.h>
#include "msr_nuiapi.h"
#include "KinectHelper.h"

KinectHelper::KinectHelper()
{
}

KinectHelper::~KinectHelper()
{
Finalize();
}

size_t KinectHelper::GetDeviceCount()
{
int result(0);
if (FAILED(MSR_NUIGetDeviceCount(&result))) return 0;
return (size_t)result;
}

KinectHelper::InstanceInfo * KinectHelper::GetInstanceByIndex(size_t index)
{
INuiInstance * instance = NULL;
for (InstanceVector::iterator i = m_Instances.begin();
i != m_Instances.end(); i++)
{
instance = (*i).first;
if (instance->InstanceIndex() == ( int )index) return &(*i);
}
if (!instance)
{
if (!FAILED(MSR_NuiCreateInstanceByIndex(( int )index, &instance)))
{
InstanceInfo info;
info.first = instance;
info.second = NULL;
m_Instances.push_back(info);
return &(m_Instances.at(m_Instances.size()-1));
}
}
return NULL;
}

void KinectHelper::Finalize()
{
for (InstanceVector::const_iterator i = m_Instances.begin();
i != m_Instances.end(); i++)
{
if ((*i).first && (*i).second)
{
(*i).first->NuiShutdown();
MSR_NuiDestroyInstance((*i).first);
}
}
}

wxString KinectHelper::GetDeviceName(size_t index)
{
BSTR result;
DWORD size;
InstanceInfo * info = GetInstanceByIndex(index);
if (info != NULL)
{
INuiInstance * instance = info->first;
if (instance != NULL && instance->MSR_NuiGetPropsBlob(
MsrNui::INDEX_UNIQUE_DEVICE_NAME, &result, &size))
{
wxString name = result;
SysFreeString(result);
return name;
}
}
return wxT( "Unknown Kinect Sensor" );
}

bool KinectHelper::IsDeviceOK(size_t deviceIndex)
{
return GetInstanceByIndex(deviceIndex) != NULL;
}

InstanceInfo संरचना में एक INuiInstance उदाहरण के लिए एक संकेतक होता है, जिसके साथ हम डिवाइस का नाम, साथ ही उस स्ट्रीम को हैंडल कर सकते हैं जिसमें छवि कैप्चर की गई है (बाद में चर्चा की जानी है)।
wxKinectHelper वर्ग में InstanceInfo संरचनाओं और उपकरणों की संख्या और प्रत्येक डिवाइस का नाम प्राप्त करने के तरीकों के लिए एक वेक्टर है। wxKinectHelper वर्ग के विध्वंसक में, Finalize() विधि को कहा जाता है, जो सभी खुले छवि कैप्चर थ्रेड्स को बंद कर देता है, और फिर INuiInstance सभी उदाहरणों को हटा देता है।
अब आपको हमारे आवेदन में उपकरणों की सूची प्राप्त करने की कार्यक्षमता को जोड़ना होगा।

wxKinectHelperMainFrame.h
...
class KinectTestMainFrame: public wxFrame, public wxThreadHelper
{
...
void ShowDevices();
...
KinectHelper * m_KinectHelper;
}
...

wxKinectHelperMainFrame.cpp
...
void KinectTestMainFrame::ShowDevices()
{
size_t count = m_KinectHelper->GetDeviceCount();
m_DeviceListBox->Clear();
for (size_t i = 0; i < count; ++i)
{
int item = m_DeviceListBox->Append(
m_KinectHelper->GetDeviceName(i));
m_DeviceListBox->SetClientData(item, ( void *)i);
}
}

नतीजतन, एप्लिकेशन लॉन्च करने के बाद, हमें उपलब्ध Kinect उपकरणों (छवि 5) की एक सूची मिलती है।



Kinect के साथ एक छवि प्राप्त करना


इससे पहले कि आप किसी उपकरण से छवि कैप्चर करना शुरू करें, आपको इसे आरंभ करना होगा। यह INuiInstance::NuiInitialize() पद्धति का उपयोग करके किया जाता है, जो एक पैरामीटर के रूप में एक बिट मास्क होता है जो डिवाइस सबसिस्टम की सूची का वर्णन करता है जिसे हम उपयोग करने की योजना बनाते हैं (गहराई सेंसर, कैमरा, या वीडियो पर खिलाड़ियों की खोज)।
HRESULT NuiInitialize(
DWORD dwFlags,
);

Kinect के साथ एक छवि प्राप्त करने के लिए, छवि कैप्चर स्ट्रीम को इनिशियलाइज़ करना आवश्यक है। इन उद्देश्यों के लिए, INuiInstance:: NuiImageStreamOpen() विधि का उपयोग किया जाता है, जो मापदंडों के रूप में लेता है:

डिवाइस से छवियों को कैप्चर करने से रोकने के लिए, आपको INuiInstance::NuiShutdown() विधि को कॉल करने की आवश्यकता है, और जब आप INuiInstance उदाहरण के साथ काम करना समाप्त कर INuiInstance , INuiInstance MSR_NuiDestroyInstance() फ़ंक्शन का उपयोग करके मेमोरी को खाली करने की आवश्यकता होती है, जिसके पैरामीटर में आप INuiInstance ऑब्जेक्ट में एक पॉइंटर पास करते हैं।

गहराई बफर हो रही है


गहराई बफ़र शुरू करने के लिए, आपको INuiInstance:: NuiImageStreamOpen() विधि को कॉल करना INuiInstance:: NuiImageStreamOpen() और पहले पैरामीटर के रूप में NUI_IMAGE_TYPE_DEPTH_AND_PLAYER_INDEX या NUI_IMAGE_TYPE_DEPTH ध्वज वाले मान को पास करना होगा। बाद की प्रोसेसिंग के लिए सबसे उपयुक्त बफर NUI_IMAGE_TYPE_DEPTH_AND_PLAYER_INDEX फ़्लैग का उपयोग करके प्राप्त किया गया था। स्रोत कोड में, एक समान कॉल इस तरह दिखाई देगी:
if (FAILED(info->first->NuiImageStreamOpen(NUI_IMAGE_TYPE_DEPTH_AND_PLAYER_INDEX,
NUI_IMAGE_RESOLUTION_320x240, 0,
3,
hDepthFrameEvent,
&info->second))) { /* Handle error here */ }

ऊपर वर्णित कॉल के परिणामस्वरूप, चर info->second में इमेज कैप्चर स्ट्रीम के लिए एक हैंडल होगा। hDepthFrameEvent इवेंट hDepthFrameEvent CreateEvent() फ़ंक्शन का उपयोग करके बनाया जा सकता है।

जब कोई नई छवि उपलब्ध होगी, hDepthFrameEvent ईवेंट को hDepthFrameEvent जाएगा। इस घटना की प्रतीक्षा में WaitForMultipleObjects() या WaitForSingleObject() फ़ंक्शन का उपयोग करके लागू किया जा सकता है।

आप NuiImageStreamGetNextFrame() विधि का उपयोग करके डिवाइस से बफर प्राप्त कर सकते हैं, जिसे आपको मापदंडों के रूप में पारित करने की आवश्यकता है:

virtual HRESULT NuiImageStreamGetNextFrame(
_In_ HANDLE hStream,
_In_ DWORD dwMillisecondsToWait,
_Deref_out_ CONST NUI_IMAGE_FRAME **ppcImageFrame
);

परिणामस्वरूप NUI_IMAGE_FRAME उदाहरण में NUI_IMAGE_FRAME हम वर्तमान में NuiImageBuffer *pFrameTexture में सबसे अधिक रुचि रखते हैं।
बफर डेटा के साथ सीधे काम करने के लिए, आपको LockRect() विधि को कॉल करना होगा। LockRect() विधि LockRect() चार पैरामीटर हैं, जिनमें से दो का उपयोग एपीआई के बीटा संस्करण में किया जाता है।
पहले पैरामीटर के रूप में, आपको 0 पास करने की आवश्यकता है, दूसरे पैरामीटर के रूप में, KINECT_LOCKED_RECT संरचना के लिए एक संकेतक, जिसमें, फ़ंक्शन सफलतापूर्वक पूरा होने के बाद, बफर के साथ काम करने के लिए डेटा लिखा जाएगा। हम NULL को तीसरे पैरामीटर के रूप में, 0 को चौथे पैरामीटर के रूप में पास करते हैं।
STDMETHODIMP LockRect(
UINT Level,
KINECT_LOCKED_RECT* pLockedRect,
CONST RECT* pRectUsuallyNull,
DWORD Flags
);

इसके अलावा, KINECT_LOCKED_RECT संरचना में KINECT_LOCKED_RECT हम pBits फ़ील्ड में रुचि रखते हैं, जिसमें सीधे गहराई डेटा होता है। बफर में प्रत्येक पिक्सेल के लिए, 2 बाइट्स आवंटित किए जाते हैं। आधिकारिक मंच पर अक्सर पूछे जाने वाले प्रश्नों को देखते हुए, डेटा प्रारूप इस प्रकार है:
NUI_INITIALIZE_FLAG_USES_DEPTH_AND_PLAYER_INDEX ध्वज का उपयोग करते NUI_INITIALIZE_FLAG_USES_DEPTH_AND_PLAYER_INDEX , 12 न्यूनतम महत्वपूर्ण बिट्स को गहराई मान के लिए आवंटित किया जाता है और शेष 3 बिट्स प्लेयर इंडेक्स के लिए आवंटित किए जाते हैं, सबसे महत्वपूर्ण बिट का उपयोग नहीं किया जाता है।
NUI_INITIALIZE_FLAG_USES_DEPTH ध्वज का उपयोग करते NUI_INITIALIZE_FLAG_USES_DEPTH , निचले 12 बिट्स को गहराई मान के लिए आवंटित किया जाता है, बाकी का उपयोग नहीं किया जाता है।
ग्रे के रंगों में एक छवि प्राप्त करने के लिए, हमें इस तरह से गहराई मान को सामान्य करने की आवश्यकता है जैसे 0 से 255 तक की सीमा में मूल्य प्राप्त करना। आप इस तरह से कर सकते हैं:
USHORT RealDepth = (s & 0xfff8) >> 3;
BYTE l = 255 - (BYTE)(256*RealDepth/0x0fff);
RGBQUAD q;
q.rgbRed = q.rgbBlue = q.rgbGreen = l;
return q;

प्राप्त छवि के साथ काम पूरा करने के लिए, बफर के लिए आवंटित मेमोरी को मुक्त करना आवश्यक है। यह NuiImageStreamReleaseFrame() विधि का उपयोग करके किया जा सकता है, जो एक स्ट्रीम डिस्क्रिप्टर और एक सूचक को NUI_IMAGE_FRAME उदाहरण में NUI_IMAGE_FRAME रूप में लेता है।

आइए संक्षेप में बताते हैं कि इस समय हमारे पास क्या है:

अब देखते हैं कि आप यह सब कैसे अभ्यास में डाल सकते हैं:
wxKinectHelper.h
class KinectHelper
{
...
const wxSize & GetFrameSize();
BYTE * CreateDataBuffer();
void FreeDataBuffer(BYTE * data);
size_t GetDataBufferLength();

bool StartGrabbing(size_t deviceIndex, HANDLE hDepthFrameEvent);
bool ReadKinectFrame(size_t deviceIndex, BYTE * data);

bool IsDeviceOK(size_t deviceIndex);
bool IsGrabbingStarted(size_t deviceIndex);

static RGBQUAD Nui_ShortToQuad_Depth( USHORT s );
protected :
InstanceVector m_Instances;
wxSize m_FrameSize;
...
};

wxKinectHelper.cpp
...
void ReadLockedRect(KINECT_LOCKED_RECT & LockedRect, int w, int h, BYTE * data)
{
if ( LockedRect.Pitch != 0 )
{
BYTE * pBuffer = (BYTE*) LockedRect.pBits;

// draw the bits to the bitmap
USHORT * pBufferRun = (USHORT*) pBuffer;
for ( int y = 0 ; y < h ; y++ )
{
for ( int x = 0 ; x < w ; x++ )
{
RGBQUAD quad = KinectHelper::Nui_ShortToQuad_Depth( *pBufferRun );
pBufferRun++;
int offset = (w * y + x) * 3;
data[offset + 0] = quad.rgbRed;
data[offset + 1] = quad.rgbGreen;
data[offset + 2] = quad.rgbBlue;
}
}
}
}
...
BYTE * KinectHelper::CreateDataBuffer()
{
size_t length = GetDataBufferLength();
BYTE * result = (BYTE*)CoTaskMemAlloc(length);
memset(result, 0, length);
return result;
}

size_t KinectHelper::GetDataBufferLength()
{
return m_FrameSize.GetWidth() * m_FrameSize.GetHeight() * 3;
}

void KinectHelper::FreeDataBuffer(BYTE * data)
{
CoTaskMemFree((LPVOID)data);
}

void KinectHelper::Finalize()
{
for (InstanceVector::const_iterator i = m_Instances.begin();
i != m_Instances.end(); i++)
{
if ((*i).first && (*i).second)
{
(*i).first->NuiShutdown();
MSR_NuiDestroyInstance((*i).first);
}
}
}

bool KinectHelper::StartGrabbing(size_t deviceIndex, HANDLE hDepthFrameEvent)
{
do
{
InstanceInfo * info = GetInstanceByIndex(deviceIndex);
if (!info || !info->first) break ;
if (FAILED(info->first->NuiInitialize(
NUI_INITIALIZE_FLAG_USES_DEPTH_AND_PLAYER_INDEX))) break ;
if (FAILED(info->first->NuiImageStreamOpen(
NUI_IMAGE_TYPE_DEPTH_AND_PLAYER_INDEX,
NUI_IMAGE_RESOLUTION_320x240, 0,
3,
hDepthFrameEvent,
&info->second))) break ;
}
while ( false );
return false ;
}

bool KinectHelper::IsDeviceOK(size_t deviceIndex)
{
return GetInstanceByIndex(deviceIndex) != NULL;
}

bool KinectHelper::IsGrabbingStarted(size_t deviceIndex)
{
InstanceInfo * info = GetInstanceByIndex(deviceIndex);
return (info != NULL && info->first != NULL && info->second != NULL);
}

bool KinectHelper::ReadKinectFrame(size_t deviceIndex, BYTE * data)
{
do
{
if (deviceIndex < 0) break ;
InstanceInfo * info = GetInstanceByIndex((size_t)deviceIndex);
if (!info || !info->second) break ;
const NUI_IMAGE_FRAME * pImageFrame;
if (FAILED(NuiImageStreamGetNextFrame(
info->second, 200, &pImageFrame))) break ;
NuiImageBuffer * pTexture = pImageFrame->pFrameTexture;
KINECT_LOCKED_RECT LockedRect;
pTexture->LockRect( 0, &LockedRect, NULL, 0 );
ReadLockedRect(LockedRect, m_FrameSize.GetWidth(),
m_FrameSize.GetHeight(), data);
NuiImageStreamReleaseFrame(info->second, pImageFrame);
return true ;
}
while ( false );
return false ;
}

RGBQUAD KinectHelper::Nui_ShortToQuad_Depth( USHORT s )
{
USHORT RealDepth = (s & 0xfff8) >> 3;
BYTE l = 255 - (BYTE)(256*RealDepth/0x0fff);
RGBQUAD q;
q.rgbRed = q.rgbBlue = q.rgbGreen = l;
return q;
}

KinectTestMainFrame.h
class KinectTestMainFrame: public wxFrame, public wxThreadHelper
{
...
void OnDEVICELISTBOXSelected( wxCommandEvent& event );
...
void ShowDevices();
void StopGrabbing();
HANDLE m_NewDepthFrameEvent;
KinectHelper * m_KinectHelper;
BYTE * m_pDepthBuffer;
wxImage * m_CurrentImage;
int m_SelectedDeviceIndex;
};

KinectTestMainFrame.cpp
...
BEGIN_EVENT_TABLE( KinectTestMainFrame, wxFrame )
EVT_LISTBOX( ID_DEVICE_LISTBOX, KinectTestMainFrame::OnDEVICELISTBOXSelected )
END_EVENT_TABLE()
...
void KinectTestMainFrame::Init()
{
m_NewDepthFrameEvent = CreateEvent( NULL, TRUE, FALSE, NULL );
m_KinectHelper = new KinectHelper;
m_pDepthBuffer = m_KinectHelper->CreateDataBuffer();
m_CurrentImage = new wxImage(
m_KinectHelper->GetFrameSize().GetWidth(),
m_KinectHelper->GetFrameSize().GetHeight(),
m_pDepthBuffer, true );
m_SelectedDeviceIndex = -1;
m_MainSizer = NULL;
m_DeviceListBox = NULL;
m_Canvas = NULL;
}
...
KinectTestMainFrame::~KinectTestMainFrame()
{
StopGrabbing();
wxDELETE(m_CurrentImage);
m_KinectHelper->FreeDataBuffer(m_pDepthBuffer);
wxDELETE(m_KinectHelper);
}
...
wxThread::ExitCode KinectTestMainFrame::Entry()
{
while (!GetThread()->TestDestroy())
{
int mEventIndex = WaitForMultipleObjects(
1, &m_NewDepthFrameEvent, FALSE, 100);
switch (mEventIndex)
{
case 0:
{
wxCriticalSectionLocker lock (m_CS);
m_KinectHelper->ReadKinectFrame(
m_SelectedDeviceIndex, m_pDepthBuffer);
m_Canvas->Refresh();
}
break ;
default :
break ;
}
}
return NULL;
}
...
void KinectTestMainFrame::OnDEVICELISTBOXSelected( wxCommandEvent& event )
{
do
{
StopGrabbing();
size_t deviceIndex =
(size_t)m_DeviceListBox->GetClientData( event .GetInt());
if (deviceIndex < 0 || deviceIndex >
m_KinectHelper->GetDeviceCount()) break ;
m_SelectedDeviceIndex = deviceIndex;
if (!m_KinectHelper->IsDeviceOK(deviceIndex)) break ;
if (!m_KinectHelper->IsGrabbingStarted(deviceIndex))
{
m_KinectHelper->StartGrabbing(
deviceIndex, m_NewDepthFrameEvent);
if (CreateThread() != wxTHREAD_NO_ERROR) break ;
m_Canvas->SetCurrentImage(m_CurrentImage);
GetThread()->Run();
}
}
while ( false );
}

void KinectTestMainFrame::StopGrabbing()
{
if (GetThread())
{
if (GetThread()->IsAlive())
{
GetThread()->Delete();
}
if (m_kind == wxTHREAD_JOINABLE)
{
if (GetThread()->IsAlive())
{
GetThread()->Wait();
}
wxDELETE(m_thread);
}
else
{
m_thread = NULL;
}
}
}

जब अनुप्रयोग wxKinectHelper , तो wxKinectHelper ऑब्जेक्ट रिज़ॉल्यूशन (320x240x24) के अनुसार, गहराई बफर के लिए मेमोरी आवंटित करता है। फिर, आबंटित स्मृति क्षेत्र को m_CurrentImage ऑब्जेक्ट के लिए RGB बफर के रूप में स्थानांतरित किया जाता है।
जब कोई डिवाइस उपलब्ध उपकरणों की सूची में m_CurrentImage जाता है, तो डिवाइस से छवि कैप्चर स्ट्रीम शुरू होती है, और m_CurrentImage ऑब्जेक्ट कैनवास m_CurrentImage जुड़ा होता है।
Entry() विधि डिवाइस से एक नई छवि की प्रतीक्षा करती है। जब कोई छवि उपलब्ध होती है, तो आरजीबी बफर नए मूल्यों से भर जाता है, और फिर कैनवास को फिर से तैयार किया जाता है।
नतीजतन, आवेदन शुरू करने और सूची में डिवाइस के नाम पर क्लिक करने के बाद, हमें कुछ ऐसा मिलेगा (छवि 6)।



कैमरे से रंगीन छवि प्राप्त करना


डिवाइस के कैमरे से एक छवि प्राप्त करने के लिए, आपको NuiInitialize() विधि को कॉल करते समय NUI_INITIALIZE_FLAG_USES_COLOR ध्वज को निर्दिष्ट करना होगा, और NuiImageStreamOpen() विधि को कॉल करते हुए कम से कम 640x480 का रिज़ॉल्यूशन भी निर्दिष्ट करना होगा।
नतीजतन, कोड कुछ इस तरह दिखाई देगा:
if (FAILED(info->first->NuiInitialize(
NUI_INITIALIZE_FLAG_USES_DEPTH_AND_PLAYER_INDEX|
NUI_INITIALIZE_FLAG_USES_COLOR))) break ;
if (FAILED(info->first->NuiImageStreamOpen(NUI_IMAGE_TYPE_COLOR,
NUI_IMAGE_RESOLUTION_640x480, 0,
3,
hDepthFrameEvent,
&info->second))) break ;

तदनुसार, KINECT_LOCKED_RECT संरचना में डेटा RGBA प्रारूप में निहित है (SDK में उपलब्ध RGBQUAD संरचना डेटा तक पहुंचने के लिए काफी उपयुक्त है)। इस प्रकार, आरजीबी बफर प्राप्त करने के लिए कोड इस तरह दिखाई देगा:
if ( LockedRect.Pitch != 0 )
{
BYTE * pBuffer = (BYTE*) LockedRect.pBits;
for ( int y = 0 ; y < h ; y++ )
{
for ( int x = 0 ; x < w ; x++ )
{
RGBQUAD * quad = ((RGBQUAD*)pBuffer) + x;
int offset = (w * y + x) * 3;
data[offset + 0] = quad->rgbRed;
data[offset + 1] = quad->rgbGreen;
data[offset + 2] = quad->rgbBlue;
}
pBuffer += LockedRect.Pitch;
}
}

खिलाड़ियों की स्थिति की ट्रैकिंग (कंकाल ट्रैकिंग)


खिलाड़ियों के "कंकाल" के खंडों को प्राप्त करने और प्रदर्शित करने के लिए एल्गोरिथ्म सामान्य छवियों से भिन्न होता है।

कंकाल खंडों को प्राप्त करने की संभावना को सक्षम करने के लिए, NuiInitialize() मान वाले एक ध्वज को पास करने के लिए NuiInitialize() विधि आवश्यक है, और NuiInitialize() विधि को कॉल करें, जो कि एक घटना विवरणक के रूप में एक घटना विवरणक पास करता है। और दूसरे पैरामीटर के रूप में, झंडे का एक सेट (एसडीके का बीटा संस्करण इस पैरामीटर को अनदेखा करता है, इसलिए आप 0 पास कर सकते हैं)।

कंकाल खंडों को प्राप्त करने के प्रवाह को पूरा करने के लिए, आपको NuiSkeletonTrackingDisable() विधि को कॉल करना होगा।
कोड में, यह इस तरह दिखेगा:
if (FAILED(info->first->NuiSkeletonTrackingEnable(hSkeletonFrameEvent, 0)))
{ /* error */ };

आप एक डेटा बफ़र प्राप्त कर सकते हैं जिसमें NuiSkeletonGetNextFrame() पद्धति का उपयोग करने वाले खिलाड़ियों की स्थिति के बारे में जानकारी है, जो मापदंडों के रूप में लेता है:

NuiSkeletonGetNextFrame() विधि को कॉल करने के बाद, हमें NUI_SKELETON_FRAME संरचना का एक उदाहरण मिलता है। आइए इसे और अधिक विस्तार से देखें।
struct _NUI_SKELETON_FRAME {
LARGE_INTEGER liTimeStamp;
DWORD dwFrameNumber;
DWORD dwFlags;
Vector4 vFloorClipPlane;
Vector4 vNormalToGravity;
NUI_SKELETON_DATA SkeletonData[NUI_SKELETON_COUNT];
} NUI_SKELETON_FRAME;


जैसा कि आप NUI_SKELETON_FRAME संरचना के विवरण से देख सकते हैं, सीमित संख्या में खिलाड़ी समर्थित हैं (वर्तमान SDK संस्करण में, NUI_SKELETON_COUNT का मान 6 है)।

अब NUI_SKELETON_DATA संरचना पर विचार करें:
struct _NUI_SKELETON_DATA {
NUI_SKELETON_TRACKING_STATE eTrackingState;
DWORD dwTrackingID;
DWORD dwEnrollmentIndex;
DWORD dwUserIndex;
Vector4 Position;
Vector4 SkeletonPositions[NUI_SKELETON_POSITION_COUNT];
NUI_SKELETON_POSITION_TRACKING_STATE
eSkeletonPositionTrackingState[NUI_SKELETON_POSITION_COUNT];
DWORD dwQualityFlags;
} NUI_SKELETON_DATA;


जैसा कि आप NUI_SKELETON_DATA संरचना के विवरण से देख सकते हैं, समर्थित संयुक्त जोड़ों की संख्या NUI_SKELETON_POSITION_COUNT बराबर संख्या तक सीमित है।
अब, ऊपर वर्णित एपीआई का उपयोग करके खिलाड़ियों के निर्देशांक प्राप्त करने के कार्यान्वयन पर विचार करें:

KinectHelper.h
...
struct KinectStreams
{
HANDLE hDepth;
HANDLE hColor;
HANDLE hSkeleton;
KinectStreams() : hDepth(NULL), hColor(NULL), hSkeleton(NULL) {}
};
...

KinectHelper.cpp
void KinectHelper::Finalize()
{
for (InstanceVector::const_iterator i = m_Instances.begin();
i != m_Instances.end(); i++)
{
if ((*i).first)
{
...
if ((*i).second.hSkeleton != NULL)
{
(*i).first->NuiSkeletonTrackingDisable();
}
MSR_NuiDestroyInstance((*i).first);
}
}
}

bool KinectHelper::StartGrabbing(size_t deviceIndex,
HANDLE hDepthFrameEvent,
HANDLE hColorFrameEvent,
HANDLE hSkeletonFrameEvent)
{
do
{
if (hDepthFrameEvent == NULL &&
hColorFrameEvent == NULL &&
hSkeletonFrameEvent == NULL) break ;
InstanceInfo * info = GetInstanceByIndex(deviceIndex);
if (!info || !info->first) break ;
if (FAILED(info->first->NuiInitialize(
NUI_INITIALIZE_FLAG_USES_DEPTH_AND_PLAYER_INDEX |
NUI_INITIALIZE_FLAG_USES_COLOR |
NUI_INITIALIZE_FLAG_USES_SKELETON))) break ;
...
if (hSkeletonFrameEvent != NULL)
{
if (FAILED(info->first->NuiSkeletonTrackingEnable(
hSkeletonFrameEvent, 0))) break ;
info->second.hSkeleton = hSkeletonFrameEvent;
}
}
while ( false );
return false ;
}

void * KinectHelper::ReadSkeletonFrame(size_t deviceIndex)
{
do
{
if (deviceIndex < 0) break ;
InstanceInfo * info = GetInstanceByIndex((size_t)deviceIndex);
if (!info || !info->second.hColor) break ;
NUI_SKELETON_FRAME * frame = new NUI_SKELETON_FRAME;
if (FAILED(info->first->NuiSkeletonGetNextFrame(200, frame))) break ;
return frame;
}
while ( false );
return NULL;
}

प्लेयर कंकाल प्रतिपादन

फिलहाल, हमारे पास इस बारे में जानकारी है कि:

अब हमें किसी तरह आवेदन में प्राप्त आंकड़ों को दिखाना होगा।
इससे पहले कि आप NUI_SKELETON_FRAME डेटा के साथ कुछ करें, आपको उन्हें प्रीप्रोसेस पर भेजने की आवश्यकता है। प्रीप्रोसेसिंग NuiTransformSmooth() विधि द्वारा किया जाता है - यह चिकोटी और अचानक आंदोलनों से बचने के लिए खंडों के निर्देशांक को फ़िल्टर करता है। मापदंडों के रूप में, NuiTransformSmooth() विधि NUI_SKELETON_FRAME संरचना के लिए एक संकेतक NUI_SKELETON_FRAME है और वैकल्पिक रूप से, एक NUI_TRANSFORM_SMOOTH_PARAMETERS ऑब्जेक्ट के लिए एक पॉइंटर प्रीप्रोसेसिंग मापदंडों से युक्त होता है।
HRESULT NuiTransformSmooth(
NUI_SKELETON_FRAME *pSkeletonFrame,
CONST NUI_TRANSFORM_SMOOTH_PARAMETERS *pSmoothingParams
);

कंकाल खंडों को प्रदर्शित करने के लिए, उनके निर्देशांक को छवि निर्देशांक में बदलना आवश्यक है। यह NuiTransformSkeletonToDepthImageF() पद्धति का उपयोग करके किया जा सकता है, जो मापदंडों के रूप में लेता है:

VOID NuiTransformSkeletonToDepthImageF(
Vector4 vPoint,
_Out_ FLOAT *pfDepthX,
_Out_ FLOAT *pfDepthY
);

सभी आर्टिक्यूलेशन बिंदुओं के निर्देशांक प्राप्त करने के बाद, आप साधारण ग्राफिक प्राइमेटिव्स का उपयोग करके उन्हें कैनवास पर प्रदर्शित कर सकते हैं।

कंकाल सेगमेंट को प्रदर्शित करने के लिए अभ्यास में कोड कैसा दिखता है:
SkeletonPainter.h
#pragma once

#include <wx/wx.h>

class SkeletonPainterImpl;

class SkeletonPainter
{
public :
SkeletonPainter();
~SkeletonPainter();
void DrawSkeleton(wxDC & dc, void * data);
private :
SkeletonPainterImpl * m_Impl;
};

SkeletonPainter.cpp
#include "SkeletonPainter.h"
#if defined(__WXMSW__)
#include "SkeletonPainterImplMSW.h"
#endif

SkeletonPainter::SkeletonPainter()
{
#if defined(__WXMSW__)
m_Impl = new SkeletonPainterImplMSW;
#else
m_Impl = NULL;
#endif
}

SkeletonPainter::~SkeletonPainter()
{
wxDELETE(m_Impl);
}

void SkeletonPainter::DrawSkeleton(wxDC & dc, void * data)
{
if (m_Impl)
{
m_Impl->DrawSkeleton(dc, data);
}
}

SkeletonPainterImpl.h
#pragma once

#include <wx/wx.h>

class SkeletonPainterImpl
{
public :
virtual ~SkeletonPainterImpl() {}
virtual void DrawSkeleton(wxDC & dc, void * data) = 0;
};

SkeletonPainterImplMSW.h
#pragma once

#include "SkeletonPainterImpl.h"
#include "msr_nuiapi.h"

class SkeletonPainterImplMSW : public SkeletonPainterImpl
{
public :
~SkeletonPainterImplMSW();
void DrawSkeleton(wxDC & dc, void * data);
private :
void Nui_DrawSkeleton(wxDC & dc, NUI_SKELETON_DATA * data, size_t index);
void Nui_DrawSkeletonSegment(wxDC & dc, wxPoint * points, int numJoints, ... );

static wxPen m_SkeletonPen[6];
};

SkeletonPainterImplMSW.cpp
#include "SkeletonPainterImplMSW.h"

wxPen SkeletonPainterImplMSW::m_SkeletonPen[6] =
{
wxPen(wxColor(255, 0, 0), wxSOLID),
...
};

SkeletonPainterImplMSW::~SkeletonPainterImplMSW()
{
}

void SkeletonPainterImplMSW::DrawSkeleton(wxDC & dc, void * data)
{
do
{
NUI_SKELETON_FRAME * frame =
reinterpret_cast<NUI_SKELETON_FRAME*>(data);
if (!frame) break ;
int skeletonCount(0);
for ( int i = 0 ; i < NUI_SKELETON_COUNT ; i++ )
{
if ( frame->SkeletonData[i].eTrackingState ==
NUI_SKELETON_TRACKED )
{
skeletonCount++;
}
}
if (!skeletonCount) break ;
NuiTransformSmooth(frame, NULL);
for (size_t i = 0 ; i < NUI_SKELETON_COUNT ; i++ )
{
if (frame->SkeletonData[i].eTrackingState ==
NUI_SKELETON_TRACKED)
{
Nui_DrawSkeleton(dc, &frame->SkeletonData[i], i );
}
}
}
while ( false );
}

void SkeletonPainterImplMSW::Nui_DrawSkeleton(wxDC & dc,
NUI_SKELETON_DATA * data, size_t index)
{
wxPoint points[NUI_SKELETON_POSITION_COUNT];
float fx(0), fy(0);
wxSize imageSize = dc.GetSize();
for (size_t i = 0; i < NUI_SKELETON_POSITION_COUNT; i++)
{
NuiTransformSkeletonToDepthImageF(
data->SkeletonPositions[i], &fx, &fy);
points[i].x = ( int ) ( fx * imageSize.GetWidth() + 0.5f );
points[i].y = ( int ) ( fy * imageSize.GetHeight() + 0.5f );
}

Nui_DrawSkeletonSegment(dc,points,4,
NUI_SKELETON_POSITION_HIP_CENTER,
NUI_SKELETON_POSITION_SPINE,
NUI_SKELETON_POSITION_SHOULDER_CENTER,
NUI_SKELETON_POSITION_HEAD);
Nui_DrawSkeletonSegment(dc,points,5,
NUI_SKELETON_POSITION_SHOULDER_CENTER,
NUI_SKELETON_POSITION_SHOULDER_LEFT,
NUI_SKELETON_POSITION_ELBOW_LEFT,
NUI_SKELETON_POSITION_WRIST_LEFT,
NUI_SKELETON_POSITION_HAND_LEFT);
Nui_DrawSkeletonSegment(dc,points,5,
NUI_SKELETON_POSITION_SHOULDER_CENTER,
NUI_SKELETON_POSITION_SHOULDER_RIGHT,
NUI_SKELETON_POSITION_ELBOW_RIGHT,
NUI_SKELETON_POSITION_WRIST_RIGHT,
NUI_SKELETON_POSITION_HAND_RIGHT);
Nui_DrawSkeletonSegment(dc,points,5,
NUI_SKELETON_POSITION_HIP_CENTER,
NUI_SKELETON_POSITION_HIP_LEFT,
NUI_SKELETON_POSITION_KNEE_LEFT,
NUI_SKELETON_POSITION_ANKLE_LEFT,
NUI_SKELETON_POSITION_FOOT_LEFT);
Nui_DrawSkeletonSegment(dc,points,5,
NUI_SKELETON_POSITION_HIP_CENTER,
NUI_SKELETON_POSITION_HIP_RIGHT,
NUI_SKELETON_POSITION_KNEE_RIGHT,
NUI_SKELETON_POSITION_ANKLE_RIGHT,
NUI_SKELETON_POSITION_FOOT_RIGHT);
}

void SkeletonPainterImplMSW::Nui_DrawSkeletonSegment(wxDC & dc,
wxPoint * points, int numJoints, ...)
{
va_list vl;
va_start(vl,numJoints);
wxPoint segmentPositions[NUI_SKELETON_POSITION_COUNT];

for ( int iJoint = 0; iJoint < numJoints; iJoint++)
{
NUI_SKELETON_POSITION_INDEX jointIndex =
va_arg(vl,NUI_SKELETON_POSITION_INDEX);
segmentPositions[iJoint].x = points[jointIndex].x;
segmentPositions[iJoint].y = points[jointIndex].y;
}

dc.SetPen(*wxBLUE_PEN);
dc.DrawLines(numJoints, segmentPositions);

va_end(vl);
}

एक आवेदन में SkeletonPainter वर्ग का उपयोग इस तरह दिखेगा:

KinectTestMainFrame.h
...
class KinectTestMainFrame: public wxFrame, public wxThreadHelper
{
...
HANDLE m_NewSkeletonFrameEvent;
wxImage m_SkeletonImage;
...
};
...

KinectTestMainFrame.cpp
...
wxThread::ExitCode KinectTestMainFrame::Entry()
{
HANDLE eventHandles[3];
eventHandles[0] = m_NewDepthFrameEvent;
eventHandles[1] = m_NewColorFrameEvent;
eventHandles[2] = m_NewSkeletonFrameEvent;
SkeletonPainter painter;
while (!GetThread()->TestDestroy())
{
int mEventIndex = WaitForMultipleObjects(
_countof(eventHandles), eventHandles, FALSE, 100);
switch (mEventIndex)
{
...
case 2:
{
void * frame = m_KinectHelper->ReadSkeletonFrame(
m_SelectedDeviceIndex);
if (frame)
{
wxBitmap bmp(
m_SkeletonImage.GetWidth(),
m_SkeletonImage.GetHeight());
wxMemoryDC dc(bmp);
painter.DrawSkeleton(dc, frame);
m_KinectHelper->ReleaseSkeletonFrame(frame);
dc.SelectObject(wxNullBitmap);
m_SkeletonImage = bmp.ConvertToImage();
m_SkeletonCanvas->Refresh();
}
}
break ;
default :
break ;
}
}
return NULL;
}

ऊपर वर्णित कार्यों के परिणामस्वरूप, हमें इस तरह से कुछ प्राप्त करना चाहिए (चित्र 7):



एप्लिकेशन में प्लेटफ़ॉर्म-विशिष्ट कोड से छुटकारा पाना



ऊपर वर्णित उदाहरण सभी के लिए अच्छा है, सिवाय इसके कि परियोजना उपयोगकर्ता इंटरफ़ेस विकसित करने के लिए एक क्रॉस-प्लेटफॉर्म लाइब्रेरी का उपयोग करती है, और जीयूआई कोड का एक हिस्सा एक एपीआई का उपयोग करके लिखा जाता है जो केवल विंडोज के लिए विशिष्ट है।

Kinect के साथ काम करने के लिए कई तृतीय-पक्ष लाइब्रेरी हैं, उदाहरण के लिए, libfreenect या OpenNI , लेकिन पहले से ही इस स्तर पर हमारे पास एक स्थिति थी कि Microsoft से एसडीके का उपयोग करने के लिए एप्लिकेशन कोड बंधा हुआ है।

इस कष्टप्रद गलतफहमी को हल करने के लिए, आप डिवाइस से छवियों को प्राप्त करने से संबंधित कोड को एक अलग धरनेवाला वर्ग में निकाल सकते हैं, और KinectHelper वर्ग की कार्यक्षमता को उपकरणों की एक सूची प्राप्त करने और हड़पने वाले उदाहरण बनाने के लिए सीमित कर सकते हैं:

KinectGrabberBase.h
#pragma once

#include <wx/wx.h>

class KinectGrabberBase
{
public :
KinectGrabberBase(wxEvtHandler * handler);
virtual ~KinectGrabberBase();

virtual bool GrabDepthFrame(unsigned char * data) = 0;
virtual bool GrabColorFrame(unsigned char * data) = 0;
virtual void * GrabSkeletonFrame() = 0;

virtual bool Start() = 0;
virtual bool Stop() = 0;
virtual bool IsStarted() = 0;

const wxSize & GetDepthFrameSize();
const wxSize & GetColorFrameSize();
protected :
wxSize m_DepthFrameSize;
wxSize m_ColorFrameSize;
wxEvtHandler * m_Handler;
};

BEGIN_DECLARE_EVENT_TYPES()

DECLARE_LOCAL_EVENT_TYPE(KINECT_DEPTH_FRAME_RECEIVED, -1)
DECLARE_LOCAL_EVENT_TYPE(KINECT_COLOR_FRAME_RECEIVED, -1)
DECLARE_LOCAL_EVENT_TYPE(KINECT_SKELETON_FRAME_RECEIVED, -1)

END_DECLARE_EVENT_TYPES()

KinectGrabberBase.cpp
#include "KinectGrabberBase.h"

DEFINE_EVENT_TYPE(KINECT_DEPTH_FRAME_RECEIVED)
DEFINE_EVENT_TYPE(KINECT_COLOR_FRAME_RECEIVED)
DEFINE_EVENT_TYPE(KINECT_SKELETON_FRAME_RECEIVED)
...

KinectGrabberMSW.h
#pragma once

#include "KinectGrabberBase.h"
#include "MSR_NuiApi.h"

class KinectGrabberMSW : public KinectGrabberBase, public wxThreadHelper
{
...
private :
virtual wxThread::ExitCode Entry();
BYTE * CreateDepthDataBuffer();
BYTE * CreateColorDataBuffer();
size_t GetDepthDataBufferLength();
size_t GetColorDataBufferLength();
void FreeDataBuffer(BYTE * data);
bool ReadDepthFrame();
bool ReadColorFrame();
bool ReadSkeletonFrame();

void ReadDepthLockedRect(KINECT_LOCKED_RECT & LockedRect,
int w, int h, BYTE * data);
void ReadColorLockedRect(KINECT_LOCKED_RECT & LockedRect,
int w, int h, BYTE * data);
static RGBQUAD Nui_ShortToQuad_Depth( USHORT s );
void ResetEvents();
void StopThread();
bool CopyLocalBuffer(BYTE * src, BYTE * dst, size_t count);
HANDLE m_NewDepthFrameEvent;
HANDLE m_NewColorFrameEvent;
HANDLE m_NewSkeletonFrameEvent;
HANDLE m_DepthStreamHandle;
HANDLE m_ColorStreamHandle;
BYTE * m_DepthBuffer;
BYTE * m_ColorBuffer;
INuiInstance * m_Instance;
size_t m_DeviceIndex;
NUI_SKELETON_FRAME m_SkeletonFrame;
};

KinectGrabberMSW.cpp
#include "KinectGrabberMSW.h"

KinectGrabberMSW::KinectGrabberMSW(wxEvtHandler * handler, size_t deviceIndex)
: KinectGrabberBase(handler), m_DeviceIndex(deviceIndex), m_Instance(NULL)
{
m_DepthBuffer = CreateDepthDataBuffer();
m_ColorBuffer = CreateColorDataBuffer();
ResetEvents();
do
{
if (FAILED(MSR_NuiCreateInstanceByIndex(( int )m_DeviceIndex, &m_Instance))) break ;
if (FAILED(m_Instance->NuiInitialize(
NUI_INITIALIZE_FLAG_USES_DEPTH_AND_PLAYER_INDEX |
NUI_INITIALIZE_FLAG_USES_COLOR |
NUI_INITIALIZE_FLAG_USES_SKELETON))) break ;
}
while ( false );
}
...
void * KinectGrabberMSW::GrabSkeletonFrame()
{
do
{
if (!GetThread() || !GetThread()->IsAlive() ||
!m_Instance || !m_NewSkeletonFrameEvent) break ;
return &m_SkeletonFrame;
}
while ( false );
return NULL;
}

bool KinectGrabberMSW::Start()
{
do
{
if (!m_Instance) break ;
if (GetThread() && GetThread()->IsAlive()) break ;

if (CreateThread() != wxTHREAD_NO_ERROR) break ;

m_NewDepthFrameEvent = CreateEvent( NULL, TRUE, FALSE, NULL );
m_NewColorFrameEvent = CreateEvent( NULL, TRUE, FALSE, NULL );
m_NewSkeletonFrameEvent = CreateEvent( NULL, TRUE, FALSE, NULL );

if (FAILED(m_Instance->NuiImageStreamOpen(
NUI_IMAGE_TYPE_DEPTH_AND_PLAYER_INDEX,
NUI_IMAGE_RESOLUTION_320x240, 0,
3,
m_NewDepthFrameEvent,
&m_DepthStreamHandle))) break ;
if (FAILED(m_Instance->NuiImageStreamOpen(NUI_IMAGE_TYPE_COLOR,
NUI_IMAGE_RESOLUTION_640x480, 0,
4,
m_NewColorFrameEvent,
&m_ColorStreamHandle))) break ;
if (FAILED(m_Instance->NuiSkeletonTrackingEnable(
m_NewSkeletonFrameEvent, 0))) break ;

GetThread()->Run();

return true ;
}
while ( false );
return false ;
}
...
wxThread::ExitCode KinectGrabberMSW::Entry()
{
HANDLE eventHandles[3];
eventHandles[0] = m_NewDepthFrameEvent;
eventHandles[1] = m_NewColorFrameEvent;
eventHandles[2] = m_NewSkeletonFrameEvent;
while (!GetThread()->TestDestroy())
{
int mEventIndex = WaitForMultipleObjects(
_countof(eventHandles), eventHandles, FALSE, 100);
switch (mEventIndex)
{
case 0: ReadDepthFrame(); break ;
case 1: ReadColorFrame(); break ;
case 2: ReadSkeletonFrame(); break ;
default :
break ;
}
}
return NULL;
}
...
void KinectGrabberMSW::StopThread()
{
if (GetThread())
{
if (GetThread()->IsAlive())
{
GetThread()->Delete();
}
if (m_kind == wxTHREAD_JOINABLE)
{
if (GetThread()->IsAlive())
{
GetThread()->Wait();
}
wxDELETE(m_thread);
}
else
{
m_thread = NULL;
}
}
wxYield();
}

bool KinectGrabberMSW::ReadDepthFrame()
{
do
{
if (m_DeviceIndex < 0 || !m_Instance) break ;
const NUI_IMAGE_FRAME * pImageFrame;
if (FAILED(NuiImageStreamGetNextFrame(
m_DepthStreamHandle, 200, &pImageFrame))) break ;
NuiImageBuffer * pTexture = pImageFrame->pFrameTexture;
KINECT_LOCKED_RECT LockedRect;
pTexture->LockRect( 0, &LockedRect, NULL, 0 );
ReadDepthLockedRect(LockedRect,
m_DepthFrameSize.GetWidth(),
m_DepthFrameSize.GetHeight(),
m_DepthBuffer);
NuiImageStreamReleaseFrame(m_DepthStreamHandle, pImageFrame);
if (m_Handler)
{
wxCommandEvent e(KINECT_DEPTH_FRAME_RECEIVED, wxID_ANY);
e.SetInt(m_DeviceIndex);
m_Handler->AddPendingEvent(e);
}
return true ;
}
while ( false );
return false ;
}

bool KinectGrabberMSW::ReadColorFrame()
{
do
{
if (m_DeviceIndex < 0 || !m_Instance) break ;
const NUI_IMAGE_FRAME * pImageFrame;
if (FAILED(NuiImageStreamGetNextFrame(
m_ColorStreamHandle, 200, &pImageFrame))) break ;
NuiImageBuffer * pTexture = pImageFrame->pFrameTexture;
KINECT_LOCKED_RECT LockedRect;
pTexture->LockRect( 0, &LockedRect, NULL, 0 );
ReadColorLockedRect(LockedRect,
m_ColorFrameSize.GetWidth(),
m_ColorFrameSize.GetHeight(),
m_ColorBuffer);
NuiImageStreamReleaseFrame(m_ColorStreamHandle, pImageFrame);
if (m_Handler)
{
wxCommandEvent e(KINECT_COLOR_FRAME_RECEIVED, wxID_ANY);
e.SetInt(m_DeviceIndex);
m_Handler->AddPendingEvent(e);
}
return true ;
}
while ( false );
return false ;
}

bool KinectGrabberMSW::ReadSkeletonFrame()
{
do
{
if (m_DeviceIndex < 0 || !m_Instance) break ;
if (FAILED(m_Instance->NuiSkeletonGetNextFrame(200, &m_SkeletonFrame))) break ;
if (m_Handler)
{
wxCommandEvent e(KINECT_SKELETON_FRAME_RECEIVED, wxID_ANY);
e.SetInt(m_DeviceIndex);
m_Handler->AddPendingEvent(e);
}
return true ;
}
while ( false );
return false ;
}

void KinectGrabberMSW::ReadDepthLockedRect(KINECT_LOCKED_RECT & LockedRect, int w, int h, BYTE * data)
{
if ( LockedRect.Pitch != 0 )
{
BYTE * pBuffer = (BYTE*) LockedRect.pBits;
USHORT * pBufferRun = (USHORT*) pBuffer;
for ( int y = 0 ; y < h ; y++ )
{
for ( int x = 0 ; x < w ; x++ )
{
RGBQUAD quad = KinectGrabberMSW::Nui_ShortToQuad_Depth( *pBufferRun );
pBufferRun++;
int offset = (w * y + x) * 3;
data[offset + 0] = quad.rgbRed;
data[offset + 1] = quad.rgbGreen;
data[offset + 2] = quad.rgbBlue;
}
}
}
}

void KinectGrabberMSW::ReadColorLockedRect(KINECT_LOCKED_RECT & LockedRect, int w, int h, BYTE * data)
{
if ( LockedRect.Pitch != 0 )
{
BYTE * pBuffer = (BYTE*) LockedRect.pBits;
for ( int y = 0 ; y < h ; y++ )
{
for ( int x = 0 ; x < w ; x++ )
{
RGBQUAD * quad = ((RGBQUAD*)pBuffer) + x;
int offset = (w * y + x) * 3;
data[offset + 0] = quad->rgbRed;
data[offset + 1] = quad->rgbGreen;
data[offset + 2] = quad->rgbBlue;
}
pBuffer += LockedRect.Pitch;
}
}
}
...

KinectHelper.h
#pragma once

class KinectGrabberBase;

class KinectHelper
{
public :
KinectHelper();
~KinectHelper();

size_t GetDeviceCount();
wxString GetDeviceName(size_t index);
KinectGrabberBase * CreateGrabber(wxEvtHandler * handler, size_t index);
};

KinectHelper.cpp
...
wxString KinectHelper::GetDeviceName(size_t index)
{
BSTR result;
DWORD size;
INuiInstance * instance(NULL);
wxString name = wxT( "Unknown Kinect Sensor" );
if (!FAILED(MSR_NuiCreateInstanceByIndex(index, &instance)))
{
if (instance != NULL)
{
if (instance->MSR_NuiGetPropsBlob(
MsrNui::INDEX_UNIQUE_DEVICE_NAME,
&result, &size))
{
name = result;
SysFreeString(result);
}
MSR_NuiDestroyInstance(instance);
}
}
return name;
}

KinectGrabberBase * KinectHelper::CreateGrabber(wxEvtHandler * handler, size_t index)
{
#if defined(__WXMSW__)
return new KinectGrabberMSW(handler, index);
#else
return NULL;
#endif
}
...

RGB बफ़र्स के लिए मेमोरी का आवंटन, साथ ही छवियों को एक अलग स्ट्रीम में कैप्चर करने के लिए कोड को फॉर्म क्लास से हटाया जा सकता है। अब फॉर्म क्लास इस तरह दिखेगा:

KinectTestMainFrame.h
class KinectTestMainFrame: public wxFrame
{
...
void OnDepthFrame(wxCommandEvent & event );
void OnColorFrame(wxCommandEvent & event );
void OnSkeletonFrame(wxCommandEvent & event );
...
wxImage m_CurrentImage;
int m_SelectedDeviceIndex;
wxImage m_ColorImage;
wxImage m_SkeletonImage;
KinectGrabberBase * m_Grabber;
...
};

KinectTestMainFrame.cpp
...
BEGIN_EVENT_TABLE( KinectTestMainFrame, wxFrame )
...
EVT_COMMAND (wxID_ANY, KINECT_DEPTH_FRAME_RECEIVED, \
KinectTestMainFrame::OnDepthFrame)
EVT_COMMAND (wxID_ANY, KINECT_COLOR_FRAME_RECEIVED, \
KinectTestMainFrame::OnColorFrame)
EVT_COMMAND (wxID_ANY, KINECT_SKELETON_FRAME_RECEIVED, \
KinectTestMainFrame::OnSkeletonFrame)
END_EVENT_TABLE()
...
void KinectTestMainFrame::OnDEVICELISTBOXSelected( wxCommandEvent& event )
{
do
{
size_t deviceIndex =
(size_t)m_DeviceListBox->GetClientData( event .GetInt());
if (deviceIndex < 0 ||
deviceIndex > m_KinectHelper->GetDeviceCount()) break ;
m_SelectedDeviceIndex = deviceIndex;
StartGrabbing();
}
while ( false );
}

void KinectTestMainFrame::StartGrabbing()
{
StopGrabbing();
m_Grabber = m_KinectHelper->CreateGrabber( this , m_SelectedDeviceIndex);
m_CurrentImage = wxImage(
m_Grabber->GetDepthFrameSize().GetWidth(),
m_Grabber->GetDepthFrameSize().GetHeight());
m_ColorImage = wxImage(
m_Grabber->GetColorFrameSize().GetWidth(),
m_Grabber->GetColorFrameSize().GetHeight());
m_SkeletonImage = wxImage(
m_Grabber->GetDepthFrameSize().GetWidth(),
m_Grabber->GetDepthFrameSize().GetHeight());
m_DepthCanvas->SetCurrentImage(&m_CurrentImage);
m_ColorCanvas->SetCurrentImage(&m_ColorImage);
m_SkeletonCanvas->SetCurrentImage(&m_SkeletonImage);
if (!m_Grabber->Start())
{
StopGrabbing();
}
}
...
void KinectTestMainFrame::OnDepthFrame(wxCommandEvent & event )
{
do
{
if (!m_Grabber) break ;
m_Grabber->GrabDepthFrame(m_CurrentImage.GetData());
m_DepthCanvas->Refresh();
}
while ( false );
}

void KinectTestMainFrame::OnColorFrame(wxCommandEvent & event )
{
do
{
if (!m_Grabber) break ;
m_Grabber->GrabColorFrame(m_ColorImage.GetData());
m_ColorCanvas->Refresh();
}
while ( false );
}

void KinectTestMainFrame::OnSkeletonFrame(wxCommandEvent & event )
{
do
{
if (!m_Grabber) break ;
SkeletonPainter painter;
wxBitmap bmp(m_SkeletonImage.GetWidth(), m_SkeletonImage.GetHeight());
wxMemoryDC mdc(bmp);
painter.DrawSkeleton(mdc, m_Grabber->GrabSkeletonFrame());
mdc.SelectObject(wxNullBitmap);
m_SkeletonImage = bmp.ConvertToImage();
m_SkeletonCanvas->Refresh();
}
while ( false );
}

जैसा कि आप कोड से देख सकते हैं, एक नया फ्रेम प्राप्त करते समय wxFrame क्लास, wxEvtHandler ऑब्जेक्ट (और wxWidgets में wxFrame वर्ग wxEvtHandler से ली गई है) को एक अधिसूचना भेजता है। फॉर्म में ईवेंट हैंडलर होते हैं जिन्हें ग्रैबर से सूचना मिलने पर कॉल किया जाता है।

कारण यह है कि KinectGrabberBase::GrabSkeletonFrame() विधि रिटर्न void* भी काफी सरल है - यदि आप विभिन्न एसडीके (अनधिकृत सहित) का उपयोग करके छवि पर कब्जा लागू करते हैं, तो यह एक तथ्य नहीं है कि सभी एसडीके खिलाड़ियों की स्थिति के बारे में जानकारी प्राप्त करेंगे। समान डेटा संरचनाओं के रूप में। किसी भी मामले में, समन्वय को पोस्ट-प्रोसेसिंग के लिए भेजा जाना चाहिए। इस स्थिति में, हड़पने वाले से पॉइंटर प्राप्त करने वाले कोड को खुद ही पता चल जाएगा कि उसे किस प्रकार के डेटा में बदलना है। ग्राफिकल इंटरफ़ेस को ग्रैबर की आंतरिक संरचना के बारे में जानने की आवश्यकता नहीं है।

निष्कर्ष में


अंत में, मैं यह नोट करना चाहूंगा कि यद्यपि Microsoft SDK बीटा स्थिति में है, यह काफी प्रयोग करने योग्य है, हालांकि Kinect प्रबंधन कार्यक्षमता पूरी तरह से लागू नहीं हुई है (उदाहरण के लिए, libfreenect, आपको डिवाइस पर एल ई डी को नियंत्रित करने की अनुमति देता है, और आधिकारिक SDK, प्रलेखन को देखते हुए। नह) ं हो सकता। पुस्तकालय आश्चर्यजनक रूप से काम करता है। यह ध्यान देने योग्य है कि डेवलपर्स ने मेमोरी लीक से बचने के लिए ध्यान रखा। यदि, उदाहरण के लिए, आप बाहर निकलते समय स्ट्रीम को बंद करना भूल जाते हैं, तो Visual Studio डीबगर मेमोरी लीक की रिपोर्ट नहीं करेगा, सबसे अधिक संभावना है कि सब कुछ सही ढंग से खत्म हो जाएगा और लाइब्रेरी से अनलोड होने पर मेमोरी से हटा दिया जाएगा।
परीक्षण एप्लिकेशन और लाइब्रेरी के लिए स्रोत कोड Google कोड - wxKinectHelper पर पाया जा सकता है।
मैं परियोजना के विकास और हड़पने वालों के नए कार्यान्वयन के लिए तत्पर हूं। वर्तमान में libfreenect को वश में करने की कोशिश कर रहा है। बॉक्स से बाहर और प्रारंभिक shamanism के बिना, छवियों को प्राप्त करने के लिए छोड़कर सब कुछ शुरू करना संभव था - एलईडी संकेतक झपकाते हैं और इंजन पूरी तरह से गुलजार करता है। मैं OpenNI के साथ भी ऐसा ही करने की कोशिश कर रहा हूं।

उपयोगी लिंक


Microsoft अनुसंधान Kinect SDK

Kinect SDK



SDK



, Kinect SDK




C++ Kinect SDK ( ) , — .

Source: https://habr.com/ru/post/In123588/


All Articles