用OpenCV的SVM实现简单的手势识别(切水果)[附源码]

基于HOG特征的SVM分类器实现


在机器学习领域,支持向量机SVM(Support Vector Machine)是一个有监督的学习模型,通常用来进行模式识别、分类、以及回归分析。
方向梯度直方图(Histogram of Oriented Gradient, HOG)特征是一种在计算机视觉和图像处理中用来进行物体检测的特征描述子。HOG特征通过计算和统计图像局部区域的梯度方向直方图来构成特征

一、计算HOG

void calculateHog(const Mat& src, vector<float>& descriptors,size windowSize,size blockSize,size cellSize)
{
	//梯度方向数 nbins=9
	HOGDescriptor myHog = HOGDescriptor(src.size(), windowSize, blockSize, cellSize,9);
	myHog.compute(src.clone(), descriptors, Size(1, 1), Size(0, 0));
}

二、SVM

因此容易写得一个基于HOG特征SVM分类器

#ifndef HOGSVM_H
#define HOGSVM_H

#include "cvHeadspace.h"
#include <iostream>
#include <vector>
using namespace cv;
using namespace cv::ml;
class HogSVM
{
public:
    HogSVM(std::string xmlFilePath,Size sWindow,Size sBlock,Size sCell,int enBins=9)
    {
        mySVM = Algorithm::load<SVM>(xmlFilePath);
        HogDesSize[0]=sWindow;
        HogDesSize[1]=sBlock;
        HogDesSize[2]=sCell;
        nbins=enBins;
    }
    int getLabel(const Mat &src)
    {
        std::vector<float> imageDescriptor;
        calculateHog(src, imageDescriptor,HogDesSize[0],HogDesSize[1],HogDesSize[2]);
        Mat testDescriptor = Mat::zeros(1, imageDescriptor.size(), CV_32FC1);
        for (size_t i = 0; i < imageDescriptor.size(); i++)
        {
            testDescriptor.at<float>(0, i) = imageDescriptor[i];
        }
        float  label = pHogSVM->predict(testDescriptor);
        return (int)label;
    }
private:
    void calculateHog(const Mat& src, std::vector<float>& descriptors,size windowSize,size blockSize,size cellSize)
{
	//梯度方向数 nbins默认为9
	HOGDescriptor myHog = HOGDescriptor(src.size(), windowSize, blockSize, cellSize,nbins);
	myHog.compute(src.clone(), descriptors, Size(1, 1), Size(0, 0));
}
	int nbins;
	Size HogDesSize[3];
    Ptr<SVM> pHogSVM;
};


#endif // HOGSVM_H

三、测试

简单尝试了一下训练,然后把训练的xml读入,做了测试(这里表格里的单位应该是ms不是s)

int label = tempSvm.getLabel(rROI);

在这里插入图片描述

四、小demo

运用SVM+Qt+openCV写了小demo测试
在这里插入图片描述

适当调整灵敏度后点击“抢占”就能调用windows.api控制鼠标,开始愉快(智杖)的玩耍了!

在这里插入图片描述

五、因为有人需要,更新了一下

时间比较久,有些东西已经没了,不过代码还在,之前没上传的原因就是感觉代码写得太烂,而且就是个本科作业级别的玩具,不好意思上传,但是有人需要,所以上传修改了一下文章,如下。

该项目主要有三个工程,
一个是vs_test_project用于前期的手势识别的测试。
还有一个是svm_train用于训练svm.xml文件。这两个工程文件全部贴在下面了。
QT的gui工程用于展示,但这个工程我没备份😂主要就是调windows api,同时展示一下中间图片变换的过程。

当时是本科作业,主要参考了这位开源的代码和论文,肤色检测部分的原理可以参考这个老哥的本科毕业论文。我主要在这位老哥的工作上干了两件事情:
①做完肤色分割后,是靠肤色点的数量来区别脸部和手部,效果相当不尽人意,于是我在github上找到了一个现有的去除脸部的xml模型,直接用openCV调用即可
②模板匹配的效果太差,于是将模板匹配替换为了HOG+SVM,提升明显
当时作业的C++水平约等于C with class,勿喷

vs_test_project FaceDetector.h

#pragma once

#include <opencv2/core/core.hpp>

using namespace cv;
using namespace std;

class FaceDetector {
public:
	FaceDetector(void);
	void removeFaces(Mat &input, Mat &output);
}; 

