基于OpenCV的简易实时手势识别(含代码)_手势识别代码-程序员宅基地

技术标签: 奇奇怪怪的练手小项目  计算机视觉  图像处理  opencv  

1.基本信息介绍

这是我大一寒假时写着玩的,非常简陋。基于凸包检测,所以实际上是计算指尖数量判断1~5的手势。又为1 ~3手势赋了控制鼠标操作的功能(但不能移动鼠标,而且因为手势识别不太准确所以这个功能实现得很废/doge)。(才疏学浅,希望有生之年能写个更好的
版本信息:Visual Studio2015 OpenCV4.1.1
语言:C/C++
(至于为什么不用python,现在当事人也很后悔

1.1实验步骤

(1)图像捕获
直接调用笔记本内置摄像头,使图像绕y轴对称翻转,得到内置(前置)摄像头所拍摄的视频画面的镜像画面,从而得到以操作者为第一视角的正向画面。

(2)肤色检测
先将图像由RGB空间转换至YCrCb空间。
然后将图像成Y(像素的亮度)、Cb(红色分量与亮度的信号差值)、Cr(蓝色分量与亮度的信号差值)三个单通道图像。
再提取Cb、Cr两通道的图像,进行数值判断,满足Cr>133 && Cr<173 && Cb>77 && Cb<127的点即判断为肤色部分。
最后将完成肤色切割的图像进行二值化。

(3)图像预处理
本实验先使用开运算(即先腐蚀后膨胀)对二值化后的手掌图像进行处理,去除图中的小孤立点,消除较小连通域,保留较大连通域,在不明显改变较大连通域面积的同时平滑连通域的边界,是手掌轮廓更明显,为之后的漫水填充做准备。
然后进行高斯滤波,从而消除图像上的高斯噪声。
再通过漫水填充算法,将手掌中因光线角度等因素在肤色检测中缺失的部分填充。
最后图像腐蚀处理漫水填充后的图像,将细小的噪声去除的同时,将图像主要区域的面积缩小。为之后的多边形拟合曲线求得图像近似轮廓做准备。

(4)指尖检测
本实验先用多边形逼近手部轮廓,求得近似轮廓。
再使用凸包检测函数对手部轮廓进一步进行多边形逼近,进而获得一个凸多边形。找到重心位置,通过比较凸包的顶点与重心的y轴坐标,去除纵坐标小于重心纵坐标的顶点,保留纵坐标大于重心的凸包的顶点,再规定凸点间距离范围以消除由同一个指尖产生的多个凸包顶点,得到指尖数量。

(5)模拟鼠标
最后通过得到的指尖数量,控制鼠标操作。
当指尖数量=1时,在图像重心处显示“Left”,同时执行鼠标左键单击功能。
当指尖数量=2时,在图像重心处显示“Double click”,同时执行鼠标左键双击功能。
当指尖数量=3时,在图像重心处显示“Right”,同时执行鼠标右键单击功能。

1.2效果展示

请添加图片描述

#include<opencv2\opencv.hpp>
#include<iostream>
#include<vector>
#include<algorithm>
#include<math.h>
#include<Windows.h>

using namespace std;
using namespace cv;

/*介绍基本信息*/
void Introduce()
{
    
	cout << "\n----------------------------------------------------------------------------";
	cout << "\n功能:以手势代替鼠标进行左右键点击";
	cout << "\n版本信息:Visual Studio2015	OpenCV4.1.1";
	cout << "\n-------------------------------------指令集---------------------------------";
	cout << "\n手势1:单击鼠标左键Left";
	cout << "\n手势2:双击鼠标左键Double click";
	cout << "\n手势3:单击鼠标右键Right";
	cout << "\n----------------------------------------------------------------------------\n";
}

2.肤色检测+二值化+开运算+高斯模糊

2.1 flip()函数原型

本实验通过使图像绕y轴对称翻转,得到内置(前置)摄像头所拍摄的视频画面的镜像画面,从而得到以操作者为第一视角的正向画面。

flip()函数原型
flip(	InputArray		src,
OutputArray	dst,
Int				flipCode
)

①src:输入图像。
②dst:输出图像,与src具有相同的大小、数据类型及通道数。
③flipCode:翻转方式标志。数值大于0表示绕y轴翻转;数值等于0表示绕x轴翻转;数值小于0,表示绕两个轴翻转。

2.2cvtColor()函数原型

本实验中肤色检测步骤如下:
①通过颜色模型转换函数cvtColor()函数将图像由RGB空间转换至YCrCb空间。
②通过多通道分离函数split()将图像成Y(像素的亮度)、Cb(红色分量与亮度的信号差值)、Cr(蓝色分量与亮度的信号差值)三个单通道图像。
③提取Cb、Cr两通道的图像,进行数值判断,满足Cr>133 && Cr<173 && Cb>77 && Cb<127的点即判断为肤色部分。
④将完成肤色切割的图像进行二值化。

cvtColor()函数原型
cvtColor(	InputArray		src,
OutputArray	dst,
int		code,
int		dstCn	=0
)

①src:待转换颜色模型的原始图像。
②dst:转换颜色模型后的目标图像。
③code:颜色空间转换的标志。本实验使用的标志参数为。
④dstCn:目标图像中的通道数。若参数为0,则从src和代码中自动导出通道数。本实验中使用默认参数。

2.3split()函数原型

split()函数原型
split(	const		Mat& src,
Mat *		mvbegin
)
split(	InputArray				m,
OutputArrayOfArrays	mv
)

①src:待分离的多通道图像。
②mvbegin:分离后的单通道图像,为数组形式,数组大小需要与图像的通道数一致。
③m:待分离的多通道图像。
④mv:分离后的单通道图像,为向量(vector)形式。

2.4GaussianBlur()函数原型

在图像采集的众多过程中都容易引用高斯噪声。高斯滤波器考虑了像素滤波器中心距离的影响,以滤波器中心位置为高斯分布的均值,根据高斯分布公式和每个像素离中心位置的距离计算出滤波器内每个位置的数值,从而形成一个高斯滤波器。在将高斯滤波器与图像之间进行滤波操作,进而实现对图像的高斯滤波。
本实验使用GaussianBlur()函数进行高斯滤波。

GaussianBlur()函数原型
GaussianBlur(	InputArray		src,
OutputArray	dst,
Size			ksize,
double			sigmaX,
double			sigmaY=0,
int				borderType=BORDER_DEFAULT(默认参数)
)

①src:待高斯滤波的图像,图像的数据类型必须为CV_8U、CV_16U、CV_16S、CV_32F或CV_64F,通道数目任意。
②dst:输出图像,与src尺寸、通道数、数据类型都相同。
③ksize:高斯滤波器的尺寸。滤波器必须是政奇数。如果尺寸为0,则由标准偏差计算尺寸。
④sigmaX:X轴方向的高斯滤波器标准偏差。
⑤sigmaY:Y轴方向的高斯滤波器标准偏差。如果输入量为0,则将其设置为等于sigmaX;如果两个轴的标准差都为0,则根据输入的高斯滤波器尺寸计算标准偏差。
⑥borderType:像素外推法选择标志。(边界外推方法标志见下表)

方法标志参数 简记 作用
BORDER_CONSTANT 0 用特定值填充
BORDER_REPLICATE 1 两端复制填充
BORDER_REFLECT 2 倒序填充
BORDER_WRAP 3 正序填充
BORDER_REFLECT_101 4 不包含边界值的倒序填充
BORDER_TRANSPARENT 5 随机填充
BORDER_REFLECT101 4 同BORDER_REFLECT_101
BORDER_DEFAULT 4 同BORDER_DEFAULLT
BORDER_ISOLATED 16 不关心感兴趣区域之外的部

2.5Code

/*基于YCrCb空间的肤色检测+二值化+开运算+高斯模糊*/
Mat skin(Mat&ImageIn)
{
    
		Mat Image_y;
		flip(ImageIn, Image_y, 1);//将图像沿y轴翻转,即镜像
		namedWindow("前置摄像头", WINDOW_NORMAL | WINDOW_KEEPRATIO);imshow("前置摄像头", Image_y);

		Mat Image = Image_y.clone();//用clone()函数复制图像
		Mat YCrCb_Image;
		cvtColor(Image, YCrCb_Image, COLOR_BGR2YCrCb);
		vector<Mat>Y_Cr_Cb;
		split(YCrCb_Image, Y_Cr_Cb);
		Mat CR = Y_Cr_Cb[1];
		Mat CB = Y_Cr_Cb[2];
		Mat ImageOut = Mat::zeros(Image.size(), CV_8UC1);//zeros():构建一个全为0的矩阵,即创建一个全黑的图片

		//Cr>133 && Cr<173 && Cb>77 && Cb<127
		for (int i = 0; i < Image.rows; i++)
		{
    
			for (int j = 0; j < Image.cols; j++)
			{
    
				if (CR.at<uchar>(i, j) >= 133 && CR.at<uchar>(i, j) <= 173 && CB.at<uchar>(i, j) >= 77 && CB.at<uchar>(i, j) <= 127)
				{
    
					ImageOut.at<uchar>(i, j) = 255;
				}
			}
		}

		Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));//结构元素 表示内核为一个3*3的矩形
		morphologyEx(ImageOut, ImageOut, MORPH_OPEN, kernel);//使用morphologyEx()函数进行开运算
		GaussianBlur(ImageOut, ImageOut, Size(3, 3), 5);
		
		return ImageOut;
}

