博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
kinect for windows - DepthBasics-D2D详解
阅读量:5750 次
发布时间:2019-06-18

本文共 13504 字,大约阅读时间需要 45 分钟。

引自:http://blog.csdn.net/itcastcpp/article/details/20282667

Depth在kinect中经常被翻译为深度图,指的是图像到摄像头的距离,这些距离数据能让机器知道物理距离有多远。kinect通过两个红外摄像头来实现这个功能的。在这个例子里,就实现了深度图的提取和现实功能。

下面我们来研究下这个例子的代码,让我们对kinect for windows的开发包有个粗浅的认识。

代码结构:

主要的代码是DepthBasic.cpp,这个代码实现了深度图的读取

另外一个主要的代码文件时ImageRenderer,这个代码实现的是,将获取的深度图,展示在窗口上。

 

main函数:

int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow){    CDepthBasics application;    application.Run(hInstance, nCmdShow);}

  从main函数看,main函数只调用了两个函数,一个是CDepthBasics的构造函数,另外一个是CDepthBasics的Run函数。构造函数只是初始化,我们略过,继续看Run函数。

CDepthBasics::Run函数,解释都在代码中,大家可以看注释

int CDepthBasics::Run(HINSTANCE hInstance, int nCmdShow){	MSG       msg = {0};	WNDCLASS  wc;	// Dialog custom window class win32创建窗口前的准备工作,构造窗口类结构	ZeroMemory(&wc, sizeof(wc));	wc.style         = CS_HREDRAW | CS_VREDRAW;	wc.cbWndExtra    = DLGWINDOWEXTRA;	wc.hInstance     = hInstance;	wc.hCursor       = LoadCursorW(NULL, IDC_ARROW);	wc.hIcon         = LoadIconW(hInstance, MAKEINTRESOURCE(IDI_APP));	wc.lpfnWndProc   = DefDlgProcW;	wc.lpszClassName = L"DepthBasicsAppDlgWndClass";	// 注册窗口类	if (!RegisterClassW(&wc))	{		return 0;	}	// Create main application window  用该窗口类创建对话框	HWND hWndApp = CreateDialogParamW(		hInstance,		MAKEINTRESOURCE(IDD_APP),		NULL,		(DLGPROC)CDepthBasics::MessageRouter, 		reinterpret_cast
(this)); // Show window 显示对话框 ShowWindow(hWndApp, nCmdShow); // 这个用来检测kinect消息的event const int eventCount = 1; HANDLE hEvents[eventCount]; // Main message loop windwos消息循环,在这个消息循环里,如果没有kinect,那就是简单的处理窗口消息即可,但是.... while (WM_QUIT != msg.message) { // 为什么在这里赋值,相当于每次循环都赋值?因为这个句柄随着消息处理会变化 hEvents[0] = m_hNextDepthFrameEvent; // 检查kinect事件,第一个参数1表示等待一个句柄,第二个参数是消息数组,第三个参数指示是不是要等数组里的所有消息,参数是false // 第四个参数是等待多久,INFINITE表示永远,第五个参数呢,因为第四个参数说没有kinect消息这个函数就一直阻塞这里,那么它肯定可能影响正常的windows消息处理 // 所以第五个参数表示说有些情况下也要打断这个等待,QS_ALLINPUT就表示在有windows消息时,该函数也不再阻塞的继续往下执行 DWORD dwEvent = MsgWaitForMultipleObjects(eventCount, hEvents, FALSE, INFINITE, QS_ALLINPUT); // Check if this is an event we're waiting on and not a timeout or message 返回WAIT_OBJECT_0表示kinect有消息来,否则表示没消息 if (WAIT_OBJECT_0 == dwEvent) { // 处理kinect的消息 Update(); } // 处理windows消息 if (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) { // If a dialog message will be taken care of by the dialog proc if ((hWndApp != NULL) && IsDialogMessageW(hWndApp, &msg)) { continue; } TranslateMessage(&msg); DispatchMessageW(&msg); } } return static_cast
(msg.wParam);}

  从Run函数可以看出,对kinect的处理主要在Update函数中,在看Update函数之前,我们要考虑一点,就是对象的初始化,在构造函数中,只是赋值为NULL,像m_hNextDepthFrameEvent这样的对象,它是啥时候被初始化的呢?这个就需要读者对windows窗口机制要有一些了解了,在Run的时候,调用CreateDialogParamW函数时,系统会给对话框发送窗口初始化消息WM_INITDIALOG消息,那么这些句柄的初始化,都是在窗口初始化的时候做的,所以在看Update函数之前,我们先看窗口初始化的处理:

