首页 > 代码库 > Kinect v2程序设计(C++) Body 篇

Kinect v2程序设计(C++) Body 篇

Kinect SDK v2预览版的主要功能的使用介绍,基本上完成了。这次,是关于取得Body(人体姿势)方法的说明。

上一节,是使用Kinect SDK v2预览版从Kinect v2预览版取得BodyIndex(人体区域)的方法。

这一节介绍从Kinect取得Body(人体姿势)的方法。

Body
到目前为止,Kinect能取得Depth(通过传感器的距离信息)和BodyIndex(人体区域)。并且基于这些数据可以取得人体姿势

Kinect的人体姿势,是向学习了基于庞大数量的姿势信息的识别器里,输入人体区域的信息来推定的(注:因为男女老少高矮胖瘦体形各不相同所以必须基于神经网络的数据库才能准确识别人体)。详细还请参考Microsoft Research发表的论文。

这个论文在IEEE CVPR 2011(计算机视觉及模式认识领域的首位会议)发表获奖Best Paper。
Microsoft Research“Real-Time Human Pose Recognition in Parts from a Single Depth Image”

背景技术说不定很复杂,不过开发者通过Kinect SDK可以简单地取得和使用人体姿势。

人体的姿势数据,可以得到头,手,脚等3维的位置,基于这些可以实现姿势的识别。

这个人体区域,在Kinect SDK v1被称为「Skeleton」,不过,在Kinect SDK v2预览版里更名为「Body」。

这一节,介绍取得Body的方法。

示例程序
使用Kinect SDK v2预览版取得BodyColor图像叠加显示为「●(圆点)」的示例程序展示。还有基于Body数据Hand State(手的状态)也做了显示。第2节有介绍取得数据的阶段摘录解说,这个示例程序的全部内容在下面的github里公开。

https://github.com/UnaNancyOwen/Kinect2Sample


图1 Kinect SDK v2预览版的数据取得流程(重发)

「Sensor」
取得「Sensor」
  1. // Sensor
  2. IKinectSensor* pSensor; ……1
  3. HRESULT hResult = S_OK;
  4. hResult = GetDefaultKinectSensor( &pSensor ); ……2
  5. if( FAILED( hResult ) ){
  6. std::cerr << "Error : GetDefaultKinectSensor" << std::endl;
  7. return -1;
  8. }
  9. hResult = pSensor->Open(); ……3
  10. if( FAILED( hResult ) ){
  11. std::cerr << "Error : IKinectSensor::Open()" << std::endl;
  12. return -1;
  13. }
列表1.1 相当于图1「Source」的部分
1 Kinect v2预览版的Sensor接口。
2 取得默认的Sensor。
3 打开Sensor。

「Source」
从「Sensor」取得「Source」。
  1. // Source
  2. IBodyFrameSource* pBodySource; ……1
  3. hResult = pSensor->get_BodyFrameSource( &pBodySource ); ……2
  4. if( FAILED( hResult ) ){
  5. std::cerr << "Error : IKinectSensor::get_BodyFrameSource()" << std::endl;
  6. return -1;
  7. }
列表1.2 相当于图1「Source」的部分
1 Body Frame的Source接口。
2 从Sensor取得Source。

这里只是关于取得Body的源代码解说,不过,为了案例程序的显示,也同时取得了Color。

「Reader」
「Source」从打开「Reader」。
  1. // Reader
  2. IBodyFrameReader* pBodyReader; ……1
  3. hResult = pBodySource->OpenReader( &pBodyReader ); ……2
  4. if( FAILED( hResult ) ){
  5. std::cerr << "Error : IBodyFrameSource::OpenReader()" << std::endl;
  6. return -1;
  7. }
列表1.3 相当于图1「Reader」的部分
1 Body  Frame的Reader接口。
2 从Source打开Reader。

「Frame」~「Data」
从「Reader」取得最新的「Frame」(列表1.5)。

在这之前,为了可以和从传感器取得的坐标匹配,取得ICoordinateMapper的接口(列表1.4),由于Color照相机和Depth传感器的位置是分开的因此需要把body数据和Color图像的位置进行匹配。
  1. // Coordinate Mapper
  2. ICoordinateMapper* pCoordinateMapper; ……1
  3. hResult = pSensor->get_CoordinateMapper( &pCoordinateMapper ); ……2
  4. if( FAILED( hResult ) ){
  5. std::cerr << "Error : IKinectSensor::get_CoordinateMapper()" << std::endl;
  6. return -1;
  7. }
列表1.4,坐标匹配接口的取得
1 Frame之间的坐标匹配的接口。
2 从Sensor获取坐标匹配的接口。