3.连通空心部分+腐蚀

3.1 floodFill()函数原型

漫水填充法是根据像素灰度值之间的差值寻找相同区域以实现分割。本实验通过floodFill()函数,将手掌中因光线角度等因素在肤色检测中缺失的部分填充。
漫水填充法主要步骤如下:
①选择种子点。
②以种子为中心,判断4-领域或者8-领域的像素值与中子点像素值的差值,将差值小于阈值的像素点添加进区域内。
③将新加入的像素点作为新的种子点,反复执行第二步,直到没有新的像素点被添加进该区域为止。

floodFill()函数原型
floodFill(	InputOutputArray		image,
InputOutputArray		mask,
Point					seedPoint,
Scalar					newVal,
Rect					*rect=0,
Scalar					loDiff = Scalar(),
Scalar					upDiff = Scalar(),
int						flags = 4
)

①image:输入及输出图像,可以为CV_8U或CV_32F数据类型的单通道或三通道图像。
②mask:掩码矩阵,尺寸比输入图像宽和高各大2的单通道图像,用于标记漫水填充的区域
③seedPoint:种子点,可以为图像范围内任意一点。
④newVal:归入种子点区域内像素点的新像素值,该值会直接作用在原图中。
⑤rect:种子点漫水填充区域的最小矩形边界,默认值为0,表示不输出边界。
⑥loDiff:添加进种子点区域条件的下界差值,当邻域某像素点的像素值域与种子点像素值的差值大于该值时,该像素点被添加进种子所在的区域。
⑦upDiff:添加进种子点区域条件的上界差值,当种子点像素值与邻域某像素点的像素值的差值小于该值时,该像素点被添加进种子点所在的区域。
⑧flags:漫水填充法的操作标志,由3部分构成,分别表示邻域种类、掩码矩阵中被填充像素点的像素值和填充算法的规则,填充算法可选的标志如下表