vs_test_project FaceDetector.cpp

#include "FaceDetector.h"
#include <opencv2/core/core.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/calib3d/calib3d.hpp>
#include <opencv2/imgproc/types_c.h>
#include <opencv2/opencv.hpp>



Rect getFaceRect(Mat input);

String faceClassifierFileName = "haarcascade_frontalface_alt.xml";
CascadeClassifier faceCascadeClassifier;

FaceDetector::FaceDetector(void) {
	if (!faceCascadeClassifier.load(faceClassifierFileName))
		throw runtime_error("can't load file " + faceClassifierFileName);
}

void FaceDetector::removeFaces(Mat &input, Mat &output) {
	vector<Rect> faces;
	Mat frameGray;

	cvtColor(input, frameGray, CV_BGR2GRAY);
	equalizeHist(frameGray, frameGray);

	faceCascadeClassifier.detectMultiScale(frameGray, faces, 1.1, 2, 0 | CASCADE_SCALE_IMAGE, Size(100, 100));

	for (size_t i = 0; i < faces.size(); i++) {
		rectangle(
			output,
			Point(faces[i].x, faces[i].y),
			Point(faces[i].x + faces[i].width, faces[i].y + faces[i].height),
			Scalar(0, 0, 0),
			-1
		);
	}
}

Rect getFaceRect(Mat input) {
	vector<Rect> faceRectangles;
	Mat inputGray;

	cvtColor(input, inputGray, CV_BGR2GRAY);
	equalizeHist(inputGray, inputGray);

	faceCascadeClassifier.detectMultiScale(inputGray, faceRectangles, 1.1, 2, 0 | CASCADE_SCALE_IMAGE, Size(120, 120));

	if (faceRectangles.size() > 0)
		return faceRectangles[0];
	else
		return Rect(0, 0, 1, 1);
}

vs_test_project HogSvm.h

#pragma once
#include <opencv2/core/core.hpp>
#include <opencv2/opencv.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/calib3d/calib3d.hpp>
#include <opencv2/imgproc/types_c.h>
#include <opencv2/imgproc/imgproc_c.h>
#include <opencv2/ml/ml.hpp> 
#include <Windows.h>
#include<time.h>
using namespace std;
using namespace cv;
using namespace cv::ml;
class HogSvm
{
public:
	HogSvm(std::string xmlFilePath)
	{
		mySVM = Algorithm::load<SVM>(xmlFilePath);
	}
	int getLabel(const Mat &src)
	{
		vector<float> imageDescriptor;
		coumputeHog(src, imageDescriptor);
		Mat testDescriptor = Mat::zeros(1, imageDescriptor.size(), CV_32FC1);
		for (size_t i = 0; i < imageDescriptor.size(); i++)
		{
			testDescriptor.at<float>(0, i) = imageDescriptor[i];
		}
		float  label = mySVM->predict(testDescriptor);
		return (int)label;
	}
private:
	void coumputeHog(const Mat& src, vector<float>& descriptors)
	{
		HOGDescriptor myHog = HOGDescriptor(src.size(), Size(32, 32), Size(8, 8), Size(8, 8), 9);
		myHog.compute(src.clone(), descriptors, Size(1, 1), Size(0, 0));
	}
	Ptr<SVM> mySVM;
};


vs_test_project SkinSegment.h

#pragma once
#include <opencv2/core/core.hpp>
#include <opencv2/opencv.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/calib3d/calib3d.hpp>
#include <opencv2/imgproc/types_c.h>
#include <opencv2/imgproc/imgproc_c.h>
#include <opencv2/ml/ml.hpp> 
#include <Windows.h>
#include<time.h>
#include <set>
#include <stack>
#include <vector>
#include <algorithm>
using namespace cv;
using namespace std;
//值域
struct ValueRange {
	int lowerBound;
	int upperBound;
	ValueRange() {
		lowerBound = 0; 
		upperBound= 0;
	}
	ValueRange(int pLowerBound, int pUpperBound) :lowerBound(pLowerBound), upperBound(pUpperBound) {}
	int getDistance()
	{
		return upperBound - lowerBound;
	}
	void updateValueRange(int capVal) {
		lowerBound = min(lowerBound, capVal);
		upperBound = max(upperBound, capVal);
	}
};

