python⼿势识别代码_⼿把⼿教你如何实现Python⼿势识别与
控制(含代码及动图)...
Python⼿势识别与控制
概述
本⽂中的⼿势识别与控制功能主要采⽤ OpenCV 库实现, OpenCV是⼀个基于BSD许可(开源)发⾏的跨平台计算机视觉库, 可以运⾏在Linux, Windows, Android和Mac-OS操作系统上. 它轻量级⽽且⾼效—-由⼀系列 C 函数和少量 C++ 类构成, 同时提供了Python, Ruby, MATLAB等语⾔的接⼝, 实现了图像处理和计算机视觉⽅⾯的很多通⽤算法.
本⽂主要使⽤了OpenCV的视频采集, 图像⾊域转换, 颜⾊通道分割, ⾼斯滤波, OSTU⾃动阈值, 凸点检测, 边缘检测, 余弦定理计算⼿势等功能.
准备⼯作
安装 Python-OpenCV 库
利⽤ -i 为pip指令镜像源, 这⾥使⽤电⼦科技⼤学的源, 速度⽐官⽅源更快.
安装 Numpy 科学计算库
安装 PyAutogui 库
图像的基本操作
import numpy as np
import cv2
imname = "6358772.jpg"
# 读⼊图像
'''
python怎么读取py文件使⽤函数 cv2.imread() 读⼊图像。这幅图像应该在此程序的⼯作路径,或者给函数提供完整路径.
警告:就算图像的路径是错的,OpenCV 也不会提醒你的,但是当你使⽤命令print(img)时得到的结果是None。
'''
img = cv2.imread(imname, cv2.IMREAD_COLOR)
'''
imread函数的第⼀个参数是要打开的图像的名称(带路径)
第⼆个参数是告诉函数应该如何读取这幅图⽚. 其中
cv2.IMREAD_COLOR 表⽰读⼊⼀副彩⾊图像, alpha 通道被忽略, 默认值
cv2.IMREAD_ANYCOLOR 表⽰读⼊⼀副彩⾊图像
cv2.IMREAD_GRAYSCALE 表⽰读⼊⼀副灰度图像
cv2.IMREAD_UNCHANGED 表⽰读⼊⼀幅图像,并且包括图像的 alpha 通道
'''
# 显⽰图像
'''
使⽤函数 cv2.imshow() 显⽰图像。窗⼝会⾃动调整为图像⼤⼩。第⼀个参数是窗⼝的名字,
其次才是我们的图像。你可以创建多个窗⼝,只要你喜欢,但是必须给他们不同的名字.
'''
cv2.imshow("image", img) # "image" 参数为图像显⽰窗⼝的标题, img是待显⽰的图像数据
cv2.waitKey(0) #等待键盘输⼊,参数表⽰等待时间,单位毫秒.0表⽰⽆限期等待
cv2.destroyAllWindows() # 销毁所有cv创建的窗⼝
# 也可以销毁指定窗⼝:
#cv2.destroyWindow("image") # 删除窗⼝标题为"image"的窗⼝
# 保存图像
'''
使⽤函数 cv2.imwrite() 来保存⼀个图像。⾸先需要⼀个⽂件名,之后才是你要保存的图像。
保存的图⽚的格式由后缀名决定.
'''
#cv2.imwrite(imname + "01.png", img)
cv2.imwrite(imname + "01.jpg", img)
摄像头数据采集
我们经常需要使⽤摄像头捕获实时图像。OpenCV 为这种应⽤提供了⼀个⾮常简单的接⼝。让我们使⽤摄像头来捕获⼀段视频,并把它转换成灰度视频显⽰出来。从这个简单的任务开始吧。
为了获取视频,你应该创建⼀个 VideoCapture 对象。它的参数可以是设备的索引号,或者是⼀个视频⽂件。设备索引号就是在指定要使⽤的摄像头。⼀般的笔记本电脑都有内置摄像头。所以参数就是 0。你可以通过设置成 1 或者其他的来选择别的摄像头。之后,你就可以⼀帧⼀帧的捕获视频了。但是最后,别忘了停⽌捕获视频。
情况下上⾯的代码会报错。你可以使⽤cap.isOpened(),来检查是否成功初始化了。如果返回值是True,那就没有问题。否则就要使⽤函数 cap.open()。
class Capture(object):
'''
Capture object
:param deviceID: device ID of your capture device, defaults to 0
:type deviceID: :obj:`int`
Example
>>> import pygr
>>> cap = pygr.Capture()
'''
def __init__(self, deviceID=0): # ID为0, 表⽰从默认的摄像头读取视频数据
self.deviceID = deviceID
self.capture = cv2.VideoCapture(self.deviceID) #
def read(self):
_, frame = ad() # 调⽤默认摄像头捕获⼀帧图像
frame = cv2.bilateralFilter(frame, 5, 50, 100) # 对捕获到的图像进⾏双边滤波
image = Image.fromarray(frame) # 转换图像数据格式
return image
视频数据的处理
为了更准确的识别视频数据中包含的⼿势信息, 需要对视频数据进⾏预处理, 包括背景减除, ⼈体⽪肤侦测.
背景减除
在很多基础应⽤中背景检出都是⼀个⾮常重要的步骤。例如顾客统计,使⽤⼀个静态摄像头来记录进⼊和离开房间的⼈数,或者是交通摄像头,需要提取交通⼯具的信息等。在所有的这些例⼦中,⾸先要将⼈或车单独提取出来。
技术上来说,我们需要从静⽌的背景中提取移动的前景。如果你有⼀张背景(仅有背景不含前景)图像,⽐如没有顾客的房间,没有交通⼯具的道路等,那就好办了。我们只需要在新的图像中减去背景就可以得到前景对象了。
但是在⼤多数情况下,我们没有这样的(背景)图像,所以我们需要从我们有的图像中提取背景。如果图像中的交通⼯具还有影⼦的话,那这个⼯作就更难了,因为影⼦也在移动,仅仅使⽤减法会把影⼦也当成前景。真是⼀件很复杂的事情。为了实现这个⽬的科学家们已经提出了⼏种算法。OpenCV 中已经包含了其中三种⽐较容易使⽤的⽅法: BackgroundSubtractorMOG , BackgroundSubtractorMOG2 , BackgroundSubtractorGMG。这⾥我们使⽤的是 BackgroundSubtractorMOG2 .
BackgroundSubtractorMOG 和 BackgroundSubtractorMOG2
BackgroundSubtractorMOG2 是⼀个以混合⾼斯模型为基础的前景/背景分割算法。它是 P.KadewTraKuPong和 R.Bowden 在 2001年提出的。它使⽤ K(K=3 或 5)个⾼斯分布混合对背景像素进⾏建模。使⽤这些颜⾊(在整个视频中)存在时间的长短作为混合的权重。背景的颜⾊⼀般持续的时间
最长,⽽且更加静⽌。⼀个像素怎么会有分布呢?在 x,y 平⾯上⼀个像素就是⼀个像素没有分布,但是我们现在讲的背景建模是基于时间序列的,因此每⼀个像素点所在的位置在整个时间序列中就会有很多值,从⽽构成⼀个分布。
在编写代码时,我们需要使⽤函数: ateBackgroundSubtractorMOG() 创建⼀个背景对象。这个函数有些可选参数,⽐如要进⾏建模场景的时间长度,⾼斯混合成分的数量,阈值等。将他们全部设置为默认值。然后在整个视频中我们是需要使⽤backgroundsubtractor.apply() 就可以得到前景的掩模了。
BackgroundSubtractorMOG2 也是以⾼斯混合模型为基础的背景/前景分割算法。它是以 2004 年和 2006 年 Z.Zivkovic 的两篇⽂章为基础的。这个算法的⼀个特点是它为每⼀个像素选择⼀个合适数⽬的⾼斯分布。(上⼀个⽅法中我们使⽤是 K ⾼斯分布)。这样就会对由于亮度等发⽣变化引起的场景变化产⽣更好的适应。
和前⾯⼀样我们需要创建⼀个背景对象。但在这⾥我们我们可以选择是否检测阴影。如果 detectShadows = True(默认值),它就会检测并将影⼦标记出来,但是这样做会降低处理速度。影⼦会被标记为灰⾊。
我们这⾥使⽤的就是 BackgroundSubtractorMOG2 算法, 详细代码如下:
# 移除视频数据的背景噪声
def _remove_background(frame):
fgbg = ateBackgroundSubtractorMOG2() # 利⽤BackgroundSubtractorMOG2算法消除背景
# fgmask = bgModel.apply(frame)
fgmask = fgbg.apply(frame)
# kernel = StructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
# res = phologyEx(fgmask, cv2.MORPH_OPEN, kernel)
kernel = np.ones((3, 3), np.uint8)
fgmask = de(fgmask, kernel, iterations=1)
res = cv2.bitwise_and(frame, frame, mask=fgmask)
return res
# 视频数据的⼈体⽪肤检测
def _divskin_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图像⼆值化
return skin
⼿势检测与识别
利⽤opencv提供的 convexityDefects 凹点检测函数检测图像凹陷的点, 然后利⽤, 然后根据凹陷点中的 (开始点, 结束点, 远点)的坐标, 利⽤余弦定理计算两根⼿指之间的夹⾓, 其必为锐⾓, 根据锐⾓的个数判
别⼿势.
其中,锐⾓个数为0 ,表⽰ ⼿势是 拳头 或 ⼀,
锐⾓个数为0 ,表⽰ ⼿势是 拳头 或 ⼀,
锐⾓个数为1 ,表⽰ ⼿势是 剪⼑
锐⾓个数为2 ,表⽰ ⼿势是 三,
锐⾓个数为3 ,表⽰ ⼿势是 四,
锐⾓个数为4 ,表⽰ ⼿势是 布
凹陷点计算
对象上的任何凹陷都被称为凸缺陷。OpenCV 中有⼀个函数 cv.convexityDefect() 可以帮助我们到凸缺
陷. 函数调⽤如下. 如果要查凸缺陷,在使⽤函数 vexHull 凸包时,参数returnPoints⼀定要是 False.
hull = vexHull(cnt, returnPoints = False)
defects = vexityDefects(cnt,hull)
它会返回⼀个数组,其中每⼀⾏包含的值是 [起点,终点,最远的点,到最远点的近似距离]。
# 检测图像中的凸点(⼿指)个数
def _get_contours(array):
# 利⽤findContours检测图像中的轮廓, 其中返回值contours包含了图像中所有轮廓的坐标点
_, contours, _ = cv2.findContours(array, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
return contours
# 根据图像中凹凸点中的 (开始点, 结束点, 远点)的坐标, 利⽤余弦定理计算两根⼿指之间的夹⾓, 其必为锐⾓, 根据锐⾓的个数判别⼿势.
def _get_defects_count(array, contour, defects, verbose = False):
ndefects = 0
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 = _get_eucledian_distance(beg, end)
b = _get_eucledian_distance(beg, far)
c = _get_eucledian_distance(end, far)
angle = math.acos((b ** 2 + c ** 2 - a ** 2) / (2 * b * c)) # * 57
if angle <= math.pi/2 :#90:
ndefects = ndefects + 1
if verbose:
cv2.circle(array, far, 3, _COLOR_RED, -1)
if verbose:
cv2.line(array, beg, end, _COLOR_RED, 1)
return array, ndefects
def grdetect(array, verbose = False):
event = Event(Event.NONE)
copy = py()
array = _remove_background(array) # 移除背景, add by wnavy
thresh = _divskin_detetc(array)
contours = _get_py()) # 计算图像的轮廓
largecont = max(contours, key = lambda contour: urArea(contour))
hull = vexHull(largecont, returnPoints = False) # 计算轮廓的凸点
defects = vexityDefects(largecont, hull) # 计算轮廓的凹点
if defects is not None:
# 利⽤凹陷点坐标, 根据余弦定理计算图像中锐⾓个数
copy, ndefects = _get_defects_count(copy, largecont, defects, verbose = verbose) # 根据锐⾓个数判断⼿势, 会有⼀定的误差
if ndefects == 0:
event.setType(Event.ZERO)
elif ndefects == 1:
event.setType(Event.TWO)
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论