使用Python基于OpenCV+MediaPipe追踪手势并控制音量
Posted WakingHours
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用Python基于OpenCV+MediaPipe追踪手势并控制音量相关的知识,希望对你有一定的参考价值。
利用Python基于OpenCV+MediaPipe追踪手势并实现控制音量
写在前面
说明
下文中所有代码均没有封装到函数或者类(模块)中, 因为笔者发现将代码封装到类的后, 再通过main(测试)调用类时,这样代码会更加美观, 可读性更强, 但是事实上效果却不尽如意, 在笔者测试的过程中手部识别和追踪手部的效果都不如直接写在一个main函数中来的稳定.
并且调用类所产生的追踪手部图像(img)和标号(mark)变得及其不稳定, 并且有极大的概率出现追踪(tracking)手部的时候失效的情况.
笔者也不知道出现该现象的原因, 希望有大佬可以指明方向.简介
1.OpenCV简介
OpenCV是一个基于BSD许可(开源)发行的跨平台的计算机视觉和机器学习的软件库, 它实现了图像处理和计算机视觉方向的很多通用算法,它由C++语言编写, 它具有C++, Java, Python和MATLAB接口,可以运行在Linux, Windows, android和Mac OS操作系统上运行.
它提供了很多图像操作,并且是标准的计算机视觉API
OpenCv中文官方文档: http://www.woshicver.com/
2.MediaPipe简介
MediaPipe是谷歌开源的多面体机器学习框架, 里面包含了很多各种各样的模型, 谷歌都已经训练好了, 我们只需调用即可.
MediaPipe 的核心框架由 C++ 实现,主要概念包括Packet、Stream、Calculator、Graph以及子图Subgraph。数据包是最基础的数据单位,一个数据包代表了在某一特定时间节点的数据,例如一帧图像或一小段音频信号;数据流是由按时间顺序升序排列的多个数据包组成,一个数据流的某一特定Timestamp只允许至多一个数据包的存在;而数据流则是在多个计算单元构成的图中流动。MediaPipe 的图是有向的——数据包从Source Calculator或者 Graph Input Stream流入图直至在Sink Calculator 或者 Graph Output Stream离开。
3.配置环境
开发环境
Python 3.8.5
VSCode或pycharm
所需的库
opencv-python 4.5.3.56
mediapipe 0.8.7.3
pycaw 20181226
numpy 1.19.5
PyAutoGUI 0.9.52 [可选].用于做一些自动化操作
安装库文件只需要使用 pip install xxx 命令即可.
当然, 在最后的GitHub链接部分, 我也会给出所需的requestment.txt
最终效果演示
手部21节点说明
通过标号,我们就可以实时追踪确定的手指
开始开发
初始化部分
初始化流,初始化变量,初始化声音模块,以及后期映射的部分
#################################
# 相机参数
wCam, hCam = 640, 480
videoCap = cv2.VideoCapture(0)
videoCap.set(3, wCam) # 设置摄像头高度
videoCap.set(4, hCam) # 设置这项头高度
#################################
# 显示FPS
cTime = 0
pTime = 0
##################################
# mediapipe模块初始化
mode = False
maxHands = 2
detectionCon = 0.5
trackCon = 0.5
mpHands = mp.solutions.hands
hands = mpHands.Hands(mode, maxHands, detectionCon, trackCon)
mpDraw = mp.solutions.drawing_utils
################################
# 音量模块初始化
devices = AudioUtilities.GetSpeakers()
interface = devices.Activate(
IAudioEndpointVolume._iid_, CLSCTX_ALL, None)
volume = cast(interface, POINTER(IAudioEndpointVolume))
# volume.GetMute() # 静音
# volume.GetMasterVolumeLevel() # 主音量等级
# volume.GetVolumeRange() # 主音量范围
# volume.SetMasterVolumeLevel(-20.0, None) # 设置主音量
# print(volume.GetVolumeRange()) # (-63.5, 0.0, 0.5)
#################################
# 获取当前音量范围(range)和最大最小音量
volumeRange = volume.GetVolumeRange() # 获取当前主音量范围
minVol = volumeRange[0]
maxVol = volumeRange[1]
# 后面映射部分需要用到
vol = 0
volBar = 400
volPer = 0
#################################
findHands函数
思路
对获取的摄像头,通过cv2.read()读取每一帧图像,然后对其加工,若存在手,则遍历,最后通过画图工具对img进行画出.
实现
# 用来追踪发现并且追踪手部
def findHands(self, img, draw=True): # 对传入的图像, 是否draw
self.results = self.hands.process(img) # 对每帧图像进行加工
if self.results.multi_hand_landmarks: # 检测到手, 并且返回标号
for oneHand in self.results.multi_hand_landmarks: # 遍历所有手(maxHand)
if draw: # 是否标记出
self.mpDraw.draw_landmarks(img, oneHand, self.mpHands.HAND_CONNECTIONS)
return img # 返回加工好的图像
getLmList函数
思路
遍历枚举后的标号, 返回的x,y是针对整个画幅的比例, 所以乘以画幅, 就可以得到具体的位置坐标
实现
def getMarkList(result_):
mark_list = [] # 初始化空列表
if result_.multi_hand_landmarks: # 如果有标号(检测到手)
oneHand = result_.multi_hand_landmarks[0] # 只检测一个手
for num, local in enumerate(oneHand.landmark): # 遍历枚举
h, w, c = img.shape # 获取画幅
local_x, local_y = int(local.x * w), int(local.y * h) # 比例放大, 得到位置
mark_list.append([num, local_x, local_y]) # 添加到mark_List
return mark_list
volumeControl函数
思路
用getMarkList函数获取的标号列表, 通过索引获取想要检测的标号, 然后检测两指间的距离, 映射到音量模块上, 从而实现控制音量的效果.
注意: cv中的色彩是GBR
实现
def volumeControl(img):
if len(markList) != 0: # 如果检测出点了
# print(lmList[4], lmList[8d10, (122, 255, 0), cv2.FILLED)
thumb_x, thumb_y = markList[4][1], markList[4][2]
index_x, index_y = markList[8][1], markList[8][2]
little_x, little_y = markList[20][1], markList[20][2]
cx, cy = (thumb_x + index_x) // 2, (thumb_y + index_y) // 2 # 找到拇指和食指的中心
# 高亮我们想要检测的标号
cv2.circle(img, (thumb_x, thumb_y), 10, (122, 255, 0), cv2.FILLED)
cv2.circle(img, (index_x, index_y), 10, (122, 255, 0), cv2.FILLED)
cv2.circle(img, (little_x, little_y), 10, (0, 255, 0), cv2.FILLED)
cv2.circle(img, (cx, cy), 6, (122, 255, 0), cv2.FILLED)
# 然后我们来在我们想标记的中间画根线
cv2.line(img, (thumb_x, thumb_y), (little_x, little_y), (0, 255, 255), 2)
cv2.line(img, (thumb_x, thumb_y), (index_x, index_y), (255, 0, 255), 3) # 参数分别是: 要显示到的图像, 坐标1, 坐标2, BGR, 厚度
# 那么我们如何获取长度呢, 很简单嘛, 空间中的欧几里得范数:sqrt(x*x+y*y), math.hypot()就可以直接计算出
thumb_index_distance = math.hypot(index_x - thumb_x, index_y - thumb_y)
little_thumb_distance = math.hypot(little_x - thumb_x, little_y - thumb_y)
# print(thumb_index_distance) # 打印长度, 看两根手指最大和最小的范围
# 接下来我们就要改变系统音量了: pycaw在github中可以找到
# 手指的长度范围: Hand range: 20~200
# 声音的范围: Volume Range: -65~0 ( 0为最大音量)
# 我们需要一个映射, 这里就用到了numpy.interp()
vol = np.interp(thumb_index_distance, [20, 160], [minVol, maxVol]) # 音量的转换
volBar = np.interp(thumb_index_distance, [20, 160], [400, 150]) # 音量条的转换
volPer = np.interp(thumb_index_distance, [20, 160], [0, 100]) # 转换百分比
# print(int(thumb_index_distance), vol)
# 此时我们就可以控制我们的音量了
volume.SetMasterVolumeLevel(vol, None) # 设置主音量
# 我们最后一件事情能够做的就是显示音量:
# cv2.putText(img,f"volume: {vol}",(0,80),cv2.FONT_HERSHEY_COMPLEX,1,(0,255,255))
cv2.rectangle(img, (30, 150), (60, 400), (255, 255, 20), 3) # 画一个矩形
cv2.rectangle(img, (30, int(volBar)), (60, 400), (255, 255, 20), cv2.FILLED) # 填充矩形
cv2.putText(img, f"{str(int(volPer))}%", (20, 430), cv2.FONT_HERSHEY_PLAIN, 2, # 位置, 字体, 比例
(255, 255, 20), 2) # BGR颜色, 线的宽度
# 根据你的检测精度和距离, 合适的设定你的阈值
if thumb_index_distance <= 25: # 当长度<=25, 我认为食指和拇指一块了
cv2.circle(img, (cx, cy), 10, (0, 50, 255), cv2.FILLED) # 当检测到合在一起了, 就改变颜色
pyautogui.hotkey('ctrl', 'alt', 'right') # 网易云热键热键:切歌
# time.sleep(1) # 等待, 不要重复切换热键
cv2.waitKey(200)
# 暂停
if little_thumb_distance <= 25: # 过小时, 就在暂停图像,等待恢复
# cv2.waitKey(0)
time.sleep(2)
dispFPS函数
显示FPS的函数,除以当前时间帧,显示FPS
def dispFPS(img):
global cTime, pTime
# 显示FPS
cTime = time.time()
fps = 1 / (cTime - pTime)
pTime = cTime
cv2.putText(img, f"FPS: {str(int(fps))}", (0, 30), cv2.FONT_HERSHEY_PLAIN, 2, # 位置, 字体, 比例
(10, 255, 0), 2) # BGR颜色, 线的宽度
最后
我仍然在优化代码,希望感兴趣的朋友可以一起交流
请多给我的GitHub点点star,我将感激不尽
GitHub连接: https://github.com/ComputerVision
本次代码在 VolumeControl中可以找到
联系方式:WakingHoursHUC@outlook.com
以上是关于使用Python基于OpenCV+MediaPipe追踪手势并控制音量的主要内容,如果未能解决你的问题,请参考以下文章
Python/OpenCV - 基于机器学习的 OCR(图像到文本)