//像素搜素类
class SearchPixel {
public:
	//种子填充
	static void SeedFillfunc(int grayTrackingValue, const cv::Mat& binaryImg, int& blockNums, ValueRange(*yAxisRealm), ValueRange(*xAxisRealm), vector<pair<int, int> >(*pointSet));
	static Mat WaterShedToBin(const cv::Mat& binaryImg);
};

//线性变换
class LinearConvert {
public:
	static void MirrorConvert(Mat pSrc,Mat &pDst)
	{
		int row = pSrc.rows;
		int col = pSrc.cols;
		pDst = pSrc.clone();
		for (int i = 0; i < col; i++) {
			pSrc.col(col - 1 - i).copyTo(pDst.col(i));
		}
	}

};

//颜色空间筛
class SkinSieve
{
public:
	/*HSV颜色空间H范围筛选法*/
	static Mat HcbCrConverter(Mat& src, Mat graySrc, Mat ycrcbSrc, Mat hsvSrc, bool isReturnMask);
	static Mat CbCrSieveFunc(Mat& src, ValueRange cbRange, ValueRange crRange);
	static Mat HSVSieveFunc(Mat& src, ValueRange hRange, ValueRange sRange, ValueRange vRange, bool isReturnMask);
	static Mat OstuConverter(Mat& src);
	static Mat GrayConverter(Mat& src) {
		Mat binImage;
		cvtColor(src, binImage, CV_BGR2GRAY);
		return binImage;
	}
	static Mat YCrCbConverter(Mat& src) {
		Mat Image;
		cvtColor(src, Image, CV_BGR2YCrCb);
		return Image;
	}
	static Mat HSvConverter(Mat& src) {
		Mat Image;
		cvtColor(src, Image, CV_BGR2HSV);
		return Image;
	}
private:
};

//统计类
class Statistic {
public:
	//像素统计
	static void pixelStatistic(float* ColorRatio, int markedNumber, ValueRange* xAxisRealm, ValueRange* yAxisRealm, Mat pSrc)
	{
		for (int k = 0; k < markedNumber; k++)
		{
			ColorRatio[k] = 1;
			if (((xAxisRealm[k].getDistance() + 1) > 50) && ((xAxisRealm[k].getDistance() + 1) < 300) && ((yAxisRealm[k].getDistance() > 150) && ((yAxisRealm[k].getDistance() + 1) < 450)))
			{
				int fusepoint = 0;
				for (int j = yAxisRealm[k].lowerBound; j < yAxisRealm[k].upperBound; j++)
				{
					uchar* current = pSrc.ptr< uchar>(j);
					for (int i = xAxisRealm[k].lowerBound; i < xAxisRealm[k].upperBound; i++)
					{
						if (current[i] == 255)
							fusepoint++;
					}
				}
				ColorRatio[k] = float(fusepoint) / ((xAxisRealm[k].getDistance() + 1) * (yAxisRealm[k].getDistance() + 1));
			}
		}
	}
	static void pixelStatistic(float* ColorRatio, int markedNumber, int* xMin, int* xMax, int* yMin, int* yMax, Mat pSrc)
	{
		for (int k = 0; k < markedNumber; k++)
		{
			ColorRatio[k] = 1;
			if (((xMax[k] - xMin[k] + 1) > 50) && ((xMax[k] - xMin[k] + 1) < 300) && ((yMax[k] - yMin[k] + 1) > 150) && ((yMax[k] - yMin[k] + 1) < 450))
			{
				int fusepoint = 0;
				for (int j = yMin[k]; j < yMax[k]; j++)
				{
					uchar* current = pSrc.ptr< uchar>(j);
					for (int i = xMin[k]; i < xMax[k]; i++)
					{
						if (current[i] == 255)
							fusepoint++;
					}
				}
				ColorRatio[k] = float(fusepoint) / ((xMax[k] - xMin[k] + 1) * (yMax[k] - yMin[k] + 1));
			}
		}
	}
};

// 分水岭
class WatershedSegment {
private:
	cv::Mat markers;
public:
	void setMarkers(const cv::Mat& markerImage) {

		// 转换为整数图像
		markerImage.convertTo(markers, CV_32S);
	}

	cv::Mat process(const cv::Mat& image) {
		// 适用分水岭
		cv::watershed(image, markers);
		return markers;
	}

