主要内容:
- NiTE2手部跟踪流程
- 代码演示
- 总结
一、NiTE2手部跟踪流程
我自己都感觉到天天在重复着相同的代码,但我觉得没什么不好的,对于新东西的学习只有在重复再重复的过程中,才能积累经验,较少犯“低级错误”的几率,所以在开始之前,让我们再熟练熟练NITE 2的手部跟踪流程,主要包括以下几个步骤:
1. 初始化NITE环境: nite::NiTE::initialize();
2. 创建HandTracker手部跟踪器: HandTracker mHandTracker; mHandTracker.create(&mDevice);
3. 设定手势探测(GESTURE_WAVE、GESTURE_CLICK和GESTURE_HAND_RAISE):mHandTracker.startGestureDetection( GESTURE_WAVE );等等;
4. 创建并读取HandTracker Frame信息:HandTrackerFrameRef mHandFrame; mHandTracker.readFrame( &mHandFrame );
5. 整个界面帧信息进行分析,统计得到符合的手势信息:const nite::Array<GestureData>& aGestures = mHandFrame.getGestures();
6. 通过跟踪得到的手势信息,开始对该特定手进行手部跟踪:const Point3f& rPos = rGesture.getCurrentPosition();HandId mHandID; mHandTracker.startHandTracking( rPos, &mHandID );
7. 读取并统计目前被跟踪的手信息:const nite::Array<HandData>& aHands = mHandFrame.getHands();
8. 确定手部是否属于跟踪状态,开始自己的操作:
if( rHand.isTracking() )
{
// 得到手心坐标
const Point3f& rPos = rHand.getPosition();
。。。
}
9. 关闭跟踪器:mHandTracker.destroy();
10. 最后关闭NITE环境:nite::NiTE::shutdown();
二、代码演示
在谈谈NITE 2与OpenCV结合的第一个程序和谈谈NITE 2与OpenCV结合提取指尖坐标中我们都是在深度图像中对获得的手部信息进行处理,但不知道在彩色图像中,手部跟踪获得手心坐标是怎么样的?是否也和深度图像显示一样,能够很好的定位到真正的手心中?为了回答自己的这些问题,模仿谈谈人体骨骼坐标在彩色图像中显示中的方法,将通过NiTE2手部跟踪得到的手心坐标映射到彩色图像和深度图像中,并显示对比。具体解释和代码如下:
#include "stdafx.h" #include <iostream>// OpenCV 头文件 #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <opencv2/imgproc/imgproc.hpp>#include <OpenNI.h> #include <NiTE.h>using namespace std; using namespace openni; using namespace nite;int main( int argc, char **argv ) {// 初始化OpenNI OpenNI::initialize();// 打开Kinect设备 Device mDevice;mDevice.open( ANY_DEVICE );// 创建深度数据流 VideoStream mDepthStream;mDepthStream.create( mDevice, SENSOR_DEPTH );// 设置VideoMode模式 VideoMode mDepthMode;mDepthMode.setResolution( 640, 480 );mDepthMode.setFps( 30 );mDepthMode.setPixelFormat( PIXEL_FORMAT_DEPTH_1_MM );mDepthStream.setVideoMode(mDepthMode);// 同样的设置彩色数据流 VideoStream mColorStream;mColorStream.create( mDevice, SENSOR_COLOR );// 设置VideoMode模式 VideoMode mColorMode;mColorMode.setResolution( 640, 480 );mColorMode.setFps( 30 );mColorMode.setPixelFormat( PIXEL_FORMAT_RGB888 );mColorStream.setVideoMode( mColorMode);// 设置深度图像映射到彩色图像 mDevice.setImageRegistrationMode( IMAGE_REGISTRATION_DEPTH_TO_COLOR );// 为了得到骨骼数据,先初始化NiTE NiTE::initialize();// 创建HandTracker跟踪器 HandTracker mHandTracker;mHandTracker.create(&mDevice);// 设定手势探测(GESTURE_WAVE、GESTURE_CLICK和GESTURE_HAND_RAISE) mHandTracker.startGestureDetection( GESTURE_WAVE );mHandTracker.startGestureDetection( GESTURE_CLICK );//mHandTracker.startGestureDetection( GESTURE_HAND_RAISE ); mHandTracker.setSmoothingFactor(0.1f);// 创建深度图像显示cv::namedWindow("Depth Image", CV_WINDOW_AUTOSIZE);// 创建彩色图像显示cv::namedWindow( "Hand Image", CV_WINDOW_AUTOSIZE );// 环境初始化后,开始获取深度数据流和彩色数据流 mDepthStream.start();mColorStream.start();// 获得最大深度值int iMaxDepth = mDepthStream.getMaxPixelValue();while( true ){// 创建OpenCV::Mat,用于显示彩色数据图像 cv::Mat cImageBGR;// 读取深度数据帧信息流 VideoFrameRef mDepthFrame;mDepthStream.readFrame(&mDepthFrame);// 读取彩色数据帧信息流 VideoFrameRef mColorFrame;mColorStream.readFrame( &mColorFrame );//将深度数据转换成OpenCV格式const cv::Mat mImageDepth( mDepthFrame.getHeight(), mDepthFrame.getWidth(), CV_16UC1, (void*)mDepthFrame.getData());// 为了让深度图像显示的更加明显一些,将CV_16UC1 ==> CV_8U格式 cv::Mat mScaledDepth;mImageDepth.convertTo( mScaledDepth, CV_8U, 255.0 / iMaxDepth );// 将彩色数据流转换为OpenCV格式,记得格式是:CV_8UC3(含R\G\B)const cv::Mat mImageRGB( mColorFrame.getHeight(), mColorFrame.getWidth(),CV_8UC3, (void*)mColorFrame.getData() );// RGB ==> BGR cv::cvtColor( mImageRGB, cImageBGR, CV_RGB2BGR );// 读取帧信息 HandTrackerFrameRef mHandFrame;mHandTracker.readFrame( &mHandFrame );// 整个界面帧信息进行分析,找到符合的手势const nite::Array<GestureData>& aGestures = mHandFrame.getGestures();for( int i = 0; i < aGestures.getSize(); ++ i ){const GestureData& rGesture = aGestures[i];// 得到的手势信息中还包含了当前手势的坐标位置const Point3f& rPos = rGesture.getCurrentPosition();cout << " 手势位置为: (" << rPos.x << ", " << rPos.y << ", " << rPos.z << ")" << endl;// 得到手势识别后,开始手部跟踪 HandId mHandID;mHandTracker.startHandTracking( rPos, &mHandID );cout << "确定手势位置,开始手部跟踪" << endl;}const nite::Array<HandData>& aHands = mHandFrame.getHands();for( int i = 0; i < aHands.getSize(); ++ i ){const HandData& rHand = aHands[i];if( rHand.isNew() )cout << " Start tracking";else if( rHand.isLost() )cout << " Lost";// 确定手部是否属于跟踪状态if( rHand.isTracking() ){// 得到手心坐标const Point3f& rPos = rHand.getPosition();cout << " at " << rPos.x << ", " << rPos.y << ", " << rPos.z;cv::Point2f aPoint;mHandTracker.convertHandCoordinatesToDepth(rPos.x, rPos.y, rPos.z, &aPoint.x, &aPoint.y);// 将手心坐标映射到彩色图像和深度图像中cv::circle( cImageBGR, aPoint, 3, cv::Scalar( 0, 0, 255 ), 4 );cv::circle( mScaledDepth, aPoint, 3, cv::Scalar(0, 0, 255), 4);// 在彩色图像中画出手的轮廓边 cv::Point2f ctlPoint, ctrPoint, cdlPoint, cdrPoint;ctlPoint.x = aPoint.x - 100;ctlPoint.y = aPoint.y - 100;ctrPoint.x = aPoint.x - 100;ctrPoint.y = aPoint.y + 100;cdlPoint.x = aPoint.x + 100;cdlPoint.y = aPoint.y - 100;cdrPoint.x = aPoint.x + 100;cdrPoint.y = aPoint.y + 100;cv::line( cImageBGR, ctlPoint, ctrPoint, cv::Scalar( 255, 0, 0 ), 3 );cv::line( cImageBGR, ctlPoint, cdlPoint, cv::Scalar( 255, 0, 0 ), 3 );cv::line( cImageBGR, cdlPoint, cdrPoint, cv::Scalar( 255, 0, 0 ), 3 );cv::line( cImageBGR, ctrPoint, cdrPoint, cv::Scalar( 255, 0, 0 ), 3 );// 在深度图像中画出手的轮廓边 cv::Point2f mtlPoint, mtrPoint, mdlPoint, mdrPoint;mtlPoint.x = aPoint.x - 100;mtlPoint.y = aPoint.y - 100;mtrPoint.x = aPoint.x - 100;mtrPoint.y = aPoint.y + 100;mdlPoint.x = aPoint.x + 100;mdlPoint.y = aPoint.y - 100;mdrPoint.x = aPoint.x + 100;mdrPoint.y = aPoint.y + 100;cv::line( mScaledDepth, mtlPoint, mtrPoint, cv::Scalar( 255, 0, 0 ), 3 );cv::line( mScaledDepth, mtlPoint, mdlPoint, cv::Scalar( 255, 0, 0 ), 3 );cv::line( mScaledDepth, mdlPoint, mdrPoint, cv::Scalar( 255, 0, 0 ), 3 );cv::line( mScaledDepth, mtrPoint, mdrPoint, cv::Scalar( 255, 0, 0 ), 3 );}}// 显示图像cv::imshow( "Depth Image", mScaledDepth );cv::imshow( "Hand Image", cImageBGR );// 按键“q”退出循环if( cv::waitKey( 1 ) == 'q' )break;}// 先销毁手部跟踪器 mHandTracker.destroy();// 销毁彩色数据流和深度数据流 mColorStream.destroy();mDepthStream.destroy();// 关闭Kinect设备 mDevice.close();// 关闭NITE和OpenNI环境 NiTE::shutdown();OpenNI::shutdown();return 0; }
程序运行结果见下图:
接着画出手部运动轨迹,直接上代码:
#include <array> #include <iostream> #include <map> #include <vector>// OpenCV 头文件 #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <opencv2/imgproc/imgproc.hpp>// NiTE 头文件 #include <OpenNI.h> #include <NiTE.h>using namespace std; using namespace openni; using namespace nite;int main( int argc, char **argv ) {// 初始化OpenNI OpenNI::initialize();// 打开Kinect设备 Device mDevice;mDevice.open( ANY_DEVICE );// 创建深度数据流 VideoStream mDepthStream;mDepthStream.create( mDevice, SENSOR_DEPTH );// 设置VideoMode模式 VideoMode mDepthMode;mDepthMode.setResolution( 640, 480 );mDepthMode.setFps( 30 );mDepthMode.setPixelFormat( PIXEL_FORMAT_DEPTH_1_MM );mDepthStream.setVideoMode(mDepthMode);// 同样的设置彩色数据流 VideoStream mColorStream;mColorStream.create( mDevice, SENSOR_COLOR );// 设置VideoMode模式 VideoMode mColorMode;mColorMode.setResolution( 640, 480 );mColorMode.setFps( 30 );mColorMode.setPixelFormat( PIXEL_FORMAT_RGB888 );mColorStream.setVideoMode( mColorMode);// 设置深度图像映射到彩色图像 mDevice.setImageRegistrationMode( IMAGE_REGISTRATION_DEPTH_TO_COLOR );// 初始化 NiTEif( NiTE::initialize() != nite::STATUS_OK ){cerr << "NiTE initial error" << endl;return -1;}// 创建HandTracker跟踪器 HandTracker mHandTracker;if( mHandTracker.create() != nite::STATUS_OK ){cerr << "Can't create user tracker" << endl;return -1;}// 设定手势探测(GESTURE_WAVE、GESTURE_CLICK和GESTURE_HAND_RAISE) mHandTracker.startGestureDetection( GESTURE_WAVE );mHandTracker.startGestureDetection( GESTURE_CLICK );//mHandTracker.startGestureDetection( GESTURE_HAND_RAISE ); mHandTracker.setSmoothingFactor(0.1f);// 创建深度图像显示cv::namedWindow("Depth Image", CV_WINDOW_AUTOSIZE);// 创建彩色图像显示cv::namedWindow( "Color Image", CV_WINDOW_AUTOSIZE );// 保存点坐标map< HandId,vector<cv::Point2f> > mapHandData;vector<cv::Point2f> vWaveList;vector<cv::Point2f> vClickList;cv::Point2f ptSize( 3, 3 );array<cv::Scalar,8> aHandColor;aHandColor[0] = cv::Scalar( 255, 0, 0 );aHandColor[1] = cv::Scalar( 0, 255, 0 );aHandColor[2] = cv::Scalar( 0, 0, 255 );aHandColor[3] = cv::Scalar( 255, 255, 0 );aHandColor[4] = cv::Scalar( 255, 0, 255 );aHandColor[5] = cv::Scalar( 0, 255, 255 );aHandColor[6] = cv::Scalar( 255, 255, 255 );aHandColor[7] = cv::Scalar( 0, 0, 0 );// 环境初始化后,开始获取深度数据流和彩色数据流 mDepthStream.start();mColorStream.start();// 获得最大深度值int iMaxDepth = mDepthStream.getMaxPixelValue();// startwhile( true ){// 创建OpenCV::Mat,用于显示彩色数据图像 cv::Mat cImageBGR;// 读取彩色数据帧信息流 VideoFrameRef mColorFrame;mColorStream.readFrame( &mColorFrame );// 将彩色数据流转换为OpenCV格式,记得格式是:CV_8UC3(含R\G\B)const cv::Mat mImageRGB( mColorFrame.getHeight(), mColorFrame.getWidth(),CV_8UC3, (void*)mColorFrame.getData() );// RGB ==> BGR cv::cvtColor( mImageRGB, cImageBGR, CV_RGB2BGR );// 获取手Frame HandTrackerFrameRef mHandFrame;if( mHandTracker.readFrame( &mHandFrame ) == nite::STATUS_OK ){openni::VideoFrameRef mDepthFrame = mHandFrame.getDepthFrame();// 将深度数据转换成OpenCV格式const cv::Mat mImageDepth( mDepthFrame.getHeight(), mDepthFrame.getWidth(), CV_16UC1, (void*)mDepthFrame.getData() );// 为了让深度图像显示的更加明显一些,将CV_16UC1 ==> CV_8U格式 cv::Mat mScaledDepth, mImageBGR;mImageDepth.convertTo( mScaledDepth, CV_8U, 255.0 / 10000 );// 将灰度图转换成BGR格式,为了画出点的颜色坐标和轨迹 cv::cvtColor( mScaledDepth, mImageBGR, CV_GRAY2BGR );// 检测手势const nite::Array<GestureData>& aGestures = mHandFrame.getGestures();for( int i = 0; i < aGestures.getSize(); ++ i ){const GestureData& rGesture = aGestures[i];const Point3f& rPos = rGesture.getCurrentPosition();cv::Point2f rPos2D;mHandTracker.convertHandCoordinatesToDepth( rPos.x, rPos.y, rPos.z, &rPos2D.x, &rPos2D.y );// 画点switch( rGesture.getType() ){case GESTURE_WAVE:vWaveList.push_back( rPos2D );break;case GESTURE_CLICK:vClickList.push_back( rPos2D );break;}// 手部跟踪 HandId mHandID;if( mHandTracker.startHandTracking( rPos, &mHandID ) != nite::STATUS_OK )cerr << "Can't track hand" << endl;}// 得到手心坐标const nite::Array<HandData>& aHands = mHandFrame.getHands();for( int i = 0; i < aHands.getSize(); ++ i ){const HandData& rHand = aHands[i];HandId uID = rHand.getId();if( rHand.isNew() ){mapHandData.insert( make_pair( uID, vector<cv::Point2f>() ) );}if( rHand.isTracking() ){// 将手心坐标映射到彩色图像和深度图像中const Point3f& rPos = rHand.getPosition();cv::Point2f rPos2D;mHandTracker.convertHandCoordinatesToDepth( rPos.x, rPos.y, rPos.z, &rPos2D.x, &rPos2D.y );mapHandData[uID].push_back( rPos2D );}if( rHand.isLost() )mapHandData.erase( uID );}// 画点和轨迹for( auto itHand = mapHandData.begin(); itHand != mapHandData.end(); ++ itHand ){const cv::Scalar& rColor = aHandColor[ itHand->first % aHandColor.size() ];const vector<cv::Point2f>& rPoints = itHand->second;for( int i = 1; i < rPoints.size(); ++ i ){cv::line( mImageBGR, rPoints[i-1], rPoints[i], rColor, 2 );cv::line( cImageBGR, rPoints[i-1], rPoints[i], rColor, 2 );}}// 画 click gesture 轨迹for( auto itPt = vClickList.begin(); itPt != vClickList.end(); ++ itPt ){cv::circle( mImageBGR, *itPt, 5, cv::Scalar( 0, 0, 255 ), 2 );cv::circle( cImageBGR, *itPt, 5, cv::Scalar( 0, 0, 255 ), 2 );}// 画 wave gesture 轨迹for( auto itPt = vWaveList.begin(); itPt != vWaveList.end(); ++ itPt ){cv::rectangle( mImageBGR, *itPt - ptSize, *itPt + ptSize, cv::Scalar( 0, 255, 0 ), 2 );cv::rectangle( cImageBGR, *itPt - ptSize, *itPt + ptSize, cv::Scalar( 0, 255, 0 ), 2 );}// 显示imagecv::imshow( "Depth Image", mImageBGR );cv::imshow("Color Image", cImageBGR);mHandFrame.release();}else{cerr << "Can't get new frame" << endl;}// 按键“q”退出循环if( cv::waitKey( 1 ) == 'q' )break;}mHandTracker.destroy();mColorStream.destroy();NiTE::shutdown();OpenNI::shutdown();return 0; }
运行结果:
三、总结
最后说明的是:根据自己的感觉写代码,没做封装、优化、重构,完全是面向过程,而且肯定还存在细节的问题,会在后面进一步优化的。 写的粗糙,欢迎指正批评~~~