(轉載請註明出處)
使用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代那樣設置平滑參數,也說明精度的提高。
爲了模擬原作的抖動,只能自己手動模擬了。
範例下載地址:點擊這裏