百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

手把手教你如何实现Python手势识别与控制(含代码及动图)

toyiye 2024-06-21 11:55 14 浏览 0 评论


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脚本到web开发、爬虫、数据分析、数据可视化、机器学习等。需要这些资料的只要关注我,在后台私信回复:“01”即可免费领取。

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码