小工具知识网

当前位置: 首页 > 行业知识 > opencv+mediapipe 手势识别控制电脑音量(详细注释解析)

行业知识

opencv+mediapipe 手势识别控制电脑音量(详细注释解析)

2026-11-03 14:13:52 来源:互联网转载

       前段时间社团布置了一个手势识别控制电脑音量的小任务,今天记录一下学习过程,将大佬作品在我的贫瘠的基础上解释一下~ 

项目主要由以下4个步骤组成:

1、使用OpenCV读取摄像头视频流

2、识别手掌关键点像素坐标

3、根据拇指和食指指尖的坐标,利用勾股定理计算距离

4、将距离等比例转为音量大小,控制电脑音量

最终的效果是这样的:

库 

首先介绍一下应用的几个库

opencv  

OpenCV是Intel开源计算机视觉库。OpenCV的全称是:Open Source Computer Vision Library

对于这个,我们应该已经不再陌生了,毕竟已经学习了很久啦

mediapipe

一个新朋友! 

MediaPipe是一个用于构建机器学习管道的框架,用于处理视频、音频等时间序列数据。MediaPipe依赖OpenCV来处理视频,FFMPEG来处理音频数据。它还有其他依赖项,如OpenGL/Metal、Tensorflow、Eigen等。 在这个例子中,将使用它来进行手势的识别。

python中的一些标准库 

time 

  (1)、time库概述

         time库是Python中处理时间的标准库

         import time

         time.()

  (2)、time库包含三类函数

         - 时间获取:time()   ctime()   gmtime()

         - 时间格式化:strftime()   strptime()

         - 程序计时:sleep()   perf_counter()

 math

内置数学类函数库,math库不支持复数类型,仅支持整数和浮点数运算。

