大家好,我是一名本科生,我的主要学习方向是计算机视觉以及人工智能。按照目前的学习进度来说,我就是一小白,在这里写下自己编写的程序,与大家分享,记录一下自己的成长。
今天与大家分享的是基于OpenCv的手势识别。
思路分析
获取图片,在图片中找到手,然后进行一系列的闭运算,降噪平滑处理,轮廓查找,凸缺陷检测。(如果你不太理解这些操作,别着急在下面的源码中我会尝试着解释)
然后根据凸包缺陷的个数来判断手指的个数。
遇到的困难
在上述的过程借助opencv库很容易就可以实现,但实现的过程中令人头疼的地方。
在图像中找出手部是很麻烦的,一开始按照书上的方法是将读入的图片由BGR转换为HSV模式,由HSV来确定手的肤色范围,将手从图像中分离出来,代码如下:
//设置肤色范围,该范围的数值是百度出来的人体肤色范围lower_skin = np.array([0,28,70],dtype = np.uint8)upper_skin = np.array([20,255,255],dtype = np.uint8)//根据肤色范围进行手的查找,//cv2.inRange函数会将图像内不在该范围区域设置为黑色mask = cv2.inRange(img_hsv,lower_skin,upper_skin)//但是该方法的效果不理想,找出的手部不准确
我又在网上查找一番,找到了比HSV好的方法:椭圆肤色检测
//这里我写成了一个类class check_skin(): def __init__(self): // 创建椭圆模型 self.ellipse_mode = np.zeros((256, 256), dtype=np.uint8) /// 在图像上绘制白色椭圆 cv2.ellipse(self.ellipse_mode, (113, 155), (23, 15), 43, 0, 360, (255, 255, 255), -1) def check_finger(self,path): img = cv2.imread(path, cv2.IMREAD_COLOR) // 图像皮肤掩膜创建 skin_mask = np.zeros(img.shape[:2], dtype=np.uint8) // 将图像转换为YCBCR img_ycrcb = cv2.cvtColor(img, cv2.COLOR_BGR2YCR_CB) for i in range(img.shape[0]): for j in range(img.shape[1]): cr = img_ycrcb[i, j][1] cb = img_ycrcb[i, j][2] if self.ellipse_mode[cr, cb] > 0: skin_mask[i, j] = 255 img = cv2.bitwise_and(img,img,mask = skin_mask) return img
这个方法要比HSV检测出来的手部效果好的多,效果图就不展示了,有兴趣的可以自己实验一下。
更多的肤色检测方法:请点击这里
手部识别的问题解决后,我来讲一下手势识别的过程
首先,了解一下凸包与凸缺陷
红色为凸包,蓝色点为凸缺陷的最深点(即边缘点到凸包距离最大点),绿色是轮廓。红色与绿色之间的区域即为凸缺陷。
接着我们使用函数:cv2.convexityDefects,convexityDefects:输出参数,检测到的最终结果,返回一个数组,其中每一行包含的值是[起点,终点,最远的点,到最远点的近似距离]。前三个点都是轮廓索引。前三个值得含义分别为:凸缺陷的起始点,凸缺陷的终点,凸缺陷的最深点(即边缘点到凸包距离最大点)
就是上图的蓝色小点,然后根据convexityDefects返回数组的每一行的前三个值,构成的三角形由于人手指伸开的时候所构成的三角形角度总是小于90度,来去除不属于手指的凸缺陷,然后统计小于90度的凸缺陷个数再加1就是手指的个数。
在伸出的手指的个数为一和零的时候统计凸缺陷是不可行的,如图:
这时候应该统计轮廓以及凸包的面积,设置比例关系来判断手指为1和0的情况。
代码
import numpy as npimport cv2import timeimport math#将肤色检测的椭圆模型设计为类class check_skin(): def __init__(self): # 创建椭圆模型 self.ellipse_mode = np.zeros((256, 256), dtype=np.uint8) # 在图像上绘制白色椭圆 cv2.ellipse(self.ellipse_mode, (113, 155), (23, 15), 43, 0, 360, (255, 255, 255), -1) def check_finger(self,path): img = cv2.imread(path, cv2.IMREAD_COLOR) # 图像皮肤掩膜创建 skin_mask = np.zeros(img.shape[:2], dtype=np.uint8) # 将图像转换为YCBCR img_ycrcb = cv2.cvtColor(img, cv2.COLOR_BGR2YCR_CB) for i in range(img.shape[0]): for j in range(img.shape[1]): cr = img_ycrcb[i, j][1] cb = img_ycrcb[i, j][2] if self.ellipse_mode[cr, cb] > 0: skin_mask[i, j] = 255 img = cv2.bitwise_and(img,img,mask = skin_mask) return img #将类初始化为对象check_finger = check_skin()#这个列表储存图片名称img_path = ['zero','one','two','three','four','five']for path in img_path:#图片地址合成以及椭圆模型检测 finger_img = check_finger.check_finger(path+'.JPG') #转换为灰度图像 finger_img_gray = cv2.cvtColor(finger_img,cv2.COLOR_BGR2GRAY) #阈值函数转化为二值图像 _,finger_img_binary = cv2.threshold(finger_img_gray,50,255,cv2.THRESH_BINARY) #进行闭运算 kernel = np.ones((4,4),dtype = np.uint8) finger_img_binary = cv2.morphologyEx(finger_img_binary,cv2.MORPH_CLOSE,kernel,iterations = 2) #高斯滤波进行去噪平滑 finger_img_binary = cv2.GaussianBlur(finger_img_binary,(3,3),50) #轮廓查找 contours, hierarchy = cv2.findContours(finger_img_binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) area_cont = cv2.contourArea(contours[0]) #凸缺陷检测 hull1 = cv2.convexHull(contours[0]) area_hull = cv2.contourArea(hull1) #根据轮廓面积和轮廓凸包面积的比例来确定zero和one percentage = (area_hull-area_cont)/area_cont print(percentage) hull = cv2.convexHull(contours[0], returnPoints=False) convex = cv2.convexityDefects(contours[0],hull) #用来统计小于90的角的个数 count = 0 for i in range(convex.shape[0]): s,e,f,d = convex[i][0] start = contours[0][s][0] end = contours[0][e][0] far = contours[0][f][0] #计算三角形的三边的长度 a = math.sqrt((start[0]-far[0])**2+(start[1]-far[1])**2) b = math.sqrt((end[0]-far[0])**2+(end[1]-far[1])**2) c = math.sqrt((start[0]-end[0])**2+(start[1]-end[1])**2) #计算角度 angle = math.acos((a**2+b**2-c**2)/(2*a*b))*57 #画出角度 cv2.line(finger_img,start,far,(0,255,0),2) cv2.line(finger_img,end,far,(0,255,0),2) # #在角上放上角的度数 # cv2.putText(finger_img,str(angle),far,cv2.FONT_HERSHEY_COMPLEX,1,(0,255,0),1) #判断角度小于九十度的角的个数 if angle<90: count += 1 if count == 0 : if percentage < 0.2 : cv2.putText(finger_img,str(count),(50,20),cv2.FONT_HERSHEY_COMPLEX,1,(0,255,0),1) else: cv2.putText(finger_img, str(count+1), (50,20), cv2.FONT_HERSHEY_COMPLEX, 1, (0, 255, 0), 1) else: cv2.putText(finger_img, str(count+1), (50,20), cv2.FONT_HERSHEY_COMPLEX, 1, (0, 255, 0), 1) cv2.imshow(path,finger_img) cv2.waitKey(0)cv2.destroyAllWindows()
结尾
后来在网上无意间发现了mediapipe这个手势检测库,然后我使用这个库重新又写了一个手势识别的程序,如果大家想要了解,请看我的下一个文章。
最后,创作不易,请各位支持一下。我也是小白一名,欢迎大家的指导,交流。