操作标志参数 简记 含义
FLOODFILL_FIXED_RANGE 1<<16 如果设置该参数,那么仅考虑当前像素值与初始种子点像素之间的差值,否则考虑新种子点像素值与当前像素值之间的差异,即范围是否浮动的标志
FLOODFILL_MASK_ONLY 1<<17 如果设置,那么该函数不会更改原始图像,即忽略第四个参数newVal,只生成掩码矩阵

3.2 morphologyEx()函数原型

本实验中使用开运算处理肤色检测处理后的图像,去除图中的噪声,消除较小连通域,保留较大连通域,并且能够在不明显改变较大连通域面积的同时平滑连通域的边界,为之后的漫水填充做准备。
本实验中还使用了图像腐蚀处理漫水填充后的图像,将细小的噪声去除的同时,将图像主要区域的面积缩小。为之后的多边形拟合曲线求得图像近似轮廓做准备。
OpenCV4中提供了图像腐蚀和膨胀运算不同组合形式的morphologyEx()函数。

morphologyEx()函数原型
MorphologyEx(	InputArray		src,
OutoutArray	dst,
int				op,
InputArray		kernel,
Point	anchor = point(-1,-1),
int		iterations = 1,
Int		borderType = BORDER_CONSTANT,
Const Scalar & borderValue = morphologyDefaultBorderValue()
)

src:输入图像
dst:形态学操作后的输出图像
op:形态学操作类型的标志,可选择的标志及其函数如下表所示
kernel:结构元素,可以自己生成,也可以用getStructuringElement()函数生成
anchor:中心点在结构元素中的位置,默认参数为结构元素的集合中心点。
iterations:处理的次数
borderType:像素外推法选择标志
borderValue:使用边界不变外推法时的边界值。

