基于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

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

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

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

1.2效果展示

基于OpenCV的简易实时手势识别(含代码)
#include
#include
#include
#include
#include
#include

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

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_CONSTANT0用特定值填充BORDER_REPLICATE1两端复制填充BORDER_REFLECT2倒序填充BORDER_WRAP3正序填充BORDER_REFLECT_1014不包含边界值的倒序填充BORDER_TRANSPARENT5随机填充BORDER_REFLECT1014同BORDER_REFLECT_101BORDER_DEFAULT4同BORDER_DEFAULLTBORDER_ISOLATED16不关心感兴趣区域之外的部

2.5Code


Mat skin(Mat&ImageIn)
{
        Mat Image_y;
        flip(ImageIn, Image_y, 1);
        namedWindow("前置摄像头", WINDOW_NORMAL | WINDOW_KEEPRATIO);imshow("前置摄像头", Image_y);

        Mat Image = Image_y.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);

        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));
        morphologyEx(ImageOut, ImageOut, MORPH_OPEN, kernel);
        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_RANGE1<

Original: https://blog.csdn.net/weixin_52205096/article/details/122769732
Author: 全自动学习机器
Title: 基于OpenCV的简易实时手势识别(含代码)

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/632884/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球