手势识别系列博文2:KNN算法识别手势

  • 前言
  • 原理介绍
  • 代码实现


前言

书山有路勤为径,学海无涯苦做舟
琴某人辛辛苦苦码的报告,当然不能让你们这么容易复制过去(๑• . •๑)

原理介绍

opencv 手部姿态_图像处理


opencv 手部姿态_最小值_02


opencv 手部姿态_opencv_03


opencv 手部姿态_opencv_04


opencv 手部姿态_最小值_05

代码实现

1.程序中有很多冗余的函数
2.要运行此代码还需要提前制作好模板库,否则识别个der啊
3.代码有点乱,不想改了

#include <iostream>
#include <string>
#include <opencv2\opencv.hpp>
#include <stdio.h>
using namespace cv;
using namespace std;


//-----调参窗口的回调函数-------------------------------------//
int minH = 2, maxH = 13;//肤色分割阈值白天建议3 14
int minR = 95, minG = 40, minB = 20, max_min = 15, absR_G= 10;
int minCr = 138, maxCr = 243, minCb = 77, maxCb = 127;
int ecl_x = 113, ecl_y = 156, leng_x = 24, leng_y = 23, ang =43;
int match_number = -1, temp_number = -1;//第几种手势中的第几个模板

//图片编号
int num = 0, flag = 0, hand_num = 0 ;//调试时用来保存图片
Mat frame, frameH, frameHSV, frameYCrCb; //不同颜色空间下的图片
Mat  RIOframe, RIOresult, resultRGB; //二值化得到的图像,识别出的皮肤区域,最终结果,将结果显示在原图
Mat allContRIO, delContRIO,delContframe; //所有轮廓二值图片, 筛选后轮廓二值图片, 筛选后轮廓的RGB图片
vector <Mat> RGBchannels, HSVchannels;     //RGB通道分离,HSV通道分离
vector< vector<Point> > mContoursProc;  //当前图片的轮廓
vector< vector< vector<Point>> > mContoursLib; //模板库轮廓5*6条
vector< vector< Mat > > tempImageLib;  //模板库照片

//-----调参窗口的回调函数-------------------------------------//
//HSV调参滑块的函数
void trackBarMinH(int pos, void* userdata) {}	//分割H通道时的最小值
void trackBarMaxH(int pos, void* userdata) {}	//分割H通道时的最大值
//RGB
void trackBarMinR(int pos, void* userdata) {}	//分割R通道时的最小值
void trackBarMinG(int pos, void* userdata) {}	//分割G通道时的最小值
void trackBarMinB(int pos, void* userdata) {}	//分割B通道时的最小值
void trackBarmax_min(int pos, void* userdata) {}//max(RGB)-min(RGB)
void trackBarabsR_G(int pos, void* userdata) {}	//absR_G
//YCrCb
void trackBarMinCr(int pos, void* userdata) {}	
void trackBarMaxCr(int pos, void* userdata) {}	
void trackBarMinCb(int pos, void* userdata) {}	
void trackBarMaxCb(int pos, void* userdata) {}	
//YCrCb_eclipse
void trackBarecl_x(int pos, void* userdata) {}	
void trackBarecl_y(int pos, void* userdata) {}	
void trackBarleng_x(int pos, void* userdata) {}	
void trackBarleng_y(int pos, void* userdata) {}	
void trackBarang(int pos, void* userdata) {}	

//-----6种肤色识别方法-------------------------------------//
void hand_HSV();
void hand_RGB();
void hand_YCbCr_ellipse();//椭圆模型
void hand_YCbCr();
void hand_YCbCr_Otsu();
void hand_opencv();

//-------手势识别的功能函数----------------------------------//
void loadTemplate();// 载入模板的轮廓
void find_contours(Mat srcImage);//提取二值化图形的边界
void calcute_fft();//计算傅里叶描述子,这里没有用到
void hand_match();//与模板进行匹配
void draw_result();//得到匹配结果

