手势音量识别

项目大概:

OpenCV实现手势音量控制:使用OpenCV和mediapipe库进行手势识别,并利用手势距离控制电脑音量。

今天在看一个大佬的文章的时候发现了好玩的东西,可以用手势控制电脑音量,

原文如下:

OpenCV实现手势音量控制-CSDN博客

他的文章非常详细非常好。

1.问题出现

        但是我在将源码赋值下来跑的时候就发现了一个问题,每次启动之后直接就退出了,摄像头启动后也是马上关闭。然后就查了很多资料也没有发现什么结果。加上我对openCV也不是很了解,于是我直接询问了ChatGPT哈哈哈

不得不说openAI真的做的太好了,我先让他分析了一下大佬的这段代码

2.chatGPT解决问题

然后是这样回复我的,图片太大不好截图,我就复制内容了

(1)先让它理解这段代码

这段代码是一个用于手势控制电脑音量的程序,主要通过使用MediaPipe库检测摄像头捕捉的图像中的手势,计算手指间的距离,并将其映射到电脑音量和矩形显示上。以下是代码的主要功能和结构:

1. 导入必要的库:
   - `cv2`: OpenCV库,用于图像处理和显示。
   - `mediapipe`: 提供了用于手部检测的API。
   - `pycaw`: 用于控制系统音量的库。
   - `time`, `math`, `numpy`: 基本的时间、数学和数组处理库。

2. 创建 `HandControlVolume` 类:
   - 在初始化方法中,设置了Mediapipe手部检测和绘制的相关对象,以及获取系统音量范围的对象。

3. `recognize` 方法:
   - 打开摄像头并设置一些视频参数,包括分辨率和画面显示的一些参数。
   - 使用 `mp.solutions.hands` 创建手部检测对象。
   - 在循环中,处理每一帧图像:
      - 通过 `hands.process` 处理图像,得到手部关键点的坐标。
      - 遍历检测到的每只手,获取手指的关键点坐标。
      - 根据手指关键点的坐标计算手指长度,并将手指长度映射到电脑音量和矩形显示上。
      - 更新电脑音量。
      - 在图像上标注手指和连接线,并显示矩形和帧率。
      - 使用 OpenCV 的窗口显示图像,按 ESC 键退出循环。

4. 关于音量控制:
   - 使用 `pycaw` 库获取系统音量范围。
   - 使用 `self.volume.SetMasterVolumeLevel` 方法设置电脑音量。

5. 关于图像显示:
   - 使用 OpenCV 在图像上绘制手指关键点、连接线、矩形和帧率。
   - 使用 `cv2.imshow` 显示图像,按 ESC 键退出循环。

6. 其他:
   - 代码中对帧率、图像显示和音量设置进行了一些调整和控制。

总体来说,这段代码结合了手势检测和音量控制,通过摄像头捕捉手势并映射到系统音量上,以及通过图形界面展示手势和音量信息。

(2)再让它分析问题

然后我就告诉他我的问题,会闪退,他是这样分析的

        如果程序在打开摄像头后立即退出,有几个可能的原因需要检查:

        1. **摄像头连接问题:** 确保摄像头正常工作,可以尝试使用其他摄像头或检查摄像头的连接状态。

        2. **摄像头读取问题:** 在代码中,有一行 `cap = cv2.VideoCapture(0)` 用于打开摄像头。确保摄像头设备的索引正确(通常是 0),或者尝试使用其他索引。

        3. **错误处理:** 在循环内有一个 `cap.isOpened()` 的条件判断,如果摄像头未成功打开,会打印 "空帧" 并继续下一次循环。检查是否有打印出 "空帧",这可能是导致程序退出的原因之一。

        4. **窗口名称检查:** 在退出条件中,有一行 `cv2.getWindowProperty('MediaPipe Hands', cv2.WND_PROP_VISIBLE) < 1`,确保这个窗口名称与实际显示的窗口名称匹配。或者,可以直接使用 `cv2.waitKey(5) & 0xFF == 27` 作为退出条件。

        5. **MediaPipe Hands 库版本:** 检查使用的 `mediapipe` 库的版本是否与代码兼容。某些版本的库可能会导致问题,可以尝试更新或降级库版本。

尝试解决以上问题中的一个或多个,看看是否能够解决程序瞬间退出的问题。如果问题仍然存在,可以尝试在程序中加入更多的输出语句,以便更好地调试并定位问题所在。

注意我标红的部分

前几条我都检查了没问题,到这里我就发现了这个退出条件

然后修改为它给的条件直接完美运行

(3)发现问题所在

于是问题就来了,为什么这个判断条件会导致程序直接退出呢

原因如下

        这个条件 cv2.getWindowProperty('MediaPipe Hands', cv2.WND_PROP_VISIBLE) < 1 的目的是检查名为 'MediaPipe Hands' 的窗口是否可见。如果这个窗口不可见(比如被关闭了),条件的值就会小于 1,触发退出循环。

        然而,如果 'MediaPipe Hands' 窗口不存在,cv2.getWindowProperty 可能会抛出异常,导致程序崩溃。因此,建议在使用这个条件之前,确保你已经创建了一个具有相同名称的窗口。

        在大佬的代码中,使用 cv2.imshow('xyp', image) 创建的窗口名称是 'xyp',而不是 'MediaPipe Hands'。因此,条件应该修改为 cv2.getWindowProperty('xyp', cv2.WND_PROP_VISIBLE) < 1

修改后的退出条件如下:

其实就是对代码的不熟悉导致的问题罢了哈哈哈哈哈哈解决啦~~~

3.修改后的完美运行代码

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 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.SetMute(0, None)
        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

        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
                # 转为RGB
                image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
                # 镜像
                image = cv2.flip(image, 1)
                # mediapipe模型处理
                results = hands.process(image)

                image.flags.writeable = True
                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
                            # 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)
                            # 画指尖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('xyp', image)
                # if cv2.waitKey(5) & 0xFF == 27 or cv2.getWindowProperty('MediaPipe Hands', cv2.WND_PROP_VISIBLE) < 1:
                #     break
                if cv2.waitKey(5) & 0xFF == 27 or cv2.getWindowProperty('xyp', cv2.WND_PROP_VISIBLE) < 1:
                    break
            cap.release()
control = HandControlVolume()
control.recognize()

结语

发现问题就解决问题,最后也谢谢大佬提供的代码供大家学习

拜拜ヾ( ̄▽ ̄)Bye~Bye~

你可能感兴趣的:(AI学习之路,纠错分析,Python之路,python,人工智能,opencv)