Python手势识别与控制

概述

本文中的手势识别与控制功能主要采用 OpenCV 库实现, OpenCV是一个基于BSD许可(开源)发行的跨平台计算机视觉库, 可以运行在Linux, Windows, Android和Mac-OS操作系统上. 它轻量级而且高效—-由一系列 C 函数和少量 C++ 类构成, 同时提供了Python, Ruby, MATLAB等语言的接口, 实现了图像处理和计算机视觉方面的很多通用算法.

本文主要使用了OpenCV的视频采集, 图像色域转换, 颜色通道分割, 高斯滤波, OSTU自动阈值, 凸点检测, 边缘检测, 余弦定理计算手势等功能.

准备工作

安装 Python-OpenCV 库

  • pip install opencv-python -i https://mirrors.ustc.edu.cn/pypi/web/simple

利用 -i 为pip指令镜像源, 这里使用电子科技大学的源, 速度比官方源更快.

安装 Numpy 科学计算库

  • pip install numpy -i https://mirrors.ustc.edu.cn/pypi/web/simple

安装 PyAutogui 库

  • pip install pyautogui -i https://mirrors.ustc.edu.cn/pypi/web/simple

图像的基本操作


  1. import numpy as np
  2. import cv2
  3. imname = "6358772.jpg"
  4. # 读入图像
  5. '''
  6. 使用函数 cv2.imread() 读入图像。这幅图像应该在此程序的工作路径,或者给函数提供完整路径.
  7. 警告:就算图像的路径是错的,OpenCV 也不会提醒你的,但是当你使用命令print(img)时得到的结果是None。
  8. '''
  9. img = cv2.imread(imname, cv2.IMREAD_COLOR)
  10. '''
  11. imread函数的第一个参数是要打开的图像的名称(带路径)
  12. 第二个参数是告诉函数应该如何读取这幅图片. 其中
  13. cv2.IMREAD_COLOR 表示读入一副彩色图像, alpha 通道被忽略, 默认值
  14. cv2.IMREAD_ANYCOLOR 表示读入一副彩色图像
  15. cv2.IMREAD_GRAYSCALE 表示读入一副灰度图像
  16. cv2.IMREAD_UNCHANGED 表示读入一幅图像,并且包括图像的 alpha 通道
  17. '''
  18. # 显示图像
  19. '''
  20. 使用函数 cv2.imshow() 显示图像。窗口会自动调整为图像大小。第一个参数是窗口的名字,
  21. 其次才是我们的图像。你可以创建多个窗口,只要你喜欢,但是必须给他们不同的名字.
  22. '''
  23. cv2.imshow("image", img) # "image" 参数为图像显示窗口的标题, img是待显示的图像数据
  24. cv2.waitKey(0) #等待键盘输入,参数表示等待时间,单位毫秒.0表示无限期等待
  25. cv2.destroyAllWindows() # 销毁所有cv创建的窗口
  26. # 也可以销毁指定窗口:
  27. #cv2.destroyWindow("image") # 删除窗口标题为"image"的窗口
  28. # 保存图像
  29. '''
  30. 使用函数 cv2.imwrite() 来保存一个图像。首先需要一个文件名,之后才是你要保存的图像。
  31. 保存的图片的格式由后缀名决定.
  32. '''
  33. #cv2.imwrite(imname + "01.png", img)
  34. cv2.imwrite(imname + "01.jpg", img)

摄像头数据采集

我们经常需要使用摄像头捕获实时图像。OpenCV 为这种应用提供了一个非常简单的接口。让我们使用摄像头来捕获一段视频,并把它转换成灰度视频显示出来。从这个简单的任务开始吧。

为了获取视频,你应该创建一个 VideoCapture 对象。它的参数可以是设备的索引号,或者是一个视频文件。设备索引号就是在指定要使用的摄像头。一般的笔记本电脑都有内置摄像头。所以参数就是 0。你可以通过设置成 1 或者其他的来选择别的摄像头。之后,你就可以一帧一帧的捕获视频了。但是最后,别忘了停止捕获视频。

