分享

图像识别(13)——手势识别(1)——用matchShapes识别手形数字

 启云_9137 2017-12-23

博主QQ:1356438802

QQ群:473383394——UVC&OpenCV473383394


平台:Win7 64bits + Qt 5.3.0 MinGW 32bit + OpenCV 2.4.10



前面的几篇文章中,已经做到了基本的LED灯光点捕捉和轨迹绘制,接下来我要开始手势识别的研究过程。


其实手势识别包含很多个方面,首先是手的检测(从图片中提取出手),然后根据交互功能的要求,有动态的识别,比如左右挥手,还有静态的手形识别,比如识别手指个数。手势识别能实现的识别精细度和复杂度,决定了手势交互的多样性和适用性。


一般做手形识别,会先做肤色分割,因为人的皮肤颜色(黄种人哈),在HSV颜色空间下,和背景相比有明显的差别,所以利用肤色可以很好的提取手的区域。



在此 我使用形状匹配(matchShapes)来做,手形的对比,识别出手形代表的数字意义。当然这种方法非常依赖于模板的丰富性,有很大的局限性。

但是只要我保持小步快跑,快速迭代,当我越来越深入的时候,程序就越来越成熟。先搞个案例,来激励下自己嘛!


废话少说直接上代码:

/* * 摄像头读取--> * HSV颜色空间转换--> * H通道分离--> * 中值滤波--> * 肤色分割--> * 形态学运算--> * 轮廓检测及过滤--> * 轮廓形状匹配 * */#include #include #include using namespace cv;using namespace std;#define MAXVALUE (80)#define KERNEL_SIZE (7)int minVal = 7, maxVal = 20;int is_using_canny = 0;int canny_threshold = 3;Mat frame; //原始图像帧vector channels; //HSV通道分离Mat frameH; //H通道Mat result; //最终结果Mat resultRGB; //将结果显示在原图int match_number = -1;//模板总数#define TEMPLATE_NUMS (10)vector<> > mContoursTemp; //轮廓模板集vector<> > mContoursProc; //待处理轮廓集//分割H通道时的最小值void trackBarMin(int pos, void* userdata){}//分割H通道时的最大值void trackBarMax(int pos, void* userdata){}//是否使用Canny边缘检测void isUsingCanny(int pos, void* userdata){}//分割H通道时的最大值void cannyThreshold(int pos, void* userdata){}//载入模板的轮廓void init_hand_template(void);// 对肤色分割、滤波去噪、开运算后图像进行轮廓提取并过滤void hand_contours(Mat &srcImage);// 将目标轮廓与模板轮廓进行匹配void hand_template_match(void);// 在图片的左上角标注数字void number_draw(Mat &img, int num);// 将Mat中的每个元素设置为某个数值void setMatInt(Mat & input_image, uchar val);int main(){ // 载入模板的轮廓 init_hand_template();#if 1 namedWindow('original'); namedWindow('TrackBar'); namedWindow('result'); createTrackbar('minVal', 'TrackBar', &minVal, MAXVALUE, trackBarMin); createTrackbar('maxVal', 'TrackBar', &maxVal, MAXVALUE, trackBarMax); createTrackbar('is_using_canny', 'TrackBar', &is_using_canny, 1, isUsingCanny); createTrackbar('canny_threshold', 'TrackBar', &canny_threshold, 120, cannyThreshold); // 打开摄像头 VideoCapture capture(0); if(false == capture.isOpened()) { cout < 'camera="" open="" failed!'="">< endl;="" return="" -1;="" }="" mat="" kernel="getStructuringElement(MORPH_RECT," size(kernel_size,="" kernel_size));="" while(true)="" {="" 获取图片帧="" capture="">> frame; if(true == frame.empty()) { cout < 'get="" no="" frame'="">< endl;="" break;="" }="" 显示原始图片="" imshow('original',="" frame);="" resultrgb="frame.clone();" 转换hsv颜色通道="" cvtcolor(frame,="" frame,="" cv_bgr2hsv);="" hsv通道分离="" split(frame,="" channels);="" frameh="channels[0];" 显示h通道图片//="" imshow('h-channels',="" frameh);="" ------------------------------滤波平滑-----------------------------="" 中值滤波:="" 可以很好的去除椒盐噪声,而且ksize越大效果越好。="" medianblur(frameh,="" frameh,="" 11);="" 肤色分离,="" 二值化——使用opencv自带函数="" inrange(frameh,="" scalar(minval),="" scalar(maxval),="" result);="" ----------------------------形态学运算-----------------------------="" *="" 腐蚀="" morph_erode="" *="" 膨胀="" morph_dilate="" *="" 开运算="" morph_open="" *="" 闭运算="" morph_close="" *="" 形态学梯度="" morph_gradient="" *="" 顶帽="" morph_tophat="" *="" 黑帽="" morph_blackhat="" */="" morphologyex(result,="" result,="" morph_open,="" kernel);="" 显示结果图片="" imshow('before="" contour="" detect',="" result);="" 对肤色分割、滤波去噪、开运算后图像进行轮廓提取并过滤="" hand_contours(result);="" 将目标轮廓与模板轮廓进行匹配="" hand_template_match();="" 显示结果图片//="" imshow('result',="" result);="" 将匹配结果显示到图片的左上角="" number_draw(resultrgb,="" match_number);="" imshow('resultrgb',="" resultrgb);="" char="" key="(char)waitKey(10);" if(27="=" key)="" {="" break;="" }="" }#endif="" return="" 0;}char="" *tmp_names[template_nums]="{'1.bmp','2.bmp','3.bmp','4.bmp','5.bmp','6.bmp','7.bmp','8.bmp','9.bmp','10.bmp'};//载入模板的轮廓void" init_hand_template(void){="" mat="" srcimage;="" mat="" dstimage;=""><> > mContours; vector< vec4i=""> mHierarchy; for(int i = 0; i < template_nums;="" i++)="" {="" 读取文件="" srcimage="imread(tmp_names[i]," imread_grayscale);="" if(true="=" srcimage.empty())="" {="" cout="">< 'failed="" to="" load="" image:="" '="">< tmp_names="">< 'contours="" size=' << mContours.size() << endl; if(mContours.size() > 0) {// drawContours(dstImage, mContours, 0, Scalar(0, 0, 255), 1, 8, mHierarchy);// imshow(tmp_names[i], dstImage); mContoursTemp.insert(mContoursTemp.end(), mContours.begin(), mContours.end()); } } cout << ' mcontourstemp="" size=' << mContoursTemp.size() << endl;}// 对肤色分割、滤波去噪、开运算后图像进行轮廓提取并过滤void hand_contours(Mat &srcImage){ Mat imageProc = srcImage.clone(); Size sz = srcImage.size(); Mat draw = Mat::zeros(sz, CV_8UC3); vector< vector > mContours; vector< Vec4i > mHierarchy;#if 0 // 经过实验验证此处增加Canny边缘检测,对实验结果没有改善 if(1 == is_using_canny) { cout << ' canny_threshold=' << canny_threshold << endl; Canny(imageProc, imageProc, canny_threshold, canny_threshold * 2, 3); }#endif //只查找最外层轮廓 findContours(imageProc, mContours, mHierarchy, RETR_EXTERNAL, CHAIN_APPROX_NONE, Point(0, 0)); mContoursProc.clear(); //清空上次图像处理的轮廓 if(mContours.size() > 0) { // 绘制所有轮廓 drawContours(draw, mContours, -1, Scalar(0, 0, 255), 1, 8, mHierarchy); imshow(' all="" contours',="" draw);="" double="" contarea="0;" double="" imagearea="sz.width" *="" sz.height;="" const="" int="" size="mContours.size();" rect="" bound;="" for(int="" i="0;" i="">< size;="" i++)="" {="" contarea="contourArea(mContours[i]);" 过滤小面积的轮廓="" if(contarea/imagearea="">< 0.015)="" {="" continue;="" }="" 如果轮廓边界与窗口贴近或者相连,则排除="" bound="boundingRect(mContours[i]);" if(bound.x="">< 2="" ||="" bound.y="">< 2="" ||="" (bound.x="" +="" bound.width="" +="" 2)=""> sz.width || (bound.y + bound.height + 2) > sz.height) { continue; } //剩下的轮廓就是基本符合条件的轮廓,保存起来 mContoursProc.push_back(mContours[i]); }// cout < 'mcontoursproc.size=' << mContoursProc.size() << endl; // 绘制过滤后的轮廓// setMatInt(draw, 0); //将矩阵所有元素赋值为某个值 draw = Scalar::all(0); //将矩阵所有元素赋值为某个值 drawContours(draw, mContoursProc, -1, Scalar(0, 0, 255), 1, 8); imshow(' filter="" contours',="" draw);="" }}//="" 将目标轮廓与模板轮廓进行匹配void="" hand_template_match(void){="" if((mcontoursproc.size()="=" 0)="" ||="" (mcontourstemp.size()="=" 0))="" {//="" cout="">< 'there="" are="" no="" contours="" to="" match'="">< endl;="" return;="" }="" double="" hu="1.0," hutmp="0.0;" const="" int="" size="mContoursProc.size();" int="" m="-1," n="-1;" match_number="-1;" for(int="" i="0;" i="">< template_nums;="" i++)="" {="" for(int="" j="0;" j="">< size;="" j++)="" {="" hutmp="matchShapes(mContoursTemp[i]," mcontoursproc[j],="" cv_contours_match_i1,="" 0);="" hu矩越小,匹配度越高="" if(hutmp="">< hu)="" {="" hu="huTmp;" 保存好,是哪个轮廓和哪个模板匹配上了="" m="i;" n="j;" }="" }="" }="" cout="">< '************m=' << (m+1) << ' ;="" n=' << n << ' ;="" hu=' << hu << endl; // 匹配到的数字 match_number = m + 1;}const char *num_char[] = {' 1',="" '2',="" '3',="" '4',="" '5',="" '6',="" '7',="" '8',="" '9',="" '10'};//="" 在图片的左上角标注数字void="" number_draw(mat="" &img,="" int="" num){="" 未识别到任何数字="" if(num="">< 1)="" {="" return;="" }="" string="" text="num_char[num-1];" puttext(img,="" text,="" point(5,="" 100),="" font_hershey_simplex,="" 4,="" scalar(0,="" 0,="" 255),="" 5);//="" int="" baseline="0;//" size="" textsize="getTextSize(text," font_hershey_simplex,="" 2,="" 2,="" &baseline);//="" cout="">< 'textsize.w=' << textSize.width << ' textsize.h=' << textSize.height << endl;}//将Mat中的每个元素设置为某个数值void setMatInt(Mat & input_image, uchar val){ //行数 int rows = input_image.rows; //列数x通道数=每一行的元素个数 int cols = input_image.cols * input_image.channels(); for(int i = 0; i< rows; i++) { uchar *pdata = input_image.ptr(i); for(int j = 0; j < cols; j++) { pdata[j] = val; } }}



值得说明的是,在做肤色分割时,我只做了H通道的过滤,而没有管S通道和V通道,因为我嫌麻烦,之前看了很多别人的案例,H、S、V每个通道要inRange,然后还有什么高光补偿,搞得非常复杂,结果运行起来效果还不如我的代码。我不否认,应该要增加S和V通道,三个通道结合起来,可以将肤色限制在一个更狭窄的区域,可以消除其他类肤色区域的干扰。但是呢,本身肤色分割,并不是一个非常普遍适应的解决方案,鲁棒性不好,所以我感觉在怎么深入研究它,意义不大。


程序运行效果:






















源码下载:

http://download.csdn.net/detail/luoyouren/9792803



    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多