Kinect学习(1)——DepthBasics-D2D

参考: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;
        }
    }
}

暂时先看这些,大致了解过程和原理,有需要再有针对性地详细研究。:)

你可能感兴趣的:(Kinect学习(1)——DepthBasics-D2D)