当前位置: 首页 > news >正文

使用Python和MediaPipe实现手势控制音量(Win/Mac)

1. 依赖库介绍

OpenCV

OpenCV(Open Source Computer Vision Library)是一个开源的计算机视觉和机器学习软件库。它包含了数百个计算机视觉算法。

MediaPipe

MediaPipe是一个跨平台的机器学习解决方案库,可以用于实时人类姿势估计、手势识别等任务。

PyCaw

PyCaw是一个Python库,用于控制Windows上的音频设备。

Python版本

本来在Python 3.11环境中进行测试,结果一直报错,似乎是mediapipe库的问题,换了Python 3.12环境后顺利解决

安装依赖

pip install mediapipe
pip install comtypes
pip install pycaw
pip install numpy
pip install opencv-python

2. 程序结构

程序主要分为以下几个部分:

  1. 初始化MediaPipe和音量控制接口。
  2. 从摄像头获取视频流。
  3. 处理视频帧以检测手部位置和姿态。
  4. 计算手指之间的距离,并将其映射到音量控制上。
  5. 显示处理后的图像,包括手部标志和音量指示。

3. 代码详解

3.1 初始化

首先,我们需要导入必要的库,并初始化MediaPipe和音量控制接口。

import cv2
import mediapipe as mp
from ctypes import cast, POINTER
from comtypes import CLSCTX_ALL
from pycaw.pycaw import AudioUtilities, IAudioEndpointVolume
import time
import math
import numpy as npclass HandControlVolume:def __init__(self):self.mp_drawing = mp.solutions.drawing_utilsself.mp_drawing_styles = mp.solutions.drawing_stylesself.mp_hands = mp.solutions.handsdevices = AudioUtilities.GetSpeakers()interface = devices.Activate(IAudioEndpointVolume._iid_, CLSCTX_ALL, None)self.volume = cast(interface, POINTER(IAudioEndpointVolume))self.volume.SetMute(0, None)self.volume_range = self.volume.GetVolumeRange()

3.2 主函数

recognize函数是程序的核心,负责处理视频流并进行手势识别和音量控制。

def recognize(self):fpsTime = time.time()cap = cv2.VideoCapture(0)resize_w = 640resize_h = 480rect_height = 0rect_percent_text = 0with self.mp_hands.Hands(min_detection_confidence=0.7,min_tracking_confidence=0.5,max_num_hands=2) as hands:while cap.isOpened():success, image = cap.read()image = cv2.resize(image, (resize_w, resize_h))if not success:print("空帧.")continueimage.flags.writeable = Falseimage = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)image = cv2.flip(image, 1)results = hands.process(image)image.flags.writeable = Trueimage = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)if results.multi_hand_landmarks:for hand_landmarks in results.multi_hand_landmarks:self.mp_drawing.draw_landmarks(image,hand_landmarks,self.mp_hands.HAND_CONNECTIONS,self.mp_drawing_styles.get_default_hand_landmarks_style(),self.mp_drawing_styles.get_default_hand_connections_style())landmark_list = []for landmark_id, finger_axis in enumerate(hand_landmarks.landmark):landmark_list.append([landmark_id, finger_axis.x, finger_axis.y, finger_axis.z])if landmark_list:thumb_finger_tip = landmark_list[4]thumb_finger_tip_x = math.ceil(thumb_finger_tip[1] * resize_w)thumb_finger_tip_y = math.ceil(thumb_finger_tip[2] * resize_h)index_finger_tip = landmark_list[8]index_finger_tip_x = math.ceil(index_finger_tip[1] * resize_w)index_finger_tip_y = math.ceil(index_finger_tip[2] * resize_h)finger_middle_point = (thumb_finger_tip_x + index_finger_tip_x) // 2, (thumb_finger_tip_y + index_finger_tip_y) // 2thumb_finger_point = (thumb_finger_tip_x, thumb_finger_tip_y)index_finger_point = (index_finger_tip_x, index_finger_tip_y)image = cv2.circle(image, thumb_finger_point, 10, (255, 0, 255), -1)image = cv2.circle(image, index_finger_point, 10, (255, 0, 255), -1)image = cv2.circle(image, finger_middle_point, 10, (255, 0, 255), -1)image = cv2.line(image, thumb_finger_point, index_finger_point, (255, 0, 255), 5)line_len = math.hypot((index_finger_tip_x - thumb_finger_tip_x),(index_finger_tip_y - thumb_finger_tip_y))min_volume = self.volume_range[0]max_volume = self.volume_range[1]vol = np.interp(line_len, [50, 300], [min_volume, max_volume])rect_height = np.interp(line_len, [50, 300], [0, 200])rect_percent_text = np.interp(line_len, [50, 300], [0, 100])self.volume.SetMasterVolumeLevel(vol, None)cv2.putText(image, str(math.ceil(rect_percent_text)) + "%", (10, 350),cv2.FONT_HERSHEY_PLAIN, 3, (255, 0, 0), 3)image = cv2.rectangle(image, (30, 100), (70, 300), (255, 0, 0), 3)image = cv2.rectangle(image, (30, math.ceil(300 - rect_height)), (70, 300), (255, 0, 0), -1)cTime = time.time()fps_text = 1 / (cTime - fpsTime)fpsTime = cTimecv2.putText(image, "FPS: " + str(int(fps_text)), (10, 70),cv2.FONT_HERSHEY_PLAIN, 3, (255, 0, 0), 3)cv2.imshow('MediaPipe Hands', image)if cv2.waitKey(5) & 0xFF == 27 or cv2.getWindowProperty('MediaPipe Hands', cv2.WND_PROP_VISIBLE) < 1:breakcap.release()

