-
2021-10-17 16:31:40
import cv2 import matplotlib.pyplot as plt import numpy as np import time import os import mediapipe as mp mphands = mp.solutions.hands mpdraw = mp.solutions.drawing_utils wcap , hcap = 640 , 480 #定义摄像头的宽 和 高 cap = cv2.VideoCapture(0) cap.set(3,wcap) cap.set(4,hcap) pTime = 0 while True: ret , img = cap.read() imgRGB = cv2.cvtColor(img,cv2.COLOR_BGR2RGB) #转换rgb为bgr bgr mediapipe才可识别 with mphands.Hands(min_detection_confidence=0.65,min_tracking_confidence=0.65) as hands: result = hands.process(imgRGB) # print(result.multi_hand_landmarks) if result.multi_hand_landmarks: for hand_landmarks in result.multi_hand_landmarks: mpdraw.draw_landmarks(img,hand_landmarks,mphands.HAND_CONNECTIONS) cTime = time.time() fps = 1/(cTime - pTime) pTime = cTime cv2.putText(img,f'fps:{int(fps)}',(20,45),cv2.FONT_HERSHEY_COMPLEX,1,(255,0,0),2,8,0) cv2.imshow("image",img) if cv2.waitKey(1) == 27: break cap.release() #释放摄像头 cv2.destroyWindow() #窗口关闭
人丑不好意思哈哈哈哈
更多相关内容 -
基于Opencv手势识别系统_手势识别方案
2020-08-02 12:19:11基于 Opencv的手势识别系统分析 指导老师:鲁晓军 作者:毛晓洁 日期:2013.10.18 基于 Opencv的手势识别系统分析 系统研究背景 手势识别流程分析 系统总体结构 系统的设计与实现 总结 1.系统研究背景及应用 人机交互在... -
基于Opencv的手势识别源码
2021-04-10 16:03:27我是用的opencv自己获取的自己手势的数据集、测试集。 第二部分-dataset自定义数据集以及训练: 采用tensorflow 的dataset模块创建自己数据集和tensorflow.keras api实现模型的构建以及训练. 第三部分-模型预测与应用 -
基于Opencv手势识别系统汇编.ppt
2020-09-27 06:20:51基于 Opencv的手势识别系统分析 指导老师:鲁晓军 作者:毛晓洁 日期:2013.10.18 基于 Opencv的手势识别系统分析 系统研究背景 手势识别流程分析 系统总体结构 系统的设计与实现 总结 1.系统研究背景及应用 人机交互在... -
基于Opencv的手势识别
2018-04-27 16:30:38基于Opencv凸包检测的手势识别,使用训练好的XML文件,可以识别0,1,2,3,4,5,6,8等手势。 -
基于OPENCV手势识别的启蒙教育机器人的设计与实现.pdf
2021-08-14 07:03:56#资源达人分享计划# -
几个基于openCV开发的手势识别代码
2017-05-12 17:03:34一个实现石头剪刀布的小程序,基于opencv,在vs2010和opencv2.4.4下可以完美运行,另一个是一个根据手势播放音频的,还有一个提供参考 -
opencv手势识别与匹配
2015-04-26 19:40:16一种基于opencv模式识别的手势识别,能够识别手指个数, -
基于opencv的手势识别
2020-11-13 19:30:17基于opencv的手势识别,可以在一个窗口上显示手势和识别到底是哪个手势。 用C语言实现的。 基于opencv的手势识别,可以在一个窗口上显示手势和识别到底是哪个手势。 用C语言实现的。 -
基于OpenCV的数字手势识别算法
2017-12-09 13:51:26手势识别 基于OpenCV的数字手势识别算法 -
opencv手势识别(大作业).zip
2020-03-01 14:00:05基于Python的OpenCV手势识别程序代码+文档+演示视频,利用图像数据处理,用Python编写的手势识别小游戏 -
基于OpenCV的手势识别完整项目(Python3.7)
2019-08-09 08:52:41这是我的本科毕设题目,刚开始接触机器学习这方面,感谢CSDN和GitHub上的大佬,...项目简介:基于Win10 + Python3.7的环境,利用Python的OpenCV、Sklearn和PyQt5等库搭建了一个较为完整的手势识别系统,用于识别日...这是我的本科毕设题目,刚开始接触机器学习这方面,感谢CSDN和GitHub上的大佬,网上类似项目很多,方法也有很多,自己顺带进行了整理,边做毕设边分享一下自己学习心得吧,也算是梳理一下所学知识,各大佬有什么好的建议还请指出,不吝赐教。
项目简介:基于Win10 + Python3.7的环境,利用Python的OpenCV、Sklearn和PyQt5等库搭建了一个较为完整的手势识别系统,用于识别日常生活中1-10的静态手势。
整个项目的资源:https://download.csdn.net/download/qq_41562704/11471042(包含手势库和已训练的模型,可以直接运行使用)
环境:Win10 + Python3.7 + OpenCV3.4.5,各个库的安装就不多说了
最终的效果图如图所示:
整个项目分为四个部分,即预处理,特征提取,模型训练,界面设计
预处理
这部分需要完成摄像头录制手势后,提取出手的轮廓线
这部分参考资料:
https://blog.csdn.net/ifruoxi/article/details/78091954(获取手势,基于Python)
https://blog.csdn.net/qq_22527639/article/details/81501565(肤色检测:方法全,理论介绍的也很全面,基于C++)
https://blog.csdn.net/shadow_guo/article/details/43602051(基于RGB空间肤色检测,基于Python)
https://blog.csdn.net/weixin_40893939/article/details/84527037(基于HSV空间和YCrCb空间肤色检测,基于Python)
https://blog.csdn.net/Eastmount/article/details/83581277(腐蚀膨胀理论介绍,基于Python)
https://blog.csdn.net/dz4543/article/details/80655067(轮廓提取,基于Python)
1.获取手势
主要是调用OpenCV,创建main.py和 picture.py
main.py 当前负责录像,picture负责处理图像
main.py
import cv2 import picture as pic font = cv2.FONT_HERSHEY_SIMPLEX #设置字体 size = 0.5 #设置大小 width, height = 300, 300 #设置拍摄窗口大小 x0,y0 = 300, 100 #设置选取位置 cap = cv2.VideoCapture(0) #开摄像头 if __name__ == "__main__": while(1): ret, frame = cap.read() #读取摄像头的内容 frame = cv2.flip(frame, 2) roi = pic.binaryMask(frame, x0, y0, width, height) #取手势所在框图并进行处理 key = cv2.waitKey(1) & 0xFF#按键判断并进行一定的调整 #按'j''l''u''j'分别将选框左移,右移,上移,下移 #按'q'键退出录像 if key == ord('i'): y0 += 5 elif key == ord('k'): y0 -= 5 elif key == ord('l'): x0 += 5 elif key == ord('j'): x0 -= 5 if key == ord('q'): break cv2.imshow('frame', frame) #播放摄像头的内容 cap.release() cv2.destroyAllWindows() #关闭所有窗口
2.图像预处理
预处理在picture.py中完成。
预处理的主要步骤为:去噪 -> 肤色检测 -> 二值化 -> 形态学处理 -> 轮廓提取,其中最麻烦的两项为肤色检测和轮廓提取。
2.1去噪
即滤波,主要是为了实现对图像噪声的消除,增强图像的效果,其实个人感觉这里滤波的作用不是很明显,也可以选择不滤波,在肤色检测后会有二次滤波。
#以3*3的模板进行均值滤波 blur = cv2.blur(roi, (3,3)) #以3*3的模板进行高斯滤波,最后一个参数表示x与y方向的标准差,给0的话,函数会自己运算 blur = cv2.GaussianBlur(roi, (3,3), 0) #中值滤波 blur = cv2.medianBlur(roi,5) #双边滤波,9为区域的直径,后面两个参数是空间高斯函数标准差和灰度值相似性高斯函数标准差 blur = cv2.bilateralFilter(img,9,75,75)
均值滤波器、高斯滤波器、中值滤波器、双边滤波器都可以进行使用。推荐使用双边滤波器,该滤波器考虑了图像的空间关系,也考虑图像的灰度关系。双边滤波同时使用了空间高斯权重和灰度相似性高斯权重,确保了边界不会被模糊掉。不过我在处理中直接省去了去噪这个过程。
2.2 肤色检测 + 二值化处理
picture.py
方法一:基于RGB颜色空间
判断条件:
在均匀光照下,R>95 AND G>40 B>20 AND MAX(R,G,B)-MIN(R,G,B)>15 AND ABS(R-G)>15 AND R>G AND R>B;
在侧光拍摄环境下,R>220 AND G>210 AND B>170 AND ABS(R-G)<=15 AND R>B AND G>B
import cv2 import numpy as np def binaryMask(frame, x0, y0, width, height): cv2.rectangle(frame,(x0,y0),(x0+width, y0+height),(0,255,0)) #画出截取的手势框图 roi = frame[y0:y0+height, x0:x0+width] #获取手势框图 cv2.imshow("roi", roi) #显示手势框图 res = skinMask(roi) #进行肤色检测 cv2.imshow("res", res) #显示肤色检测后的图像 return res ##########方法一################### ##########BGR空间的手势识别######### def skinMask(roi): rgb = cv2.cvtColor(roi, cv2.COLOR_BGR2RGB) #转换到RGB空间 (R,G,B) = cv2.split(rgb) #获取图像每个像素点的RGB的值,即将一个二维矩阵拆成三个二维矩阵 skin = np.zeros(R.shape, dtype = np.uint8) #掩膜 (x,y) = R.shape #获取图像的像素点的坐标范围 for i in range(0, x): for j in range(0, y): #判断条件,不在肤色范围内则将掩膜设为黑色,即255 if (abs(R[i][j] - G[i][j]) > 15) and (R[i][j] > G[i][j]) and (R[i][j] > B[i][j]): if (R[i][j] > 95) and (G[i][j] > 40) and (B[i][j] > 20) \ and (max(R[i][j],G[i][j],B[i][j]) - min(R[i][j],G[i][j],B[i][j]) > 15): skin[i][j] = 255 elif (R[i][j] > 220) and (G[i][j] > 210) and (B[i][j] > 170): skin[i][j] = 255 res = cv2.bitwise_and(roi,roi, mask = skin) #图像与运算 return res
效果图:
方法二:基于HSV颜色空间
判断条件:0<=H<=20,S>=48,V>=50
肤色检测的方式不同影响的是skinMask,之后的代码只是修改skinMask函数,picture.py中其他代码不需要改动。
##########方法二################### ########HSV颜色空间H范围筛选法###### def skinMask(roi): low = np.array([0, 48, 50]) #最低阈值 high = np.array([20, 255, 255]) #最高阈值 hsv = cv2.cvtColor(roi, cv2.COLOR_BGR2HSV) #转换到HSV空间 mask = cv2.inRange(hsv,low,high) #掩膜,不在范围内的设为255 res = cv2.bitwise_and(roi,roi, mask = mask) #图像与运算 return res
效果图:
方法三:椭圆肤色检测模型
在YCrCb空间,肤色像素点会聚集到一个椭圆区域。先定义一个椭圆模型,然后将每个RGB像素点转换到YCrCb空间比对是否在椭圆区域,是的话判断为皮肤。
##########方法三################### #########椭圆肤色检测模型########## def skinMask(roi): skinCrCbHist = np.zeros((256,256), dtype= np.uint8) cv2.ellipse(skinCrCbHist, (113,155),(23,25), 43, 0, 360, (255,255,255), -1) #绘制椭圆弧线 YCrCb = cv2.cvtColor(roi, cv2.COLOR_BGR2YCR_CB) #转换至YCrCb空间 (y,Cr,Cb) = cv2.split(YCrCb) #拆分出Y,Cr,Cb值 skin = np.zeros(Cr.shape, dtype = np.uint8) #掩膜 (x,y) = Cr.shape for i in range(0, x): for j in range(0, y): if skinCrCbHist [Cr[i][j], Cb[i][j]] > 0: #若不在椭圆区间中 skin[i][j] = 255 res = cv2.bitwise_and(roi,roi, mask = skin) return res
效果图:
方法四:YCrCb颜色空间的Cr分量+Otsu法阈值分割算法
针对YCrCb中Cr分量的处理,对CR通道单独进行Otsu处理,Otsu方法opencv里用threshold,Otsu算法是对图像的灰度级进行聚类。
################方法四#################### ####YCrCb颜色空间的Cr分量+Otsu法阈值分割算法 def skinMask(roi): YCrCb = cv2.cvtColor(roi, cv2.COLOR_BGR2YCR_CB) #转换至YCrCb空间 (y,cr,cb) = cv2.split(YCrCb) #拆分出Y,Cr,Cb值 cr1 = cv2.GaussianBlur(cr, (5,5), 0) _, skin = cv2.threshold(cr1, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) #Ostu处理 res = cv2.bitwise_and(roi,roi, mask = skin) return res
效果图:
方法五:Cr,Cb范围筛选法
该方法与方法一、二类似,不同的只是颜色空间不相同
判断条件:133<=Cr<=173 77<=Cb<=127
##########方法五################### ########Cr,Cb范围筛选法########### def skinMask(roi): YCrCb = cv2.cvtColor(roi, cv2.COLOR_BGR2YCR_CB) #转换至YCrCb空间 (y,cr,cb) = cv2.split(YCrCb) #拆分出Y,Cr,Cb值 skin = np.zeros(cr.shape, dtype = np.uint8) (x,y) = cr.shape for i in range(0, x): for j in range(0, y): #每个像素点进行判断 if(cr[i][j] > 130) and (cr[i][j] < 175) and (cb[i][j] > 77) and (cb[i][j] < 127): skin[i][j] = 255 res = cv2.bitwise_and(roi,roi, mask = skin) return res
效果图:
方法六:OpenCV自带AdaptiveSkinDetector
关于该函数的使用可以参考http://www.cnblogs.com/tornadomeet/archive/2012/11/20/2778740.html
最终方案选择:在几种方式中选择效果比较好的,RGB和HSV的效果一般,而且曝光的话,效果更差,YCrCb是一个单独把亮度分离开来的颜色模型,使用这个颜色模型的话,像肤色不会受到光线亮度而发生改变,方法三和四均可。
2.3 形态学处理
即便是比较好的肤色检测算法,分割出来的手势,也难免有黑点,或者背景有白点,这时候需要对分割出来的手势图进行进一步处理,主要是腐蚀膨胀两个操作。
腐蚀和膨胀是针对白色部分(高亮部分而言)。从数学角度来说,膨胀或者腐蚀操作就是将图像(或图像的一部分区域,称之为A)与核(称之为B)进行卷积。
膨胀就是求局部最大值操作,即计算核B覆盖的区域的像素点的最大值,并把这个最大值赋值给参考点指定的像素,这样就会使图像中的高亮区域逐渐增长。
腐蚀就是求局部最小值操作,即计算核B覆盖的区域的像素点的最小值,并把这个最小值赋值给参考点指定的像素,这样就会使图像中的高亮区域逐渐减少。开运算:先腐蚀后膨胀,去除孤立的小点,毛刺
闭运算:先膨胀后腐蚀,填平小孔,弥合小裂缝
在binaryMask函数中return前面添加以下代码,进行开运算
kernel = np.ones((3,3), np.uint8) #设置卷积核 erosion = cv2.erode(res, kernel) #腐蚀操作 cv2.imshow("erosion",erosion) dilation = cv2.dilate(erosion, kernel)#膨胀操作 cv2.imshow("dilation",dilation)
效果如图:
可以看到背景杂质点去掉了
2.4 轮廓提取
在binaryMask函数中return前面添加以下代码,对肤色检测后的图像提取手势区域
binaryimg = cv2.Canny(res, 50, 200) #二值化,canny检测 h = cv2.findContours(binaryimg,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE) #寻找轮廓 contours = h[1] #提取轮廓 ret = np.ones(res.shape, np.uint8) #创建黑色幕布 cv2.drawContours(ret,contours,-1,(255,255,255),1) #绘制白色轮廓 cv2.imshow("ret", ret)
特征提取
这部分主要任务是对第一部分提取的轮廓点坐标提取出他们的傅里叶描述子,建立手势特征库
参考资料:
https://github.com/timfeirg/Fourier-Descriptors(提取特征代码比较完整)
https://github.com/alessandroferrari/elliptic-fourier-descriptors(椭圆傅里叶描述子的提取)
https://www.cnblogs.com/edie0902/p/3658174.html(傅里叶算子的数学思想)
3 傅里叶算子提取
将picture.py中的提取轮廓点部分删去,添加
import fourierDescriptor as fd ret, fourier_result = fd.fourierDesciptor(res)
创建fourierDescriptor.py
在这个文件中完成对轮廓点坐标的傅里叶描述子的提取,具体代码如下:
import cv2 import numpy as np MIN_DESCRIPTOR = 32 # surprisingly enough, 2 descriptors are already enough ##计算傅里叶描述子 def fourierDesciptor(res): #Laplacian算子进行八邻域检测 gray = cv2.cvtColor(res, cv2.COLOR_BGR2GRAY) dst = cv2.Laplacian(gray, cv2.CV_16S, ksize = 3) Laplacian = cv2.convertScaleAbs(dst) contour = find_contours(Laplacian)#提取轮廓点坐标 contour_array = contour[0][:, 0, :]#注意这里只保留区域面积最大的轮廓点坐标 ret_np = np.ones(dst.shape, np.uint8) #创建黑色幕布 ret = cv2.drawContours(ret_np,contour[0],-1,(255,255,255),1) #绘制白色轮廓 contours_complex = np.empty(contour_array.shape[:-1], dtype=complex) contours_complex.real = contour_array[:,0]#横坐标作为实数部分 contours_complex.imag = contour_array[:,1]#纵坐标作为虚数部分 fourier_result = np.fft.fft(contours_complex)#进行傅里叶变换 #fourier_result = np.fft.fftshift(fourier_result) descirptor_in_use = truncate_descriptor(fourier_result)#截短傅里叶描述子 #reconstruct(ret, descirptor_in_use) return ret, descirptor_in_use def find_contours(Laplacian): #binaryimg = cv2.Canny(res, 50, 200) #二值化,canny检测 h = cv2.findContours(Laplacian,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE) #寻找轮廓 contour = h[1] contour = sorted(contour, key = cv2.contourArea, reverse=True)#对一系列轮廓点坐标按它们围成的区域面积进行排序 return contour #截短傅里叶描述子 def truncate_descriptor(fourier_result): descriptors_in_use = np.fft.fftshift(fourier_result) #取中间的MIN_DESCRIPTOR项描述子 center_index = int(len(descriptors_in_use) / 2) low, high = center_index - int(MIN_DESCRIPTOR / 2), center_index + int(MIN_DESCRIPTOR / 2) descriptors_in_use = descriptors_in_use[low:high] descriptors_in_use = np.fft.ifftshift(descriptors_in_use) return descriptors_in_use ##由傅里叶描述子重建轮廓图 def reconstruct(img, descirptor_in_use): #descirptor_in_use = truncate_descriptor(fourier_result, degree) #descirptor_in_use = np.fft.ifftshift(fourier_result) #descirptor_in_use = truncate_descriptor(fourier_result) #print(descirptor_in_use) contour_reconstruct = np.fft.ifft(descirptor_in_use) contour_reconstruct = np.array([contour_reconstruct.real, contour_reconstruct.imag]) contour_reconstruct = np.transpose(contour_reconstruct) contour_reconstruct = np.expand_dims(contour_reconstruct, axis = 1) if contour_reconstruct.min() < 0: contour_reconstruct -= contour_reconstruct.min() contour_reconstruct *= img.shape[0] / contour_reconstruct.max() contour_reconstruct = contour_reconstruct.astype(np.int32, copy = False) black_np = np.ones(img.shape, np.uint8) #创建黑色幕布 black = cv2.drawContours(black_np,contour_reconstruct,-1,(255,255,255),1) #绘制白色轮廓 cv2.imshow("contour_reconstruct", black) #cv2.imwrite('recover.png',black) return black
这里需要注意:
轮廓提取后进行了二次去噪,即只保留区域面积最大的曲线,效果如图
其次关于利用傅里叶算子重建轮廓图,在实际使用过程中并不需要,仅仅为了在测试阶段检验效果
取32项傅里叶算子的时候,重建效果如下,基本可以还原手势形状。
4 建立特征库
这个部分的任务是采集手势1-10,同时利用旋转平移等操作对得到的手势库进行扩充。然后对整个手势库中的每张照片中的手势轮廓线计算傅里叶描述子并保存。我采取的方案是每个手势采集20份,然后扩充为200份,这里的数目可以自己调节。保存格式为"x_i",表示手势_x的第i张图片
4.1 数据增强
在数据增强前,先采集手势,在项目文件夹中创建一个“image”文件夹保存样本库,"test_image"保存测试库(看需求,可以不用)
创建data_augmention.py
对测试库进行操作的时候仅仅需要修改一下path及相关的数字
import random import cv2 path = './' + 'image' + '/' #旋转 def rotate(image, scale=0.9): angle = random.randrange(-90, 90)#随机角度 w = image.shape[1] h = image.shape[0] #rotate matrix M = cv2.getRotationMatrix2D((w/2,h/2), angle, scale) #rotate image = cv2.warpAffine(image,M,(w,h)) return image if __name__ == "__main__": for i in range(5, 6): cnt = 21#计数 for j in range(1, 21): roi = cv2.imread(path + str(i) + '_' + str(j)+'.png') for k in range(12): img_rotation = rotate(roi)#旋转 cv2.imwrite(path + str(i) + '_' + str(cnt)+ '.png',img_rotation) cnt += 1 img_flip = cv2.flip(img_rotation,1)#翻转 cv2.imwrite(path + str(i) + '_' + str(cnt)+ '.png',img_flip) cnt += 1 print(i,'_',j,'完成')
4.2 计算手势库的特征
创建loadData.py文件
import fourierDescriptor as fd import cv2 import numpy as np path = './' + 'feature' + '/' path_img = './' + 'image' + '/' if __name__ == "__main__": for i in range(1, 11): for j in range(1, 201): roi = cv2.imread(path_img + str(i) + '_' + str(j) + '.png') descirptor_in_use = abs(fd.fourierDesciptor(roi)) fd_name = path + str(i) + '_' + str(j) + '.txt' # fd_name = path + str(i) + '.txt' with open(fd_name, 'w', encoding='utf-8') as f: temp = descirptor_in_use[1] for k in range(1, len(descirptor_in_use)): x_record = int(100 * descirptor_in_use[k] / temp) f.write(str(x_record)) f.write(' ') f.write('\n') print(i, '_', j, '完成')
对手势库中10个手势的200份图片一一进行操作,保存的特征的格式与图片格式一致,用txt文件进行保存
模型训练
这个部分的主要任务是利用已有的样本库训练SVM模型并保存
这部分参考资料:
https://cuijiahua.com/blog/2017/11/ml_8_svm_1.html(SVM的原理)
https://cuijiahua.com/blog/2017/11/ml_9_svm_2.html(sklearn的使用)
https://blog.csdn.net/qysh123/article/details/80063447(SVM调参)
5 训练SVM模型
使用网格搜索法进行调参,利用joblib模块保存模型
import numpy as np from os import listdir from sklearn.externals import joblib from functools import reduce from sklearn.svm import SVC from sklearn.model_selection import GridSearchCV import matplotlib.pyplot as plt path = './' + 'feature' + '/' model_path = "./model/" test_path = "./test_feature/" test_accuracy = [] #读txt文件并将每个文件的描述子改为一维的矩阵存储 def txtToVector(filename, N): returnVec = np.zeros((1,N)) fr = open(filename) lineStr = fr.readline() lineStr = lineStr.split(' ') for i in range(N): returnVec[0, i] = int(lineStr[i]) return returnVec def tran_SVM(N): svc = SVC() parameters = {'kernel':('linear', 'rbf'), 'C':[1, 3, 5, 7, 9, 11, 13, 15, 17, 19], 'gamma':[0.00001, 0.0001, 0.001, 0.1, 1, 10, 100, 1000]}#预设置一些参数值 hwLabels = []#存放类别标签 trainingFileList = listdir(path) m = len(trainingFileList) trainingMat = np.zeros((m,N)) for i in range(m): fileNameStr = trainingFileList[i] classNumber = int(fileNameStr.split('_')[0]) hwLabels.append(classNumber) trainingMat[i,:] = txtToVector(path+fileNameStr,N)#将训练集改为矩阵格式 print("数据加载完成") clf = GridSearchCV(svc, parameters, cv=5, n_jobs=8)#网格搜索法,设置5-折交叉验证 clf.fit(trainingMat,hwLabels) print(clf.return_train_score) print(clf.best_params_)#打印出最好的结果 best_model = clf.best_estimator_ print("SVM Model save...") save_path = model_path + "svm_efd_" + "train_model.m" joblib.dump(best_model,save_path)#保存最好的模型 def test_SVM(clf,N): testFileList = listdir(test_path) errorCount = 0#记录错误个数 mTest = len(testFileList) for i in range(mTest): fileNameStr = testFileList[i] classNum = int(fileNameStr.split('_')[0]) vectorTest = txtToVector(test_path+fileNameStr,N) valTest = clf.predict(vectorTest) #print("分类返回结果为%d\t真实结果为%d" % (valTest, classNum)) if valTest != classNum: errorCount += 1 print("总共错了%d个数据\n错误率为%f%%" % (errorCount, errorCount/mTest * 100)) ####训练 + 验证##### if __name__ == "__main__": tran_SVM(31) clf = joblib.load(model_path + "svm_efd_" + "train_model.m") test_SVM(clf,31)
训练结果如图:
界面设计
字面意思,这部分的任务就是设计一个界面可以实时调用已经训练好的模型预测手势
小声嘀咕一句:原来Python有这么好用的写界面的库呀(。-ω-)zzz
这个部分也不是必须的,只不过为了稍微好看那么一点,可以直接修改main.py,使得按p的时候就进行预测
import classfier as cf ...... elif key == ord('p'): descirptor_in_use = abs(fourier_result) fd_test = np.zeros((1,31)) temp = descirptor_in_use[1] for k in range(1,len(descirptor_in_use)): fd_test[0,k-1] = int(100 * descirptor_in_use[k] / temp) test_svm = cf.test_fd(fd_test) print("test_svm =",test_svm) test_svm_efd = cf.test_efd(efd_test) print("test_svm_efd =",test_svm_efd) cv2.imshow('frame', frame) #播放摄像头的内容
这部分参考资料:
https://blog.csdn.net/niuyongjie/article/details/81161559(PyQt安装,我安装的是5.11.3版本)
http://code.py40.com/pyqt5/16.html(PyQt教程)
6 PyQt设计界面
创建myGUI.py文件
第一次用PyQt这个库,所以尺寸方面的控制用来最古老的方式(数字控制)还请大家见谅(╥╯^╰╥)
import sys from PyQt5.QtWidgets import QApplication, QWidget, QToolTip, \ QPushButton,QMessageBox,QDesktopWidget, QLabel from PyQt5.QtGui import QFont,QIcon,QPixmap,QImage from PyQt5.QtCore import QTimer import cv2 import picture as pic import classify as cf import numpy as np class myWindow(QWidget): def __init__(self,parent = None): super(myWindow,self).__init__(parent) self.timer_camera = QTimer() self.cap = cv2.VideoCapture() self.initUI() self.slot_init() def initUI(self): self.mylabel() self.myButton() self.myLabelPic() self.setFixedSize(670,520) self.center() self.setWindowIcon(QIcon('icon.jpg')) self.setWindowTitle('gesture recognition') def mylabel(self): label_roi = QLabel('原图',self) label_roi.setStyleSheet("QLabel{font-size:18px;}") label_roi.resize(60,30) label_roi.move(120,15) label_res = QLabel('轮廓线', self) label_res.setStyleSheet("QLabel{font-size:18px;}") label_res.resize(60, 30) label_res.move(480, 15) label_pre = QLabel('预测', self) label_pre.setStyleSheet("QLabel{font-size:20px;}") label_pre.resize(50,30) label_pre.move(400,400) label_result = QLabel('结果', self) label_result.setStyleSheet("QLabel{font-size:20px;}") label_result.resize(50, 30) label_result.move(400,430) def myLabelPic(self): self.label_show_roi = QLabel(self) self.label_show_roi.setFixedSize(301,301) self.label_show_roi.move(20,50) self.label_show_roi.setStyleSheet("QLabel{background:white;}") self.label_show_roi.setAutoFillBackground(True) self.label_show_ret = QLabel(self) self.label_show_ret.setFixedSize(301, 301) self.label_show_ret.move(350, 50) self.label_show_ret.setStyleSheet("QLabel{background:white;}") self.label_show_ret.setAutoFillBackground(True) self.label_show_recognition = QLabel('0',self) self.label_show_recognition.setStyleSheet("QLabel{background:white;}") self.label_show_recognition.setStyleSheet("QLabel{font-size:50px;}") self.label_show_recognition.setFixedSize(100,100) self.label_show_recognition.move(500, 380) self.label_show_recognition.setAutoFillBackground(True) def myButton(self): QToolTip.setFont(QFont('SansSerif', 10)) self.button_open_camera = QPushButton('打开相机', self) self.button_open_camera.setToolTip('按i,k,j,l可以进行上下左右调整') self.button_open_camera.resize(100,30) self.button_open_camera.move(100, 400) self.butoon_recognition = QPushButton('开始预测', self) self.butoon_recognition.setFixedSize(100, 30) self.butoon_recognition.move(100, 450) def slot_init(self): self.button_open_camera.clicked.connect(self.button_open_camera_click) self.butoon_recognition.clicked.connect(self.button_recognition_click) self.timer_camera.timeout.connect(self.show_camera) def button_open_camera_click(self): if self.timer_camera.isActive() == False: self.cap.open(0) self.timer_camera.start(30) self.button_open_camera.setText(u'关闭相机') else: self.timer_camera.stop() self.cap.release() self.label_show_roi.clear() self.label_show_ret.clear() self.label_show_recognition.setText('0') self.button_open_camera.setText(u'打开相机') def button_recognition_click(self): descirptor_in_use = abs(self.fourier_result) fd_test = np.zeros((1, 31)) temp = descirptor_in_use[1] for k in range(1, len(descirptor_in_use)): fd_test[0, k - 1] = int(100 * descirptor_in_use[k] / temp) efd_test = np.zeros((1, 15)) for k in range(1, len(self.efd_result)): temp = np.sqrt(self.efd_result[k][0] ** 2 + self.efd_result[k][1] ** 2) + np.sqrt( self.efd_result[k][2] ** 2 + self.efd_result[k][3] ** 2) efd_test[0, k - 1] = (int(1000 * temp)) test_knn, test_svm = cf.test_fd(fd_test) print("test_knn =", test_knn) print("test_svm =", test_svm) test_knn_efd, test_svm_efd = cf.test_efd(efd_test) print("test_knn_efd =", test_knn_efd) print("test_svm_efd =", test_svm_efd) num = [0]*11 num[test_knn[0]] += 1 num[test_svm[0]] += 1 num[test_knn_efd[0]] += 1 num[test_svm_efd[0]] += 1 res = 0 for i in range(1, 11): if num[i] >= 2: res = i break print(res) self.label_show_recognition.setText(str(res)) def show_camera(self): width, height = 300, 300 # 设置拍摄窗口大小 x0, y0 = 300, 100 # 设置选取位置 flag, frame = self.cap.read() roi, res, ret, self.fourier_result, self.efd_result = pic.binaryMask(frame, x0, y0, width, height) roi = cv2.cvtColor(roi, cv2.COLOR_BGR2RGB) show_roi = QImage(roi.data, roi.shape[1], roi.shape[0], QImage.Format_RGB888) show_ret = QImage(ret.data, ret.shape[1], ret.shape[0], QImage.Format_Grayscale8) self.label_show_roi.setPixmap(QPixmap.fromImage(show_roi)) self.label_show_ret.setPixmap(QPixmap.fromImage(show_ret)) def closeEvent(self, QCloseEvent): reply = QMessageBox.question(self, 'Message',"Are you sure to quit?", QMessageBox.Yes |QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: if self.cap.isOpened(): self.cap.release() if self.timer_camera.isActive(): self.timer_camera.stop() QCloseEvent.accept() else: QCloseEvent.ignore() def center(self): qr = self.frameGeometry() cp = QDesktopWidget().availableGeometry().center() qr.moveCenter(cp) self.move(qr.topLeft()) if __name__ == "__main__": app = QApplication(sys.argv) win = myWindow() win.show() sys.exit(app.exec())
后面几个部分由于时间关系,讲得不如前面的详细,向大家表示歉意,一些细节部分建议大家下载我的源码来看。有问题的地方欢迎讨论~源码中另外也用了椭圆傅里叶描述子作为特征。
-
基于OpenCV的手势识别(剪刀、石头、布).rar
2021-03-14 13:14:59基于OpenCV实现的手势识别,可识别出剪刀石头布.资源中包含实现代码思路,源代码以及效果图. -
基于OpenCV的简易实时手势识别(含代码)
2022-02-02 15:14:46基于OpenCV的简易实时手势识别1.基本信息介绍1.1实验步骤1.2效果展示2.肤色检测+二值化+开运算+高斯模糊2.1 flip()函数原型2.2cvtColor()函数原型2.3split()函数原型2.4GaussianBlur()函数原型2.5Code3.连通空心部分...基于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 椭圆结构元素,矩形的内接椭圆元素为17.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 -
OpenCV+python手势识别框架和简单例子
2019-04-15 16:01:20OpenCV+python手势识别框架和简单例子 基于 静默虚空 大神文章的原码修改调通的。https://blog.csdn.net/linsk/article/details/76457955 -
python基于opencv手势识别上的图像处理(基本步骤总结与案例呈现)
2021-07-31 11:04:49当初选择opencv这个库是因为感觉它较skimage对新手比较友好,现在在学图像识别之前想把手势识别再看一遍,且思且记,以便日后复习。 好了废话不多说,上干货! 首先,我们要对手势识别的基本步骤做一个了解:打开...前言:在各类的智能识别中,手势识别是比较简单的一种了。本人大二,在大一下学期做了一个简单的树莓派摄像头手势识别的程序。当初选择opencv这个库是因为感觉它较skimage对新手比较友好,现在在学图像识别之前想把手势识别再看一遍,且思且记,以便日后复习。
好了废话不多说,上干货!
首先,我们要对手势识别的基本步骤做一个了解:打开摄像头 ——>截取图像手势——>图像处理——>手势模型匹配预测——>给出判断。再本篇博客中我重点介绍的是图像处理这个环节,因为这是最基本的,手势模型预测可能会涉及机器学习(还是有点难度的)。大家可以参考这一篇博客:https://cuijiahua.com/blog/2017/11/ml_8_svm_1.html
第一步:去噪即滤波,因为我们截取的图像可能会包含很多无用的干扰信息,所以这一步是对图像进行预处理。代码块如下:
blur = cv2.bilateralFilter(img,9,75,75)
主要函数方法介绍:1.cv2的bilateral Filter方法含有四个参数,img是图像,9代表处理面积,75是空间高斯标准差和相似性高斯函数标准差(这边至于为啥是75应该是个数据我们不在深究)
效果图:
第二步:二值化黑白处理
def binaryMask(frame,x0,y0,width,height): cv2.rectangle(frame,(x0,y0),(x0+width,y0+height),(0,255,0)) roi = frame[y0:y0+height,x0:x0+width] cv2.imshow("roi",roi)#读取roi文件 res = skinMask(roi) cv2.imshow("res",res)
这边定义一个函数,有四个参数分别是:frame要处理的图像,x0和y0要处理图片的左上角的坐标,width和heignt是你要处理图片的长和宽。该函数的作用就是绘制一个手势框图来获取最初的手势图像。
主要函数方法介绍:1.rectangle绘制一个矩形区域即绘制手势框图。
2.fream绘制手势框图。
3.imshow显示手势框图。
第三步:肤色检测,所谓肤色检测顾名思义就是把人皮肤的颜色的区域保留,不是人皮肤颜色的区域掩膜成黑色。
具体的代码如下:
def skinMask(roi): rgb = cv2.cvtColor(roi, cv2.COLOR_BGR2RGB) #转换到RGB空间 (R,G,B) = cv2.split(rgb) #获取图像每个像素点的RGB的值,即将一个二维矩阵拆成三个二维矩阵 skin = np.zeros(R.shape, dtype = np.uint8) #掩膜 (x,y) = R.shape #获取图像的像素点的坐标范围 for i in range(0, x): for j in range(0, y): #判断条件,不在肤色范围内则将掩膜设为黑色,即255 if (abs(R[i][j] - G[i][j]) > 15) and (R[i][j] > G[i][j]) and (R[i][j] > B[i][j]): if (R[i][j] > 95) and (G[i][j] > 40) and (B[i][j] > 20) \ and (max(R[i][j],G[i][j],B[i][j]) - min(R[i][j],G[i][j],B[i][j]) > 15): skin[i][j] = 255 elif (R[i][j] > 220) and (G[i][j] > 210) and (B[i][j] > 170): skin[i][j] = 255 res = cv2.bitwise_and(roi,roi, mask = skin) #图像与运算 return res
定义函数skinMask参数为roi,想用numpy中的zeros方法返回一个给定形态和类型的用0填充的数组,YCrCb则是返回的roi把它转换到RGB空间。因为imread读取的图片是以BGR的形式的。
这边我提一句奥,那边if里面的条件判断语句是在一般的光线下,所有的的RGB特定数据,也就是说在强光或者是很暗的环境下手势识别的效果可能没有那么好。
一般光照条件下的判断条件:R>95 AND G>40 B>20 AND MAX(R,G,B)-MIN(R,G,B)>15 AND ABS(R-G)>15 AND R>G AND R>B
在侧光拍摄环境下,R>220 AND G>210 AND B>170 AND ABS(R-G)<=15 AND R>B AND G>B
效果图:
第四步:轮廓提取
camera = cv2.VideoCapture(0) camera.set(10, 150) while camera.isOpened(): ret, frame = camera.read() threshold = cv2.getTrackbarPos('trh1', 'trackbar') frame = cv2.bilateralFilter(frame, 5, 50, 100) # smoothing filter frame = cv2.flip(frame, 1) # flip the frame horizontally cv2.rectangle(frame, (int(cap_region_x_begin * frame.shape[1]), 0), (frame.shape[1], int(cap_region_y_end * frame.shape[0])), (255, 0, 0), 2) cv2.imshow('original', frame) # Main operation if isBgCaptured == 1: # this part wont run until background captured img = removeBG(frame) img = img[0:int(cap_region_y_end * frame.shape[0]), int(cap_region_x_begin * frame.shape[1]):frame.shape[1]] # clip the ROI # cv2.imshow('mask', img) # convert the image into binary image gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) blur = cv2.GaussianBlur(gray, (blurValue, blurValue), 0) # cv2.imshow('blur', blur) ret, thresh = cv2.threshold(blur, threshold, 255, cv2.THRESH_BINARY) # cv2.imshow('ori', thresh) # get the coutours thresh1 = copy.deepcopy(thresh) contours, hierarchy = cv2.findContours(thresh1, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) length = len(contours) maxArea = -1 if length > 0: for i in range(length): # find the biggest contour (according to area) temp = contours[i] area = cv2.contourArea(temp) if area > maxArea: maxArea = area ci = i res = contours[ci] hull = cv2.convexHull(res) drawing = np.zeros(img.shape, np.uint8) cv2.drawContours(drawing, [res], 0, (0, 255, 0), 2) cv2.drawContours(drawing, [hull], 0, (0, 0, 255), 3) cv2.imshow('output', drawing)
效果图:
总结:实现了简单的图像处理和轮廓绘制。为接下来的特征提取打下了基础
-
基于OPENCV的手势识别技术
2020-12-11 13:44:04基于OPENCV的手势识别技术 本篇博客主要介绍基于OPENCV的手势识别程序,代码为C++,OPENCV版本为OPENCV3会有较为详细的实现流程和源码,并且做到源码尽量简单,注释也自认为较为清晰,希望能帮助到大家。 -
基于OpenCV+MediaPipe的手势识别(数字、石头剪刀布等手势识别)
2022-03-03 14:01:16基于OpenCV+MediaPipe的手势识别(数字、石头剪刀布等手势识别) 可识别左右手,共定义了15种手势,可以自行增加 https://blog.csdn.net/weixin_53403301/article/details/123232435 -
基于OPENCV的手势识别
2020-12-12 00:15:25基于OPENCV的手势识别程序,代码为C++,OPENCV版本为OPENCV3会有较为详细的实现流程和源码,并且做到源码尽量简单,注释也自认为较为清晰,希望能帮助到大家。 -
基于openCV的手势识别
2021-02-06 15:50:50主要运用的知识就是opencv,python基本语法,图像处理基础知识,下面看效果: 捕捉摄像头(获取视频) cap = cv2.VideoCapture("C:/Users/lenovo/Videos/wgs.mp4")#读取文件 #cap = cv2.VideoCapture(0)#读取摄像头 ... -
毕业设计 - 题目:基于机器视觉opencv的手势检测 手势识别 算法 - 深度学习 卷积神经网络 opencv python
2021-12-20 11:14:05毕业设计 - 题目:基于机器视觉opencv的手势检测 手势识别 算法 - 深度学习 卷积神经网络 opencv python -
基于Opencv的简易手势识别
2021-07-05 07:48:47软件功能:识别摄像头所捕获的区域手指所指示的个数,并打印在画面上。 性能指标:能成功识别由单只手比划出的0-5数字并准确显示 话不多说直接上效果图吧 一、工具与环境 工具:Anaconda,... -
基于OpenCv的简单手势识别(课程设计)
2019-02-26 14:07:05基于OpenCv视觉库实现识别手势1-5,IDE采用的是Visual Studio 2015。 图像可实现动态采集,通过修改代码可以调用移动设备的摄像头。 原理是提前把手势1-5的图像存放在工程文件中,再把实时采集到的手势图像与之对比... -
基于Python+Open CV的手势识别算法设计
2022-04-03 11:47:10伴随着人工智能时代的到来,人机交互的领域也逐渐成为研究的一大...选取图片中固定位置作为手势输入,用红线画出手势识别框,基于hsv的肤色检测,进行高斯滤波,找出轮廓,求出图像中手势的凹凸点,手指间角度求取。 -
【计算机视觉】基于Python—OpenCV的手势识别详解(一)
2021-10-17 12:18:49【计算机视觉】基于Python—OpenCV的手势识别详解(一) -
手势检测(基于OpenCV)
2019-09-11 23:04:45用opencv2.3.1做的静态手势检测。先滤波去噪-->转换到HSV空间-->根据皮肤在HSV空间的分布做出阈值判断,这里用到了inRange函数,然后进行一下形态学的操作,去除噪声干扰,是手的边界更加清晰平滑-->得到的... -
基于OpenCV3.0的手势识别.rar
2019-05-14 11:07:22基于OpenCV3.0和vs2012软件的手势识别的石头剪刀布的游戏 -
基于opencv3.0手势识别
2016-06-15 22:56:03基于opencv3.0手势识别 -
基于Opencv的简易手势识别!逐行分析!超级精细的注释!大作业成品值得拥有!
2021-07-05 10:23:16内含文件:超详细注释手势识别py程序+半成品可以套用课题论文一份