20个基础到进阶版的OpenCV4.9.0趣味项目(C++版)(八)——石头、剪刀、布识别手势识别(传统方法)
文章目录
一、引言
手势识别是一种通过图像或视频分析来识别和理解人手姿态的技术。它在虚拟现实、增强现实、游戏控制等领域有着广泛的应用。本文将基于OpenCV,通过C++代码实现一个简单的手势识别系统,能够识别出用户做出的石头、剪刀、布手势。
二、核心知识
1.YCrCb空间转换和提取
1)YCrCb色彩空间:
YCrCb色彩空间是一种亮度-色度模型,其中Y表示亮度分量,Cr和Cb表示色度分量。这种色彩空间广泛应用于视频编码中,因为它可以有效地将图像信息分解为亮度和色度分量,从而简化处理过程并提高效率。
Y分量: 表示图像的亮度信息,其取值范围为0到255。亮度分量反映了图像中每个像素的明暗程度。
Cr分量: 表示图像的红色色度信息,其取值范围为-128到127。红色色度分量反映了图像中红色成分与亮度的差异。
Cb分量: 表示图像的蓝色色度信息,其取值范围同样为-128到127。蓝色色度分量反映了图像中蓝色成分与亮度的差异。
2)分割:
利用Cr反映了RGB输入信号红色部分与RGB信号亮度值之间的差异。
步骤:
(1)将RGB图像转换到YCrCb颜色空间,提取Cr分量图像
(2)对Cr做自二值化阈值分割处理(Otsu法)
2.凸包
凸包(Convex Hull)是计算几何中的一个重要概念。在一个实数向量空间V中,对于给定集合X,所有包含X的凸集的交集S被称为X的凸包。在二维欧几里得空间中,凸包可以想象为一条刚好包住所有点的橡皮圈,或者用不严谨的话来说,凸包就是将最外层的点连接起来构成的凸多边形,它能包含点集中所有的点。
凸包计算原理:
OpenCV中计算凸包的具体算法并没有在官方文档中明确说明,但通常使用的是一些经典的凸包算法,如Graham扫描法、Jarvis步进法或QuickHull算法。这些算法的核心思想是通过迭代的方式,逐步确定凸包的顶点。以下是这些算法的基本步骤:
1)点集排序: 首先选择一个参考点(通常是横坐标最小的点),然后按照极角对点集进行排序。这样可以确保后续处理时点的顺序是合理的。
2)初始化凸包: 将排序后的第一个点和第二个点加入凸包。
3)扫描点集: 从第三个点开始,依次扫描点集。对于每个点,判断它与凸包上的最后两个点构成的线段的关系(左转、右转或共线)。如果是左转,说明该点位于凸包上,将其加入凸包;如果是右转或共线,说明该点不在凸包上,忽略它。
4)更新凸包: 不断重复扫描点集的步骤,直到扫描完所有点。最终得到的凸包就是包含所有点的最小凸多边形。
3.凸缺陷
凸缺陷(Convexity Defects)是指一组点在其凸包(Convex Hull)和形状之间的偏差部分。简单来说,凸缺陷描述了物体轮廓与其凸包之间的凹陷区域。凸包是将最外层的点连接起来构成的凸多边形,它能包含点集中所有的点,而凸缺陷则是这个凸多边形与物体实际轮廓之间的“空隙”。
凸缺陷计算原理:
在OpenCV中,凸缺陷的计算是基于凸包检测的结果进行的。首先,使用cv2.convexHull函数计算给定点集的凸包。然后,通过比较凸包与原始轮廓之间的差异,可以确定凸缺陷的位置和特征。
具体来说,凸缺陷的计算涉及以下几个步骤:
1)凸包检测: 使用cv::convexHull函数计算给定点集的凸包。
2)轮廓与凸包比较: 遍历原始轮廓上的每个点,计算其与凸包之间的距离和位置关系。
3)凸缺陷识别: 根据轮廓点与凸包之间的距离和位置关系,识别出凸缺陷的起始点、终止点和最远点。
4)凸缺陷特征提取: 提取凸缺陷的深度、长度等特征信息,用于后续的图像分析和处理。
4.Otsu法阈值分割
Otsu法的基本思想是通过遍历所有可能的阈值,将图像分割为前景(目标)和背景两部分,使得这两部分之间的类间方差最大(或等价地,类内方差最小)。这个最佳的灰度级别即被选为分割阈值。
算法步骤:
1)计算图像的直方图: 直方图表示图像中每个灰度级别的像素数量。
2)归一化直方图: 计算每个灰度级别的概率分布。
3)遍历所有可能的阈值: 对于每一个灰度级别,都将其视为分割阈值T,将图像分割为前景(灰度值大于T)和背景(灰度值小于等于T)两部分。
4)计算类间方差: 根据前景和背景的灰度值及其所占的比例,计算当前阈值T下的类间方差。
5)选择最佳阈值: 遍历结束后,选择类间方差最大的阈值T作为最佳分割阈值。
三、代码演示
前面已经把主要核心介绍完了,接下来就是如何把图片里面的手掌提取出来并识别为对应的手势。
double cont_s, convex_s;//手势轮廓面积,凸包面积
double area_k, arc_k;//面积比,弧长比
Mat frame, hsv, dst, morp, binary, imgbinary;
vector<vector<cv::Point>> contours, contour;
vector<double> area;
cv::Mat img = cv::imread("..\\Hand\\TestPic\\Paper_mask.jpg", cv::IMREAD_COLOR);
frame = img;
cv::namedWindow("轮廓", cv::WINDOW_NORMAL);
GaussianBlur(frame, frame, Size(5, 5), 0, 0);
dst = YCrCb_Otsu_detect(frame);//检测皮肤
Mat kernel = getStructuringElement(MORPH_ELLIPSE, Size(5, 5));
morphologyEx(dst, morp, MORPH_OPEN, kernel);
Canny(morp, binary, 50, 200);
vector<Vec4i> hierachy;
findContours(binary, contours, hierachy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);// 提取最外层轮廓
area.clear();
for (int i = 0; i < contours.size(); ++i) {
area.push_back(contourArea(contours[i]));
}
int maxPosition = max_element(area.begin(), area.end()) - area.begin();//求面积最大的轮廓所在位置
vector<vector<cv::Point>> convex(1);
vector<vector<int>> intConvex(1);
cont_s = contourArea(contours[maxPosition]);
if (cont_s >= 400)
{
convexHull(contours[maxPosition], convex[0], false, false);//求手势的凸包轮廓(显示)
convexHull(Mat(contours[maxPosition]), intConvex[0], false);//求手势的凸包轮廓(计算凸缺陷)
drawContours(frame, convex, 0, cv::Scalar(0, 255, 0), 3);
drawContours(frame, contours, maxPosition, cv::Scalar(255, 0, 0), 3);
convex_s = contourArea(convex[0]);
area_k = convex_s / cont_s;//计算面积比
arc_k = arcLength(contours[maxPosition], true) / arcLength(convex[0], true);//计算周长比
Rect rects = boundingRect(contours[maxPosition]);//获得包围手势轮廓的矩形
RNG rngs = { 12345 };
Scalar colors = Scalar(rngs.uniform(0, 255), rngs.uniform(0, 255), rngs.uniform(0, 255));
rectangle(frame, rects, colors, 3);//画出包围手势轮廓的矩形
vector<vector<cv::Vec4i>> defects(1);
cv::convexityDefects(Mat(contours[maxPosition]), intConvex[0], defects[0]);
int fNums = 0;
//绘制凸缺陷
for (int j = 0; j < defects[0].size(); j++) {
cv::Vec4i& v = defects[0][j];
float depth = v[3] / 256.0;
if (depth > 350) {
int startidx = v[0];
Point start(contours[maxPosition][startidx]);
int endidx = v[1];
Point end(contours[maxPosition][endidx]);
int faridx = v[2];
Point far(contours[maxPosition][faridx]);
line(frame, start, end, Scalar(0, 0, 255), 6);
line(frame, start, far, Scalar(0, 255, 0), 6);
line(frame, end, far, Scalar(0, 255, 0), 6);
circle(frame, far, 6, Scalar(0, 0, 255), -1);
fNums++;
}
}
printf("凸缺陷数为:%d...\r\n", fNums);
switch (fNums)
{
case 0: {
printf("当前手势为:石头...\r\n");
}break;
case 1: {
printf("当前手势为:剪刀...\r\n");
}break;
case 4: {
printf("当前手势为:布...\r\n");
}break;
default:
break;
}
}
imshow("轮廓", frame);
cv::waitKey(0);
return 0;
可看到识别当前的手势为布。
四、总结
本文介绍了一个基于OpenCV的石头、剪刀、布手势识别系统。通过图像预处理、轮廓提取、凸包计算以及凸缺陷分析,我们未来将进一步优化算法,提高识别精度和鲁棒性,并探索更多手势识别技术的应用场景。
如果你喜欢我的文章就关注我吧!
标签:缺陷,20,进阶,frame,凸包,contours,识别,手势,cv From: https://blog.csdn.net/weixin_39985355/article/details/143050852