形态学操作类型标志参数 简记 含义
MORPH_ERODE 0 图像腐蚀
MORPH_DILATE 1 图像膨胀
MORPH_OPEN 2 开运算
MORPH_CLOSE 3 闭运算
MORPH_GRANDIENT 4 形态学梯度
MORPH_TOPHAT 5 顶帽运算
MORPH_BLACKHAT 6 黑帽运算
MORPH_HITMISS 7 击中击不中运算

3.3Code

/*连通空心部分+腐蚀*/
Mat Floodfill(Mat&Img_src)
{
    
	Size f_size = Img_src.size();
	Mat image = Mat::zeros(f_size.height + 2, f_size.width + 2, CV_8UC1);
	Img_src.copyTo(image(Range(1, f_size.height + 1), Range(1, f_size.width + 1)));
	floodFill(image, Point(0, 0), Scalar(255));
	Mat cutImg, Img_dst;
	image(Range(1, f_size.height + 1), Range(1, f_size.width + 1)).copyTo(cutImg);
	Img_dst = Img_src | (~cutImg);

	Mat kernel1 = getStructuringElement(MORPH_RECT, Size(10, 10));//结构元素 表示内核为一个10*10的矩形
	morphologyEx(Img_dst,Img_dst, MORPH_ERODE, kernel1);//使用morphologyEx()函数进行腐蚀运算

	return Img_dst;
}

4.多边形拟合曲线

4.1approxPolyDP()函数原型

本实验通过approxPolyDP()函数对图像进行处理,用多边形逼近手部轮廓,求得近似轮廓,为之后的凸包检测做准备。

approxPolyDP()函数原型
approxPolyDP(	InputArray		curve,
OutputArray	approxCurve,
double 		epsilon,
bool			closed
)

①curve:输入轮廓像素点。
②approxCurve:多边形逼近结果,以多边形顶点坐标的形式给出。
③epsilon:逼近的精度,即原始曲线和逼近曲线之间的最大距离。
④closed:逼近曲线是否为封闭曲线的标志,true表示封闭。

4.2Code

/*计算两点间距离*/
double distance(Point a, Point b)
{
    
	double distance = sqrt(abs((a.x - b.x)*(a.x - a.x) + (a.y - b.y)*(a.y - b.y)));
	return distance;
}

/*将坐标点连接成封闭图形*/
void draw(Mat Img1, Mat Img2)
{
    
	for (int i = 0;i < Img1.rows;i++)
	{
    
		if (i == Img1.rows - 1)
		{
    
			Vec2i point1 = Img1.at<Vec2i>(i);
			Vec2i point2 = Img1.at<Vec2i>(0);
			line(Img2, point1, point2, Scalar(255, 255, 255), 2, 8, 0);
			break;
		}
		Vec2i point1 = Img1.at<Vec2i>(i);
		Vec2i point2 = Img1.at<Vec2i>(i + 1);
		line(Img2, point1, point2, Scalar(255, 255, 255), 5, 8, 0);
	}
}

/*多边形拟合曲线绘制近似轮廓*/
Mat approx(Mat&Img_src)
{
    
	Mat Img_dst = Mat::zeros(Img_src.size(), CV_8UC1);

	vector<vector<Point>>contours;
	vector<Vec4i>hierarchy;
	findContours(Img_src, contours, hierarchy, 0, 2, Point());

	for (int t = 0;t < contours.size();t++)
	{
    
		Mat app;
		approxPolyDP(contours[t], app, 15, true);
		draw(app, Img_dst);
	}
	return Img_dst;
}

5.凸包检测+重心+ 鼠标操作

5.1convexHull()函数原型

在图形学中,将二维平面上的点集最外层的点连接起来构成的凸多边形称为凸包。
本实验通过用于物体凸包检测的convexHull()函数,对手部轮廓进一步进行多边形逼近,进而获得一个凸多边形。找到重心位置,通过比较凸包的顶点与重心的y轴坐标,得到纵坐标大于重心的凸包的顶点,再规定凸点间距离范围,得到指尖数量。