cap.read() 返回一个布尔值(True/False)。如果帧读取的是正确的,就是 True。所以最后你可以通过检查他的返回值来查看视频文件是否已经到了结尾。有时 cap 可能不能成功的初始化摄像头设备。这种情况下上面的代码会报错。你可以使用cap.isOpened(),来检查是否成功初始化了。如果返回值是True,那就没有问题。否则就要使用函数 cap.open()。


  1. class Capture(object):
  2. '''
  3. Capture object
  4. :param deviceID: device ID of your capture device, defaults to 0
  5. :type deviceID: :obj:`int`
  6. Example
  7. >>> import pygr
  8. >>> cap = pygr.Capture()
  9. '''
  10. def __init__(self, deviceID=0): # ID为0, 表示从默认的摄像头读取视频数据
  11. self.deviceID = deviceID
  12. self.capture = cv2.VideoCapture(self.deviceID) #
  13. def read(self):
  14. _, frame = self.capture.read() # 调用默认摄像头捕获一帧图像
  15. frame = cv2.bilateralFilter(frame, 5, 50, 100) # 对捕获到的图像进行双边滤波
  16. image = Image.fromarray(frame) # 转换图像数据格式
  17. return image

视频数据的处理

为了更准确的识别视频数据中包含的手势信息, 需要对视频数据进行预处理, 包括背景减除, 人体皮肤侦测.

背景减除

在很多基础应用中背景检出都是一个非常重要的步骤。例如顾客统计,使用一个静态摄像头来记录进入和离开房间的人数,或者是交通摄像头,需要提取交通工具的信息等。在所有的这些例子中,首先要将人或车单独提取出来。

技术上来说,我们需要从静止的背景中提取移动的前景。如果你有一张背景(仅有背景不含前景)图像,比如没有顾客的房间,没有交通工具的道路等,那就好办了。我们只需要在新的图像中减去背景就可以得到前景对象了。

但是在大多数情况下,我们没有这样的(背景)图像,所以我们需要从我们有的图像中提取背景。如果图像中的交通工具还有影子的话,那这个工作就更难了,因为影子也在移动,仅仅使用减法会把影子也当成前景。真是一件很复杂的事情。为了实现这个目的科学家们已经提出了几种算法。OpenCV 中已经包含了其中三种比较容易使用的方法: BackgroundSubtractorMOG , BackgroundSubtractorMOG2 , BackgroundSubtractorGMG。这里我们使用的是 BackgroundSubtractorMOG2 .

BackgroundSubtractorMOG 和 BackgroundSubtractorMOG2

BackgroundSubtractorMOG2 是一个以混合高斯模型为基础的前景/背景分割算法。它是 P.KadewTraKuPong和 R.Bowden 在 2001 年提出的。它使用 K(K=3 或 5)个高斯分布混合对背景像素进行建模。使用这些颜色(在整个视频中)存在时间的长短作为混合的权重。背景的颜色一般持续的时间最长,而且更加静止。一个像素怎么会有分布呢?在 x,y 平面上一个像素就是一个像素没有分布,但是我们现在讲的背景建模是基于时间序列的,因此每一个像素点所在的位置在整个时间序列中就会有很多值,从而构成一个分布。

在编写代码时,我们需要使用函数: cv2.createBackgroundSubtractorMOG() 创建一个背景对象。这个函数有些可选参数,比如要进行建模场景的时间长度,高斯混合成分的数量,阈值等。将他们全部设置为默认值。然后在整个视频中我们是需要使用 backgroundsubtractor.apply() 就可以得到前景的掩模了。

BackgroundSubtractorMOG2 也是以高斯混合模型为基础的背景/前景分割算法。它是以 2004 年和 2006 年 Z.Zivkovic 的两篇文章为基础的。这个算法的一个特点是它为每一个像素选择一个合适数目的高斯分布。(上一个方法中我们使用是 K 高斯分布)。这样就会对由于亮度等发生变化引起的场景变化产生更好的适应。

和前面一样我们需要创建一个背景对象。但在这里我们我们可以选择是否检测阴影。如果 detectShadows = True(默认值),它就会检测并将影子标记出来,但是这样做会降低处理速度。影子会被标记为灰色。