	// 以图像的形式返回结果
	cv::Mat getSegmentation() {
		cv::Mat tmp;
		// 标签高于255的所有段
		// 将被赋值为255
		markers.convertTo(tmp, CV_8U);
		return tmp;
	}

	// 以图像的形式返回分水岭
	cv::Mat getWatersheds() {
		cv::Mat tmp;
		markers.convertTo(tmp, CV_8U, 255, 255);
		return tmp;
	}
};

vs_test_project SkinSegment.cpp

#include "SkinSegment.h"
Mat SkinSieve::HcbCrConverter(Mat& src, Mat graySrc, Mat ycrcbSrc, Mat hsvSrc, bool isReturnMask)
{
	Mat Y, Cr, Cb, H;
	vector<Mat> channels, channels1;
	Mat binImage;
	graySrc.copyTo(binImage);
	// 通道分离
	split(ycrcbSrc, channels);
	split(hsvSrc, channels1);
	Cr = channels.at(1);	// 分离出【色调Cr】
	Cb = channels.at(2);	// 分离出【饱和度Cb】
	H = channels1.at(0);	// 分离出【H】

	// 肤色检测,输出二值图像
	for (int j = 1; j < Cr.rows - 1; j++)	// 遍历图像像素点
	{
		uchar* currentCr = Cr.ptr< uchar>(j);
		uchar* currentCb = Cb.ptr< uchar>(j);
		uchar* currentH = H.ptr< uchar>(j);
		uchar* current = binImage.ptr< uchar>(j);

		for (int i = 1; i < Cb.cols - 1; i++)
		{
			if ((currentCr[i] >= 135) && (currentCr[i] <= 170) && (currentCb[i] >= 94) && (currentCb[i] <= 125) && (currentH[i] >= 1) && (currentH[i] <= 23))
				current[i] = 255;
			else
				current[i] = 0;
		}
	}
	if (isReturnMask == true) {
		return binImage;
	}
	Mat detect;
	src.copyTo(detect, binImage);
	return detect;
}
/*Mat SkinSieve::HcbCrConverter(Mat& src,Mat &graySrc,Mat &ycrcbSrc,Mat &hsvSrc,bool isReturnMask)
{
	Mat Y, Cr, Cb, H;
	vector<Mat> channels, channels1;
	Mat binImage ,tmp ,tmp1;
	// 颜色空间变换(GRAY to Bin)
	graySrc.copyTo(binImage);

	// 颜色空间变换(RGB to YCrCb)
	ycrcbSrc.copyTo(tmp);

	// 颜色空间变换(RGB to HSV)
	hsvSrc.copyTo(tmp1);

	// 通道分离
	split(tmp, channels);
	split(tmp1, channels1);
	Cr = channels.at(1);	// 分离出【色调Cr】
	Cb = channels.at(2);	// 分离出【饱和度Cb】
	H = channels1.at(0);	// 分离出【H】

	// 肤色检测,输出二值图像
	for (int j = 1; j < Cr.rows - 1; j++)	// 遍历图像像素点
	{
		uchar* currentCr = Cr.ptr< uchar>(j);
		uchar* currentCb = Cb.ptr< uchar>(j);
		uchar* currentH = H.ptr< uchar>(j);
		uchar* current = binImage.ptr< uchar>(j);

		for (int i = 1; i < Cb.cols - 1; i++)
		{
			if ((currentCr[i] >= 135) && (currentCr[i] <= 170) && (currentCb[i] >= 94) && (currentCb[i] <= 125) && (currentH[i] >= 1) && (currentH[i] <= 23))
				current[i] = 255;
			else
				current[i] = 0;
		}
	}
	if (isReturnMask == true) {
		return binImage;
	}
	Mat detect;
	src.copyTo(detect, binImage);
	return detect;
}*/
/*
ValueRange hScope(0, 40);
ValueRange sScope(48, 360);
ValueRange vScope(50, 360);

ValueRange cbScope(102, 143);
ValueRange crScope(130, 170);
*/
Mat SkinSieve::CbCrSieveFunc(Mat& src, ValueRange cbRange, ValueRange crRange)
{
	/*YCrCb颜色空间Cr,Cb范围筛选法*/
	Mat ycrcb_image;
	int Cr = 1;
	int Cb = 2;
	cvtColor(src, ycrcb_image, CV_BGR2YCrCb); //首先转换成到YCrCb空间
	Mat output_mask = Mat::zeros(src.size(), CV_8UC1);
	for (int i = 0; i < src.rows; i++)
	{
		for (int j = 0; j < src.cols; j++)
		{
			uchar* p_mask = output_mask.ptr<uchar>(i, j);
			uchar* p_src = ycrcb_image.ptr<uchar>(i, j);
			if (p_src[Cr] >= crRange.lowerBound && p_src[Cr] <= crRange.upperBound && p_src[Cb] >= cbRange.lowerBound && p_src[Cb] <= cbRange.upperBound)
			{
				p_mask[0] = 255;
			}
		}
	}
	Mat out;
	src.copyTo(out, output_mask);;
	return out;
}
Mat SkinSieve::HSVSieveFunc(Mat& src, ValueRange hRange, ValueRange sRange, ValueRange vRange,bool isReturnMask)
{
	/*HSV颜色空间H,S,V范围筛选法*/
	Mat hsv_image;
	int h = 0;
	int s = 1;
	int v = 2;
	cvtColor(src, hsv_image, CV_BGR2HSV); //首先转换成到HSV空间
	Mat output_mask = Mat::zeros(src.size(), CV_8UC1);
	for (int i = 0; i < src.rows; i++)
	{
		for (int j = 0; j < src.cols; j++)
		{
			uchar* p_mask = output_mask.ptr<uchar>(i, j);
			uchar* p_src = hsv_image.ptr<uchar>(i, j);
			if (p_src[h] >= hRange.lowerBound && p_src[h] <= hRange.upperBound && p_src[s] >= sRange.lowerBound && p_src[s] <= sRange.upperBound && p_src[v] >= vRange.lowerBound && p_src[v] <= vRange.upperBound)
			{
				p_mask[0] = 255;
			}
		}
	}
	if (isReturnMask == true) return output_mask;
	Mat out;
	src.copyTo(out, output_mask);;
	return out;
}
Mat SkinSieve::OstuConverter(Mat& src)
{
	 Mat gray;
	 cvtColor(src, gray, CV_BGR2GRAY);

	 Mat dst;
	 threshold(gray, dst, 0, 255, CV_THRESH_OTSU);

	 return dst;
}
Mat SearchPixel::WaterShedToBin(const cv::Mat& binaryImg)
{
	Mat tempMat;
	binaryImg.convertTo(tempMat, CV_32SC1);
	Mat dst;
	for (int i = 0; i < tempMat.rows; i++)//图像四周边界不搜索
	{
		int* data = tempMat.ptr<int>(i);
		for (int j = 0; j < tempMat.cols; j++)
		{

			if (data[j] !=255)
			{
				data[j] = 0;
			}
		}
	}
	tempMat.convertTo(dst, CV_8UC1);// 矩阵数据类型转换
	return dst;
}
void SearchPixel::SeedFillfunc(int grayTrackingValue,const cv::Mat& binaryImg ,int& blockNums, ValueRange(*yAxisRealm), ValueRange(*xAxisRealm), vector<pair<int, int> >(*pointSet))
{
	if (binaryImg.empty() || binaryImg.type() != CV_8UC1)// 如果图像是空或者格式不正确就返回
	{
		return;
	}
	Mat tempMat;
	binaryImg.convertTo(tempMat, CV_32SC1);// 矩阵数据类型转换
	int blocks = 0;
	int rows = binaryImg.rows;
	int cols = binaryImg.cols;
	
	int yNeighborOffset[8] = { -1,1, 0,0,-1,1, 1,-1 };// 八连通方向
	int xNeighborOffset[8] = {  0,0,-1,1,-1,1,-1, 1 };

	for (int i = 1; i < rows - 1; i++)//图像四周边界不搜索
	{
		int* data = tempMat.ptr<int>(i);
		for (int j = 1; j < cols - 1; j++)
		{

			if (data[j] == grayTrackingValue)
			{
				stack<pair<int, int> > neighborPixels;
				neighborPixels.push(std::pair<int, int>(j, i));// 向栈顶插入元素像素位置: <j,i>
				yAxisRealm[blocks] = ValueRange(i, i);         // y坐标的范围域,用于输出画框
				xAxisRealm[blocks] = ValueRange(j, j);         // x坐标的范围域,用于输出画框
				while (!neighborPixels.empty())
				{
					pair<int, int> currentPixel = neighborPixels.top(); 
					int currentX = currentPixel.first;
					int currentY = currentPixel.second;

					yAxisRealm[blocks].updateValueRange(currentY);  // 更新x,y坐标的范围域,用于输出画框
					xAxisRealm[blocks].updateValueRange(currentX);

					pointSet[blocks].push_back(currentPixel);           // 追加current pixel,用于输出描边

					tempMat.at<int>(currentY, currentX) = 255;

					neighborPixels.pop();	// 出栈

					if ((currentX > 0) && (currentY > 0) && (currentX < (cols - 1)) && (currentY < (rows - 1)))//边界保护
					{
						for (int index = 0; index < 8; index++)
						{
							int yNeighborAxis = currentY + yNeighborOffset[index];
							int xNeighborAxis = currentX + xNeighborOffset[index];
							if (tempMat.at<int>(yNeighborAxis, xNeighborAxis) == grayTrackingValue)
								neighborPixels.push(pair<int, int>(xNeighborAxis, yNeighborAxis));
						}
					}
				}
				++blocks;
			}
		}
	}
	blockNums = blocks;
}