math库一共提供了:

  • 4个数字常数
  • 44个函数,分为4类:

    16个数值表示函数

    8个幂对数函数

    16个三角对数函数

    4个高等特殊函数

     这两个库都需要使用保留字import使用

    numpy 

    这个库也是经常使用的,它的应用如下: 

    • 创建n维数组(矩阵)
    • 对数组进行函数运算,使用函数计算十分快速,节省了大量的时间,且不需要编写循环,十分方便
    • 数值积分、线性代数运算、傅里叶变换
    • ndarray快速节省空间的多维数组,提供数组化的算术运算和高级的 广播功能。1.3 对象
    • NumPy中的核心对象是ndarray
    • ndarray可以看成数组,存放 同类元素
    • NumPy里面所有的函数都是围绕ndarray展开的

       实例分部展示

       # 导入电脑音量控制模块,实现系统与音频接口的交互, 用于控制电脑音量

      from ctypes import cast, POINTER

      ctypes

      模块ctypes是Python内建的用于调用动态链接库函数的功能模块,一定程度上可以用于Python与其他语言的混合编程。由于编写动态链接库,使用C/C++是最常见的方式,故ctypes最常用于Python与C/C++混合编程之中。

      ctypes.cast(obj,type)此函数类似于C中的强制转换运算符。它返回一个新的类型实例,该实例指向与obj相同的内存块。type必须是指针类型,obj必须是可以解释为指针的对象。

      POINTER 返回类型对象,用来给 restype 和 argtypes 指定函数的参数和返回值的类型用。

      from comtypes import CLSCTX_ALL

      comtypes

      comtypes是一个轻量级的Python COM包,基于ctypes FFI库。

      comtypes允许在纯Python中定义、调用和实现自定义和基于调度的COM接口。

      此程序包仅适用于Windows。

      from pycaw.pycaw import AudioUtilities, IAudioEndpointVolume

      在调节音量方面,上面3行经常一起出现,记下来就好 

      #导入其他库

      # 导入其他辅助库import timeimport math# 重要的科学辅助库import numpy as np

      本例中,time用于⏲计时,math用于计算根号。

       # 定义一个名为HandControlVolume的类

      class HandControlVolume:    def __init__(self):        # 初始化 medialpipe        # 导入MediaPipe库中的绘图工具函数,用于在图像上绘制检测结果        self.mp_drawing = mp.solutions.drawing_utils        # 导入MediaPipe库中的绘图样式,用于定义绘制的颜色和线条风格        self.mp_drawing_styles = mp.solutions.drawing_styles        # 导入MediaPipe库中的手部检测模型        self.mp_hands = mp.solutions.hands

      #主函数

      # 主函数    def recognize(self):        # 计算刷新率        fpsTime = time.time()        # OpenCV读取视频流,获取一个视频流对象        cap = cv2.VideoCapture(1)        # 视频分辨率        resize_w = 720        resize_h = 640        # 画面显示初始化参数        rect_height = 0        rect_percent_text = 0

      如果你的电脑是自带的摄像头,别忘了把videocapture的参数调整为0 ,我的是外接摄像头,所以参数是1

      #调用mediapipe的Hands函数,输入手指关节检测的置信度和上一帧跟踪的置信度,输入最多检测手的数目,进行关节点检测 

       

      # 调用mediapipe的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()#获取一帧当前图像,返回是否获取成功和图像数组(用numpy矩阵存储的照片)                image = cv2.resize(image, (resize_w, resize_h))#修改图像大小                if not success:#如果获取图像失败,则进入下一次循环                    print("空帧.")                    continue                # 将图片格式设置为只读状态,可以提高图片格式转化的速度                image.flags.writeable = False                # 将BGR格式存储的图片转为RGB                image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)                # 镜像处理                image = cv2.flip(image, 1)                # 将图像输入手指检测模型,得到结果                results = hands.process(image)                # 重新设置图片为可写状态,并转化会BGR格式                image.flags.writeable = True                image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)

      #当画面中检测到手掌

      # 当画面中检测到手掌,results.multi_hand_landmarks值不为falseif 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:    # 获取大拇指指尖坐标,序号为4    thumb_finger_tip = landmark_list[4]    # 向上取整,得到手指坐标的整数    thumb_finger_tip_x = math.ceil(thumb_finger_tip[1] * resize_w)#thumb_finger_tip[1]里存储的x值范围是0-1,乘以分辨率宽,便得到在图像上的位置    thumb_finger_tip_y = math.ceil(thumb_finger_tip[2] * resize_h) #thumb_finger_tip[2]里存储的x值范围是0-1,乘以分辨率高,便得到在图像上的位置    # 获取食指指尖坐标,序号为4,操作同理    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) // 2    # print(thumb_finger_tip_x)    thumb_finger_point = (thumb_finger_tip_x, thumb_finger_tip_y)    index_finger_point = (index_finger_tip_x, index_finger_tip_y)    # 用opencv的circle函数画图,将食指、拇指和中间点画出    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)    # 用opencv的line函数将食指和拇指连接在一起    image = cv2.line(image, thumb_finger_point, index_finger_point, (255, 0, 255), 5)    # math.hypot为勾股定理计算两点长度的函数,得到食指和拇指的距离    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]

        # 将指尖长度映射到音量上

        # np.interp为插值函数,简而言之,看line_len的值在[50,300]中所占比例,然后去[min_volume,max_volume]中线性寻找相应的值,作为返回值 

      vol = np.interp(line_len, [50, 300], [min_volume, max_volume])    # 将指尖长度映射到矩形显示上    rect_height = np.interp(line_len, [50, 300], [0, 200])    # 同理,通过line_len与[50,300]的比较,得到音量百分比         rect_percent_text = np.interp(line_len, [50, 300], [0, 100])    # 用之前得到的vol值设置电脑音量

       #将音量显示在屏幕上

      # 通过opencv的putText函数,将音量百分比显示到图像上cv2.putText(image, str(math.ceil(rect_percent_text)) + "%", (10, 350),            cv2.FONT_HERSHEY_PLAIN, 3, (255, 0, 0), 3)# 通过opencv的rectangle函数,画出透明矩形框image = cv2.rectangle(image, (30, 100), (70, 300), (255, 0, 0), 3)              # 通过opencv的rectangle函数,填充举行实心比例image = cv2.rectangle(image, (30, math.ceil(300 - rect_height)), (70, 300), (255, 0, 0), -1)

      # 显示刷新率FPS,cTime为程序一个循环截至的时间 

      # 显示刷新率FPS,cTime为程序一个循环截至的时间cTime = time.time()fps_text = 1 / (cTime - fpsTime)# 计算频率fpsTime = cTime# 将下一轮开始的时间置为这一轮循环结束的时间

       #音量显示的设置

      # 显示帧率cv2.putText(image, "FPS: " + str(int(fps_text)), (10, 70),            cv2.FONT_HERSHEY_PLAIN, 3, (255, 0, 0), 3)# 用opencv的函数显示摄像头捕捉的画面,以及在画面上写的字,画的框cv2.imshow('MediaPipe Hands', image)# 每次循环等待5毫秒,如果按下Esc或者窗口退出,这跳出循环if cv2.waitKey(5) & 0xFF == 27 or cv2.getWindowProperty('MediaPipe Hands', cv2.WND_PROP_VISIBLE)  

      #主程序

      # 主程序,先初始化一个手掌获取实例,然后启动recognize函数即可control = HandControlVolume()control.recognize()

      OK啦! 

       

       完整代码如下:

      # 导入OpenCVimport cv2# 导入mediapipe,用于手部关键点检测和手势识别'''    敲桌子!!!(核心关键库)'''# 无法使用GPU加速,因为此库不支持该操作import mediapipe as mp# 导入电脑音量控制模块,实现系统与音频接口的交互from ctypes import cast, POINTERfrom comtypes import CLSCTX_ALL# 用于控制电脑音量from pycaw.pycaw import AudioUtilities, IAudioEndpointVolume# 导入其他辅助库import timeimport math# 重要的科学辅助库import numpy as npclass HandControlVolume:    def __init__(self):        # 初始化 medialpipe        # 导入MediaPipe库中的绘图工具函数,用于在图像上绘制检测结果        self.mp_drawing = mp.solutions.drawing_utils        # 导入MediaPipe库中的绘图样式,用于定义绘制的颜色和线条风格        self.mp_drawing_styles = mp.solutions.drawing_styles        # 导入MediaPipe库中的手部检测模型        self.mp_hands = mp.solutions.hands        # 获取电脑音量范围        # 获取系统的音频输出设备(扬声器)        devices = AudioUtilities.GetSpeakers()        # 激活音频输出设备上的音量控制接口        interface = devices.Activate(IAudioEndpointVolume._iid_, CLSCTX_ALL, None)        # 将激活的音量控制接口转换为指针类型,并赋给实例变量volume,以方便后续使用        self.volume = cast(interface, POINTER(IAudioEndpointVolume))        # 将音量控制对象的静音状态设置为关闭(0表示关闭,1表示打开)        self.volume.SetMute(0, None)        # 通过音量控制接口的GetVolumeRange()方法获取音量控制对象的音量范围(最小值和最大值)        self.volume_range = self.volume.GetVolumeRange()# 主函数    def recognize(self):        # 计算刷新率        fpsTime = time.time()        # OpenCV读取视频流,获取一个视频流对象        cap = cv2.VideoCapture(1)        # 视频分辨率        resize_w = 720        resize_h = 640        # 画面显示初始化参数        rect_height = 0        rect_percent_text = 0        # 使用MediaPipe库中的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()                # 将图像调整为指定的分辨率                image = cv2.resize(image, (resize_w, resize_h))                # 防止摄像头掉线出现报错                if not success:                    print("空帧.")                    continue                # 提高性能                image.flags.writeable = False                # BGR转为RGB                image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)                # 镜像                image = cv2.flip(image, 1)                # mediapipe模型处理                results = hands.process(image)                # 将图像的可写标志image.flags.writeable设置为True,以重新启用对图像的写入操作                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:                        # 在画面标注手指                        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) // 2                            thumb_finger_point = (thumb_finger_tip_x, thumb_finger_tip_y)                            index_finger_point = (index_finger_tip_x, index_finger_tip_y)                            # 画指尖2点                            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)                            # 勾股定理计算长度                            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)                # 显示刷新率FPS                cTime = time.time()                fps_text = 1 / (cTime - fpsTime)                fpsTime = cTime                cv2.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)  

上一篇:揭秘验证码自动识别的源码奥秘,深度解析背后的技术秘密!

下一篇:pe怎么清除服务器密码?