需要使用到的包
from collections import dequeimport cv2
import numpy as np
import math
import shutilimport sys
import os
import time#这个求出现频率最高的太慢了,所以把它放弃了
from collections import Counter
准备好安装包后需要获取图片
def star():while camera.isOpened():global frameret, frame = camera.read()frame = cv2.flip(frame, 1)cv2.imshow('ori', frame)## img=frame[0:int(0.8 * frame.shape[0]),# int(0.5 * frame.shape[1]):frame.shape[1]][0:int(0.8 * frame.shape[0]),# int(0.5 * frame.shape[1]):frame.shape[1]]frame,ndefects=grdetect(frame)# cv2.imshow('min', img)k = cv2.waitKey(1)if k == 27:camera.release()cv2.destroyAllWindows()break
写进方法start中
视频也是图片构成的,只是在不同帧展示不同的图片而已。这里根据自己电脑的性能选择取图片的频率。
然后一处图片的噪点
def _remove_background(frame):fgbg = cv2.createBackgroundSubtractorMOG2() # 利用BackgroundSubtractorMOG2算法消除背景fgmask = fgbg.apply(frame)kernel = np.ones((3, 3), np.uint8)fgmask = cv2.erode(fgmask, kernel, iterations=1)res = cv2.bitwise_and(frame, frame, mask=fgmask)# cv2.imshow('res',fgmask)return res
最终是给机器看的,在让他处理之前尽量降低影响条件。
再根据皮肤识别获取手的大致形状
def _bodyskin_detetc(frame):# 肤色检测: YCrCb之Cr分量 + OTSU二值化ycrcb = cv2.cvtColor(frame, cv2.COLOR_BGR2YCrCb) # 分解为YUV图像,得到CR分量(_, cr, _) = cv2.split(ycrcb)cr1 = cv2.GaussianBlur(cr, (5, 5), 0) # 高斯滤波_, skin = cv2.threshold(cr1, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) # OTSU图像二值化# cv2.imshow('skin',skin)return skin
获取大致轮廓
def _get_contours(array):# 利用findContours检测图像中的轮廓, 其中返回值contours包含了图像中所有轮廓的坐标点contours, _ = cv2.findContours(array, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)return contours
接下来准对这个大致图形进行处理
这里利用到了 凹凸图处理(腐蚀,另外一个名词想不起来了!)
# 根据图像中凹凸点中的 (开始点, 结束点, 远点)的坐标, 利用余弦定理计算两根手指之间的夹角, 其必为锐角, 根据锐角的个数判别手势.
def _get_defects_count(array, contour,defects, verbose = False):ndefects = 0list=[]for i in range(defects.shape[0]):s,e,f,_ = defects[i,0]beg = tuple(contour[s][0])end = tuple(contour[e][0])far = tuple(contour[f][0])a = math.sqrt((beg[0] - end[0]) ** 2 + (beg[1] - end[1]) ** 2)b = math.sqrt((beg[0] - far[0]) ** 2 + (beg[1] - far[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)) # * 57if angle <= math.pi/2 :#90:if far[1]>350:#有的不需要的杂点在这里给他筛出breakndefects = ndefects + 1if verbose:cv2.circle(array, far, 3, [255,0,0], -1)if verbose:cv2.line(array, beg, end, [0,255,0], 1)cv2.circle(array, beg, 3, [255, 255, 0], -1)list.append(beg)cv2.imshow('arry',array)return array,ndefects,list
根据处理咱们可以获取得到五个数据,咱们需要利用其中的三个数据 (开始点, 结束点, 远点)通过反余弦定理求出手指岔开角度,进而判断伸出手指的个数。
为了使系统稳定运行这里家里arry进行缓存50个前50帧图像处理结果然后综合判断此时此刻的识别结果。
然后就是利用手势操作ppt了
def grdetect(array):global num_pptstrDath=os.getcwd()+'/'+str(num_ppt)+'.JPG'print('strDath',strDath)#读取pptppt1 = cv2.imread(strDath)copy = array.copy()array = _remove_background(array) # 移除背景, add by wnavythresh = _bodyskin_detetc(array)contours= _get_contours(thresh.copy()) # 计算图像的轮廓largecont = max(contours, key = lambda contour: cv2.contourArea(contour))hull = cv2.convexHull(largecont, returnPoints = False) # 计算轮廓的凸点try:defects = cv2.convexityDefects(largecont, hull) # 计算轮廓的凹点except:print('凸点有问题')defects=Noneif defects is not None:# 利用凹陷点坐标, 根据余弦定理计算图像中锐角个数ndefects=''copy,ndefects,list = _get_defects_count(copy, largecont, defects, verbose = True)# 根据锐角个数判断手势, 会有一定的误差if ndefects == 0:num1.append(0)num2.append(0)min_lin=10000min_point=(0,0)if (sum(num1)<50 and sum(num2)<50):for i in range(len(list)):if min_lin>list[i][1]:min_lin=list[i][1]min_point=list[i]# print('min_point=',min_point)dx.append(min_point[0])dy.append(min_point[1])# print('ppt大小',ppt1.shape)#ppt大小 (720, 960, 3)cv2.rectangle(copy, (0, 0),(int(0.8 * frame.shape[1]), int(0.6 * frame.shape[0])), (255, 0, 0), 2)# print('宽:',0.8 * frame.shape[1],'高:',0.6 * frame.shape[0])#宽: 512.0 高: 288.0cv2.circle(copy, (sum(dx)//5,sum(dy)//5), 6, [255,0 , 255], -1)#宽512:960witch = int(np.interp(sum(dx)//5, [0, 512], [0, 960]))#高288:720height = int(np.interp(sum(dy)//5, [0, 288], [0, 720]))cv2.circle(ppt1, (witch, height), 6, [255, 0, 255], -1)cv2.imshow('copy',copy)try:cv2.imshow('ppt', ppt1)except:print('少一个手指一')print(0)elif ndefects == 1:num1.append(2)num2.append(0)if (sum(num1) > 50 and sum(num2) < 50):min_lin = 10000min_point = (0, 0)for i in range(len(list)):if min_lin > list[i][1]:min_lin = list[i][1]min_point = list[i]# print('min_point=', min_point)dx.append(min_point[0])dy.append(min_point[1])# 宽512:960witch = int(np.interp(sum(dx) // 5, [0, 512], [0, 960]))# 高288:720height = int(np.interp(sum(dy) // 5, [0, 288], [0, 720]))print('sum(num1)=',sum(num1))if sum(num1)>50 and sum(num2) <50:global imgCanvasglobal xp,ypif xp==0 and yp==0:xp,yp=witch,heightcv2.line(ppt1, (xp,yp),(witch, height),[0, 255, 0], 15)cv2.line(imgCanvas, (xp, yp), (witch, height), [155,155 , 0], 15)xp, yp = witch, heightcv2.imshow('ppt',ppt1)cv2.imshow('imgCanvas',imgCanvas)elif 20<sum(num1)<50 or sum(num2) != 0:img = cv2.putText(ppt1, "pencil loading....", (50, 50), cv2.FONT_HERSHEY_SIMPLEX, 1.2, (0, 255, 0), 2)try:cv2.imshow('ppt', img)except:print('少一个手指二')print(1+1)
代码真的太长了 这里沾不下!
其中有个难点就是 手写笔迹怎么檫除,为了解决这个问题引入了蒙版概念。
效果如下:
视屏地址:https://www.bilibili.com/video/BV1sf4y1u78g/