vs_test_project main.cpp

#include <iostream>
#include <string>
#include <stack>
#include <set>

//cvHeaderFiles set
#include <opencv2/core/core.hpp>
#include <opencv2/opencv.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/calib3d/calib3d.hpp>
#include <opencv2/imgproc/types_c.h>
#include <opencv2/imgproc/imgproc_c.h>
#include <opencv2/ml/ml.hpp> 
#include <Windows.h>
#include<time.h>
#include "SkinSegment.h"
#include "FaceDetector.h"
#include "HogSvm.h"
using namespace std;
using namespace cv;
using namespace cv::ml;
void drawPointSet(Mat& src, vector<pair<int, int > > pointSet, cv::Scalar color);

int main()
{
	//输入帧和输出帧
	Mat frame, out;
	Mat copy;
	FaceDetector faceDetector;
	HogSvm tempSvm("svm.xml");
	//捕获
	VideoCapture cap(0);
	if (!cap.isOpened()) return -1;
	//颜色空间分离帧
	Mat grayImg, ycrcbImg, hsvImg;

	bool stop = false;
	while (!stop)
	{
		cap >> frame;
		if (frame.empty()) break;
		LinearConvert::MirrorConvert(frame, frame);
		frame.copyTo(copy);
		//颜色空间变换
		grayImg = SkinSieve::GrayConverter(frame);
		ycrcbImg = SkinSieve::YCrCbConverter(frame);
		hsvImg = SkinSieve::HSvConverter(frame);
		//颜色空间筛
		//out = SkinSieve::HcbCrConverter(frame, grayImg, ycrcbImg, hsvImg, true);
		out = SkinSieve::HSVSieveFunc(frame, ValueRange(0, 40), ValueRange(48, 360), ValueRange(50, 360),true);
		//人脸遮挡
		faceDetector.removeFaces(frame, out);
		//腐蚀膨胀
		erode(out, out, Mat());
		dilate(out, out, Mat());
		// 六次递归腐蚀得到前景
		Mat foreGround;
		erode(out, foreGround, cv::Mat(), cv::Point(-1, -1), 6);
		// 识别没有对象的图像像素
		Mat backGround;
		dilate(out, backGround, cv::Mat(), cv::Point(-1, -1), 6);	// 六次递归膨胀
		threshold(backGround, backGround, 1, 128, cv::THRESH_BINARY_INV);	// 二进制阈值函数图像分割cv::THRESH_BINARY_INV 超过阈值 则 值变为 0,其他为 128 黑白二值反转(反转二值阈值化)
		// 显示标记图像
		Mat markers(grayImg.size(), CV_8U, cv::Scalar(0));
		markers = foreGround + backGround;
		// 创建分水岭分割对象
		WatershedSegment segmenter;
		segmenter.setMarkers(markers);
		segmenter.process(frame);
		// 应用分水岭算法
		Mat waterShed;
		waterShed = segmenter.getSegmentation();
		//八连通
		int markedNum;
		ValueRange yRealm[20], xRealm[20];vector<pair<int, int> > borderPointSet[20];
		SearchPixel::SeedFillfunc(0, waterShed, markedNum, yRealm, xRealm, borderPointSet);
		//计算肤色比例
		float skinRatio[20];
		Statistic::pixelStatistic(skinRatio, markedNum,xRealm,yRealm,waterShed);

		Mat rROI;
		// 给符合阈值条件的位置画框
		for (int i = 0; i < markedNum; i++)
		{
			if ((skinRatio[i] < 0.78))
			{
				//框选
				Size dsize = Size(64, 64);
				rROI = Mat(dsize, CV_8UC1);
				Rect roiRect= Rect(xRealm[i].lowerBound, yRealm[i].lowerBound,xRealm[i].getDistance(),yRealm[i].getDistance());
				resize(waterShed(roiRect), rROI, dsize);
				//描边
				drawPointSet(frame, borderPointSet[i], Scalar(75, 0, 130));
				rectangle(frame, roiRect, Scalar(75, 0, 130));
				//SVM分类
				int label = tempSvm.getLabel(rROI);
				// 加标签【手部标记】
				Point end = Point(xRealm[i].lowerBound, yRealm[i].lowerBound);   
				string str = "Hand" + to_string((int)label);
				putText(frame, str, end, cv::FONT_HERSHEY_DUPLEX, 0.7, cv::Scalar(255, 0, 0), 2);	
			}
		}
		imshow("frame", frame);
		int key = waitKey(1);

		if (key == 98) // b
			stop = true;
	}
    return 0;
}

