前言
这篇文章主要是介绍多个手部的分割,是在前面的博文: 的基础上稍加改进的。因为识别有的一个应用场合就是手势语言识别,而手势一般都需要人的2只手相配合完成,因此很有必要对人体的多个手部来进行分割。
实验说明
其实本文中使用的还是OpenNI自带的一些算法实现的,因为OpenNI中自己本身就对每个手部有一个UserID的标志,所以我们每当检测到一只手时就可以把手的位置连同他的ID一起存下来,后面进行手势分割时按照检测到的不同手势分别进行分割即可。其程序流程图如下所示:
下面是本次实验特别需要注意的一些细节。
OpenNI知识点总结:
一般情况下OpenNI的回调函数中都会有一个参数pCookie,该参数就可以解决前面的博文
中提到的一个当时没有完美解决的问题:即回调函数与类的静态函数,类的静态变量这3者之间使用相互矛盾的问题。那个时候因为在回调函数中需要使用静态成员变量,所以类中普通的非静态成员变量是不能够使用的,否则程序会编译时会报错误。但是如果我们把这些普通变量在类中定义成了静态变量后,这些静态变量就属于类本身了,并不属于类的对象。因此该变量在类的其它成员函数中是不能够被使用的。这样就产生了矛盾,当时的解决方法是将这些变量不放在类中,而放在类外称为整个工程的全局变量。虽然理论上可以解决问题,不过一个跟类有关的变量竟然不能够放在类的内部,听起来就像是个大笑话!这样的封装明显不合理。
幸运的是,现在因为回调函数传进来时有了pCookie这个变量,这样我们在回调函数中可以间接使用类的非静态成员变量了,使用这些变量既不需要定义为static类型,且也可以在类的成员函数中来进行初始化。具体方法是将某个节点(比如说手部,人体,姿势等节点)的注册函数RegisterGestureCallbacks()中第3个参数设置为this指针,而不是null指针。同时在具体的回调函数中,首先把pCookie指针强制转换成COpenNI这个类的指针,然后用转换过来的指针调用需要用到的类的成员变量。
C/C++知识点总结:
pair和map的区别:map是一个容器,容器中的第一个元素表示的是键值key,其它元素分别表示以后用该容器存储数据时的数据类型。因此map中的每一条记录的key值是不能重复的。当map定义的时候只有2个集合的时候,里面的每一条记录可以用pair来存储。因此可以简单的理解一个pair对应的是一条具体的记录,而一个map是一个存放pair的容器,并且map声明了容器的属性。
当在进行pair数据类型的定义时,如果其元素中的一个已经确定,另一个还不知道,则在定义的同时可以直接传入确定的那个元素,另一个用它的数据类型后面接一个空括号即可。
在使用vector时,必须是已存在的元素才能用下标操作符进行索引。可以使用at和[]获取指定位置的数据或者给指定位置的数据赋值。
Qt知识点总结:
在QtCreator的使用中,有时候会出现两个尖括号在一起的情况,这时候没有语法错误,但是QtCreator这个编辑环境会在你的代码下出现个红色的波纹,让人看起来非常不舒服。例如:
解决方法非常简单,即把两个尖括号中间不要紧挨着,用一个空格号隔开一下即可,这时候红色的波纹警告线就消失了。
实验结果
本工程可以对多个手部进行分割,分割效果取决于OpenNI中的手部跟踪效果。其效果截图如下:
实验代码(附录有工程code下载链接)
copenni.cpp:
#ifndef COPENNI_CLASS#define COPENNI_CLASS#include #include #include
main.cpp:
#include #include "opencv2/highgui/highgui.hpp"#include "opencv2/imgproc/imgproc.hpp"#include #include "copenni.cpp"#include #define DEPTH_SCALE_FACTOR 255./4096.#define ROI_HAND_WIDTH 140#define ROI_HAND_HEIGHT 140#define MEDIAN_BLUR_K 5int XRES = 640;int YRES = 480;#define DEPTH_SEGMENT_THRESH 5using namespace cv;using namespace xn;using namespace std;int main (int argc, char **argv){ COpenNI openni; vector color_array;//采用默认的10种颜色 { color_array.push_back(Scalar(255, 0, 0)); color_array.push_back(Scalar(0, 255, 0)); color_array.push_back(Scalar(0, 0, 255)); color_array.push_back(Scalar(255, 0, 255)); color_array.push_back(Scalar(255, 255, 0)); color_array.push_back(Scalar(0, 255, 255)); color_array.push_back(Scalar(128, 255, 0)); color_array.push_back(Scalar(0, 128, 255)); color_array.push_back(Scalar(255, 0, 128)); color_array.push_back(Scalar(255, 128, 255)); } vector hand_depth(10, 0); vector hands_roi(10, Rect(320, 240, ROI_HAND_WIDTH, ROI_HAND_HEIGHT)); if(!openni.Initial()) return 1; namedWindow("color image", CV_WINDOW_AUTOSIZE); namedWindow("depth image", CV_WINDOW_AUTOSIZE); namedWindow("hand_segment", CV_WINDOW_AUTOSIZE);//显示分割出来的手的区域 if(!openni.Start()) return 1; while(1) { if(!openni.UpdateData()) { return 1; } /*获取并显示色彩图像*/ Mat color_image_src(openni.image_metadata.YRes(), openni.image_metadata.XRes(), CV_8UC3, (char *)openni.image_metadata.Data()); Mat color_image; cvtColor(color_image_src, color_image, CV_RGB2BGR); for(auto itUser = openni.hand_points.cbegin(); itUser != openni.hand_points.cend(); ++itUser) { circle(color_image, Point(itUser->second.X, itUser->second.Y), 5, color_array.at(itUser->first % color_array.size()), 3, 8); /*设置不同手部的深度*/ hand_depth.at(itUser->first) = itUser->second.Z* DEPTH_SCALE_FACTOR; /*设置不同手部的不同感兴趣区域*/ hands_roi.at(itUser->first) = Rect(itUser->second.X - ROI_HAND_WIDTH/2, itUser->second.Y - ROI_HAND_HEIGHT/2, ROI_HAND_WIDTH, ROI_HAND_HEIGHT); hands_roi.at(itUser->first).x = itUser->second.X - ROI_HAND_WIDTH/2; hands_roi.at(itUser->first).y = itUser->second.Y - ROI_HAND_HEIGHT/2; hands_roi.at(itUser->first).width = ROI_HAND_WIDTH; hands_roi.at(itUser->first).height = ROI_HAND_HEIGHT; if(hands_roi.at(itUser->first).x <= 0) hands_roi.at(itUser->first).x = 0; if(hands_roi.at(itUser->first).x > XRES) hands_roi.at(itUser->first).x = XRES; if(hands_roi.at(itUser->first).y <= 0) hands_roi.at(itUser->first).y = 0; if(hands_roi.at(itUser->first).y > YRES) hands_roi.at(itUser->first).y = YRES; } imshow("color image", color_image); /*获取并显示深度图像*/ Mat depth_image_src(openni.depth_metadata.YRes(), openni.depth_metadata.XRes(), CV_16UC1, (char *)openni.depth_metadata.Data());//因为kinect获取到的深度图像实际上是无符号的16位数据 Mat depth_image; depth_image_src.convertTo(depth_image, CV_8U, DEPTH_SCALE_FACTOR); imshow("depth image", depth_image); //取出手的mask部分 //不管原图像时多少通道的,mask矩阵声明为单通道就ok Mat hand_segment_mask(depth_image.size(), CV_8UC1, Scalar::all(0)); for(auto itUser = openni.hand_points.cbegin(); itUser != openni.hand_points.cend(); ++itUser) for(int i = hands_roi.at(itUser->first).x; i < std::min(hands_roi.at(itUser->first).x+hands_roi.at(itUser->first).width, XRES); i++) for(int j = hands_roi.at(itUser->first).y; j < std::min(hands_roi.at(itUser->first).y+hands_roi.at(itUser->first).height, YRES); j++) { hand_segment_mask.at (j, i) = ((hand_depth.at(itUser->first)-DEPTH_SEGMENT_THRESH) < depth_image.at (j, i)) & ((hand_depth.at(itUser->first)+DEPTH_SEGMENT_THRESH) > depth_image.at (j,i)); } medianBlur(hand_segment_mask, hand_segment_mask, MEDIAN_BLUR_K); Mat hand_segment(color_image.size(), CV_8UC3); color_image.copyTo(hand_segment, hand_segment_mask); imshow("hand_segment", hand_segment); waitKey(30); }}
实验总结:
本次实验基本上都是基于OpenNI的一些算法的,其分割也是简单的用深度信息进行,没有什么特别的算法,且分割过程中没有引入色彩信息。后续的工作是从将色彩信息和深度信息结合起来做分割,即达到手部分割邻域自适应选择。
附录:。