int main()
{
	// 载入模板的轮廓
	loadTemplate();

	VideoCapture capture("handData//hand.mp4");
	while (true)
	{
		//获取图片帧
		capture >> frame;
		//对视频进行降采样
		flag++;
		if(flag %6 != 0)
		{
			//cout<<"sdf ------------"<<endl;
			continue;
		}

		if (true == frame.empty())
		{
			cout << "get no frame" << endl;
			break;
		}
		resize(frame, frame, Size(frame.cols*0.5, frame.rows*0.5));//降采样

		namedWindow("1.原始图片", CV_WINDOW_NORMAL);
		imshow("1.原始图片",frame);

		//--------------------------滤波处理------------------------------------------//
		medianBlur(frameH, frameH, 5);	//中值滤波,用来去除椒盐噪声
		GaussianBlur(frame, frame, Size(7, 7), 1, 1);// 高斯滤波,用来平滑图像
		//namedWindow("2.滤波后的图像", CV_WINDOW_NORMAL);
		//imshow("2.滤波后的图像",frame);
	
		//--------------------------6种肤色检测方法------------------------------------//
	    //hand_HSV();
		//hand_RGB();
		//hand_YCbCr_ellipse();//椭圆模型
		hand_YCbCr();
		//hand_YCbCr_Otsu();
		//hand_opencv();
	    namedWindow("3.肤色分割之后的图片", CV_WINDOW_NORMAL);
	    imshow("3.肤色分割之后的图片", RIOframe);// 显示肤色分割之后的图片


	
		----------------------------------------形态学运算-----------------------------//
		Mat kernel = getStructuringElement(MORPH_RECT, Size(5, 5)); //函数返回指定形状和尺寸的结构元素
		morphologyEx(RIOresult, RIOresult, MORPH_CLOSE, kernel);//函数利用基本的膨胀和腐蚀技术,来执行更加高级形态学变换
	    morphologyEx(RIOresult, RIOresult, MORPH_OPEN, kernel);//函数利用基本的膨胀和腐蚀技术,来执行更加高级形态学变换
	    //namedWindow("4.形态学处理后的图片", CV_WINDOW_NORMAL);
	    //imshow("4.形态学处理后的图片", RIOframe);// 显示肤色分割之后的图片

		--------------------------保存图片,可以用来制作模板----------------------------------------//
		//
		//flag++;
		//if(flag % 20 == 1)
		//{ 
		//    ostringstream oss;
		//    oss<< "hand_data//template Library2//"<< num <<".jpg";
		//    imwrite(oss.str(), result);
		//	num ++;
		//};

		----------------------------------------检测边缘-----------------------------//
		find_contours(RIOresult);

		//----------------------------------------匹配边缘-----------------------------//
		hand_match();
		//----------------------------------------绘制结果-----------------------------//
		draw_result();

		RIOframe.setTo(0);//否则会出现重影
		waitKey(1);
	}
	system("pause");
	return 0;
}


//-----6种肤色识别方法-------------------------------------//
void hand_HSV()
{
	//----------------------------肤色分割调参窗口---------------------//
	//namedWindow("调参窗口", CV_WINDOW_AUTOSIZE);
	//createTrackbar("H_min", "调参窗口", &minH, 20, trackBarMinH);
	//createTrackbar("H_max", "调参窗口", &maxH, 40, trackBarMaxH);

	cvtColor(frame, frameHSV, CV_BGR2HSV);//在opencv中,其默认的颜色制式排列是BGR而非RGB
	split(frameHSV, HSVchannels);//分离后, channels[0]对应H, channels[1]对应S, channels[2]对应
	frameH = HSVchannels[0];
	//namedWindow("2.H通道图片", CV_WINDOW_NORMAL);
	//imshow("2.H通道图片", frameH);//显示H通道图片

	//--------------------------------------滤波平滑-----------------------------------// 
	//medianBlur(frameH, frameH, 7);	// 中值滤波,可以很好的去除椒盐噪声,而且ksize越大效果越好。
	//GaussianBlur(frameH, frameH, Size(5, 5), 1, 1);

	inRange(frameH, Scalar(minH), Scalar(maxH), RIOresult);
	frame.copyTo(RIOframe, RIOresult);
	 namedWindow("分割得到的RIO", CV_WINDOW_NORMAL);
	 imshow("分割得到的RIO",RIOresult);// 显示肤色分割之后的图片
	 namedWindow("提取到的区域RGB", CV_WINDOW_NORMAL);
	 imshow("提取到的区域RGB", RIOframe);// 显示肤色分割之后的图片
};