我们这里使用的就是 BackgroundSubtractorMOG2 算法, 详细代码如下:


  1. # 移除视频数据的背景噪声
  2. def _remove_background(frame):
  3. fgbg = cv2.createBackgroundSubtractorMOG2() # 利用BackgroundSubtractorMOG2算法消除背景
  4. # fgmask = bgModel.apply(frame)
  5. fgmask = fgbg.apply(frame)
  6. # kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
  7. # res = cv2.morphologyEx(fgmask, cv2.MORPH_OPEN, kernel)
  8. kernel = np.ones((3, 3), np.uint8)
  9. fgmask = cv2.erode(fgmask, kernel, iterations=1)
  10. res = cv2.bitwise_and(frame, frame, mask=fgmask)
  11. return res
  12. # 视频数据的人体皮肤检测
  13. def _divskin_detetc(frame):
  14. # 肤色检测: YCrCb之Cr分量 + OTSU二值化
  15. ycrcb = cv2.cvtColor(frame, cv2.COLOR_BGR2YCrCb) # 分解为YUV图像,得到CR分量
  16. (_, cr, _) = cv2.split(ycrcb)
  17. cr1 = cv2.GaussianBlur(cr, (5, 5), 0) # 高斯滤波
  18. _, skin = cv2.threshold(cr1, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) # OTSU图像二值化
  19. return skin

手势检测与识别

利用opencv提供的 convexityDefects 凹点检测函数检测图像凹陷的点, 然后利用, 然后根据凹陷点中的 (开始点, 结束点, 远点)的坐标, 利用余弦定理计算两根手指之间的夹角, 其必为锐角, 根据锐角的个数判别手势.

其中,锐角个数为0 ,表示 手势是 拳头 或 一,

锐角个数为0 ,表示 手势是 拳头 或 一,

锐角个数为1 ,表示 手势是 剪刀

锐角个数为2 ,表示 手势是 三,

锐角个数为3 ,表示 手势是 四,

锐角个数为4 ,表示 手势是 布

凹陷点计算

对象上的任何凹陷都被称为凸缺陷。OpenCV 中有一个函数 cv.convexityDefect() 可以帮助我们找到凸缺

陷. 函数调用如下. 如果要查找凸缺陷,在使用函数 cv2.convexHull 找凸包时,参数returnPoints一定要是 False.


  1. hull = cv2.convexHull(cnt, returnPoints = False)
  2. defects = cv2.convexityDefects(cnt,hull)

