Kinect for Windows SDK v2.0 开发笔记 (五)骨骼帧与笑面男

(转载请注明出处)

使用SDK: Kinect for Windows SDK v2.0 public preview

这次说说这骨骼帧的获取。嗯,Kinect买来就为这个啊。不然其他数据,买其他产品就行了,Kinect的卖点也是这个。

先看看这次支持的骨骼关节:

enum _JointType
    {
        JointType_SpineBase	= 0,
        JointType_SpineMid	= 1,
        JointType_Neck	= 2,
        JointType_Head	= 3,
        JointType_ShoulderLeft	= 4,
        JointType_ElbowLeft	= 5,
        JointType_WristLeft	= 6,
        JointType_HandLeft	= 7,
        JointType_ShoulderRight	= 8,
        JointType_ElbowRight	= 9,
        JointType_WristRight	= 10,
        JointType_HandRight	= 11,
        JointType_HipLeft	= 12,
        JointType_KneeLeft	= 13,
        JointType_AnkleLeft	= 14,
        JointType_FootLeft	= 15,
        JointType_HipRight	= 16,
        JointType_KneeRight	= 17,
        JointType_AnkleRight	= 18,
        JointType_FootRight	= 19,
        JointType_SpineShoulder	= 20,
        JointType_HandTipLeft	= 21,
        JointType_ThumbLeft	= 22,
        JointType_HandTipRight	= 23,
        JointType_ThumbRight	= 24,
        JointType_Count	= ( JointType_ThumbRight + 1 ) 
    } ;

支持这25个关节点,不排除会增加的可能,毕竟近景可以分辨十指。

每个关节的状态用这个结构体描述:

typedef struct _Joint
    {
    JointType JointType;
    CameraSpacePoint Position;
    TrackingState TrackingState;
    } 	Joint;

JointType就是之前的关节编号,Position是Kinect的相机空间座标,是三维的。TrackingState是目前关节的追踪状态,

有: 未追踪(0),位置是推测的(1),位置是追踪的(2)


值得说明的是这次C++的SDK也提供了判断手的状态:

enum _HandState
    {
        HandState_Unknown	= 0,
        HandState_NotTracked	= 1,
        HandState_Open	= 2,
        HandState_Closed	= 3,
        HandState_Lasso	= 4
    } ;

有:未知(0),未追踪(1),摊开(2),握拳(3)以及Lasso(4),Lasso不知道怎么翻译,大概就是处于摊开与握拳之间的状态,

比如:或者甚至这样都能称为Lasso。


使用方法和之前的差不多,说说不同的:

        IBody* ppBodies[BODY_COUNT] = {0};

        if (SUCCEEDED(hr))
        {
            hr = pBodyFrame->GetAndRefreshBodyData(BODY_COUNT, ppBodies);
        }

这样获取6个IBody接口,用完了记得释放,一个循环释放完