void hand_RGB()
{
	//----------------------------肤色分割调参窗口---------------------//
	//namedWindow("调参窗口", CV_WINDOW_AUTOSIZE);
	//createTrackbar("R_min", "调参窗口", &minR, 255, trackBarMinR);
	//createTrackbar("G_min", "调参窗口", &minG, 255, trackBarMinG);
	//createTrackbar("B_min", "调参窗口", &minB, 255, trackBarMinB);
	//createTrackbar("max_min","调参窗口",&max_min, 255, trackBarmax_min);
	//createTrackbar("R_G",   "调参窗口", &absR_G, 255, trackBarabsR_G);

	//imshow("4.肤色分割之后的图片", frame);
	Mat tempresult = Mat(frame.rows, frame.cols, CV_8UC3, Scalar(0));
	for (int i = 0; i < frame.rows; i++)
     {
        for (int j = 0; j < frame.cols;j++)
        {
			int r,g,b;
			r = frame.at<cv::Vec3b>(i,j)[2];
			g = frame.at<cv::Vec3b>(i,j)[1];
			b = frame.at<cv::Vec3b>(i,j)[0];

			if( r>minR && g>minG && b>minB && max(max (r,g),b) - min(min (r,g),b)> max_min && abs(r-g)>absR_G && r>g && r>b)
			//if( r>95 && g>40 && b>20 && max(max (r,g),b) - min(min (r,g),b)> 15 && abs(r-g)>15 && r>g && r>b )
			{
				tempresult.at<cv::Vec3b>(i,j) = Vec3b(255, 255, 255);
			}
			else if( r>220 && g>210 &&b>170 && abs(r-g)<=15 && r>b && g<b)
			{
				tempresult.at<cv::Vec3b>(i,j) = Vec3b(255, 255, 255);
			}
		}

	  };

	 RIOresult = tempresult.clone();
	 frame.copyTo(RIOframe, tempresult);
	 //namedWindow("分割得到的RIO", CV_WINDOW_NORMAL);
	 //imshow("分割得到的RIO",RIOresult);// 显示肤色分割之后的图片
	 //namedWindow("提取到的区域RGB", CV_WINDOW_NORMAL);
	 //imshow("提取到的区域RGB", RIOframe);// 显示肤色分割之后的图片

};