它会返回一个数组,其中每一行包含的值是 [起点,终点,最远的点,到最远点的近似距离]。


  1. # 检测图像中的凸点(手指)个数
  2. def _get_contours(array):
  3. # 利用findContours检测图像中的轮廓, 其中返回值contours包含了图像中所有轮廓的坐标点
  4. _, contours, _ = cv2.findContours(array, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
  5. return contours
  6. # 根据图像中凹凸点中的 (开始点, 结束点, 远点)的坐标, 利用余弦定理计算两根手指之间的夹角, 其必为锐角, 根据锐角的个数判别手势.
  7. def _get_defects_count(array, contour, defects, verbose = False):
  8. ndefects = 0
  9. for i in range(defects.shape[0]):
  10. s,e,f,_ = defects[i,0]
  11. beg = tuple(contour[s][0])
  12. end = tuple(contour[e][0])
  13. far = tuple(contour[f][0])
  14. a = _get_eucledian_distance(beg, end)
  15. b = _get_eucledian_distance(beg, far)
  16. c = _get_eucledian_distance(end, far)
  17. angle = math.acos((b ** 2 + c ** 2 - a ** 2) / (2 * b * c)) # * 57
  18. if angle <= math.pi/2 :#90:
  19. ndefects = ndefects + 1
  20. if verbose:
  21. cv2.circle(array, far, 3, _COLOR_RED, -1)
  22. if verbose:
  23. cv2.line(array, beg, end, _COLOR_RED, 1)
  24. return array, ndefects
  25. def grdetect(array, verbose = False):
  26. event = Event(Event.NONE)
  27. copy = array.copy()
  28. array = _remove_background(array) # 移除背景, add by wnavy
  29. thresh = _divskin_detetc(array)
  30. contours = _get_contours(thresh.copy()) # 计算图像的轮廓
  31. largecont = max(contours, key = lambda contour: cv2.contourArea(contour))
  32. hull = cv2.convexHull(largecont, returnPoints = False) # 计算轮廓的凸点
  33. defects = cv2.convexityDefects(largecont, hull) # 计算轮廓的凹点
  34. if defects is not None:
  35. # 利用凹陷点坐标, 根据余弦定理计算图像中锐角个数
  36. copy, ndefects = _get_defects_count(copy, largecont, defects, verbose = verbose)
  37. # 根据锐角个数判断手势, 会有一定的误差
  38. if ndefects == 0:
  39. event.setType(Event.ZERO)
  40. elif ndefects == 1:
  41. event.setType(Event.TWO)
  42. elif ndefects == 2:
  43. event.setType(Event.THREE)
  44. elif ndefects == 3:
  45. event.setType(Event.FOUR)
  46. elif ndefects == 4:
  47. event.setType(Event.FIVE)
  48. return event

手势控制

只要能够检测到手势, 相应的控制就简单很多了, 这里主要模拟手势控制web页面滚动, 手势 表示向下滚动, 手势 表示向上滚动. 只要明白了手势识别的核心原理, 更复杂的手势控制就完全看个人想象, 比如网络上有很多人实现 手势控制chrome浏览器中的那只小恐龙, 我也尝试过, 但是控制起来难度太大了, 还有的是实现手势播放, 切换歌曲. 只要有python, 这一切都很简单.


  1. # imports - app imports
  2. import pygr
  3. import cv2
  4. import time
  5. from base import Keycode, Event
  6. import pyautogui
  7. if __name__ == '__main__':
  8. pad = pygr.PyGR(size = (480, 320), verbose = True)
  9. #pad = pygr.PyGR(size = (480, 320))
  10. pad.show()
  11. while cv2.waitKey(10) not in [Keycode.ESCAPE, Keycode.Q, Keycode.q]:
  12. event = pad.get_event()
  13. print("Event:", event.type, "Tip:", event.tip)
  14. if event.type == Event.FIVE : # 手势五, 向下滚动
  15. pyautogui.scroll(-30) # scroll down 10 "clicks"
  16. elif event.type == Event.FOUR :# 手势四, 向上滚动
  17. pyautogui.scroll(30) # scroll up 10 "clicks"
  18. time.sleep(0.1)

补充说明

为了demo简洁明了, 本文中省去了代码里的无关细节和调试部分, 比如程序运行时的实时识别和标记窗口部分的代码没有介绍, 但是代码中都有详细注释. 另外,对于不想研究细节只想 拿来主义 的同学, 只需要仿照上面 main中引入相关模块, 然后创建并运行PyGR模块, 不需要任何多余的操作.

运行方法

  1. python main.py

注意事项

  • main.py中调用PYGR函数时传入 verbose = True 参数,不然不会显示调试框
  • 背景尽量纯色,以提高识别准确率,手要放在红色方框区域内

就这两条,你们方便就加在开头吧。我在评论里说了的

程序运行截图

手势识别

手势控制

项目内文件截图

好了,今天就分享到这里,希望本文对大家有所帮助,如果大家觉得有用可以点个关注支持一下谢谢!

python高斯噪声怎么去除_手把手教你如何实现Python手势识别与控制(含代码及动图)...相关推荐

  1. python高斯噪声怎么去除_【OpenCV+Python】线性滤波amp;非线性滤波

    线性滤波 本次教程将介绍几种OpenCV常用的滤波器,将介绍它们详细的原理,图像滤波对于OpenCV图像处理来说是至关重要的一环,它在整个OpenCV中的分量是举足轻重的,我们必须完完全全的掌握它. ...

  2. python高斯噪声怎么去除_高斯过程和高斯过程回归

    本文未经允许禁止转载,谢谢合作. 本文我们介绍高斯过程及其在机器学习中应用的一个例子--高斯过程回归. 高斯过程在语音合成中有广泛的应用,我计划在之后的文章中介绍一些应用,但本节我们重点讨论相关的基础 ...

  3. python画史迪仔_手把手教你如何使用Python来生成马赛克画!

    今天小伙伴问我,你知道什么是马赛克画,我笑了笑,你是说哪种哦?我知道一种,不过不是某些电影的马赛克哦~~ 马赛克画是一张由小图拼成的大图,本文的封面就是我们的效果图,放大看细节,每一块都是一张独立的图 ...

  4. python爬取网页文本_手把手教你如何用Python爬取网站文本信息

    提取网页源代码--Requests 工具包 在我们提取网络信息之前,我们必须将网页的源代码进行提取,Requests工具包现在可以说是最好用和最普及的静态网页爬虫工具,它是由大神Kenneth Rei ...

  5. 软件_手把手教vscode配置c++,python开发环境

    原创:软件_手把手教vscode配置c++,python开发环境 之前主用Python作为项目开发语言,将项目迁移到arm边缘盒子上后发现arm的cpu不给力,软件速度低于预期,所以计划将部分程序改为 ...

  6. python k线合成_手把手教你写一个Python版的K线合成函数

    手把手教你写一个Python版的K线合成函数 在编写.使用策略时,经常会使用一些不常用的K线周期数据.然而交易所.数据源又没有提供这些周期的数据.只能通过使用已有周期的数据进行合成.合成算法已经有一个 ...

  7. python处理时间序列非平稳_手把手教你用Python处理非平稳时间序列

    简介 预测一个家庭未来三个月的用电量,估计特定时期道路上的交通流量,预测一只股票在纽约证券交易所交易的价格--这些问题都有什么共同点? 它们都属于时间序列数据的范畴!如果没有"时间" ...

  8. python 时间序列prophet 模型分析_手把手教你用Prophet快速进行时间序列预测(附Prophet和R代码)...

    原标题:手把手教你用Prophet快速进行时间序列预测(附Prophet和R代码) 作者:ANKIT CHOUDHARY:翻译:王雨桐:校对:丁楠雅: 本文约3000字,建议阅读12分钟. 本文将通过 ...

  9. python网页爬虫循环获取_手把手教你用 Python 搞定网页爬虫

    原标题:手把手教你用 Python 搞定网页爬虫 编译:欧剃 作为数据科学家的第一个任务,就是做网页爬取.那时候,我对使用代码从网站上获取数据这项技术完全一无所知,它偏偏又是最有逻辑性并且最容易获得的 ...

最新文章

  1. python常见的排序算法_常见排序算法之python实现
  2. python百度百科api-Python即时网络爬虫:API说明
  3. MFC工程 : view.h 包含错误, 提示 undeclared identifier 等错误
  4. hdu4421 2-sat(枚举二进制每一位)
  5. socket(套接字)详解一种通讯机制
  6. SAP gateway的307重定向
  7. java面试题:集合_Java:选择正确的集合
  8. 运算符优先级 速查表
  9. C#调用Bing的在线翻译接口Translator
  10. 软考- 高级信息系统项目管理师,第一章 信息化与信息系统
  11. python pandas 可视化初步使用 -- 股票价格区间天数统计柱状图
  12. 2022JK工作室第二次招新赛题解
  13. solidity-msg.sender到底是什么?
  14. 寻找亚马逊测评师邮箱_关于亚马逊测评一些普及
  15. html 占两行,css – 将html页面分成两行50%的高度
  16. linux下好玩的文本工具-figlet
  17. 四轴笔记----无线透传模块|无线图传|遥控和接收机|无线数传
  18. Win10 双屏:主屏和左右屏设置
  19. COMP 3023代写、代写COMP 3023、代做 C++ - Assignment、 代编码C++ - Assignment
  20. 独享带宽和共享带宽有哪些区别?

热门文章

  1. 中原文化与中原崛起――徐光春
  2. 网上投稿,怎么投最赚钱?[zt]
  3. EXCEL单元格不能填充颜色的解决方法
  4. Java继承和多态--笔记本USB接口案例
  5. Linuxvim和bash基础笔记(自用)
  6. 谈谈免费开源的知识产权话题
  7. 古代那些为国奉献的人
  8. 常见编程语言OEP入口整理
  9. 源码下载丨如何制作炫酷又高效的 3D 技能特效?带你轻松放大招!
  10. ROS暑期学校暨人工智能与机器人视频回放和分享信息(2022)