使用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节点说明

    通过标号,我们就可以实时追踪确定的手指

landmarks



开始开发

初始化部分

初始化流,初始化变量,初始化声音模块,以及后期映射的部分

#################################
# 相机参数
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

  • 10
    点赞
  • 100
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: PythonOpenCV库和MediaPipe工具包是可以一起使用的,以实现手势识别的功能。 首先,需要在Python中安装OpenCV库和MediaPipe工具包。可以使用pip命令来安装它们: ``` pip install opencv-python pip install mediapipe ``` 安装完成后,就可以开始使用了。 首先,导入必要的库: ```python import cv2 import mediapipe as mp ``` 接下来,创建一个MediaPipe的Hand对象和一个OpenCV的VideoCapture对象,用于读取摄像头输入: ```python mp_hands = mp.solutions.hands hands = mp_hands.Hands() cap = cv2.VideoCapture(0) ``` 然后,使用一个循环来读取摄像头输入并进行手势识别: ```python while True: ret, frame = cap.read() if not ret: break frame_RGB = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) results = hands.process(frame_RGB) if results.multi_handedness: for hand_landmarks in results.multi_hand_landmarks: # 在这里可以对hand_landmarks进行处理和识别手势的操作 cv2.imshow('Gesture Recognition', frame) if cv2.waitKey(1) == ord('q'): break ``` 在循环中,首先将读取到的帧转换为RGB格式,然后使用Hands对象的process方法对该帧进行手势识别。得到的结果存储在results变量中。 在对每个检测到的手部进行循环处理时,可以使用hand_landmarks来获取该手的关键点坐标。可以根据这些关键点的位置和运动轨迹来实现手势的识别和分析。 最后,通过cv2.imshow方法显示图像,并使用cv2.waitKey方法等待用户操作。当用户按下"q"键时,循环终止,程序退出。 通过以上步骤,就可以使用PythonOpenCV库和MediaPipe工具包实现手势识别的功能了。当然,实际的手势识别算法和操作需要根据具体需求进行进一步的开发和优化。 ### 回答2: Python OpenCVMediaPipe结合使用可以实现手势识别。首先,我们需要安装必要的库和工具,包括Pythonopencv-pythonmediapipe和其他依赖项。 然后,我们可以使用MediaPipe提供的HandTracking模块来检测手部的关键点。它使用机器学习模型来识别手势,并返回手部关键点的坐标。我们可以通过OpenCV的视频捕捉模块读取摄像头的实时图像。 接下来,我们通过应用MediaPipe的HandTracking模块获取手部关键点的坐标,并使用OpenCV将这些坐标绘制到图像上,以便我们可以实时看到手部的位置和动作。 完成这些基本的设置后,我们可以定义特定的手势,例如拇指和食指的指尖接触,作为一个简单的示例。我们可以通过检查特定的关键点之间的距离和角度来识别这种手势。如果关键点之间的距离较小并且角度较小,则我们可以确定手势是拇指和食指的指尖接触。 我们可以使用类似的方法来识别其他手势,比如手掌的张开和闭合,拳头的形成等等。我们可以定义一系列规则和阈值来确定特定手势的识别。 最后,我们可以根据检测到的手势执行特定的操作。例如,当识别到拇指和食指的指尖接触时,我们可以触发相机的快门,实现手势拍照。 总之,PythonOpenCVMediaPipe结合使用可以实现手势识别。我们可以利用MediaPipe的HandTracking模块检测手部关键点,并使用OpenCV实时绘制手势位置。通过定义特定手势的规则,我们可以识别各种手势并执行相应操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值