3.3 启动程序

最后,通过实例化HandControlVolume类并调用recognize方法来启动程序。

control = HandControlVolume()
control.recognize()

3.4 测试效果

在这里插入图片描述

4. Mac版本程序

主要功能

  • 使用MediaPipe检测手部姿态。
  • 通过计算手指之间的距离来调整系统音量。
  • 使用AppleScript来控制Mac系统的音量。

Mac版本所需依赖库

pip install mediapipe
pip install numpy
pip install opencv-python
pip install applescript

代码实现

import cv2
import mediapipe as mp
from ctypes import cast, POINTER
import applescript as al
import time
import math
import numpy as npclass HandControlVolume:def __init__(self):self.mp_drawing = mp.solutions.drawing_utilsself.mp_drawing_styles = mp.solutions.drawing_stylesself.mp_hands = mp.solutions.handsdef recognize(self):fpsTime = time.time()cap = cv2.VideoCapture(0)resize_w = 640resize_h = 480rect_height = 0rect_percent_text = 0with self.mp_hands.Hands(min_detection_confidence=0.7,min_tracking_confidence=0.5,max_num_hands=2) as hands:while cap.isOpened():success, image = cap.read()image = cv2.resize(image, (resize_w, resize_h))if not success:print("空帧.")continueimage.flags.writeable = Falseimage = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)image = cv2.flip(image, 1)results = hands.process(image)image.flags.writeable = Trueimage = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)if results.multi_hand_landmarks:for hand_landmarks in results.multi_hand_landmarks:self.mp_drawing.draw_landmarks(image,hand_landmarks,self.mp_hands.HAND_CONNECTIONS,self.mp_drawing_styles.get_default_hand_landmarks_style(),self.mp_drawing_styles.get_default_hand_connections_style())landmark_list = []for landmark_id, finger_axis in enumerate(hand_landmarks.landmark):landmark_list.append([landmark_id, finger_axis.x, finger_axis.y, finger_axis.z])if landmark_list:thumb_finger_tip = landmark_list[4]thumb_finger_tip_x = math.ceil(thumb_finger_tip[1] * resize_w)thumb_finger_tip_y = math.ceil(thumb_finger_tip[2] * resize_h)index_finger_tip = landmark_list[8]index_finger_tip_x = math.ceil(index_finger_tip[1] * resize_w)index_finger_tip_y = math.ceil(index_finger_tip[2] * resize_h)finger_middle_point = (thumb_finger_tip_x + index_finger_tip_x) // 2, (thumb_finger_tip_y + index_finger_tip_y) // 2thumb_finger_point = (thumb_finger_tip_x, thumb_finger_tip_y)index_finger_point = (index_finger_tip_x, index_finger_tip_y)image = cv2.circle(image, thumb_finger_point, 10, (255, 0, 255), -1)image = cv2.circle(image, index_finger_point, 10, (255, 0, 255), -1)image = cv2.circle(image, finger_middle_point, 10, (255, 0, 255), -1)image = cv2.line(image, thumb_finger_point, index_finger_point, (255, 0, 255), 5)line_len = math.hypot((index_finger_tip_x - thumb_finger_tip_x),(index_finger_tip_y - thumb_finger_tip_y))vol = np.interp(line_len, [50, 300], [0, 100])rect_height = np.interp(line_len, [50, 300], [0, 200])rect_percent_text = np.interp(line_len, [50, 300], [0, 100])al.run('set volume output volume ' + str(vol))cv2.putText(image, str(math.ceil(rect_percent_text)) + "%", (10, 350),cv2.FONT_HERSHEY_PLAIN, 3, (255, 0, 0), 3)image = cv2.rectangle(image, (30, 100), (70, 300), (255, 0, 0), 3)image = cv2.rectangle(image, (30, math.ceil(300 - rect_height)), (70, 300), (255, 0, 0), -1)cTime = time.time()fps_text = 1 / (cTime - fpsTime)fpsTime = cTimecv2.putText(image, "FPS: " + str(int(fps_text)), (10, 70),cv2.FONT_HERSHEY_PLAIN, 3, (255, 0, 0), 3)cv2.imshow('MediaPipe Hands', image)if cv2.waitKey(5) & 0xFF == 27:breakcap.release()

