前言
手势识别非常重要的一个特点是要体验要好,即需要以用户为核心。而手势的定位一般在手势识别过程的前面,在上一篇博文 中已经介绍过怎样获取手势区域,且取得了不错的效果,但是那个手势部位的提取有一个大的缺点,即需要人站立起来,当站立起来后才能够分隔出手。而手势在人之间的交流时,并不一定要处于站立状态,所以这不是一个好的HCI。因此本文介绍的手势部位的提取并不需要人处于站立状态,同样取得了不错的效果。
实验说明
其实,本实验实现的过程非常简单。首先通过手部的跟踪来获取手所在的坐标,手部跟踪可以参考本人前面的博文:。当定位到手所在的坐标后,因为该坐标是3D的,因此在该坐标领域的3维空间领域内提取出手的部位即可,整个过程的大概流程图如下:
OpenCV知识点总结:
调用Mat::copyTo()函数时,如果需要有mask操作,则不管源图像是多少通道的,其mask矩阵都要定义为单通道,另外可以对一个mask矩阵画一个填充的矩形来达到使mask矩阵中对应ROI的位置的值为设定值,这样就不需要去一一扫描赋值了。
在使用OpenCV的Mat矩阵且需要对该矩阵进行扫描时,一定要注意其取值顺序,比如说列和行的顺序,如果弄反了,则经常会报内存错误。
实验结果
本实验并不要求人的手一定要放在人体的前面,且也不需要人一定是处在比较简单的背景环境中,本实验结果允许人处在复杂的背景环境下,且手可以到处随便移动。当然了,环境差时有时候效果就不太好。
下面是3张实验结果的截图,手势分隔图1:
手势分隔图2:
手势分隔图3:
实验主要部分代码即注释(附录有工程code下载链接):
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; int hand_depth; Rect roi; roi.x = XRES/2; roi.y = YRES/2; roi.width = ROI_HAND_WIDTH; roi.height = 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); circle(color_image, Point(hand_point.X, hand_point.Y), 5, Scalar(255, 0, 0), 3, 8); 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); /*下面的代码是提取手的轮廓部分*/ hand_depth = hand_point.Z * DEPTH_SCALE_FACTOR; roi.x = hand_point.X - ROI_HAND_WIDTH/2; roi.y = hand_point.Y - ROI_HAND_HEIGHT/2; if(roi.x <= 0) roi.x = 0; if(roi.x >= XRES) roi.x = XRES; if(roi.y <=0 ) roi.y = 0; if(roi.y >= YRES) roi.y = YRES; //取出手的mask部分 //不管原图像时多少通道的,mask矩阵声明为单通道就ok Mat hand_segment_mask(depth_image.size(), CV_8UC1, Scalar::all(0)); for(int i = roi.x; i < std::min(roi.x+roi.width, XRES); i++) for(int j = roi.y; j < std::min(roi.y+roi.height, YRES); j++) { hand_segment_mask.at (j, i) = ((hand_depth-DEPTH_SEGMENT_THRESH) < depth_image.at (j, i)) & ((hand_depth+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(20); }}
copenni,cpp:
#ifndef COPENNI_CLASS#define COPENNI_CLASS#include #include #include
实验总结:
本次实验简单的利用OpenNI的手部跟踪功能提实时分隔出了人体手所在的部位。但是该分隔效果并不是特别好,以后可以改进手利用色彩信息来分隔出手的区域,或者计算出自适应手部位的区域。另外,本程序只是暂时分隔出一个手,以后可以扩展到分隔出多个手的部位.
参考资料:
附录:。