接下来的列表1.5,是和连载第2节一样的方法,表示的是Color图形的取得。

  1. int width = 1920;
  2. int height = 1080;
  3. unsigned int bufferSize = width * height * 4 * sizeof( unsigned char );
  4. cv::Mat bufferMat( height width CV_8UC4 );
  5. cv::Mat bodyMat( height / 2 width / 2 CV_8UC4 );
  6. cv::namedWindow( "Body" );
  7. // Color Table
  8. cv::Vec3b color[6];
  9. color[0] = cv::Vec3b( 255 0 0 );
  10. color[1] = cv::Vec3b( 0 255 0 );
  11. color[2] = cv::Vec3b( 0 0 255 );
  12. color[3] = cv::Vec3b( 255 255 0 );
  13. color[4] = cv::Vec3b( 255 0 255 );
  14. color[5] = cv::Vec3b( 0 255 255 );
  15. while( 1 ){
  16. // Color Frame ……1
  17. IColorFrame* pColorFrame = nullptr;
  18. hResult = pColorReader->AcquireLatestFrame( &pColorFrame );
  19. if( SUCCEEDED( hResult ) ){
  20. hResult = pColorFrame->CopyConvertedFrameDataToArray( bufferSize reinterpret_cast<BYTE*>( bufferMat.data ), ColorImageFormat_Bgra );
  21. if( SUCCEEDED( hResult ) ){
  22. cv::resize( bufferMat bodyMat cv::Size(), 0.5 0.5 );
  23. }
  24. }
  25. SafeRelease( pColorFrame );
  26. /* Body部分在列表1.6 */
  27. // Show Window
  28. cv::imshow( "Body" bodyMat );
  29. if( cv::waitKey( 10 ) == VK_ESCAPE ){
  30. break;
  31. }
  32. }
列表1.5,相当于图1「Frame」「Data」的部分(第1部分)
1 为了显示Body数据取得Color图像,详细见第2节。
  
列表1.5中的Body部分的代码,在下面的列表1.6里显示。
  1. // Body Frame
  2. IBodyFrame* pBodyFrame = nullptr; ……1
  3. hResult = pBodyReader->AcquireLatestFrame( &pBodyFrame ); ……2
  4. if( SUCCEEDED( hResult ) ){
  5. IBody* pBody[BODY_COUNT] = { 0 }; ……3
  6. hResult = pBodyFrame->GetAndRefreshBodyData( BODY_COUNT pBody ); ……3
  7. if( SUCCEEDED( hResult ) ){
  8. for( int count = 0; count < BODY_COUNT; count++ ){
  9. BOOLEAN bTracked = false; ……4
  10. hResult = pBody[count]->get_IsTracked( &bTracked ); ……4
  11. if( SUCCEEDED( hResult ) && bTracked ){
  12. Joint joint[JointType::JointType_Count]; ……5
  13. hResult = pBody[count]->GetJoints( JointType::JointType_Count joint ); ……5
  14. if( SUCCEEDED( hResult ) ){
  15. // Left Hand State
  16. HandState leftHandState = HandState::HandState_Unknown; ……6
  17. hResult = pBody[count]->get_HandLeftState( &leftHandState ); ……6
  18. if( SUCCEEDED( hResult ) ){
  19. ColorSpacePoint colorSpacePoint = { 0 }; ……7
  20. hResult = pCoordinateMapper->MapCameraPointToColorSpace( joint[JointType::JointType_HandLeft].Position &colorSpacePoint ); ……7
  21. if( SUCCEEDED( hResult ) ){
  22. int x = static_cast<int>( colorSpacePoint.X );
  23. int y = static_cast<int>( colorSpacePoint.Y );
  24. if( ( x >= 0 ) && ( x < width ) && ( y >= 0 ) && ( y < height ) ){
  25. if( leftHandState == HandState::HandState_Open ){ ……8
  26. cv::circle( bufferMat cv::Point( x y ), 75 cv::Scalar( 0 128 0 ), 5 CV_AA );
  27. }
  28. else if( leftHandState == HandState::HandState_Closed ){ ……8
  29. cv::circle( bufferMat cv::Point( x y ), 75 cv::Scalar( 0 0 128 ), 5 CV_AA );
  30. }
  31. else if( leftHandState == HandState::HandState_Lasso ){ ……8
  32. cv::circle( bufferMat cv::Point( x y ), 75 cv::Scalar( 128 128 0 ), 5 CV_AA );
  33. }
  34. }
  35. }
  36. }
  37. // Right Hand State
  38. /* 和左手一样,获取右手Hand State绘制状态。 */
  39. // Joint ……9
  40. for( int type = 0; type < JointType::JointType_Count; type++ ){
  41. ColorSpacePoint colorSpacePoint = { 0 };
  42. pCoordinateMapper->MapCameraPointToColorSpace( joint[type].Position &colorSpacePoint );
  43. int x = static_cast< int >( colorSpacePoint.X );
  44. int y = static_cast< int >( colorSpacePoint.Y );
  45. if( ( x >= 0 ) && ( x < width ) && ( y >= 0 ) && ( y < height ) ){
  46. cv::circle( bufferMat cv::Point( x y ), 5 static_cast<cv::Scalar>( color[count] ), -1 CV_AA );
  47. }
  48. }
  49. }
  50. }
  51. }
  52. cv::resize( bufferMat bodyMat cv::Size(), 0.5 0.5 );
  53. }
  54. }
  55. SafeRelease( pBodyFrame );