每个接口使用类似下面的代码获取数据:

       for (int i = 0; i < nBodyCount; ++i)
       {
           IBody* pBody = ppBodies[i];
           if (pBody)
           {
               BOOLEAN bTracked = false;
               hr = pBody->get_IsTracked(&bTracked);

               if (SUCCEEDED(hr) && bTracked)
               {
                   Joint joints[JointType_Count];
                   HandState leftHandState = HandState_Unknown;
                   HandState rightHandState = HandState_Unknown;

                   pBody->get_HandLeftState(&leftHandState);
                   pBody->get_HandRightState(&rightHandState);

                   hr = pBody->GetJoints(_countof(joints), joints);
                   if (SUCCEEDED(hr))
                   {
                       // XXXXXXXX
                   }
               }
           }
代码很简单,从方法名字就能看出来。这里的可视化方法是用微软提供SDK例子里面的方法,

大概就是相连的关节使用直线连起来之类的,这部分代码相当无聊,有Direct2D基础的同学可以无视,详细请看范例。
效果如下





好了,其实之前的例子SDK提供的例子多少有,这里自然要给个原创的东西(当然创意不是原创的)。

看到标题的同学大概也能猜到了,那就是《攻壳机动队 S.A.C》(Ghost In The Shell: Stand Alone Complex)里面出现的一个人物,这个人物出场使用一个图标挡住了脸:


这里我们就要实现这个效果,算是一个实时打码的软件吧。

这里,我们的那个文字也要像原作一样旋转,为了保证流程,所以我们这次图像API选择的D2D 1.1(能够等在垂直同步)。

D2D 1.1的初始化,说实话,我都不记得,

要用D2D 1.1时,复制过来即可,毕竟不是考试


那么怎么实现那个图标呢,您可是使用图片。但是作为程序猿,使用代码即时生成是一个不错的选择。

Direct2D提供了一个硬件加速的几何体渲染接口,非常方便。旋转的文字渲染需要DirectWrite + Direct2D,学习的范围不在这里,

详细请看范例。


至于这个图标的几何形状怎么表示,当然用目测。。。这是不现实的,我在其他地方找到了一个笑面男的SVG图像,代码如下:

<?xml version="1.0" encoding="utf-8"?>

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="-160 -160 360 320">  
  <path id="f" d="m123,0a123,123 0,0 1-246,0a123,123 0,0 1 246,0"/>  
  <g fill="#057"> 
    <circle r="160"/>  
    <circle r="150" fill="#fff"/>  
    <text font-size="28" font-stretch="condensed" font-family="Impact">
      <animateTransform type="rotate" from="360 0 0" to="0 0 0" dur="10s" attributeName="transform" repeatCount="indefinite"/>
      <textPath xlink:href="#f">I thought what I'd do was, I'd pretend I was one of those deaf-mutes</textPath>
    </text>  
    <circle r="115"/>  
    <circle r="95" fill="#fff"/>  
    <path d="m-8-119h16 l2,5h-20z"/>  
    <circle cx="160" cy="0" r="40"/>  
    <path d="m-95-20v-20h255a40,40 0,0 1 0,80h-55v-20z"/>  
    <path d="m-85 0a85,85 0,0 0 170,0h-20a65,65 0,0 1-130,0z"/>  
    <path d="m-65 20v20h140v-20z"/>  
    <path d="m-115-20v10h25v30h250a20,20 0,0 0 0,-40z" fill="#fff"/>  
    <path d="m-20 10c-17-14-27-14-44 0 6-25 37-25 44 0z"/>  
    <path d="m60 10c-17-14-27-14-44 0 6-25 37-25 44 0z"/> 
  </g> 
</svg>

现在根据这个代码,可以大概写出下面的代码,部分座标经过了本人的微调。

// 创建笑面男相关
HRESULT ImageRenderer::CreateLaughingMan(){
    // 基本半径
    const FLOAT BASE_RADIUS = 135.f;

    HRESULT hr = S_OK;
    IDWriteTextFormat* pImpactFormat = nullptr;
    // 创建Impact文本格式
    hr = m_pDWriteFactory->CreateTextFormat(
        L"Impact",
        nullptr,
        DWRITE_FONT_WEIGHT_NORMAL,
        DWRITE_FONT_STYLE_NORMAL,
        DWRITE_FONT_STRETCH_CONDENSED,
        41.f/96.f*72.f,
        L"",
        &pImpactFormat
        );

    // 创建文本布局
    if (SUCCEEDED(hr)){
        pImpactFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP);
        WCHAR* text = L"I thought what I'd do was, I'd pretend I was one of those deaf-mutes";
        auto length = wcslen(text);
        hr = m_pDWriteFactory->CreateTextLayout(text, length, pImpactFormat, BASE_RADIUS, BASE_RADIUS, &m_pTextLayoutLaughingMan);
    }
    // 创建文本几何路径: 一个圆
    if (SUCCEEDED(hr)){
        D2D1_ELLIPSE ellipse;
        ellipse.point.x = 0.f;
        ellipse.point.y = 0.f;
        ellipse.radiusX = BASE_RADIUS;
        ellipse.radiusY = BASE_RADIUS;
        hr = m_pD2DFactory->CreateEllipseGeometry(&ellipse, &m_pTextAnimationPath);
    }
    // 笑面男路径
    if (SUCCEEDED(hr)){
        hr = m_pD2DFactory->CreatePathGeometry(&m_pLaughingManGeometryBlue);
        // 画线
        ID2D1GeometrySink* pSink = nullptr;
        if (SUCCEEDED(hr)){
            hr = m_pLaughingManGeometryBlue->Open(&pSink);
        }
        if (SUCCEEDED(hr)){
            auto nowPoint = D2D1::Point2F();
            pSink->SetFillMode(D2D1_FILL_MODE_WINDING);
            D2D1_ARC_SEGMENT arc;
            D2D1_BEZIER_SEGMENT bezier;
            arc.rotationAngle = 0.f;
            // <path d="m-8-119h16 l2,5h-20z"/>
            nowPoint.x = -8.f; nowPoint.y = -124.f;
            pSink->BeginFigure(nowPoint, D2D1_FIGURE_BEGIN_FILLED);
            nowPoint.x += 16.f;
            pSink->AddLine(nowPoint);
            nowPoint.x += 2.f; nowPoint.y += 5.f;
            pSink->AddLine(nowPoint);
            nowPoint.x -= 20.f;
            pSink->AddLine(nowPoint);
            pSink->EndFigure(D2D1_FIGURE_END_CLOSED);
            // <path d = "m-95-20v-20h255a40,40 0,0 1 0,80h-55v-20z" / >
            nowPoint.x = -105.f; nowPoint.y = -20.f;
            pSink->BeginFigure(nowPoint, D2D1_FIGURE_BEGIN_FILLED);
            nowPoint.y -= 20.f;
            pSink->AddLine(nowPoint);
            nowPoint.x += 270.f;
            pSink->AddLine(nowPoint);
            nowPoint.y += 80.f;
            arc.size.height = 40.f;
            arc.size.width = 40.f;
            arc.sweepDirection = D2D1_SWEEP_DIRECTION_CLOCKWISE;
            arc.point = nowPoint;
            arc.arcSize = D2D1_ARC_SIZE_SMALL;
            pSink->AddArc(&arc);
            nowPoint.x -= 55.f;
            pSink->AddLine(nowPoint);
            nowPoint.y -= 20.f;
            pSink->AddLine(nowPoint);
            nowPoint.x += 55.f;
            pSink->AddLine(nowPoint);
            nowPoint.y -= 40.f;
            arc.size.height = 20.f;
            arc.size.width = 20.f;
            arc.sweepDirection = D2D1_SWEEP_DIRECTION_COUNTER_CLOCKWISE;
            arc.point = nowPoint;
            pSink->AddArc(&arc);
            pSink->EndFigure(D2D1_FIGURE_END_CLOSED);
            // <path d="m-85 0a85,85 0,0 0 170,0h-20a65,65 0,0 1-130,0z"/>
            nowPoint.x = -85.f; nowPoint.y= 20.f;
            pSink->BeginFigure(nowPoint, D2D1_FIGURE_BEGIN_FILLED);
            nowPoint.x += 170.f;
            arc.size.height = 90.f;
            arc.size.width = 90.f;
            arc.sweepDirection = D2D1_SWEEP_DIRECTION_COUNTER_CLOCKWISE;
            arc.arcSize = D2D1_ARC_SIZE_SMALL;
            arc.point = nowPoint;
            pSink->AddArc(&arc);
            nowPoint.x -= 20.f;
            pSink->AddLine(nowPoint);
            nowPoint.x -= 130.f;
            arc.size.height = 70.f;
            arc.size.width = 70.f;
            arc.sweepDirection = D2D1_SWEEP_DIRECTION_CLOCKWISE;
            arc.point = nowPoint;
            pSink->AddArc(&arc);
            pSink->EndFigure(D2D1_FIGURE_END_CLOSED);
            // <path d="m-65 20v20h130v-20z"/>  
            nowPoint.x = -65.f; nowPoint.y = 20.f;
            pSink->BeginFigure(nowPoint, D2D1_FIGURE_BEGIN_FILLED);
            nowPoint.y += 20.f;
            pSink->AddLine(nowPoint);
            nowPoint.x += 130.f;
            pSink->AddLine(nowPoint);
            nowPoint.y -= 20.f;
            pSink->AddLine(nowPoint);
            pSink->EndFigure(D2D1_FIGURE_END_CLOSED);
            //pSink->BeginFigure(nowPoint, D2D1_FIGURE_BEGIN_FILLED);
            // <path d = "m-20 10c-17-14-27-14-44 0 6-25 37-25 44 0z" / >
            nowPoint.x = -20.f; nowPoint.y = 10.f;
            pSink->BeginFigure(nowPoint, D2D1_FIGURE_BEGIN_FILLED);
            bezier.point1.x = nowPoint.x - 17.f;
            bezier.point1.y = nowPoint.y - 14.f;
            bezier.point2.x = nowPoint.x - 27.f;
            bezier.point2.y = nowPoint.y - 14.f;
            nowPoint.x -= 44.f;
            bezier.point3 = nowPoint;
            pSink->AddBezier(&bezier);
            bezier.point1.x = nowPoint.x + 6.f;
            bezier.point1.y = nowPoint.y - 25.f;
            bezier.point2.x = nowPoint.x + 37.f;
            bezier.point2.y = nowPoint.y - 25.f;
            nowPoint.x += 44.f;
            bezier.point3 = nowPoint;
            pSink->AddBezier(&bezier);
            pSink->EndFigure(D2D1_FIGURE_END_CLOSED);
            // <path d = "m60 10c-17-14-27-14-44 0 6-25 37-25 44 0z" / >
            nowPoint.x = 60.f; nowPoint.y = 10.f;
            pSink->BeginFigure(nowPoint, D2D1_FIGURE_BEGIN_FILLED);
            bezier.point1.x = nowPoint.x - 17.f;
            bezier.point1.y = nowPoint.y - 14.f;
            bezier.point2.x = nowPoint.x - 27.f;
            bezier.point2.y = nowPoint.y - 14.f;
            nowPoint.x -= 44.f;
            bezier.point3 = nowPoint;
            pSink->AddBezier(&bezier);
            bezier.point1.x = nowPoint.x + 6.f;
            bezier.point1.y = nowPoint.y - 25.f;
            bezier.point2.x = nowPoint.x + 37.f;
            bezier.point2.y = nowPoint.y - 25.f;
            nowPoint.x += 44.f;
            bezier.point3 = nowPoint;
            pSink->AddBezier(&bezier);
            pSink->EndFigure(D2D1_FIGURE_END_CLOSED);
            hr = pSink->Close();
        }
        SafeRelease(pSink);
    }
    // 笑面男白色部分
    if (SUCCEEDED(hr)){
        hr = m_pD2DFactory->CreatePathGeometry(&m_pLaughingManGeometryWhite);
        // 画线
        ID2D1GeometrySink* pSink = nullptr;
        if (SUCCEEDED(hr)){
            hr = m_pLaughingManGeometryWhite->Open(&pSink);
        }
        if (SUCCEEDED(hr)){
            auto nowPoint = D2D1::Point2F();
            // <path d = "m-115-20v10h25v30h250a20,20 0,0 0 0,-40z" fill = "#fff" / >
            nowPoint.x = -125.f; nowPoint.y = -20.f;
            pSink->BeginFigure(nowPoint, D2D1_FIGURE_BEGIN_FILLED);
            nowPoint.y += 10.f;
            pSink->AddLine(nowPoint);
            nowPoint.x += 35.f;
            pSink->AddLine(nowPoint);
            nowPoint.y += 30.f;
            pSink->AddLine(nowPoint);
            nowPoint.x += 260.f;
            pSink->AddLine(nowPoint);
            nowPoint.y -= 40.f;
            D2D1_ARC_SEGMENT arc = { nowPoint, D2D1::SizeF(20.f, 20.f), 0.f, D2D1_SWEEP_DIRECTION_COUNTER_CLOCKWISE, D2D1_ARC_SIZE_SMALL };
            pSink->AddArc(&arc);
            pSink->EndFigure(D2D1_FIGURE_END_CLOSED);
            hr = pSink->Close();
        }
    }
    // 笑面蓝
    if (SUCCEEDED(hr)){
        hr = m_pD2DDeviceContext->CreateSolidColorBrush(D2D1::ColorF(0x005577), &m_pLaughingBlueBrush);
    }
    // 笑面白
    if (SUCCEEDED(hr)){
        hr = m_pD2DDeviceContext->CreateSolidColorBrush(D2D1::ColorF(0xFFFFFF), &m_pLaughingWhiteBrush);
    }
    SafeRelease(pImpactFormat);
    return hr;
}

反正很蛋疼




是的,我们这次使用的是D2D 1.1。之前说过,所以这里就使用轮询模式。

这里要使用彩色数据流 + 骨骼数据流,那么是否需要使用复源帧?

使用复源帧是为了保证数据同步,轮询模式下异步的效果很及时,所以这里不使用复源帧。


大致过程如下:

刷新:

    获得彩色数据 -> 复制到位图

    获得骨骼数据 -> 检查并更新头部位置,根据远近设置相对缩放率(实现近大远小,大致即可,不用精确)

渲染:

    渲染彩色帧

    渲染笑面男


Kinect2支持6人骨骼追踪,所以数据要准备6份。这样6人同时出现也能一起打码得意


效果如下:Kinect2获得的骨骼数据默认就已经平滑过了,不需要像1代那样设置平滑参数,也说明精度的提高。

为了模拟原作的抖动,只能自己手动模拟了。



范例下载地址:点击这里

发布了35 篇原创文章 · 获赞 18 · 访问量 21万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章