参考:DepthBasics-D2D详解之一 和 DepthBasics-D2D详解之二 和 DepthBasics-D2D详解之三
这是一个对Kinect深度获取程序的解析,Kinect 2.0的程序和它差不多。
DepthBasics-D2D工程就两个源代码:DepthBasics.cpp和ImageRenderer.cpp。其中,DepthBasics.cpp里的第一个wWinMain函数就是主函数。wWinMain函数主体是一个CDepthBasics类对象的创建和它的Run函数:
CDepthBasics application;
application.Run(hInstance, nShowCmd);
CDepthBasics类被创建时执行它的同名构造函数进行初始化,略。
转到Run函数实体,前半部分在创建和显示窗口,后半部分是一个对Update函数的while循环。注意,创建窗口的CreateDialogParamW承担了初始化参数的作用。所以,Run函数就是一个不停更新(获取和显示)深度数据的过程。
// Main message loop
while (WM_QUIT != msg.message)
{
Update();
while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE))
{
// If a dialog message will be taken care of by the dialog proc
if (hWndApp && IsDialogMessageW(hWndApp, &msg))
{
continue;
}
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
}
下面看Update函数,已经写了部分注释,比较好理解:
void CDepthBasics::Update()
{
// 如果m_pDepthFrameReader==NULL,说明没初始化好sensor。
// CDepthBasics初始化m_pDepthFrameReader为NULL,CreateDialogParamW的初始化包括了对sensor的初始化,具体来说是在InitializeDefaultSensor内。
if (!m_pDepthFrameReader)
{
return;
}
IDepthFrame* pDepthFrame = NULL;
// pDepthFrame获取当前帧
HRESULT hr = m_pDepthFrameReader->AcquireLatestFrame(&pDepthFrame);
if (SUCCEEDED(hr))
{
INT64 nTime = 0;
IFrameDescription* pFrameDescription = NULL;
int nWidth = 0;
int nHeight = 0;
USHORT nDepthMinReliableDistance = 0;
USHORT nDepthMaxDistance = 0;
UINT nBufferSize = 0;
UINT16 *pBuffer = NULL;
hr = pDepthFrame->get_RelativeTime(&nTime); // 当前时间
// 获取帧的宽和高 nWidth=512,nHeight=424
if (SUCCEEDED(hr))
{
hr = pDepthFrame->get_FrameDescription(&pFrameDescription);
}
if (SUCCEEDED(hr))
{
hr = pFrameDescription->get_Width(&nWidth);
}
if (SUCCEEDED(hr))
{
hr = pFrameDescription->get_Height(&nHeight);
}
// 获取深度的最小可靠距离,结果nDepthMinReliableDistance=500,应该就是0.5m
if (SUCCEEDED(hr))
{
hr = pDepthFrame->get_DepthMinReliableDistance(&nDepthMinReliableDistance);
}
// 获取深度的最大距离
if (SUCCEEDED(hr))
{
// In order to see the full range of depth (including the less reliable far field depth)
// we are setting nDepthMaxDistance to the extreme potential depth threshold
// 翻译:为了看到全范围的深度(包括较不可靠的远场深度),我们将nDepthMaxDistance设置为极端潜在深度阈值
// USHRT_MAX=0xffff
nDepthMaxDistance = USHRT_MAX;
// Note: If you wish to filter by reliable depth distance, uncomment the following line.
// 翻译:如果您想进行可靠的深度过滤,取消下行的注释。
// 如果用下行函数获取最大可靠深度的话,结果nDepthMaxDistance=4500,也就是4.5m
//// hr = pDepthFrame->get_DepthMaxReliableDistance(&nDepthMaxDistance);
}
// 获取深度帧数据的指针。
if (SUCCEEDED(hr))
{
hr = pDepthFrame->AccessUnderlyingBuffer(&nBufferSize, &pBuffer);
}
// 处理深度数据
if (SUCCEEDED(hr))
{
ProcessDepth(nTime, pBuffer, nWidth, nHeight, nDepthMinReliableDistance, nDepthMaxDistance);
}
SafeRelease(pFrameDescription);
}
SafeRelease(pDepthFrame);
}
显然最重要的部分在ProcessDepth内,下面是ProcessDepth的代码:
void CDepthBasics::ProcessDepth(INT64 nTime, const UINT16* pBuffer, int nWidth, int nHeight, USHORT nMinDepth, USHORT nMaxDepth)
{
if (m_hWnd)
{
if (!m_nStartTime)
{
m_nStartTime = nTime; // 初次进入的时候把刚才用get_RelativeTime获取的nTime当作开始时间m_nStartTime,之后不再改变m_nStartTime
}
// 以下在计算和显示帧率,时间
double fps = 0.0;
LARGE_INTEGER qpcNow = {0};
if (m_fFreq)
{
if (QueryPerformanceCounter(&qpcNow))
{
if (m_nLastCounter)
{
m_nFramesSinceUpdate++;
fps = m_fFreq * m_nFramesSinceUpdate / double(qpcNow.QuadPart - m_nLastCounter);
}
}
}
WCHAR szStatusMessage[64];
StringCchPrintf(szStatusMessage, _countof(szStatusMessage), L" FPS = %0.2f Time = %I64d", fps, (nTime - m_nStartTime));
if (SetStatusMessage(szStatusMessage, 1000, false))
{
m_nLastCounter = qpcNow.QuadPart;
m_nFramesSinceUpdate = 0;
}
}
// Make sure we've received valid data 确定接收到了有效数据
// m_pDepthRGBX是在最开始的构造函数CDepthBasics中初始化的RGBX(RGBQUAD)存储空间,大小为512x424,每个像素内按B、G、R、reserved排列,都是8位Byte型,因此一个像素占32位(4 Byte)
// pBuffer是存储深度数据的指针,指向第一个深度数据
// (nWidth == cDepthWidth) && (nHeight == cDepthHeight) 是检测读取的数据尺寸对不对
if (m_pDepthRGBX && pBuffer && (nWidth == cDepthWidth) && (nHeight == cDepthHeight))
{
RGBQUAD* pRGBX = m_pDepthRGBX;
// end pixel is start + width*height - 1
// pBufferEnd是末深度数据地址+1
const UINT16* pBufferEnd = pBuffer + (nWidth * nHeight);
while (pBuffer < pBufferEnd) //从第一个深度数据到最后一个深度数据开始遍历
{
USHORT depth = *pBuffer; // 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.
// 由于深度数据是UINT16型,即16位无符号整型,但像素每通道只有1 Byte(8位),因此要舍去8位。
// 本程序舍去高8位,也就是保留深度细节,最大显示255深度,重复更大的距离。depth % 256等价于depth>>8,即右移8位,可以通过改变移动的位数调整精度和表示的深度范围。
// 可靠深度范围之外的深度会映射成0(黑色)。
// 条件判断语句会影响性能,建议工业级代码使用查表法。
BYTE intensity = static_cast((depth >= nMinDepth) && (depth <= nMaxDepth) ? (depth % 256) : 0);
// R、G、B通道都赋给处理后的深度值,R、G、B像素值相同时显示的就是灰度图
pRGBX->rgbRed = intensity;
pRGBX->rgbGreen = intensity;
pRGBX->rgbBlue = intensity;
++pRGBX;
++pBuffer;
}
// Draw the data with Direct2D 绘制、显示深度图
m_pDrawDepth->Draw(reinterpret_cast(m_pDepthRGBX), cDepthWidth * cDepthHeight * sizeof(RGBQUAD));
// 存储深度图,保存为bmp文件
if (m_bSaveScreenshot)
{
WCHAR szScreenshotPath[MAX_PATH];
// Retrieve the path to My Photos
GetScreenshotFileName(szScreenshotPath, _countof(szScreenshotPath));
// Write out the bitmap to disk
HRESULT hr = SaveBitmapToFile(reinterpret_cast(m_pDepthRGBX), nWidth, nHeight, sizeof(RGBQUAD) * 8, szScreenshotPath);
WCHAR szStatusMessage[64 + MAX_PATH];
if (SUCCEEDED(hr))
{
// Set the status bar to show where the screenshot was saved
StringCchPrintf(szStatusMessage, _countof(szStatusMessage), L"Screenshot saved to %s", szScreenshotPath);
}
else
{
StringCchPrintf(szStatusMessage, _countof(szStatusMessage), L"Failed to write screenshot to %s", szScreenshotPath);
}
SetStatusMessage(szStatusMessage, 5000, true);
// toggle off so we don't save a screenshot again next frame
m_bSaveScreenshot = false;
}
}
}
暂时先看这些,大致了解过程和原理,有需要再有针对性地详细研究。:)