void drawPointSet(Mat& src,vector<pair<int,int > > pointSet,cv::Scalar color)
{
	for (vector<pair<int, int >>::iterator it = pointSet.begin(); it != pointSet.end(); ++it)
		cv::circle(src,Point(it->first,it->second),1, color);
}


svm_train main.cpp
其中label.txt的每行对应于train.txt中每行的标签,train.txt每行就是一张图片的标签

//SVM多分类训练测试
#include <opencv2/opencv.hpp>

#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/imgproc/imgproc_c.h>
#include <opencv2/core/core.hpp>  
#include <opencv2/ml/ml.hpp> 
#include <iostream>
#include <fstream>

using namespace cv;
using namespace std;
using namespace cv::ml;
Size imageSize = Size(64,64);
void coumputeHog(const Mat& src, vector<float>& descriptors)
{

	HOGDescriptor myHog = HOGDescriptor(imageSize, Size(32, 32), Size(8, 8), Size(8, 8),9);
	myHog.compute(src.clone(), descriptors, Size(1, 1), Size(0, 0));
}

int main()
{
	ifstream inLabels("label.txt"), inImages("train.txt"), inTestimage("test.txt");
	string imageName;
	int imageLabel;
	vector<Mat> vecImages;
	vector<int> vecLabels;
	cv::Ptr<cv::ml::SVM> mySVM = cv::ml::SVM::create();
	mySVM->setType(cv::ml::SVM::Types::C_SVC);
	mySVM->setKernel(cv::ml::SVM::KernelTypes::LINEAR);
	mySVM->setTermCriteria(cv::TermCriteria(cv::TermCriteria::MAX_ITER, 1000, 1e-6));

	vector<float> vecDescriptors;

#if(0) //是否需要训练
	while ((inImages >> imageName) && (inLabels >> imageLabel))
	{
		Mat src = imread(imageName);
		resize(src, src, imageSize);
		vecImages.push_back(src);
		vecLabels.push_back(imageLabel);
	}
	cout << vecImages.size();
	inLabels.close();
	inImages.close();

	Mat dataDescriptors;
	Mat dataResponse = (Mat)vecLabels;
	for (size_t i = 0; i < vecImages.size(); i++)
	{
		Mat src = vecImages[i];
		Mat tempRow;
		coumputeHog(src, vecDescriptors);
		if (i == 0)
		{
			dataDescriptors = Mat::zeros(vecImages.size(), vecDescriptors.size(), CV_32FC1);
		}
		tempRow = ((Mat)vecDescriptors).t();
		tempRow.row(0).copyTo(dataDescriptors.row(i));
	}
	mySVM->train(dataDescriptors, cv::ml::SampleTypes::ROW_SAMPLE, dataResponse);
	string svmName = "svm.xml";
	mySVM->save(svmName.c_str());
#else
	mySVM=Algorithm::load<SVM>("svm.xml");
	// 预测
	string testPath;
	while (inTestimage >> testPath)
	{
		cv::TickMeter tm;
		Mat test = imread(testPath);
		tm.start();
		resize(test, test, imageSize);
		vector<float> imageDescriptor;
		coumputeHog(test, imageDescriptor);
		Mat testDescriptor = Mat::zeros(1, imageDescriptor.size(), CV_32FC1);
		for (size_t i = 0; i < imageDescriptor.size(); i++)
		{
			testDescriptor.at<float>(0, i) = imageDescriptor[i];
		}
		float  label = mySVM->predict(testDescriptor);
		cout << label << endl;
		tm.stop();
		cout <<"耗时:"<< tm.getTimeMilli() <<"ms"<<endl;
		imshow("test image", test);
		while (waitKey(1) != 115) // s
		{
		}
	}

	inTestimage.close();
#endif
	return 0;
}


