1.研究背景与意义
项目参考AAAI Association for the Advancement of Artificial Intelligence
研究背景与意义:
随着计算机视觉技术的快速发展,手势识别系统在人机交互、虚拟现实、智能监控等领域得到了广泛应用。手势识别系统可以通过分析人体的手势动作,实现与计算机的自然交互,提高用户体验和操作效率。基于OpenCV的手势1~5识别系统是一种利用计算机视觉技术,通过摄像头捕捉用户手势动作并识别其代表的数字,从而实现手势数字输入的系统。
手势识别技术的应用非常广泛。在人机交互方面,手势识别系统可以替代传统的鼠标和键盘输入方式,使用户能够通过手势来操作计算机,提高交互的自然性和便捷性。在虚拟现实领域,手势识别系统可以实现用户在虚拟环境中的自由移动和操作,增强虚拟现实的沉浸感和真实感。在智能监控方面,手势识别系统可以用于识别特定的手势动作,例如手势警报系统可以通过识别求救手势来及时报警,提高安全性和应急响应能力。
目前,基于OpenCV的手势识别系统已经取得了一定的研究进展。OpenCV是一个开源的计算机视觉库,提供了丰富的图像处理和计算机视觉算法,可以用于实现手势识别系统的各个环节,包括图像采集、预处理、特征提取和分类识别等。基于OpenCV的手势1~5识别系统是一种基于机器学习算法的手势识别系统,通过训练模型来识别手势动作所代表的数字。
然而,目前基于OpenCV的手势识别系统还存在一些挑战和问题。首先,手势识别系统需要准确地捕捉和识别用户的手势动作,但是手势动作的多样性和复杂性给图像采集和处理带来了挑战。其次,手势识别系统需要具备实时性和稳定性,能够在不同的环境和光照条件下进行准确的识别。最后,手势识别系统需要具备较高的准确率和鲁棒性,能够识别不同人的手势动作,并且对于噪声和干扰具有一定的容错能力。
因此,基于OpenCV的手势1~5识别系统的研究具有重要的意义。首先,该系统可以提供一种新的人机交互方式,改善用户体验和操作效率。其次,该系统可以应用于虚拟现实领域,提高虚拟现实的沉浸感和真实感。最后,该系统可以应用于智能监控领域,提高安全性和应急响应能力。
在研究过程中,需要解决以下几个关键问题:首先,如何准确地捕捉和识别手势动作,提高图像采集和处理的效果。其次,如何提高系统的实时性和稳定性,适应不同的环境和光照条件。最后,如何提高系统的准确率和鲁棒性,识别不同人的手势动作,并具备一定的容错能力。
总之,基于OpenCV的手势1~5识别系统是一种利用计算机视觉技术实现手势数字输入的系统,具有广泛的应用前景和重要的研究意义。通过解决关键问题,可以提高手势识别系统的准确性、实时性和稳定性,推动手势识别技术在人机交互、虚拟现实和智能监控等领域的应用。
2.图片演示
3.视频演示
基于OpenCV的手势1~5识别系统(源码&环境部署)_哔哩哔哩_bilibili
4.手势
手势的概述
手势是指人类用语言中枢建立起来的一套用手掌和手指位置、形状的特定语言系统。手势包括单幅图片的静态手势和连续的动态手势。前者是指人手的一个简单动作,即单个手形;后者是指人手与手臂组合产生一系列的动作l36]。简单地说,在手势相关的模型参数空间中,动态手势是由一连串连续的静态手势组成的运动轨迹,而其中每个静态手势可以看成一个独立的点,如果给它们分别定义一个特定的含义,那么,就成为了某种语言体系。换而言之,我们常说的静态手势是一种空间特性,而动态手势包含了时、空两种特性。手势的含义就是通过分析空间模型中点与轨迹的含义得到的。
手势的输入方式
(1)数据手套
数据手套就是一种传感器系统,可以把人的动作变为信号,并编程输入到计算机,计算机分析这些信号后,得到手势的动作信息。它同时是虚拟现实系统的一个媒介设备。数据手套的样式也很多,从复杂程度上来说,可以分为简单的数据手套和复杂的数据手套,前者可能只有几个传感器来测量手的变化,而后者会有较多的传感器来测量手的姿势变化。
通过使用数据手套,系统可以有效的得到手势的大量数据信息来识别多种手势,并且可以准确的获取手的三维运动信息。但是她也有许多缺点,如每次使用都要进行数据手套的穿戴,带来很多麻烦,而且一般数据手套的价格偏高,这样使它很难推广。
(2)基于计算机视觉
基于计算机视觉的手势识别方法是使用摄像头获取手的运动过程,从获取的视频图像中获取关键帧,并提取手势,然后对手势图像进行特征检测和参数估计,最后根据模型参数对手势进行识别,获得当前手势信息。这种方法的优点是对用户限制较少,但是需要处理的数据量大,而且处理方法相对复杂,准确率相对较低,且依赖各环节的处理方法。
基于计算机视觉的手势识别系统组成
基于计算机视觉的手势识别是通过摄像头获取手势信息,通过建立手势映射模型来识别手势信息,从而使得交互的任务更加具体化。
一个基于视觉的手势识别系统一般有以下几部分构成:
首先通过一个或多个摄像头获取视频数据流,接着系统根据手势输入的交互模型检测获取的数据流里判断是否有手势出现,要是有的话,就需要从视频信号中将这个手势分离出来。进而选择出手势模型对手势进行分析,分析过程中主要有特征检测和模型参数估计两个部分。在识别阶段,分类手势的时候要依据模型的参数,而且满足不同的需求,从而得到手势描述,最后系统按照得出的描述驱动,实现具体的应用。
手势的建模主要是通过一定的算法在所捕获的图像中确定手的位置,能够快速的定位,使得后续的工作可以及时的进行。目前有通过肤色模型的算法,而且也出现了通过Haar-like特征以及基于Adaboost算法的人脸检测等方法。在一定的程度下,这些算法都具有相当好的鲁棒性。
手势的分析通常是在确定好手势区域的基础上,然后利用几何算法,将比较明显的特征提取出来,比如手指,或者手指的顶点处。从而,在系统中就可以将相对的坐标分辨出来,从而为后面的交互提供了基础。
5.核心代码讲解
5.1 gesture.py
封装为类的代码如下:
python
class HandGestureRecognition:
def __init__(self):
self.cap = cv2.VideoCapture(0)
def run(self):
while(self.cap.isOpened()):
ret, img = self.cap.read()
cv2.rectangle(img,(300,300),(100,100),(0,255,0),0)
crop_img = img[100:300, 100:300]
grey = cv2.cvtColor(crop_img, cv2.COLOR_BGR2GRAY)
value = (35, 35)
blurred = cv2.GaussianBlur(grey, value, 0)
_, thresh1 = cv2.threshold(blurred, 127, 255,
cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)
cv2.imshow('Thresholded', thresh1)
contours, hierarchy = cv2.findContours(thresh1.copy(),cv2.RETR_TREE, \
cv2.CHAIN_APPROX_NONE)
max_area = -1
for i in range(len(contours)):
cnt=contours[i]
area = cv2.contourArea(cnt)
if(area>max_area):
max_area=area
ci=i
cnt=contours[ci]
x,y,w,h = cv2.boundingRect(cnt)
cv2.rectangle(crop_img,(x,y),(x+w,y+h),(0,0,255),0)
hull = cv2.convexHull(cnt)
drawing = np.zeros(crop_img.shape,np.uint8)
cv2.drawContours(drawing,[cnt],0,(0,255,0),0)
cv2.drawContours(drawing,[hull],0,(0,0,255),0)
hull = cv2.convexHull(cnt,returnPoints = False)
defects = cv2.convexityDefects(cnt,hull)
count_defects = 0
cv2.drawContours(thresh1, contours, -1, (0,255,0), 3)
for i in range(defects.shape[0]):
s,e,f,d = defects[i,0]
start = tuple(cnt[s][0])
end = tuple(cnt[e][0])
far = tuple(cnt[f][0])
a = math.sqrt((end[0] - start[0])**2 + (end[1] - start[1])**2)
b = math.sqrt((far[0] - start[0])**2 + (far[1] - start[1])**2)
c = math.sqrt((end[0] - far[0])**2 + (end[1] - far[1])**2)
angle = math.acos((b**2 + c**2 - a**2)/(2*b*c)) * 57
if angle <= 90:
count_defects += 1
cv2.circle(crop_img,far,1,[0,0,255],-1)
#dist = cv2.pointPolygonTest(cnt,far,True)
cv2.line(crop_img,start,end,[0,255,0],2)
#cv2.circle(crop_img,far,5,[0,0,255],-1)
......
你可以通过创建一个HandGestureRecognition的实例并调用run方法来运行这段代码。
这个程序文件名为gesture.py,主要功能是通过摄像头捕捉手势图像,并根据手势的形状判断手势的数字。
程序首先导入了需要的库,包括cv2、numpy和math。然后通过cv2.VideoCapture(0)打开摄像头,进入一个循环,不断读取摄像头捕捉到的图像。
在每一帧图像中,程序首先在图像上绘制一个矩形框,用于标记手势区域。然后从图像中裁剪出手势区域,并将其转换为灰度图像。
接下来,程序对灰度图像进行高斯模糊处理,并进行二值化处理,得到一个二值图像。然后通过cv2.findContours函数找到图像中的轮廓。
程序通过计算轮廓的面积,找到最大的轮廓,并将其绘制在裁剪后的图像上。然后通过cv2.convexHull函数找到轮廓的凸包,并将凸包绘制在图像上。
接下来,程序通过cv2.convexityDefects函数找到轮廓的凸缺陷,并计算凸缺陷的个数。根据凸缺陷的个数,判断手势的数字,并在原始图像上绘制相应的文字。
最后,程序将绘制的图像显示出来,并通过cv2.waitKey函数等待用户按下ESC键退出程序。
整个程序的功能是实时捕捉手势图像,并根据手势的形状判断手势的数字。
5.2 test.py
封装为类后的代码如下:
python
class FaceDB:
def __init__(self):
self.host = "localhost" # 主机名
self.user = "root" # 用户名
self.passwd = "ltc19981118" # 密码
self.db = "facedb" # 数据库名称
self.port = 3306
self.conn = MySQLdb.connect(host=self.host, port=self.port, user=self.user, passwd=self.passwd, db=self.db, charset='utf8')
self.cursor = self.conn.cursor()
def get_ppt_count(self, table_name):
sql = "SELECT ppt FROM " + table_name
self.cursor.execute(sql)
return len(self.cursor.fetchall())
在这个类中,我们将原来的全局变量封装为类的属性,并将数据库连接和查询操作封装为类的方法。这样可以更好地组织代码,提高代码的可读性和可维护性。
这个程序文件名为test.py,它的功能是使用PyQt5库创建一个图形用户界面,同时还引入了其他一些必要的库。程序的主要功能是连接到MySQL数据库,执行一个查询语句并打印结果的长度。具体的代码如下:
-
导入所需的库:
- PyQt5.QtCore:PyQt5的核心模块
- PyQt5.QtWidgets:PyQt5的窗口部件模块
- PyQt5.QtGui:PyQt5的图形用户界面模块
- PyQt5:PyQt5的整体模块
- numpy:用于数值计算的库
- time:用于时间相关的操作的库
- MySQLdb:用于连接MySQL数据库的库
- cv2:用于图像处理的库
- sys:用于系统相关的操作的库
- os:用于操作系统相关的操作的库
- math:用于数学计算的库
-
设置全局变量:
- host:数据库的主机名
- user:数据库的用户名
- passwd:数据库的密码
- db:数据库的名称
- port:数据库的端口号
-
连接到MySQL数据库:
- 使用MySQLdb.connect()函数连接到数据库,传入相应的参数
- 创建一个数据库连接对象conn
-
执行查询语句:
- 构建查询语句sql,从表'bingbing'中选择'ppt'列的数据
- 创建一个游标对象cursor
- 使用cursor.execute()函数执行查询语句
-
打印结果长度:
- 使用cursor.fetchall()函数获取查询结果的所有行
- 使用len()函数获取查询结果的长度
- 使用print()函数打印结果的长度
5.3 ui.py
python
class GestureRecognitionSystem(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.setupUi(self)
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(1280, 960)
MainWindow.setStyleSheet("background-image: url(\"./template/carui.png\")")
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.label = QtWidgets.QLabel(self.centralwidget)
self.label.setGeometry(QtCore.QRect(168, 60, 1000, 71))
self.label.setAutoFillBackground(False)
self.label.setStyleSheet("")
self.label.setFrameShadow(QtWidgets.QFrame.Plain)
self.label.setAlignment(QtCore.Qt.AlignCenter)
self.label.setObjectName("label")
self.label.setStyleSheet("font-size:50px;font-weight:bold;font-family:SimHei;background:rgba(255,255,255,0);")
self.label_2 = QtWidgets.QLabel(self.centralwidget)
self.label_2.setGeometry(QtCore.QRect(40, 200, 550, 501))
self.label_2.setStyleSheet("background:rgba(255,255,255,0.4);")
self.label_2.setAlignment(QtCore.Qt.AlignCenter)
self.label_2.setObjectName("label_2")
self.label_3 = QtWidgets.QLabel(self.centralwidget)
self.label_3.setGeometry(QtCore.QRect(620, 200, 550, 501))
self.label_3.setStyleSheet("background:rgba(255,255,255,0.4);")
self.label_3.setAlignment(QtCore.Qt.AlignCenter)
self.label_3.setObjectName("label_3")
self.textBrowser = QtWidgets.QTextBrowser(self.centralwidget)
self.textBrowser.setGeometry(QtCore.QRect(73, 746, 851, 174))
self.textBrowser.setStyleSheet("background:rgba(255,255,255,0.4);")
self.textBrowser.setObjectName("textBrowser")
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setGeometry(QtCore.QRect(1020, 750, 150, 40))
self.pushButton.setStyleSheet("background:rgba(53,142,255,1);border-radius:10px;padding:2px 4px;")
self.pushButton.setObjectName("pushButton")
self.pushButton_3 = QtWidgets.QPushButton(self.centralwidget)
self.pushButton_3.setGeometry(QtCore.QRect(1020, 800, 150, 40))
self.pushButton_3.setStyleSheet("background:rgba(53,142,255,1);border-radius:10px;padding:2px 4px;")
self.pushButton_3.setObjectName("pushButton_3")
self.pushButton_2 = QtWidgets.QPushButton(self.centralwidget)
self.pushButton_2.setGeometry(QtCore.QRect(1020, 850, 150, 40))
self.pushButton_2.setStyleSheet("background:rgba(53,142,255,1);border-radius:10px;padding:2px 4px;")
self.pushButton_2.setObjectName("pushButton_2")
self.pushButton_4 = QtWidgets.QPushButton(self.centralwidget)
self.pushButton_4.setGeometry(QtCore.QRect(1020, 900, 150, 40))
self.pushButton_4.setStyleSheet("background:rgba(53,142,255,1);border-radius:10px;padding:2px 4px;")
self.pushButton_4.setObjectName("pushButton_4")
MainWindow.setCentralWidget(self.centralwidget)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "手势识别系统"))
self.label.setText(_translate("MainWindow", "手势识别系统"))
self.label_2.setText(_translate("MainWindow", "请添加对象,注意路径不要存在中文"))
self.pushButton.setText(_translate("MainWindow", "选择文件"))
self.pushButton_3.setText(_translate("MainWindow", "文件检测"))
self.pushButton_2.setText(_translate("MainWindow", "实时检测"))
self.pushButton_4.setText(_translate("MainWindow", "退出系统"))
# 点击文本框绑定槽事件
self.pushButton.clicked.connect(self.openfile)
self.pushButton_3.clicked.connect(self.det)
self.pushButton_2.clicked.connect(self.det2)
self.pushButton_4.clicked.connect(self.handleCalc4)
def det2(self):
cap = cv2.VideoCapture(0)
while True:
_, frame = cap.read()
if frame is None:
break
img = frame
img0 = frame
crop_img = img
grey = cv2.cvtColor(crop_img, cv2.COLOR_BGR2GRAY)
value = (35, 35)
blurred = cv2.GaussianBlur(grey, value, 0)
_, thresh1 = cv2.threshold(blurred, 20, 255,
cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
self.showimg2(thresh1)
contours, hierarchy = cv2.findContours(thresh1.copy(), cv2.RETR_TREE, \
cv2.CHAIN_APPROX_NONE)
max_area = -1
for i in range(len(contours)):
cnt = contours[i]
area = cv2.contourArea(cnt)
if (area > max_area):
max_area = area
ci = i
cnt = contours[ci]
x, y, w, h = cv2.boundingRect(cnt)
cv2.rectangle(crop_img, (x, y), (x + w, y + h), (0, 0, 255), 0)
xx = (x + w * 0.5) / w
yy = (y + h * 0.5) / h
hull = cv2.convexHull(cnt)
drawing = np.zeros(crop_img.shape, np.uint8)
cv2.drawContours(drawing, [cnt], 0, (0, 255, 0), 0)
cv2.drawContours(drawing, [hull], 0, (0, 0, 255), 0)
......
这是一个使用PyQt5库创建的用户界面程序。程序文件名为ui.py。该程序创建了一个主窗口,窗口大小为1280x960像素,并设置了背景图片。主窗口中包含了几个标签、按钮和文本框等控件,用于显示图像和文字信息。程序还定义了一些函数,用于处理按钮点击事件和图像处理等功能。其中,det2函数实现了实时手势检测功能,openfile函数实现了选择文件功能,handleCalc4函数实现了退出系统功能,printf函数用于在文本框中显示文字信息,showimg函数用于显示图像。
6.系统整体结构
整体功能和构架概述:
这个工程是一个基于OpenCV的手势识别系统,主要包括三个程序文件:gesture.py、test.py和ui.py。
gesture.py文件负责实时捕捉手势图像,并根据手势的形状判断手势的数字。
test.py文件连接到MySQL数据库,执行一个查询语句并打印结果的长度。
ui.py文件创建了一个图形用户界面,包含了标签、按钮和文本框等控件,用于显示图像和文字信息,并实现了一些功能,如手势检测、选择文件和退出系统。
下表整理了每个文件的功能:
文件名 | 功能 |
---|---|
gesture.py | 实时捕捉手势图像,判断手势的数字 |
test.py | 连接到MySQL数据库,执行查询语句并打印结果的长度 |
ui.py | 创建图形用户界面,显示图像和文字信息,实现手势检测、选择文件和退出系统功能 |
7.手势图像的预处理
手势图像预处理是消除图像中无关的信息、增强可用信息的可检测性和最大限度地简化数据,以此来简化对手势图像进行特征分割、特征提取和配准。
手势图像预处理概述
当手势转变为数字图像信息的时候,转变过程中因受到外界影响,手势图像会因噪声而在不同程度上出现畸变。图像预处理的目的是用来消除或减少待匹配图像之间的灰度偏差和几何变化,使图像处理过程顺利进行。
手势图像预处理方法
图像预处理的一个重要任务就是去除噪声的同时尽可能地保留图像的边缘和细节。为了更好地对手势区域进行识别,需要对采集得到的图像进行一些处理,使得分割出来的手势区域更加完整。
图像滤波
(1)均值滤波
均值滤波是典型的线性滤波算法,采用的主要方法为邻域平均法。它是指使用一个模板在图像上移动,用该模板中的全体像素的平均值来代替原来像素值。
假设一幅图片f(x, y),对它使用二维模板W进行均值滤波,则输出图像如式所示。
式中,g(x,y)为输出图像,m为该模板中包含当前像素在内的像素总个数。
均值滤波本身存在着固有的缺陷,即它不能很好地保护图像细节,在去噪时图像的细节部分被破坏了,会得到模糊的图像,消除噪声点的效果不是很好。
(2)中值滤波
中值滤波法是一种非线性平滑技术,在一个窗内的所有像素点进行排序,得到的中间值作为当前像素点的像素值。能够有效抑制噪声,消除单个的噪声点。
假设一幅图片fxy),对它使用二维模板W进行中值滤波,则输出图像如式所示。
中值滤波法可以有效地消除椒盐噪声。
(3)高斯滤波
高斯滤波又称高斯模糊,高斯滤波后图像被平滑的程度取决于标准差。它的输出是领域像素的加权平均,同时离中心越近的像素权重越高。因此,相对于均值滤波,它的平滑效果更柔和,而且边缘保留的也更好。
二值化
图像阈值化分割是一种传统的最常用的图像分割方法,因其实现简单、计算量小、性能较稳定而成为图像分割中最基本和应用最广泛的分割技术。
设原始图像为f(x,y),按照一定的准则在f(x,y)中找到特征值T,将图像分割为两部分,得到二值图像g(x,y)如下式:
其中,T为阈值,bo、b分别分割后的图像两个像素值。若取: b。=0(黑),b= 1(白),即为0-1二值图像。
OpenCV中的函数接口为: void cvThreshold( ...)。
使用cvThreshold(dst,dst,130,255,Cv_THRESH_BINARY_INV)对图(a)分割得到图(b)。
在数字图像处理中,形态学处理是通过数字模板从图像中获取有用或去除无用图像细节的方法。图像形态学算法包括膨胀和腐蚀、开运算和闭运算等。腐蚀过程是通过锚点在图片上移动,每次取有效点中的最小值作为此时锚点的值。与腐蚀相反,膨胀过程是通过锚点在图片上移动,每次取有效点中的最大值作为此时锚点的值。开操作是先腐蚀、后膨胀处理。闭操作是先膨胀、后腐蚀处理。
滤波方法应用
OpenCV提供cvSmooth函数实现了图像平滑处理,该函数实现了中值滤波、高斯滤波、双边滤波三种滤波的方法。
cvSmooth 函数的接口为: void cvSmooth( const CvArrsrc,CvArr dst,intsmoothtype=CV_GAUSSIAN, int param1=3, int param2=0,double param3=0, doubleparam4=0 );
OpenCV提供了均值漂移分割算法,也可以通过设置输入参数用作滤波。它的函数接口为: void cvPyrMeanShifFiltering(const CvArrsrc,CvArr dst,double sp, double sr, intmax_level=1,CVTermCriteria termcrit = cvTermCriteria(CV_TERMCRIT_ITER + V_TERMCRIT_EPS,5,1))。
对加入椒盐噪声的的图像,分别用均值、中值、高斯、双线性和均值漂移滤波方法进行处理,结果如图所示。
形态学方法应用
在OpenCV中,已经封装好了这些函数的接口,在使用时可以直接调用。OpenCV中提供的腐蚀函数接口: cvErode(const CvArrsrc, CvArr dst, IplConvKernelelementCcV DEFAULT(NULL), int iterations CV_DEFAULT(1))。
膨胀函数的接口为: cvDilate(const CvArr src, CvArr* dst, IplConvKernel*elementcV DEFAULT(NULL), int iterations CV_DEFAULT(1))。
8.手势分割
复杂的背景和周围环境光照的改变,需要选择一种合适的颜色空间。该颜色空间要求对人体肤色具有很好的聚类性,且对光照的变化不敏感。手势分割的好坏对于特征的提取有很大的影响,有可能导致最后无法正确识别,所以,有效地分割方法是本章研究的重点。首先,从颜色空间着手,改进了一种基于HSV颜色空间的肤色分割模型,然后,又提出了一种动态阈值分割算法。还定义了一种图片间颜色距离,给出了肤色筛选算法和质心矩形扩展算法。
在3维颜色空间中使用阈值分割,是图像分割中常用的方法。这样的颜色空间有很多,如RGB、HSV、YUV等。颜色空间的选择,一般取决于数字化硬件和应用的对象(41]。在图像处理中,我们最熟悉的是RGB颜色空间。但是他在机器视觉的应用中存在严重缺陷。它的细节难以进行数字化调整。它将色调、亮度和饱和度三个量放在一起表示,很难分开。
而HSV颜色空间是为了更好进行数字化颜色处理而设计出来。由于这类空间已经去除了大多数显著地相关性,对肤色有较好的聚类性,因而可以采用阈值分割。
由于RGB颜色空间对光照过于敏感,R、G、B三个通道的相关性过强,它将色调、亮度和饱和度三个量放在一起表示,很难分开。所以进行肤色检测时,需要先转换颜色空间再进行肤色检测。通常将RGB转换到HSV或YUV(即 YCrCb):首先,HSV和YUV颜色空间对光照变化不敏感;其次,它们是为了更好进行数字化颜色处理而设计出来。由于这类空间已经去除了大多数显著地相关性,对肤色有较好的聚类性,因而可以采用恒定的阈值分割。
为了说明构造肤色模型对肤色分割的有效性,分别使用HS'V和H'S'V空间模型进行分割实验,如图所示。从图三的实验结果中可以看出HSV颜色空间模型的分割时有效的。
9.手势特征提取
手势特征提取是手势识别的关键步骤。特征提取的过程就是从手势图像中抽象出某个特定动作的特征,包括形状、轨迹、轮廓、方向、角度等。在进行特征提取时,需要对这些特征进行分析和处理,从而得到最佳特征。本章主要介绍了基于拓扑的特征提取、基于几何距的特征提取、基于傅立叶描绘子的特征提取、基于指尖检测的特征提取以及几何特征的提取,并提出了一种基于逼近轮廓缺陷圆的指尖检测算法。
手势特征提取概述
在得到有效的手势分割区域之后,下面就需要对手势图像进行特征提取,得到手势的有效描述。当选取手势的特征较合适时,才能准确的对手势进行识别。
在静态手势识别系统中,一般要求手形的特征矢量需要具有旋转、平移和缩放的不变性。即当手势绕中线旋转,或者放大缩小,或移动到另一个位置,手势的特征矢量的值都应当是保持一致的。也就是说,在同一个系统中同一个手势应该具有特征矢量的不变性。而在动态手势识别系统中,就没有这样的要求,因为旋转的手势可能会表示不同的含义。例如,手掌张开,向上或者向下旋转代表不同的含义。所以,在动态手势识别系统中,手势的特征矢量只需具有平移和缩放的不变性,不具有旋转性不变性。
手势的形状特征可由几何属性、统计属性和拓扑属性来描述,相对应的有基于几何距的特征提取、基于统计信息的特征提取、基于拓扑的特征提取。本章主要介绍基于拓扑的特征提取、基于几何矩特征的特征提取和基于傅立叶描述子的特征提取,最后,给出了一种指尖检测的方法。
基于拓扑的特征提取
基于拓扑的手势特征提取方法对比较简单的手势比较适用,但不能很好的应用于较复杂的手势识别系统。基于拓扑学的特征提取的算法如下:[46]
(1)将手势图像二值化后,找出该二值化图像中手势区域的手掌中心点;(2)以手掌的中心点作为圆心,半径为r作圆;
(3)在上一步找到的圆中,记录在这些圆上象素值发生变化的点,从О变为1或者从1变为0的点保存下来,即E={Pi = 1,2,3....., n};
(4)计算每两个点之间的距离,如果间距比域值要小,即D=|PP-|<s,那么认为这两点为噪声点,并删除这两点;
(5)重复步骤(2)~(4),直至r >w/2( w为手势区域的宽度)为止。
特征值的选取过程如下:通过执行以上步骤,对于所有的圆形路径,因为每个圆形路径在每个手指上会有两个交点,所以当前手势的手指的个数是上述步骤中得到的特征点个数的一半。找到特征点的个数BN最多的圆,可以得到其它两个特征值,分别为手腕的宽度BW,和手指到手腕的距离BD。这三个特征值BN、BW和BD可以用于手势识别的特征量,如图所示。
基于逼近轮廓缺陷圆的指尖检测
本文轮廓逼近算法采用的是D-P (Douglas-Peucker)算法。根据参考文献[53]它常用作曲线数据压缩算法,是一种垂距限值法,在数字化过程中,需要对曲线进行采样简化,即在曲线上取有限个点,将其变为折线,并且能够在一定程度上保持原有的形状。所以该算法对手势轮廓的多边形逼近后,能够得到一个较规则的多边形,从而有利于指尖的检测。
D-P算法描述如下:
Step1:设定阈值s 。
Step2:连接曲线首尾两点A、B,并作为两个基准点,得到一条曲线的弦,计算离弦最远的点C,并计算该点到弦的距离d。如果这个距离大于s,将点C记为第n个基准点,删除弦AB,可以得到两组基准顶点对,分别为第一个点和点C和将点C和最后一个点;如果所有点到曲线弦的距离都不大于s,则该弦作为曲线的逼近。
Step3:重复将Step2中得到的基准顶点对进行计算,直到所有点到他们的弦的距离都小于等于 。
由上面的过程可以看出,D-P算法是一个从整体到局部,
由浅入深的递归过程,对一般的变化平缓的曲线而言它具有良好的化简效果,且编程易于实现。
10.系统整合
参考博客《基于OpenCV的手势1~5识别系统(源码&环境部署)》
11.参考文献
[1]刘赟,孙炎辉,黄向荣,等.基于BP神经网络的手势识别系统[J].物联网技术.2013,(7).DOI:10.3969/j.issn.2095-1302.2013.07.008 .
[2]关然,徐向民,罗雅愉,等.基于计算机视觉的手势检测识别技术[J].计算机应用与软件.2013,(1).DOI:10.3969/j.issn.1000-386x.2013.01.038 .
[3]李博男,林凡.基于曲率的指尖检测方法[J].南京航空航天大学学报.2012,(4).DOI:10.3969/j.issn.1005-2615.2012.04.026 .
[4]袁里驰.基于改进的隐马尔科夫模型的词性标注方法[J].中南大学学报(自然科学版).2012,(8).
[5]邱迪.基于HSV与YCrCb颜色空间进行肤色检测的研究[J].电脑编程技巧与维护.2012,(10).DOI:10.3969/j.issn.1006-4052.2012.10.034 .
[6]杨家骏,郭远晴,魏诗云.时空数据压缩的基于 Douglas-Peucker 算法的改进与实现[J].计算机光盘软件与应用.2012,(7).176.
[7]依玉峰,高立群,郭丽.基于Mean Shift随机游走图像分割算法[J].计算机辅助设计与图形学学报.2011,(11).
[8]张争珍,石跃祥.YCgCr与YCgCb颜色空间的肤色检测[J].计算机工程与应用.2010,(34).DOI:10.3778/j.issn.1002-8331.2010.34.051 .
[9]李德启,王化喆.基于误差反向传播神经网络算法的分析与研究[J].濮阳职业技术学院学报.2010,(1).DOI:10.3969/j.issn.1672-9161.2010.01.042 .
[10]张增银,元昌安,胡建军,等.基于GEP和Baum-Welch算法训练HMM模型的研究[J].计算机工程与设计.2010,(9).