效果演示:
废话不多说!直接上源码!下面写有所有代码注释!!
import cv2
import mediapipe as mp #它包含了各种预训练的机器学习模型,可以用于姿势估计、手势识别等任务
from ctypes import cast, POINTER #ctypes 是 Python 的一个外部函数库,允许调用动态链接库中的函数
from comtypes import CLSCTX_ALL #用于操作 COM 对象
from pycaw. pycaw import AudioUtilities, IAudioEndpointVolume#是一个用于操作 Windows 音频控制的库。它被用于获取和控制计算机的音频音量。
import time
import math
import numpy as np
class HandControlVolume:
def __init__ ( self) :
# 初始化medialpipe
self. mp_drawing = mp. solutions. drawing_utils
self. mp_drawing_styles = mp. solutions. drawing_styles
self. mp_hands = mp. solutions. hands
# 获取电脑音量范围
devices = AudioUtilities. GetSpeakers ( ) # 获取扬声器信息
interface = devices. Activate ( IAudioEndpointVolume. _iid_, CLSCTX_ALL, None) # 激活音频端点音量接口。
self. volume = cast ( interface, POINTER ( IAudioEndpointVolume) ) #self. volume是指向音频端点音量接口的指针
self. volume. SetMute ( 0 , None) #0 关闭静音 1 打开静音
self. volume_range = self. volume. GetVolumeRange ( ) #获取音量范围。
#手势控制音量方法
def recognize ( self) :
# 计算刷新率
fpsTime = time. time ( ) #获取当前时间戳
# OpenCV读取视频流
cap = cv2. VideoCapture ( 0 )
# 初始化视频窗口分辨率
resize_w = 640
resize_h = 480
# 画面显示初始化参数
rect_height = 0
rect_percent_text = 0
# min _detection_confidence此参数设置手部检测被视为成功所需的最小置信度值(介于 0.0 和 1.0 之间)
# min _tracking_confidence此参数设置手部关键点被视为成功追踪所需的最小置信度值(介于 0.0 和 1.0 之间)
# max _num_hands此参数设置要检测的最大手数。
with ( 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 ( ) #success是一个布尔值,表示是否成功读取了一帧图像。image是读取的图像
image = cv2. resize ( image, ( resize_w, resize_h) ) #设置视频分辨率
if not success:
print ( "空帧." )
continue
#将图像设置为不可写入,以保护数据
image. flags. writeable = False
#将图像从BGR颜色空间转换为RGB颜色空间,模型的输入是RGB图像
image = cv2. cvtColor ( image, cv2. COLOR_BGR2RGB)
# 镜像,对图像进行水平翻转,可能是为了更好地适应MediaPipe模型的期望输入
image = cv2. flip ( image, 1 )
# 对输入的图像进行处理。处理后,results 包含了手部追踪的结果,可能包括检测到的手部数量、手部关键点的坐标等多种信息,
results = hands. process ( image)
# 将图像设置为可写入,以便可以在图像上绘制标注
image. flags. writeable = True
# 将图像从RGB颜色空间转换为BGR颜色空间
image = cv2. cvtColor ( image, cv2. COLOR_RGB2BGR)
# 判断是否有手掌
if results. multi_hand_landmarks:
# 遍历每个手掌
for hand_landmarks in results. multi_hand_landmarks:
# 在画面标注手掌所有关键点并连线。draw_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 = [ ]
# hand _landmarks. landmark 包含了手部关键点的id以及坐标信息。通过 enumerate 函数遍历每个关键点
for landmark_id, finger_axis in enumerate ( hand_landmarks. landmark) :
landmark_list. append ( [ landmark_id, finger_axis. x, finger_axis. y, finger_axis. z] )
# landmark _list中共存入21 个关键点信息。
# 0 - 手腕
# 1 - 大拇指第一关节 2 - 大拇指第二关节 3 - 大拇指第三关节 4 - 大拇指指尖
# 5 - 食指第一关节 6 - 食指第二关节 7 - 食指第三关节 8 - 食指指尖
# 9 - 中指第一关节 10 - 中指第二关节 11 - 中指第三关节 12 - 中指指尖
# 13 - 无名指第一关节 14 - 无名指第二关节 15 - 无名指第三关节 16 - 无名指指尖
# 17 - 小指第一关节 18 - 小指第二关节 19 - 小指第三关节 20 - 小指指尖
if landmark_list:
# 获取大拇指指尖坐标
thumb_finger_tip = landmark_list[ 4 ]
# thumb _finger_tip也有4 个信息。0 - 关键点ID 1 - x坐标( 归一化范围0 - 1 ) 2 - y坐标( 归一化范围0 - 1 ) 3 - z坐标( 归一化范围0 - 1 )
# math . ceil函数用于向上取整,确保获得的坐标是整数。
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)
thumb_finger_tip_y + index_finger_tip_y)
#大拇指指尖坐标
thumb_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 )
# 画2 点连线
image = cv2. line ( image,
thumb_finger_point, #大拇指坐标( 起点)
index_finger_point, #食指坐标( 终点)
( 255 , 0 , 255 ) , #连线颜色
5 ) #线宽
# 勾股定理计算两点间连线的长度。 math. hypot ( x, y) 计算x,y平方和的平方根
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 ]
# # 将俩指间线的长度映射到[ min_volume, max_volume] 范围内的音量值
vol = np. interp ( line_len,
[ 50 , 300 ] , #line_len的范围
[ min_volume, #音量范围的最小值
max_volume] ) #音量范围的最大值
# 将俩指间线的长度映射到[ 0 , 200 ] 范围内的 矩形高度
rect_height = np. interp ( line_len, [ 50 , 300 ] , [ 0 , 200 ] )
# 将俩指间线的长度映射到[ 0 , 100 ] 范围内的 矩形高度百分比
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, #字体类型,PLAIN 表示简单的字体
2 , #字体大小
( 255 , 0 , 0 ) , #字体颜色
3 ) #字体线宽
#画音量矩形方框
image = cv2. rectangle ( image,
( 30 , 100 ) , #矩形左上角坐标( x, y)
( 50 , 300 ) , #矩形右下角坐标( x, y)
( 255 , 0 , 0 ) , #颜色
3 ) #矩形线宽
#画音量实心矩形
image = cv2. rectangle ( image,
( 30 , math. ceil ( 300 - rect_height) ) , #矩形左上角坐标( x, y)
( 50 , 300 ) , #矩形右下角坐标( x, y)
( 255 , 0 , 0 ) , #颜色
- 1 ) #矩形线宽,- 1 表示实心矩形
# 显示刷新率FPS
cTime = time. time ( ) #获取当前时间戳
fps_text = 1 / ( cTime - fpsTime) #计算当前帧的帧率,即每秒处理的帧数。
fpsTime = cTime #将当前时间戳赋值给fpsTime,以便下一帧计算帧率。
#在图像上添加文本
cv2. putText ( image,
"FPS: " + str ( int ( fps_text) ) , #文本内容
( 10 , 70 ) , #文本的坐标位置
cv2. FONT_HERSHEY_PLAIN, #字体类型,PLAIN 表示简单的字体
2 , #字体大小
( 255 , 0 , 0 ) , #字体颜色
3 ) #字体线宽
# 显示画面
cv2. imshow ( 'xyp' , image)
#按下q键退出
if cv2. waitKey ( 1 ) & 0xFF == ord ( 'q' ) :
break
#释放摄像头资源
cap. release ( )
control = HandControlVolume ( ) #创建 HandControlVolume 类的实例
control. recognize ( ) # 调用 recognize 方法