大家好,我是一名本科生,我的主要学习方向是计算机视觉以及人工智能。按照目前的学习进度来说,我就是一小白,在这里写下自己编写的程序,与大家分享,记录一下自己的成长。
今天与大家分享的是基于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 np
import cv2
import time
import 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这个手势检测库,然后我使用这个库重新又写了一个手势识别的程序,如果大家想要了解,请看我的下一个文章。
最后,创作不易,请各位支持一下。我也是小白一名,欢迎大家的指导,交流。