//椭圆模型
void hand_YCbCr_ellipse()
{
	//----------------------------肤色分割调参窗口---------------------//
	//namedWindow("调参窗口", CV_WINDOW_AUTOSIZE);
	//createTrackbar("ecl_x", "调参窗口", &ecl_x, 255, trackBarecl_x);
	//createTrackbar("ecl_y", "调参窗口", &ecl_y, 255, trackBarecl_y);
	//createTrackbar("leng_x", "调参窗口", &leng_x, 255, trackBarleng_x);
	//createTrackbar("leng_y", "调参窗口", &leng_y, 255, trackBarleng_y);
	//createTrackbar("ang", "调参窗口", &ang, 360, trackBarang);

	/*椭圆皮肤模型*/
	Mat skinCrCbHist = Mat::zeros(Size(256, 256), CV_8UC1);
    //ellipse(skinCrCbHist, Point(113, 155.6), Size(23.4, 23.2), 43.0, 0.0, 360.0, Scalar(255, 255, 255), -1);
	ellipse(skinCrCbHist, Point(ecl_x, ecl_y), Size(leng_x, leng_y), ang, 0.0, 360.0, Scalar(255, 255, 255), -1);

	cvtColor(frame, frameYCrCb , CV_BGR2YCrCb);
	Mat tempresult = Mat(frame.rows, frame.cols, CV_8UC3, Scalar(0));
	for (int i = 0; i < frame.rows; i++)
     {
        for (int j = 0; j < frame.cols;j++)
        {
			int y, cr, cb;
			y = frameYCrCb.at<cv::Vec3b>(i,j)[0];
			cr = frameYCrCb.at<cv::Vec3b>(i,j)[1];
			cb = frameYCrCb.at<cv::Vec3b>(i,j)[2];
            
			if( skinCrCbHist.at<uchar>(cr,cb) >0 )
			{
			 tempresult.at<cv::Vec3b>(i,j) = Vec3b(255, 255, 255);
			}



			//if( cr>minCr && cr<maxCr && cb>minCb && cb<maxCb )
			//{
			//	tempresult.at<cv::Vec3b>(i,j) = Vec3b(255, 255, 255);
			//}
		}
	  };
	//imshow("tuoyuan",skinCrCbHist);
	 namedWindow("4.肤色分割之后的图片", CV_WINDOW_NORMAL);
	 imshow("4.肤色分割之后的图片",tempresult);// 显示肤色分割之后的图片

	RIOresult = tempresult.clone();

	frame.copyTo(RIOframe, tempresult);
	namedWindow("12.肤色分割之后的图片", CV_WINDOW_NORMAL);
	imshow("12.肤色分割之后的图片", RIOframe);// 显示肤色分割之后的图片
	//imwrite("data\\YCbCr.jpg",RIOframe);

	 waitKey(1);
};

void hand_YCbCr()
{
	//----------------------------肤色分割调参窗口---------------------//
	//namedWindow("调参窗口", CV_WINDOW_AUTOSIZE);
	//createTrackbar("Cr_min", "调参窗口", &minCr, 255, trackBarMinCr);
	//createTrackbar("Cr_max", "调参窗口", &maxCr, 255, trackBarMaxCr);
	//createTrackbar("Cb_min", "调参窗口", &minCb, 255, trackBarMinCb);
	//createTrackbar("Cb_max", "调参窗口", &maxCb, 255, trackBarMaxCb);

	cvtColor(frame, frameYCrCb , CV_BGR2YCrCb);
	Mat tempresult = Mat(frame.rows, frame.cols, CV_8UC3, Scalar(0));
	//for (int i = 0; i < frame.rows; i++)
 //    {
 //       for (int j = 0; j < frame.cols;j++)
 //       {
	//		int y, cr, cb;
	//		y = frameYCrCb.at<cv::Vec3b>(i,j)[0];
	//		cr = frameYCrCb.at<cv::Vec3b>(i,j)[1];
	//		cb = frameYCrCb.at<cv::Vec3b>(i,j)[2];          

	//		if( cr>minCr && cr<maxCr && cb>minCb && cb<maxCb )
	//		{
	//			tempresult.at<cv::Vec3b>(i,j) = Vec3b(255, 255, 255);
	//		}
	//	}
	//  };
		
	 inRange(frameYCrCb, Scalar(0,minCr,minCb), Scalar(255, maxCr, maxCb), RIOresult);
	 //namedWindow("分割得到的RIO", CV_WINDOW_NORMAL);
	 //imshow("分割得到的RIO",RIOresult);// 显示肤色分割之后的图片

	 frame.copyTo(RIOframe, RIOresult);
	 //namedWindow("提取到的区域RGB", CV_WINDOW_NORMAL);
	 //imshow("提取到的区域RGB", RIOframe);// 显示肤色分割之后的图片
};

