基于opencv的手势识别

大家好,我是一名本科生,我的主要学习方向是计算机视觉以及人工智能。按照目前的学习进度来说,我就是一小白,在这里写下自己编写的程序,与大家分享,记录一下自己的成长。

今天与大家分享的是基于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这个手势检测库,然后我使用这个库重新又写了一个手势识别的程序,如果大家想要了解,请看我的下一个文章。

最后,创作不易,请各位支持一下。我也是小白一名,欢迎大家的指导,交流。

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
乘风的头像乘风管理团队
上一篇 2023年6月7日
下一篇 2023年6月7日

相关推荐