列表1.6,相当于图1「Frame」「Data」的部分(第2部分)
1 Body的Frame接口。
2 从Reader里取得最新的Frame。
3 从Frame取得Body。  
   后面,是从人体取得数据。
4 确认能着追踪到人体。
取得人体Joint(关节)。
6 取得Hand State。
7 为了绘制,把Body座標向Color座標的坐标匹配。
   匹配的坐标是否超出绘制范围(这里Color图像的尺寸是1920×1080)的检查。
   (注:因为两个Camera位置、FOV和分辨率的不同,在匹配时不可能完全一一对应,所以必须检查坐标的有效性)
8 对应状态绘制相应颜色的○(圆型)做Hand State的可视化。
   用各自对应Open(打开:布)Closed(关闭:拳)Lasso(套索:剪)的颜色绘制。如果检查不出状态就不绘制
9 对应人体的Joint参照color table的颜色做绘制。
   和Hand State一样,Body坐标向Color坐标坐标匹配来绘制●(圆点)。

使用Kinect SDK v1从人体区域中检测获取的详细人体姿势最多支持2个人。Kinect SDK v2预览版可以检测获取全部人体区域(6人)的详细人体姿势

另外,Kinect SDK v1能取得的Joint是全身20个Kinect SDK v2预览版是追加了「脖子(=NECK)」「指尖(=HAND_TIP_LEFTHAND_TIP_RIGHT)」「大拇指(=THUMB_LEFTTHUMB_RIGHT)」5个一共25个Joint。

示例程序里,根据Joint位置参考color table的颜色绘制成「●(圆点)」来可视化
 
Kinect SDK v1(Kinect Developer Toolkit/Kinect Interaction)可以取得的Hand State有「Open(打开)」和「Closed(关闭)」的2种类型。

Kinect SDK v2预览版「Open」「Closed」基础上又增加了「Lasso(=套索)」这个状态的取得。「Lasso」能检查出竖起两个手指的状态。想象为「猜拳的(拳头剪刀布)」就好了。这里还有一个链接扩展阅读我之后会翻译。

现在,6个人中可以同时获取其中2个人的Hand State。

示例程序里,可以通过手的Joint位置的状态来着色「○(圆环)」的绘制做可视化。「Open」绿色(=「cv::Scalar(01280)」)「Closed」是红色(=「cv::Scalar(00128)」)「Lasso」用淡蓝色(=「cv::Scalar(1281280)」)表现。

Kinect SDK v1Kinect SDK v2预览版
名称SkeletonBody
人体姿勢可以取得的人数2人6人
Joint(关节)20处25处
Hand State(手的状態)2種類3種類
Hand State可以取得的人数2人2人
表1 Kinect SDK v1和Kinect SDK v2预览版的人体姿势(Skeleton,Body)的比较


图2 Kinect v1和Kinect v2预览版的可以取得的Joint

运行结果
运行这个示例程序,就像图3一样,从v2预览版取得的人体姿势和手的状态被可视化了。

图3 运行结果
Joint用●(圆点)来显示,Hand State用来○(圆环)来显示。


图4 Hand State的识别结果
「Open」是绿色,「Closed」是红色,「Lasso」浅蓝色的○(圆环)来显示。 手的状态可以清晰的识别。

总结
这一节是使用Kinect SDK v2预览版取得Body的示例程序的介绍。现在Kinect SDK v2预览版实现的主要功能基本上都被介绍了。

不过,Kinect SDK v2预览版在RTM版的发布之前预计会有2~3次的更新。近日第1次更新被预定公开给早期提供程序的参与者。一旦SDK v2预览版有公开更新本连载就会追加新的功能介绍。

Kinect v2程序设计(C++) Body 篇