void hand_YCbCr_Otsu()
{
	cvtColor(frame, frameYCrCb , CV_BGR2YCrCb);
	Mat tempresult = Mat(frame.rows, frame.cols, CV_8UC3, Scalar(0));

	Mat detect;
	vector<Mat> channels;
	split(frameYCrCb, channels);
	RIOresult = channels[1];
	threshold(RIOresult, RIOresult, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);

	frame.copyTo(RIOframe, RIOresult);

	 //namedWindow("分割得到的RIO", CV_WINDOW_NORMAL);
	 //imshow("分割得到的RIO",RIOresult);// 显示肤色分割之后的图片
	 //namedWindow("提取到的区域RGB", CV_WINDOW_NORMAL);
	 //imshow("提取到的区域RGB", RIOframe);// 显示肤色分割之后的图片
};

void hand_opencv()
{
	IplImage *frame1;
    frame1 = &IplImage(frame);  //Mat -> IplImage
	CvAdaptiveSkinDetector filter(1, CvAdaptiveSkinDetector::MORPHING_METHOD_ERODE_DILATE);

	IplImage *maskImg = cvCreateImage(cvSize(frame.cols, frame.rows), IPL_DEPTH_8U, 1);
    IplImage *skinImg = cvCreateImage(cvSize(frame.cols, frame.rows), IPL_DEPTH_8U, 3);
	cvZero(skinImg);
	filter.process(frame1, maskImg);    // process the frame
	cvCopy(frame1, skinImg, maskImg);
    Mat tmp(skinImg);  //IplImage -> Mat
    RIOresult= tmp.clone();
    cvReleaseImage(&skinImg);
    cvReleaseImage(&maskImg);
   
	 frame.copyTo(RIOframe, RIOresult);
	 //namedWindow("分割得到的RIO", CV_WINDOW_NORMAL);
	 //imshow("分割得到的RIO",RIOresult);// 显示肤色分割之后的图片
	 //namedWindow("提取到的区域RGB", CV_WINDOW_NORMAL);
	 //imshow("提取到的区域RGB", RIOframe);// 显示肤色分割之后的图片

	 //waitKey(1);
};


//--------手势识别的功能函数----------------------------------//
//提取二值化图形的边界
void find_contours(Mat srcImage)
{
	Mat imageProc = srcImage.clone();
	Size sz = srcImage.size();//尺寸
	Mat draw = Mat::zeros(sz, CV_8UC3);
	vector< vector<Point> > mContours;//轮廓点集
	vector< Vec4i > mHierarchy;//轮廓之间的索引号
	//findContours只能处理单通的二值化图像
	Mat binframe;
	if(srcImage.channels() == 3)
	{	
		vector <Mat> channel;     
		split(srcImage, channel);//分离通道
		binframe = channel[0].clone();
	}
	else
	{
		binframe = srcImage.clone();
	}

	findContours(binframe, mContours, mHierarchy, RETR_EXTERNAL, CHAIN_APPROX_NONE, Point(0, 0));//只查找最外层轮廓

	mContoursProc.clear();//清空上次图像处理的轮廓

	if (mContours.size() > 0)
	{
		drawContours(draw, mContours, -1, Scalar(0, 0, 255), 2, 8 , mHierarchy);// 绘制所有轮廓()
		allContRIO = draw.clone();
		namedWindow("5.所有轮廓", CV_WINDOW_NORMAL);
		imshow("5.所有轮廓", allContRIO);//显示所有轮廓
	    //imwrite("data//frame6.jpg", allContRIO);

		double contArea = 0;
		double imageArea = sz.width * sz.height;
		const int SIZE = mContours.size(); 
		Rect bound; //Rect矩形类,矩形界限

		for (int i = 0; i < SIZE; i++)
		{
			contArea = contourArea(mContours[i]);
			if (contArea / imageArea < 0.015)// 过滤小面积的轮廓,原函数是0.015
			{
				continue;
			}
			mContoursProc.push_back(mContours[i]);//剩下的轮廓就是基本符合条件的轮廓,保存起来
		}
      
		draw = Scalar::all(0); //将矩阵所有元素赋值为某个值
		drawContours(draw, mContoursProc,0 , Scalar(0, 0, 255), 4, 8);
		delContRIO = draw.clone();
		namedWindow("6.过滤后的轮廓", CV_WINDOW_NORMAL);
		imshow("6.过滤后的轮廓", delContRIO); //显示过滤后的轮廓
		//imwrite("data//frame7.jpg", delContRIO); 

		delContframe = frame.clone();
		drawContours(delContframe, mContoursProc, -1, Scalar(0, 0, 255), 4, 8);
		namedWindow("8.原图的轮廓", CV_WINDOW_NORMAL);
		imshow("8.原图的轮廓", delContframe); //显示过滤后的轮廓
		imwrite("data//frame8.jpg", delContframe); 
		cout<<"lunjkuo:"<<mContoursProc.size()<<endl;
	}
}