convexHull()函数原型
convexHull(	InputArray		points,
OutputArray	hull,
bool			clockwise = false,
bool			returnPoints = true

①points:输入的二维点集或轮廓坐标,数据类型为vector或者Mat。
②hull:输出的凸包的顶点的坐标或者索引,数据类型为vector或者vector。
③clockwise:方向标志。当参数取值为true时,凸包顺序为顺时针方向;当参数取值为false时,凸包顺序为逆时针方向。
④returnPoints:输出数据的类型标志。当参数取值为true时,第二个参数输出的结果是凸包顶点的坐标,数据类型为vector;当参数取值为false时,第二个参数输出的结果是凸包顶点的索引,数据类型为vector。

5.2moments()函数原型

moments()函数原型
moments(	InputArray		array,
bool			binaryImage = false
)

①array:计算矩的区域二维像素坐标集合或者单通道的CV_8U图像。
②binaryImage:是否将所有非零像素值视为1的标志,该标志只在第一个参数设置为图像类型的数据时才会起作用。
moments()函数会返回一个Moments类的变量。Moments类中含有几何矩、中心矩及归一化的几何矩的数值属性。

5.3Mouse_event()函数原型

本实验通过mouse_event()函数来代替鼠标操作。

Mouse_event()函数原型
mouse_event(	DWORD		dwFlags,
DWORD		dx,
DWORD		dy,
DWORD		dwData,
ULONG_PTR	dwExtraInfo
)

①dwFlags:标志位集,指定点集按钮和鼠标动作。
②dx:指定鼠标沿x轴的绝对位置或者从上次鼠标事件产生以来移动的数量。
③dy:指定鼠标沿y轴的绝对位置或者从上次鼠标事件产生以来移动的数量。
④dwData:如果dwFlags为MOUSEEVENT_WHEEL,则dwData指定鼠标轮移动的数量。正值表示鼠标轮向前移动,即远离用户的方向;负值表示鼠标轮向后移动,即朝向用户。如果dwFlags不是MOUSEEVENT_WHEEL,则dwData应为0。
⑤dwExtrafo:指定与鼠标事件相关的附加32位值。

鼠标动作标志参数 含义
MOUSEEVENT_ABSOLUTE dx和dy参数含有规范化的绝对坐标。如果不设置,这些参数含有相对数据:相对于上次位置的改动位置
MOUSEEVENT_MOVE 鼠标移动
MOUSEEVENT_LEFTDOWN 鼠标左键按下
MOUSEEVENT_LEFTUP 鼠标左键松开
MOUSEEVENT_RIGHTDOWN 鼠标右键按下
MOUSEEVENT_RIGHTUP 鼠标右键松开
MOUSEEVENT_MIDLEDOWN 鼠标中键按下
MOUSEEVENT_MIDLEUP 鼠标中键松开
MOUSEEVENT_WHEEL 鼠标轮被滚动,如果鼠标有一个滚轮,滚轮数量由dwData给出

5.5 Code

/*凸包检测+重心+ 鼠标操作 */
Mat CH(Mat&Image_src)
{
    
	/*轮廓*/
	Mat ImageOut = approx(Image_src);
	vector<vector<Point>>contours;
	vector<Vec4i>hierarchy;
	findContours(ImageOut, contours, hierarchy, 0, 2, Point());

	/*画重心*/
	Moments moment = moments(ImageOut, true);
	Point center(moment.m10 / moment.m00, moment.m01 / moment.m00);
	circle(ImageOut, center, 8, Scalar(255, 255, 255), -1);

	int dist;
	int sum = 0;
	for (int t = 0;t < contours.size();t++)
	{
    
		/*凸包检测*/
		vector<Point>hull;
		convexHull(contours[t], hull);

		for (size_t i = 0;i < hull.size();i++)
		{
    
			int a = hull.size();

			if (i != hull.size() - 1)
				dist = distance(hull[i], hull[i + 1]);
			int dist1 = distance(hull[i], center);
			if (hull[i].y < center.y&&dist>20)
			{
    
				circle(ImageOut, hull[i], 15, Scalar(255, 255, 255), 2, 8, 0);
				sum += 1;
			}

			if (i == hull.size() - 1)
			{
    
				line(ImageOut, hull[i], hull[0], Scalar(255, 255, 255), 5, 8, 0);
				break;
			}
			line(ImageOut, hull[i], hull[i + 1], Scalar(255, 255, 255), 5, 8, 0);
		}
	}
	cout << sum << endl;

	if (sum == 1)
	{
    
		mouse_event(MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
		string str3 = "Left";
		putText(ImageOut, str3, center, 0, 2, Scalar(255, 255, 255), 4, 8);
		waitKey(0);

	}
	if (sum == 2)
	{
    
		mouse_event(MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
		mouse_event(MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
		string str1 = "Double click";
		putText(ImageOut, str1, center, 0, 2, Scalar(255, 255, 255), 4, 8);
	}
	if (sum == 3)
	{
    
		mouse_event(MOUSEEVENTF_RIGHTDOWN | MOUSEEVENTF_RIGHTDOWN, 0, 0, 0, 0);
		string str2 = "Right";
		putText(ImageOut, str2, center, 0, 2, Scalar(255, 255, 255), 4, 8);
		waitKey(0);
	}
	return	ImageOut;
}

6.主函数 摄像头调用

6.1摄像头调用

OpenCV中为读取视频文件和调用摄像头而设计了VideoCapture类。视频文件由专门的视频读取函数进行视频读取,并将每一帧图像保存到Mat类矩阵中。
本实验通过VideoCapture类直接调用笔记本内置摄像头。

VideoCapture类调用摄像头构造函数
VideoCapture( int		index,
Int		apiPreference
)

①index:需要打开的摄像头设备的ID。
②airPreference:读取数据时设置的属性。

6.2 Code

int main()
{
    
	Introduce();
	/*调用摄像头*/
	VideoCapture capture(0);
	/*检查是否成功打开*/
	if (!capture.isOpened())
	{
    
		cout << "摄像头打开失败T_T";
		return -1;
	}

	while (1) {
    
		Mat In;
		capture >> In;

		Mat A = skin(In);
		Mat B = Floodfill(A);
		Mat Out = CH(B);

		namedWindow("Result", WINDOW_NORMAL | WINDOW_KEEPRATIO);imshow("Result", Out);
		waitKey(2);
	}
	return 0;
}

7.代码中的其他API函数

7.1 getStructuringElement()函数原型

getStructuringElement(	int			shape,
Size		ksize,
Point		anchor = Point(-1-1)

①shape:生成结构元素的种类,可选参数及其含义如表所示。
②ksize:结构元素的尺寸。
③anchor:中心点的位置,默认为结构元素的几何中心。
标志参数 简记 作用
MORPH_RECT 0 矩形结构元素,所有元素都为1
MORPH_CROSS 1 十字结构元素,中间的列和行元素为1
MORPH_ELLIPSE 2 椭圆结构元素,矩形的内接椭圆元素为1

7.2 findContours()函数原型

FindCountours(	InputArray					image,
OutputArrayOfArrays		contours,
OutputArray				hierarchy,
int							mode,
int							method,
Point						offset = Point()

①image:输入图像,数据类型为CV_8U的单通道灰度图或二值化图像。
②contours:存放检测到的轮廓,每个轮廓中放着像素的坐标。数据类型为vector<vector>。
③hierarchy:存放各个轮廓之间的结构信息,数据类型为vector。
④mode:轮廓检测模式标志。
⑤method:轮廓逼近方法标志。
⑥offset:每个轮廓点移动的可选偏移量。

轮廓检测模式标志参数 简记 含义
RETR_EXTERNAL 0 只检测最外层轮廓
RETR_LIST 1 提取所有轮廓,并放在list中。检测的轮廓不建立等级关系
RETR_CCOMP 2 提取所有轮廓,并且将其组织为双层结构。顶层为连通域的外围边界,次层为孔的内层边界
RETR_TREE 3 提取所有轮廓,并重新建立网状的轮廓结构
轮廓逼近方法标志参数 简记 含义
CHAIN_APPROX_NONE 1 获取每个轮廓的每个像素,相邻两个点的像素位置相差1
CHAIN_APPROX_SIMPLE 2 压缩水平方向、垂直方向和对角线方向的元素,只保留该方向的终点坐标

7.3 circle()函数原型

circle(	InputOutputArray  	img,
Point					center,
int						radius,
const					Scalar &color,
int						thickness = 1,
int						lineType = LINE_8,
int						shift = 0

①img:需要绘制圆形的图像。
②center:圆形的圆心位置坐标。
③radius:圆形的半径,单位为像素。
④color:圆形的颜色。
⑤thickness:轮廓的宽度,如果数值为负,则绘制一个实心圆。
⑥lineType:边界类型。
⑦shift:中心坐标和半径数值中的小数位数。

7.4 line()函数原型

line(	InputOutputArray		img,
Point					pt1,
Point					pt2,
const					scalar & color,
int						thickness = 1,
int						lineType = LINE_8,
int						shift = 0

①pt1:直线起点在图像中的坐标。
②pt2:直线终点在图像中的坐标。
③color:直线的颜色。

7.5 namedWindow()函数

namedWindow(	const	String & winname,
int		flags = WINDOW_AUTOSIZE
)

①winname:窗口名称,用作窗口的标识符。
②flags:窗口属性设置标志。在默认的情况之下,窗口所加载的标志参数为“WINDOW_AUTOSIZE|WINDOW_KEEPRATIO|WINDOW_GUI_EXPANDED”。

窗口属性标志参数 作用
WINDOW_NORMAL 显示图像后,允许用户随意调整窗口大小
WINDOW_AUTOSIZE 根据图像大小显示窗口,不允许用户调整大小
WINDOW_OPENGL 创建窗口的时候会支持OpenGL
WINDOW_FULLSCREEN 全屏显示窗口
WINDOW_FREERATIO 调整图像尺寸以充满窗口
WINDOW_KEEPRATIO 保持图像的比例
WINDOW_GUI_EXPANDED 创建的窗口允许添加工具栏和状态栏
WINDOW_GUI_NORMAL 创建没有状态栏和工具栏的窗口

7.6 imshow()函数

imshow(	const		String & winname,
InputArray 	mat
)

①winnam:要显示图像的窗口的名字,用字符串形式赋值。
②mat:要显示的图像矩阵。

8.参考文献

[1]贾建军.基于视觉的手势识別技术研究[D].哈尔滨工业大学.2008
[2]孟国庆.基于OpenCV的手势识别技术研究[D].西安科技大学.2014
[3]Gary Bradski Adrian Kaehler .学习OpenCV[M]. 于仕琪 刘瑞祯译. 北京:清华大学出版社.2014
[4]王天庆. Python人脸识别从入门到工程实践[M]. 北京:机械工业出版社.2019.4
[5]冯振 郭延宁 吕跃勇. OpenCV 4 快速入门[M].北京:人民邮电出版社.2020

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_52205096/article/details/122769732

智能推荐

艾美捷Epigentek DNA样品的超声能量处理方案-程序员宅基地

文章浏览阅读15次。空化气泡的大小和相应的空化能量可以通过调整完全标度的振幅水平来操纵和数字控制。通过强调超声技术中的更高通量处理和防止样品污染,Epigentek EpiSonic超声仪可以轻松集成到现有的实验室工作流程中,并且特别适合与表观遗传学和下一代应用的兼容性。Epigentek的EpiSonic已成为一种有效的剪切设备,用于在染色质免疫沉淀技术中制备染色质样品,以及用于下一代测序平台的DNA文库制备。该装置的经济性及其多重样品的能力使其成为每个实验室拥有的经济高效的工具,而不仅仅是核心设施。

11、合宙Air模块Luat开发:通过http协议获取天气信息_合宙获取天气-程序员宅基地

文章浏览阅读4.2k次,点赞3次,收藏14次。目录点击这里查看所有博文  本系列博客,理论上适用于合宙的Air202、Air268、Air720x、Air720S以及最近发布的Air720U(我还没拿到样机,应该也能支持)。  先不管支不支持,如果你用的是合宙的模块,那都不妨一试,也许会有意外收获。  我使用的是Air720SL模块,如果在其他模块上不能用,那就是底层core固件暂时还没有支持,这里的代码是没有问题的。例程仅供参考!..._合宙获取天气

EasyMesh和802.11s对比-程序员宅基地

文章浏览阅读7.7k次,点赞2次,收藏41次。1 关于meshMesh的意思是网状物,以前读书的时候,在自动化领域有传感器自组网,zigbee、蓝牙等无线方式实现各个网络节点消息通信,通过各种算法,保证整个网络中所有节点信息能经过多跳最终传递到目的地,用于数据采集。十多年过去了,在无线路由器领域又把这个mesh概念翻炒了一下,各大品牌都推出了mesh路由器,大多数是3个为一组,实现在面积较大的住宅里,增强wifi覆盖范围,智能在多热点之间切换,提升上网体验。因为节点基本上在3个以内,所以mesh的算法不必太复杂,组网形式比较简单。各厂家都自定义了组_802.11s

线程的几种状态_线程状态-程序员宅基地

文章浏览阅读5.2k次,点赞8次,收藏21次。线程的几种状态_线程状态

stack的常见用法详解_stack函数用法-程序员宅基地

文章浏览阅读4.2w次,点赞124次,收藏688次。stack翻译为栈,是STL中实现的一个后进先出的容器。要使用 stack,应先添加头文件include<stack>,并在头文件下面加上“ using namespacestd;"1. stack的定义其定义的写法和其他STL容器相同, typename可以任意基本数据类型或容器:stack<typename> name;2. stack容器内元素的访问..._stack函数用法

2018.11.16javascript课上随笔(DOM)-程序员宅基地

文章浏览阅读71次。<li> <a href = "“#”>-</a></li><li>子节点:文本节点(回车),元素节点,文本节点。不同节点树:  节点(各种类型节点)childNodes:返回子节点的所有子节点的集合,包含任何类型、元素节点(元素类型节点):child。node.getAttribute(at...

随便推点

layui.extend的一点知识 第三方模块base 路径_layui extend-程序员宅基地

文章浏览阅读3.4k次。//config的设置是全局的layui.config({ base: '/res/js/' //假设这是你存放拓展模块的根目录}).extend({ //设定模块别名 mymod: 'mymod' //如果 mymod.js 是在根目录,也可以不用设定别名 ,mod1: 'admin/mod1' //相对于上述 base 目录的子目录}); //你也可以忽略 base 设定的根目录,直接在 extend 指定路径(主要:该功能为 layui 2.2.0 新增)layui.exten_layui extend

5G云计算:5G网络的分层思想_5g分层结构-程序员宅基地

文章浏览阅读3.2k次,点赞6次,收藏13次。分层思想分层思想分层思想-1分层思想-2分层思想-2OSI七层参考模型物理层和数据链路层物理层数据链路层网络层传输层会话层表示层应用层OSI七层模型的分层结构TCP/IP协议族的组成数据封装过程数据解封装过程PDU设备与层的对应关系各层通信分层思想分层思想-1在现实生活种,我们在喝牛奶时,未必了解他的生产过程,我们所接触的或许只是从超时购买牛奶。分层思想-2平时我们在网络时也未必知道数据的传输过程我们的所考虑的就是可以传就可以,不用管他时怎么传输的分层思想-2将复杂的流程分解为几个功能_5g分层结构

基于二值化图像转GCode的单向扫描实现-程序员宅基地

文章浏览阅读191次。在激光雕刻中,单向扫描(Unidirectional Scanning)是一种雕刻技术,其中激光头只在一个方向上移动,而不是来回移动。这种移动方式主要应用于通过激光逐行扫描图像表面的过程。具体而言,单向扫描的过程通常包括以下步骤:横向移动(X轴): 激光头沿X轴方向移动到图像的一侧。纵向移动(Y轴): 激光头沿Y轴方向开始逐行移动,刻蚀图像表面。这一过程是单向的,即在每一行上激光头只在一个方向上移动。返回横向移动: 一旦一行完成,激光头返回到图像的一侧,准备进行下一行的刻蚀。

算法随笔:强连通分量-程序员宅基地

文章浏览阅读577次。强连通:在有向图G中,如果两个点u和v是互相可达的,即从u出发可以到达v,从v出发也可以到达u,则成u和v是强连通的。强连通分量:如果一个有向图G不是强连通图,那么可以把它分成躲个子图,其中每个子图的内部是强连通的,而且这些子图已经扩展到最大,不能与子图外的任一点强连通,成这样的一个“极大连通”子图是G的一个强连通分量(SCC)。强连通分量的一些性质:(1)一个点必须有出度和入度,才会与其他点强连通。(2)把一个SCC从图中挖掉,不影响其他点的强连通性。_强连通分量

Django(2)|templates模板+静态资源目录static_django templates-程序员宅基地

文章浏览阅读3.9k次,点赞5次,收藏18次。在做web开发,要给用户提供一个页面,页面包括静态页面+数据,两者结合起来就是完整的可视化的页面,django的模板系统支持这种功能,首先需要写一个静态页面,然后通过python的模板语法将数据渲染上去。1.创建一个templates目录2.配置。_django templates

linux下的GPU测试软件,Ubuntu等Linux系统显卡性能测试软件 Unigine 3D-程序员宅基地

文章浏览阅读1.7k次。Ubuntu等Linux系统显卡性能测试软件 Unigine 3DUbuntu Intel显卡驱动安装,请参考:ATI和NVIDIA显卡请在软件和更新中的附加驱动中安装。 这里推荐: 运行后,F9就可评分,已测试显卡有K2000 2GB 900+分,GT330m 1GB 340+ 分,GT620 1GB 340+ 分,四代i5核显340+ 分,还有写博客的小盒子100+ 分。relaybot@re...

推荐文章

热门文章

相关标签