最后,haarcascade_frontalface_alt.xml是openCV官方训练好的,直接用搜索引擎搜索就可以下载,svm.xml需要自己训练

  • 2
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
### 回答1: Python的OpenCV库和MediaPipe工具包是可以一起使用的,以实现手势识别的功能。 首先,需要在Python中安装OpenCV库和MediaPipe工具包。可以使用pip命令来安装它们: ``` pip install opencv-python pip install mediapipe ``` 安装完成后,就可以开始使用了。 首先,导入必要的库: ```python import cv2 import mediapipe as mp ``` 接下来,创建一个MediaPipe的Hand对象和一个OpenCV的VideoCapture对象,用于读取摄像头输入: ```python mp_hands = mp.solutions.hands hands = mp_hands.Hands() cap = cv2.VideoCapture(0) ``` 然后,使用一个循环来读取摄像头输入并进行手势识别: ```python while True: ret, frame = cap.read() if not ret: break frame_RGB = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) results = hands.process(frame_RGB) if results.multi_handedness: for hand_landmarks in results.multi_hand_landmarks: # 在这里可以对hand_landmarks进行处理和识别手势的操作 cv2.imshow('Gesture Recognition', frame) if cv2.waitKey(1) == ord('q'): break ``` 在循环中,首先将读取到的帧转换为RGB格式,然后使用Hands对象的process方法对该帧进行手势识别。得到的结果存储在results变量中。 在对每个检测到的手部进行循环处理时,可以使用hand_landmarks来获取该手的关键点坐标。可以根据这些关键点的位置和运动轨迹来实现手势的识别和分析。 最后,通过cv2.imshow方法显示图像,并使用cv2.waitKey方法等待用户操作。当用户按下"q"键时,循环终止,程序退出。 通过以上步骤,就可以使用Python的OpenCV库和MediaPipe工具包实现手势识别的功能了。当然,实际的手势识别算法和操作需要根据具体需求进行进一步的开发和优化。 ### 回答2: Python OpenCV和MediaPipe结合使用可以实现手势识别。首先,我们需要安装必要的库和工具,包括Python、opencv-python、mediapipe和其他依赖项。 然后,我们可以使用MediaPipe提供的HandTracking模块来检测手部的关键点。它使用机器学习模型来识别手势,并返回手部关键点的坐标。我们可以通过OpenCV的视频捕捉模块读取摄像头的实时图像。 接下来,我们通过应用MediaPipe的HandTracking模块获取手部关键点的坐标,并使用OpenCV将这些坐标绘制到图像上,以便我们可以实时看到手部的位置和动作。 完成这些基本的设置后,我们可以定义特定的手势,例如拇指和食指的指尖接触,作为一个简单的示例。我们可以通过检查特定的关键点之间的距离和角度来识别这种手势。如果关键点之间的距离较小并且角度较小,则我们可以确定手势是拇指和食指的指尖接触。 我们可以使用类似的方法来识别其他手势,比如手掌的张开和闭合,拳头的形成等等。我们可以定义一系列规则和阈值来确定特定手势的识别。 最后,我们可以根据检测到的手势执行特定的操作。例如,当识别到拇指和食指的指尖接触时,我们可以触发相机的快门,实现手势拍照。 总之,Python的OpenCV和MediaPipe结合使用可以实现手势识别。我们可以利用MediaPipe的HandTracking模块检测手部关键点,并使用OpenCV实时绘制手势位置。通过定义特定手势的规则,我们可以识别各种手势并执行相应操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值