区别分析

  1. 音量控制方式

    • Windows版本:使用PyCaw库通过COM接口控制音量。
    • Mac版本:使用AppleScript控制音量。
  2. 依赖库

    • Windows版本:依赖PyCawcomtypes库。
    • Mac版本:依赖applescript库。
  3. 代码调整

    • Mac版本注释掉了与Windows音量控制相关的代码,并替换为AppleScript命令。
    • 音量计算部分的范围从Windows的音量范围映射变为0到100的映射。
  4. 平台适配

    • Windows程序利用PyCaw库与Windows系统进行交互,而Mac程序利用AppleScript与Mac系统进行交互。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • socket编程(2) -- TCP通信
  • 【Linux】进程的基本概念(以及进程地址空间的初步了解)
  • GCN-LSTM实现时空预测
  • Mybatis拦截器介绍及其应用
  • ROM修改进阶教程------深度解析小米设备锁机型不解锁bl 刷写特殊类固件的步骤
  • 知识梳理:Postman使用详解
  • 案例 | 人大金仓助力山西政务服务核心业务系统实现全栈国产化升级改造
  • Python爬虫获取王者荣耀英雄全皮肤图片,并下载到本地
  • 在mysql中delete和truncated的相同点和区别点
  • Detrs beat yolos on real-time object detection
  • 大模型笔记3 Longformer for Extractive Summarization训练
  • 351_C++_自定义list容器的sort排序规则sortFileName,函数调用运算符 operator() 的重载,它使得一个对象可以像函数一样被调用
  • 支付通道安全:应对黑客攻击的策略与实践
  • 【SC05B】触摸芯片-高灵敏度、强抗干扰能力和稳定性
  • Matlab 判断直线上一点
  • 《Java编程思想》读书笔记-对象导论
  • exports和module.exports
  • Hexo+码云+git快速搭建免费的静态Blog
  • Mac转Windows的拯救指南
  • php面试题 汇集2
  • Python socket服务器端、客户端传送信息
  • 案例分享〡三拾众筹持续交付开发流程支撑创新业务
  • 读懂package.json -- 依赖管理
  • 想写好前端,先练好内功
  • 做一名精致的JavaScripter 01:JavaScript简介
  • 数据可视化之下发图实践
  • ​LeetCode解法汇总1410. HTML 实体解析器
  • # SpringBoot 如何让指定的Bean先加载
  • # Swust 12th acm 邀请赛# [ A ] A+B problem [题解]
  • #C++ 智能指针 std::unique_ptr 、std::shared_ptr 和 std::weak_ptr
  • $.ajax()参数及用法
  • (PySpark)RDD实验实战——取最大数出现的次数
  • (附源码)spring boot基于Java的电影院售票与管理系统毕业设计 011449
  • (十一)c52学习之旅-动态数码管
  • (学习日记)2024.03.25:UCOSIII第二十二节:系统启动流程详解
  • (一)spring cloud微服务分布式云架构 - Spring Cloud简介
  • (原創) 如何優化ThinkPad X61開機速度? (NB) (ThinkPad) (X61) (OS) (Windows)
  • ***利用Ms05002溢出找“肉鸡
  • .bat批处理(四):路径相关%cd%和%~dp0的区别
  • .java 9 找不到符号_java找不到符号
  • .libPaths()设置包加载目录
  • .mp4格式的视频为何不能通过video标签在chrome浏览器中播放?
  • .Net 8.0 新的变化
  • .net CHARTING图表控件下载地址
  • .net core 源码_ASP.NET Core之Identity源码学习
  • .Net6支持的操作系统版本(.net8已来,你还在用.netframework4.5吗)
  • .NET中统一的存储过程调用方法(收藏)
  • [@Controller]4 详解@ModelAttribute
  • [AR]Vumark(下一代条形码)
  • [BT]小迪安全2023学习笔记(第15天:PHP开发-登录验证)
  • [C#][DevPress]事件委托的使用
  • [C++]模板与STL简介
  • [DL]深度学习_Feature Pyramid Network
  • [GN] Vue3.2 快速上手 ---- 核心语法2
  • [HUBUCTF 2022 新生赛]