书名:计算机视觉40例从入门到深度学习:OpenCV-Python
作者:李立宗
出版社:电子工业出版社
出版时间:2022-07-01
ISBN:9787121436857
一、手势识别流程
- 手势识别基本流程图如图8-9所示。
图8-9 手势识别基本流程图
- Step 1:获取图像。
本步骤的主要任务是读取摄像头、划定识别区域。划定识别区域的目的在于仅识别特定区域内的手势,简化识别过程。 - Step 2:识别皮肤.
主要任务是色彩空间转换、在新的色彩空间内根据颜色范围值识别出皮肤所在区域。
色彩空间转换的目的在于将图像从BGR色彩空间转换到HSV色彩空间,以进行皮肤检测。通过皮肤颜色的范围值确定手势所在区域。 - Step 3:图像预处理。
图像预处理主要是为了去除图像内的噪声,以便后续处理。这里的图像预处理包含膨胀操作和高斯滤波 - Step 4:获取轮廓。
主要任务在于获取图像的轮廓信息,并获取其面积 - Step 5:获取凸包。
主要任务是获取轮廓的凸包信息,并获取其面积.
本步骤获取了凸包的面积,在后续步骤中用轮廓面积与凸包面积的比值来识别表示数值0的手势和表示数值1的手势。 - Step 6:计算轮廓和凸包的面积比。
主要任务是计算轮廓和凸包的面积比
本步骤获取了轮廓与凸包的面积比,根据该值与阈值(通常为0.9)的关系,识别表示数值0的手势和表示数值1的手势。 - Step 7:获取凸缺陷。
主要任务是获取手势的凸缺陷
通过函数cv2.convexHull、cv2.convexityDefects获取凸缺陷。 - Step 8:计算并绘制有效凸缺陷。
主要任务是计算有效凸缺陷的个数,并绘制凸包、凸缺陷的最远点
本步骤根据凸缺陷中的距离和角度排除了噪声的影响。 - Step 9:使用凸缺陷识别手势。
主要任务是根据凸缺陷的个数、凸缺陷与凸包的面积比进行手势识别
本步骤先对凸缺陷的个数进行判断,然后根据凸缺陷的个数判定当前手势的形状。有一个特例是,当凸缺陷的个数为0时,需要再对轮廓与凸包面积比进行判断,才能决定具体手势。 - Step 10:显示结果。
主要任务是将识别结果显示出来
二、程序实现
# -*- coding: utf-8 -*-
"""
Created on Sat Oct 14 10:25:19 2023
@author: Administrator
"""
import numpy as np
import cv2
import math
cap=cv2.VideoCapture(0,cv2.CAP_DSHOW)
# ======主程序======
while ( cap.isOpened() ):
ret,frame = cap.read() # 读取摄像头图像
# print(frame.shape) # 获取窗口大小
frame = cv2.flip(frame,1) # 绕着 y轴方向翻转图像
# ====设定一个固定区域作为识别区域==
roi = frame[10:210,0:200] # 将右上角设置为固定识别区域
cv2.rectangle(frame,(0,10),(200,210),(0,0,255),0) # 将选定的区域标记出来
# ====在hsv色彩空间内检测出皮肤======
hsv = cv2.cvtColor(roi,cv2.COLOR_BGR2HSV) #色彩空间转换
lower_skin = np.array([0,28,70],dtype=np.uint8) # 设定范围,下限
upper_skin = np.array([20,255,255],dtype=np.uint8) # 设定范围,上限
mask = cv2.inRange(hsv,lower_skin,upper_skin) # 确定手势所在区域
# =====预处理====#
kernel = np.ones((2,2),np.uint8) # 构造一个核
mask = cv2.dilate(mask,kernel,iterations=4) # 膨胀操作
mask = cv2.GaussianBlur(mask,(5,5),100) # 高斯滤波
#=========找出轮廓========
# 查找所有轮廓
contours, h =cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
#从所有轮廓中找到最大的轮廓,并将其作为手势轮廓
cnt= max(contours,key=lambda x:cv2.contourArea(x))
areacnt = cv2.contourArea(cnt) #获取轮廓面积
# =========获取轮廓的凸包=========
hull = cv2.convexHull(cnt) #获取轮廓的凸包,用于计算面积,返回坐标值
areahull = cv2.contourArea(hull) #获取凸包的面积
# ==获取轮廓面积、凸包面积,并计算二者的比值====
arearatio = areacnt/areahull
# 轮廓面积/凸包面积
# 大于0.9,表示二者面积几乎一致,是手势 0
# 否则,说明凸缺陷较大,是手势 1.
# ======获取凸缺陷=============
hull=cv2.convexHull(cnt,returnPoints=False) # 使用索引,returnPoints=False
defects = cv2.convexityDefects(cnt,hull) # 获取凸缺陷
# ======凸缺陷处理=============
n=0 # 定义凹凸点个数初始值为 0
# -------遍历凸缺陷,判断是否为手指间的凸缺陷----
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
# -----------绘制手指间的凸包最远点--
# 角度介于 20°~90° 的认为是不同手指构成的凸缺陷
if angle<=90 and d>20:
n+=1
cv2.circle(roi,far,3,[255,0,0],-1) # 用蓝色绘制最远点
# ------绘制手势的凸包------
cv2.line(roi,start,end,[0,255,0],2)
# ======通过凸缺陷个数及凸缺陷和凸包的面积比判断识别结果========
if n==0: # 0个凸缺陷,手势可能表示数值 0,也可能表示数值 1
if arearatio>0.9:
result='0' # 轮廓面积/凸包面积>0.9,判定为拳头,识别手势为数值 0
else:
result='1' # 轮廓面积/凸包面积≤0.9,说明存在很大的凸缺陷,识别手势为数值 1
elif n==1:
result='2' # 1个凸缺陷,对应2 根手指,识别手势为数值 2
elif n==2:
result='3' # 2个凸缺陷,对应3 根手指,识别手势为数值3
elif n==3:
result='4' # 3 个凸缺陷,对应4 根手指,识别手势为数值 4
elif n==4:
result='5' # 4个凸缺陷,对应5根手指,识别手势为数值 5
# ====设置与显示识别结果相关的参数=======
org=(0,80)
font = cv2.FONT_HERSHEY_PLAIN
fontScale=2
color=(0,0,255)
thickness=3
# ====显示识别结果======
cv2.putText(frame,result,org,font,fontScale,color,thickness)
cv2.imshow('frame',frame)
k = cv2.waitKey(25) & 0xff
if k == 27: # 按下“Esc”键退出
break
cv2.destroyAllWindows()
cap.release()
运行结果













网友评论