//计算轮廓傅里叶描述子
void calcute_fft( )
{
//计算轮廓的傅里叶描述子
	Point p;
	int x, y, s;
	int i = 0,j = 0,u=0;
	s = (int)mContoursProc[0].size();
	Mat src1(Size(s,1),CV_8SC2);
	//float f[9000];//轮廓的实际描述子
	float f[9000];//轮廓的实际描述子
	float fd[32];//归一化后的描述子,并取前15个
	for (u = 0; u < s; u++)
	{
		float sumx=0, sumy=0;
		for (j = 0; j < s; j++)
		{
			p = mContoursProc[0].at(j);
			x = p.x;
			y = p.y;
			sumx += (float)(x*cos(2*CV_PI*u*j/s) + y*sin(2 * CV_PI*u*j / s));
			sumy+= (float)(y*cos(2 * CV_PI*u*j / s) - x*sin(2 * CV_PI*u*j / s));
		}
		src1.at<Vec2b>(0, u)[0] = sumx;
		src1.at<Vec2b>(0, u)[1] = sumy;
		f[u] = sqrt((sumx*sumx)+(sumy*sumy));
	}
	//傅立叶描述字的归一化
	f[0] = 0;
	fd[0] = 0;
	//for (int k = 2; k < 32; k++)
	//{
	//	f[k] = f[k] / f[1];
	//	fd[k - 1] = f[k];
	//	cout << fd[k-1] << endl;
	//}
	//保存数据
	//for (int k = 0; k < 16; k++)
	//{
	//	ostringstream oss;
	//	oss<< "hand_data\\descripers\\" << hand_num<<"_"<<num <<".txt";
	//	FILE *fp = fopen(oss.str().c_str(), "a");
	//	fprintf(fp, "%8f\t", fd[k]);
 //       fclose(fp);
	//}
	//FILE *fp = fopen("1.txt", "a");
	//fprintf(fp, "\n");
	//fclose(fp);
}

