【翻译】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预览版取得Body和Color图像叠加显示为「●(圆点)」的示例程序展示。还有,基于Body数据,Hand State(手的状态)也做了显示。第2节有介绍取得数据的阶段摘录解说,这个示例程序的全部内容,在下面的github里公开。
图1 Kinect SDK v2预览版的数据取得流程(重发)
「Sensor」
取得「Sensor」
// Sensor IKinectSensor* pSensor; ……1 HRESULT hResult = S_OK; hResult = GetDefaultKinectSensor( &pSensor ); ……2 if( FAILED( hResult ) ){ std::cerr << "Error : GetDefaultKinectSensor" << std::endl; return -1; } hResult = pSensor->Open(); ……3 if( FAILED( hResult ) ){ std::cerr << "Error : IKinectSensor::Open()" << std::endl; return -1; }
列表1.1 相当于图1「Source」的部分
1 Kinect v2预览版的Sensor接口。
2 取得默认的Sensor。
3 打开Sensor。
「Source」
从「Sensor」取得「Source」。
// Source IBodyFrameSource* pBodySource; ……1 hResult = pSensor->get_BodyFrameSource( &pBodySource ); ……2 if( FAILED( hResult ) ){ std::cerr << "Error : IKinectSensor::get_BodyFrameSource()" << std::endl; return -1; }
列表1.2 相当于图1「Source」的部分
1 Body Frame的Source接口。
2 从Sensor取得Source。
这里只是关于取得Body的源代码解说,不过,为了案例程序的显示,也同时取得了Color。
「Reader」
「Source」从打开「Reader」。
// Reader IBodyFrameReader* pBodyReader; ……1 hResult = pBodySource->OpenReader( &pBodyReader ); ……2 if( FAILED( hResult ) ){ std::cerr << "Error : IBodyFrameSource::OpenReader()" << std::endl; return -1; }
列表1.3 相当于图1「Reader」的部分
1 Body Frame的Reader接口。
2 从Source打开Reader。
「Frame」~「Data」
从「Reader」取得最新的「Frame」(列表1.5)。
在这之前,为了可以和从传感器取得的坐标匹配,取得ICoordinateMapper的接口(列表1.4),由于Color照相机和Depth传感器的位置是分开的,因此需要把body数据和Color图像的位置进行匹配。
// Coordinate Mapper ICoordinateMapper* pCoordinateMapper; ……1 hResult = pSensor->get_CoordinateMapper( &pCoordinateMapper ); ……2 if( FAILED( hResult ) ){ std::cerr << "Error : IKinectSensor::get_CoordinateMapper()" << std::endl; return -1; }
列表1.4,坐标匹配接口的取得
1 Frame之间的坐标匹配的接口。
2 从Sensor获取坐标匹配的接口。
接下来的列表1.5,是和连载第2节一样的方法,表示的是Color图形的取得。
int width = 1920; int height = 1080; unsigned int bufferSize = width * height * 4 * sizeof( unsigned char ); cv::Mat bufferMat( height, width, CV_8UC4 ); cv::Mat bodyMat( height / 2, width / 2, CV_8UC4 ); cv::namedWindow( "Body" ); // Color Table cv::Vec3b color[6]; color[0] = cv::Vec3b( 255, 0, 0 ); color[1] = cv::Vec3b( 0, 255, 0 ); color[2] = cv::Vec3b( 0, 0, 255 ); color[3] = cv::Vec3b( 255, 255, 0 ); color[4] = cv::Vec3b( 255, 0, 255 ); color[5] = cv::Vec3b( 0, 255, 255 ); while( 1 ){ // Color Frame ……1 IColorFrame* pColorFrame = nullptr; hResult = pColorReader->AcquireLatestFrame( &pColorFrame ); if( SUCCEEDED( hResult ) ){ hResult = pColorFrame->CopyConvertedFrameDataToArray( bufferSize, reinterpret_cast<BYTE*>( bufferMat.data ), ColorImageFormat_Bgra ); if( SUCCEEDED( hResult ) ){ cv::resize( bufferMat, bodyMat, cv::Size(), 0.5, 0.5 ); } } SafeRelease( pColorFrame ); /* Body部分在列表1.6 */ // Show Window cv::imshow( "Body", bodyMat ); if( cv::waitKey( 10 ) == VK_ESCAPE ){ break; } }
列表1.5,相当于图1「Frame」,「Data」的部分(第1部分)
1 为了显示Body数据取得Color图像,详细见第2节。
列表1.5中的Body部分的代码,在下面的列表1.6里显示。
// Body Frame IBodyFrame* pBodyFrame = nullptr; ……1 hResult = pBodyReader->AcquireLatestFrame( &pBodyFrame ); ……2 if( SUCCEEDED( hResult ) ){ IBody* pBody[BODY_COUNT] = { 0 }; ……3 hResult = pBodyFrame->GetAndRefreshBodyData( BODY_COUNT, pBody ); ……3 if( SUCCEEDED( hResult ) ){ for( int count = 0; count < BODY_COUNT; count++ ){ BOOLEAN bTracked = false; ……4 hResult = pBody[count]->get_IsTracked( &bTracked ); ……4 if( SUCCEEDED( hResult ) && bTracked ){ Joint joint[JointType::JointType_Count]; ……5 hResult = pBody[count]->GetJoints( JointType::JointType_Count, joint ); ……5 if( SUCCEEDED( hResult ) ){ // Left Hand State HandState leftHandState = HandState::HandState_Unknown; ……6 hResult = pBody[count]->get_HandLeftState( &leftHandState ); ……6 if( SUCCEEDED( hResult ) ){ ColorSpacePoint colorSpacePoint = { 0 }; ……7 hResult = pCoordinateMapper->MapCameraPointToColorSpace( joint[JointType::JointType_HandLeft].Position, &colorSpacePoint ); ……7 if( SUCCEEDED( hResult ) ){ int x = static_cast<int>( colorSpacePoint.X ); int y = static_cast<int>( colorSpacePoint.Y ); if( ( x >= 0 ) && ( x < width ) && ( y >= 0 ) && ( y < height ) ){ if( leftHandState == HandState::HandState_Open ){ ……8 cv::circle( bufferMat, cv::Point( x, y ), 75, cv::Scalar( 0, 128, 0 ), 5, CV_AA ); } else if( leftHandState == HandState::HandState_Closed ){ ……8 cv::circle( bufferMat, cv::Point( x, y ), 75, cv::Scalar( 0, 0, 128 ), 5, CV_AA ); } else if( leftHandState == HandState::HandState_Lasso ){ ……8 cv::circle( bufferMat, cv::Point( x, y ), 75, cv::Scalar( 128, 128, 0 ), 5, CV_AA ); } } } } // Right Hand State /* 和左手一样,获取右手Hand State绘制状态。 */ // Joint ……9 for( int type = 0; type < JointType::JointType_Count; type++ ){ ColorSpacePoint colorSpacePoint = { 0 }; pCoordinateMapper->MapCameraPointToColorSpace( joint[type].Position, &colorSpacePoint ); int x = static_cast< int >( colorSpacePoint.X ); int y = static_cast< int >( colorSpacePoint.Y ); if( ( x >= 0 ) && ( x < width ) && ( y >= 0 ) && ( y < height ) ){ cv::circle( bufferMat, cv::Point( x, y ), 5, static_cast<cv::Scalar>( color[count] ), -1, CV_AA ); } } } } } cv::resize( bufferMat, bodyMat, cv::Size(), 0.5, 0.5 ); } } SafeRelease( pBodyFrame );
列表1.6,相当于图1「Frame」,「Data」的部分(第2部分)
1 Body的Frame接口。
2 从Reader里取得最新的Frame。
3 从Frame取得Body。
后面,是从人体取得数据。
4 确认能着追踪到人体。
5 取得人体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_LEFT,HAND_TIP_RIGHT)」,「大拇指(=THUMB_LEFT,THUMB_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(0,128,0)」),「Closed」是红色(=「cv::Scalar(0,0,128)」),「Lasso」用淡蓝色(=「cv::Scalar(128,128,0)」)表现。
Kinect SDK v1 | Kinect SDK v2预览版 | |
---|---|---|
名称 | Skeleton | Body |
人体姿勢可以取得的人数 | 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预览版有公开更新,本连载就会追加新的功能介绍。