LRESULT CALLBACK CDepthBasics::DlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam){	switch (message)	{	case WM_INITDIALOG:		{			// Bind application window handle			m_hWnd = hWnd;			// Init Direct2D 初始化DirectX			D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &m_pD2DFactory);			// Create and initialize a new Direct2D image renderer (take a look at ImageRenderer.h)			// We'll use this to draw the data we receive from the Kinect to the screen  创建图片展示对象			m_pDrawDepth = new ImageRenderer();			// 初始化展示对象,这里用到了窗口,用到了DirectX对象,以及宽度高度参数			HRESULT hr = m_pDrawDepth->Initialize(GetDlgItem(m_hWnd, IDC_VIDEOVIEW), m_pD2DFactory, cDepthWidth, cDepthHeight, cDepthWidth * sizeof(long));			if (FAILED(hr))			{				SetStatusMessage(L"Failed to initialize the Direct2D draw device.");			}			// Look for a connected Kinect, and create it if found,连接kinect设备			CreateFirstConnected();		}		break;

  在DlgProc的消息处理函数中我们看到了WM_INITDIALOG的处理,在这里初始化了DirectX和ImageRenderer对象,最后调用了CreateFirstConnected函数去查找和初始化Kinect对象。

 

下面是初始化Kinect设备代码

HRESULT CDepthBasics::CreateFirstConnected(){	INuiSensor * pNuiSensor;	HRESULT hr;	int iSensorCount = 0;	hr = NuiGetSensorCount(&iSensorCount); // 获取连接的kinect数量	if (FAILED(hr))	{		return hr;	}	// Look at each Kinect sensor  对每个kinect进行初始化	for (int i = 0; i < iSensorCount; ++i)	{		// Create the sensor so we can check status, if we can't create it, move on to the next		// 获取kinect对象		hr = NuiCreateSensorByIndex(i, &pNuiSensor);		if (FAILED(hr))		{			continue;		}		// Get the status of the sensor, and if connected, then we can initialize it		// 查看kinect状态,有的设备没连接电源,也许有的设备有其他异常		hr = pNuiSensor->NuiStatus();		if (S_OK == hr)  // 如果有一台正常的,那我们这个程序的初始化就算完毕了,因为这个例子只用一个kinect而已		{			m_pNuiSensor = pNuiSensor;			break;		}		// This sensor wasn't OK, so release it since we're not using it 如果是不正常的设备,那么Release掉,免得内存泄露		pNuiSensor->Release();	}	// 如果m_pNuiSensor不为空,那表明找到某一个正常的kinect设备了	if (NULL != m_pNuiSensor)	{		// 初始化kinect,用NUI_INITIALIZE_FLAG_USES_DEPTH表示要使用深度图		hr = m_pNuiSensor->NuiInitialize(NUI_INITIALIZE_FLAG_USES_DEPTH); 		if (SUCCEEDED(hr))		{			// 创建这个Event,这个Event是kinect和应用程序通信的Event,当kinect有消息时,kinect SDK会通过SetEvent来通知应用程序			// 应用程序则通过WaitObject来等待这个Event,完成通信			m_hNextDepthFrameEvent = CreateEvent(NULL, TRUE, FALSE, NULL);			// Open a depth image stream to receive depth frames			// 打开深度图流,用来接收图像			hr = m_pNuiSensor->NuiImageStreamOpen(				NUI_IMAGE_TYPE_DEPTH,           // 表示要打开深度图流				NUI_IMAGE_RESOLUTION_640x480,   // 深度图大小				0,								// 帧设置,0表示无设置				2,								// 缓存多少帧,最大为4				m_hNextDepthFrameEvent,			// 用来通信的Event句柄				&m_pDepthStreamHandle);			// 用来读取数据的流句柄,要从这里读取深度图数据		}	}	if (NULL == m_pNuiSensor || FAILED(hr))	{		SetStatusMessage(L"No ready Kinect found!");		return E_FAIL;	}	return hr;}

  

通过上篇文章,我们了解了在视频图像从kinect开发包传输到应用程序之前的一系列初始化工作,那么这篇文章主要来叙述,如何将一帧图像数据获取到,并显示出来的。

更新窗口是在Run函数消息处理中,当KinectSDK触发了m_hNextDepthFrameEvent之后,上层收到这个Event就调用Update函数去更新窗口了。关键代码如下图,Run函数的全部代码也可以从上一篇博文中找到:

 

Update函数:

void CDepthBasics::Update(){	if (NULL == m_pNuiSensor)  // 如果pNuiSensor为空,那说明设备还没有初始化	{		return;	}	// 继续判断m_hNextDepthFrameEvent是否有消息,其实这个判断这里可以去掉,但是考虑鲁棒性,还是留着吧	if ( WAIT_OBJECT_0 == WaitForSingleObject(m_hNextDepthFrameEvent, 0) )	{		ProcessDepth();  // 处理	}}

  update函数做了一些错误处理,真正的更新窗口的代码在ProcessDepth中,接下来看ProcessDepth函数

void CDepthBasics::ProcessDepth(){	HRESULT hr;	NUI_IMAGE_FRAME imageFrame;	// 通过kinect对象,从m_pDepthStreamHandle中获取图像数据,还记得m_pDepthStreamHandle么,是在初始化kinect设备时创建的深度图流	// 在这里调用这个代码的意义是:将一帧深度图,保存在imageFrame中	hr = m_pNuiSensor->NuiImageStreamGetNextFrame(m_pDepthStreamHandle, 0, &imageFrame);	if (FAILED(hr))	{		return;	}	BOOL nearMode;	INuiFrameTexture* pTexture;	// Get the depth image pixel texture,通过imageFrame把数据转化成纹理	hr = m_pNuiSensor->NuiImageFrameGetDepthImagePixelFrameTexture(		m_pDepthStreamHandle, &imageFrame, &nearMode, &pTexture);	if (FAILED(hr))	{		goto ReleaseFrame;	}	NUI_LOCKED_RECT LockedRect;	// Lock the frame data so the Kinect knows not to modify it while we're reading it,锁定数据	pTexture->LockRect(0, &LockedRect, NULL, 0);	// Make sure we've received valid data	if (LockedRect.Pitch != 0)	{		// Get the min and max reliable depth for the current frame		int minDepth = (nearMode ? NUI_IMAGE_DEPTH_MINIMUM_NEAR_MODE : NUI_IMAGE_DEPTH_MINIMUM) >> NUI_IMAGE_PLAYER_INDEX_SHIFT;		int maxDepth = (nearMode ? NUI_IMAGE_DEPTH_MAXIMUM_NEAR_MODE : NUI_IMAGE_DEPTH_MAXIMUM) >> NUI_IMAGE_PLAYER_INDEX_SHIFT;		// 将m_depthRGBX的首地址保存在rgbrun,方便赋值		BYTE * rgbrun = m_depthRGBX;		const NUI_DEPTH_IMAGE_PIXEL * pBufferRun = reinterpret_cast
(LockedRect.pBits); // end pixel is start + width*height - 1 const NUI_DEPTH_IMAGE_PIXEL * pBufferEnd = pBufferRun + (cDepthWidth * cDepthHeight); // 对m_depthRGBX也就是rgbrun赋值 while ( pBufferRun < pBufferEnd ) { // discard the portion of the depth that contains only the player index USHORT depth = pBufferRun->depth; // To convert to a byte, we're discarding the most-significant // rather than least-significant bits. // We're preserving detail, although the intensity will "wrap." // Values outside the reliable depth range are mapped to 0 (black). // Note: Using conditionals in this loop could degrade performance. // Consider using a lookup table instead when writing production code. BYTE intensity = static_cast
(depth >= minDepth && depth <= maxDepth ? depth % 256 : 0); // Write out blue byte *(rgbrun++) = intensity; // Write out green byte *(rgbrun++) = intensity; // Write out red byte *(rgbrun++) = intensity; // We're outputting BGR, the last byte in the 32 bits is unused so skip it // If we were outputting BGRA, we would write alpha here. ++rgbrun; // Increment our index into the Kinect's depth buffer ++pBufferRun; } // Draw the data with Direct2D 最后将m_depthRGBX保存的图片,显示在窗口上 m_pDrawDepth->Draw(m_depthRGBX, cDepthWidth * cDepthHeight * cBytesPerPixel); } // We're done with the texture so unlock it,解锁和释放纹理 pTexture->UnlockRect(0); pTexture->Release();ReleaseFrame: // Release the frame 释放帧 m_pNuiSensor->NuiImageStreamReleaseFrame(m_pDepthStreamHandle, &imageFrame);}

  从以上代码可以看到处理Depth数据的流程,获取一个深度图帧,然后经过转换成RGB格式,最后通过ImageRenderer::Draw函数将其画出来。但是ImageRenderer的功能和Kinect关系不大,不是我们学习的重点,因此我们可以先忽略它。那么深度图的获取详细过程就到这里,在下一篇文章中,我们来总结一下这两篇文章所提到的一些Kinect SDK中的函数,并详细了解熟悉这些函数的参数,更进一步的学习Kinect SDK。

这篇文章我们将总结一下,之前两篇文章中提到的Kinect SDK的函数接。

函数接口:

NuiGetSensorCount: 获取连接的Kinect设备个数

原型:_Check_return_ HRESULT NUIAPI NuiGetSensorCount( _In_ int * pCount );

解释:KinectSDK支持多个Kinect设备连接,这个函数能获取到Kinect设备个数。

返回值:返回S_OK是成功,其他失败,其中E_POINTER表示pCount是空指针错误。

 

NuiCreateSensorByIndex:通过索引创建INuiSensor对象

原型:_Check_return_ HRESULT NUIAPI NuiCreateSensorByIndex( _In_ int index, _Out_ INuiSensor ** ppNuiSensor );

解释:通过指定的下标,获取对应的KinectSensor对象

返回值:返回S_OK是成功,其他失败,错误码有:E_INVALIDARG(index是负数),E_NUI_BADINDEX(index超出范围),E_POINTER(ppNuiSensor是空指针)

 

NuiStatus:获取Kinect设备状态,它是INuiSensor的成员函数

原型:virtual HRESULT STDMETHODCALLTYPE NuiStatus( void) = 0;

解释:当获取到INuiSensor对象时,应该调用这个函数查看Kinect设备状态,如果状态不对,也是不能使用的。

返回值:返回S_OK是正常,其他失败,错误码如下:

 

S_NUI_INITIALIZING The device is connected, but still initializing.设备初始化中
E_NUI_NOTCONNECTED The device is not connected.设备未连接
E_NUI_NOTGENUINE The device is not a valid Kinect.不是一个正常的Kinect
E_NUI_NOTSUPPORTED The device is an unsupported model.模式不支持
E_NUI_INSUFFICIENTBANDWIDTH The device is connected to a hub without the necessary bandwidth requirements.插到usb hub中产生的问题
E_NUI_NOTPOWERED The device is connected, but unpowered.没插电
E_NUI_NOTREADY There was some other unspecified error.未知错误

 

NuiInitialize初始化Kinect设备

原型:virtual HRESULT STDMETHODCALLTYPE NuiInitialize( 

            /* [in] */ DWORD dwFlags) = 0;

参数:指定初始化模式

 

Constant Description
NUI_INITIALIZE_DEFAULT_HARDWARE_THREAD This flag was deprecated in version 1.5; it is no longer used.
NUI_INITIALIZE_FLAG_USES_AUDIO Initialize the sensor to provide audio data.
NUI_INITIALIZE_FLAG_USES_COLOR Initialize the sensor to provide color data.
NUI_INITIALIZE_FLAG_USES_DEPTH Initialize the sensor to provide depth data.
NUI_INITIALIZE_FLAG_USES_DEPTH_AND_PLAYER_INDEX Initialize the sensor to provide depth data with a player index.
NUI_INITIALIZE_FLAG_USES_SKELETON Initialize the sensor to provide skeleton data.

 

解释:一个设备只有第一次调用这个函数时候会成功,其他应用程序再调用这个函数会失败,也就是说一个设备只能给一个程序用。

返回值:S_OK成功,其他失败

 

NuiImageStreamOpen:打开Kinect流

原型: virtual HRESULT STDMETHODCALLTYPE NuiImageStreamOpen( 

            /* [in] */ NUI_IMAGE_TYPE eImageType,                        // 类型
            /* [in] */ NUI_IMAGE_RESOLUTION eResolution,        // 分辨率
            /* [in] */ DWORD dwImageFrameFlags,                         // 帧事件选项
            /* [in] */ DWORD dwFrameLimit,                                      //  kinect缓存的帧数量
            /* [in] */ HANDLE hNextFrameEvent,                                // 手动reset的event,当下一帧数据到来时,kinect会reset它
            /* [out] */ HANDLE *phStreamHandle) = 0;                  //  数据流句柄

返回值:S_OK表示成功,其他失败

 

Error Description
E_FAIL An unspecified error occurred.
E_INVALIDARG The value of the dwFlags parameter is NULL.
E_NUI_DEVICE_NOT_READY Kinect has not been initialized.
E_OUTOFMEMORY The allocation failed.
E_POINTER The hNextFrameEvent parameter is an invalid handle.

 

NuiImageStreamGetNextFrame:取一帧数据

原型:virtual HRESULT STDMETHODCALLTYPE NuiImageStreamGetNextFrame( 

            /* [in] */ HANDLE hStream, // 数据流
            /* [in] */ DWORD dwMillisecondsToWait,           // 等待时间
            /* [retval][out] */ NUI_IMAGE_FRAME *pImageFrame) = 0;  // 保存数据的指针

返回值:S_OK成功,其他失败

 

Error Description
S_FALSE The waiting timeout expired before a frame was available and hStream was opened using the  flag.
E_INVALIDARG The value of the hStream parameter is NULL.
E_NUI_FRAME_NO_DATA The waiting timeout expired before a frame was available.
E_POINTER The value of the pImageFrame parameter is NULL.

 

NuiImageFrameGetDepthImagePixelFrameTexture:将深度图转化成纹理

原型:virtual HRESULT STDMETHODCALLTYPE NuiImageFrameGetDepthImagePixelFrameTexture( 

            /* [in] */ HANDLE hStream,                                        // 数据流

            /* [in] */ NUI_IMAGE_FRAME *pImageFrame,       // 帧数据
            /* [out] */ BOOL *pNearMode,                                    // 近模式,如果kinect离物体比较近,一般就会选择近模式
            /* [out] */ INuiFrameTexture **ppFrameTexture) = 0;   // 输出参数,转换后的纹理

返回值:S_OK成功,其他失败

 

LockRect:锁住矩形区域数据,让Kinect不要修改那些数据

原型:virtual HRESULT STDMETHODCALLTYPE LockRect(

                                             UINT Level,    // 必须为0

                                             NUI_LOCKED_RECT* pLockedRect,  // 传出参数,记录矩形

                                             RECT* pRect,   // 该参数暂时不用

                                              DWORD flags) = 0;  // 该参数暂时不用

返回值:S_OK成功,其他失败

 

Error Description
E_INVALIDARG The Level parameter is not zero.
E_OUTOFMEMORY The texture buffer could not be allocated.
E_POINTER The pLockedRect parameter is NULL or the texture has zero height.

 

UnlockRect:解锁

原型:HRESULT UnlockRect(UINT level); // Level参数必须为0

返回值:S_OK成功,其他失败,可能的错误码为E_INVALIDARG(level参数不为0)

Error Description
S_FALSE The waiting timeout expired before a frame was available and hStream was opened using the  flag.
E_INVALIDARG The value of the hStream parameter is NULL.
E_NUI_FRAME_NO_DATA The waiting timeout expired before a frame was available.
E_POINTER The value of the pImageFrame parameter is NULL.
 
 
你可能感兴趣的文章
Java集合(二) Map 架构
查看>>
linux 死机分析
查看>>
BOM
查看>>
LeetCode:Nim Game - 尼姆博弈
查看>>
Alpha冲刺&总结报告(12/12)(麻瓜制造者)
查看>>
iOS:CAEmitterLayer粒子效果
查看>>
iOS: Block的循环引用
查看>>
mysql实战02 | 日志系统:一条SQL更新语句是如何执行的?
查看>>
Xamarin.Android 引导页
查看>>
LINUX系统、磁盘与进程的相关命令
查看>>
测试九 赛后感受
查看>>
ECC椭圆曲线详解(有具体实例)
查看>>
关于WechatApp学习总结
查看>>
Linux常见命令(二)
查看>>
纯数学教程 Page 325 例LXVIII (9)
查看>>
document.write()的用法和清空的原因
查看>>
【EXLUCAS模板】【拓展卢卡斯详解】【组合数高级篇】LuoGu P4720
查看>>
PyCharm切换解释器
查看>>
一些基本的灰度变换函数
查看>>
java中的@Override是否需要
查看>>