//加载模板库中的图片
void loadTemplate()
{	
	//vector< vector<vector< vector<Point>>> > mContoursLib;  //模板库轮廓5*6条
	Mat tempImg;
	//读取5*6个模板
	for (int i = 0; i < 6; i++)
	{
		//vector< vector< Mat > > tempImageLib;  //模板库照片
		vector< Mat > tempImages;               //每种手势的模板集
		vector< vector<Point> > mContoursTemp;  //每种手势的的轮廓模板集
		//vector< vector<vector< vector<Point>>> > mContoursLib;  //模板库轮廓5*6条
		for (int j = 0; j < 5; j++)
		{
			ostringstream oss;
		    oss<< "handData//templateLibrary//"<< i <<"_"<<j<<".jpg";
			tempImg = imread(oss.str(),1);
			if (true == tempImg.empty())
			{
				cout << "Failed to load image: " <<  oss.str() << endl;
				continue;
			}
			tempImages.push_back(tempImg);
			//提取单通道
			Mat sigalframe;
			if(tempImg.channels() == 3)
			{	
				vector <Mat> channel;     
				split(tempImg, channel);//分离通道
				sigalframe = channel[0].clone();
			}
			else
			{
				sigalframe = tempImg.clone();
			}
			//提取轮廓
			vector< vector<Point> > mContours;//轮廓的点集
	        vector< Vec4i > mHierarchy;       //轮廓的点集的索引
		    findContours(sigalframe, mContours, mHierarchy, RETR_EXTERNAL, CHAIN_APPROX_NONE, Point(0, 0));
			//筛选轮廓
			for (int i = 0; i < mContours.size(); i++)
			{
				double contArea = contourArea(mContours[i]);
				double imageArea = tempImg.rows * tempImg.cols;
				if (contArea / imageArea < 0.015)// 过滤小面积的轮廓,原函数是0.015
				{
					continue;
				}
				//imshow("srcimage ", srcImage);
				//由于是模板库中的照片提取轮廓,假定每个模板只有一个轮廓满足要求
				mContoursTemp.push_back(mContours[i]);
				//Size sz = tempImg.size();//尺寸
				//Mat temp = Mat::zeros(sz, CV_8UC3);
				//drawContours(temp, mContours, i, Scalar(255, 255, 255), 4, 8);
				//namedWindow("10.原图的轮廓", CV_WINDOW_NORMAL);
				//imshow("srcimage ", temp);
				//waitKey(1000);
			}
		}
	    mContoursLib.push_back(mContoursTemp);//每种手势每个模板轮廓的集合
		tempImageLib.push_back(tempImages);   //每种手势每个模板图像的集合
	}
	//查看轮廓中点的数量
	//for (int i = 0; i < mContoursLib.size(); i++)
	//{
	//	for (int j= 0; j < mContoursLib[i].size(); j++)
	//	{
	//		cout<< "size: "<< mContoursLib[i][j].size()<<endl;
	//	}
	//	cout<<endl;
	//}
	cout<<"all templates have been loaded!"<<endl;
}

//与模板进行匹配
void hand_match()
{
	if ((mContoursProc.size() == 0) || (mContoursLib.size() == 0))//如果目标轮廓的尺寸=0或模板轮廓的尺寸=0则返回,||是逻辑或运算符
	{
		cout << "There are no contours to match" << endl;
		return;
	}

	//cout << "mContoursTemp size = " << mContoursProc.size() << endl;
	double hu = 1;	//hu = 1.0
	double huTmp = 0.0;	//huTmp = 0.0
	const int SIZE = mContoursProc.size();
	int m = -1;
	int n = -1;
	int l = -1;

	for (int i = 0; i < mContoursProc.size(); i++)	//第i条轮廓
	{
		for (int j = 0; j < 6; j++)      //第j种模板
		{
			for (int k = 0; k < 2; k++)      //第i种模板的第k个模板
			{
				huTmp = matchShapes(mContoursProc[i], mContoursLib[j].at(k), CV_CONTOURS_MATCH_I1, 0);//根据计算比较两张图像Hu不变距的函数,函数返回值代表相似度大小,完全相同的图像返回值是0,返回值最大是1
				if (huTmp < hu)//hu矩越小,匹配度越高
				{
					hu = huTmp;//保存好,是哪个轮廓和哪个模板匹配上了
					m = i;  //第n条轮廓
					n = j;  //第j种模板
					l = k;  //第j种第k个模板
				}
			}
		}
	}

	cout << "match number = " << n <<endl;
	match_number = n ; // 匹配到的数字
	temp_number = l;
}
//绘制结果
void draw_result( )
{
	if (num <0)//如果未识别到任何数字则返回
	{
		return;
	}
	//加载并绘制识别到的模板
	cv::Mat roi = tempImageLib[match_number].at(temp_number).clone();
	resize(roi, roi, Size(frame.cols/6, frame.rows/6));
	roi.copyTo(delContframe(cv::Rect(0, 0, roi.cols, roi.rows)));
	 //在图像上绘制文字
	putText(delContframe , std::to_string(match_number), Point(roi.cols + 10 ,roi.rows ), FONT_HERSHEY_SIMPLEX, 4, Scalar( 255,0, 0), 8);
	namedWindow("手势识别结果", CV_WINDOW_NORMAL);
	imshow("手势识别结果",delContframe);

};