项目大概:
OpenCV实现手势音量控制:使用OpenCV和mediapipe库进行手势识别,并利用手势距离控制电脑音量。
今天在看一个大佬的文章的时候发现了好玩的东西,可以用手势控制电脑音量,
原文如下:
他的文章非常详细非常好。
1.问题出现
但是我在将源码赋值下来跑的时候就发现了一个问题,每次启动之后直接就退出了,摄像头启动后也是马上关闭。然后就查了很多资料也没有发现什么结果。加上我对openCV也不是很了解,于是我直接询问了ChatGPT哈哈哈
不得不说openAI真的做的太好了,我先让他分析了一下大佬的这段代码
2.chatGPT解决问题
然后是这样回复我的,图片太大不好截图,我就复制内容了
(1)先让它理解这段代码
这段代码是一个用于手势控制电脑音量的程序,主要通过使用MediaPipe库检测摄像头捕捉的图像中的手势,计算手指间的距离,并将其映射到电脑音量和矩形显示上。以下是代码的主要功能和结构:
- 导入必要的库:
`cv2`: OpenCV库,用于图像处理和显示。
`mediapipe`: 提供了用于手部检测的API。
`pycaw`: 用于控制系统音量的库。
`time`, `math`, `numpy`: 基本的时间、数学和数组处理库。
- 创建 `HandControlVolume` 类:
- 在初始化方法中,设置了Mediapipe手部检测和绘制的相关对象,以及获取系统音量范围的对象。
- `recognize` 方法:
打开摄像头并设置一些视频参数,包括分辨率和画面显示的一些参数。
使用 `mp.solutions.hands` 创建手部检测对象。
在循环中,处理每一帧图像:
通过 `hands.process` 处理图像,得到手部关键点的坐标。
遍历检测到的每只手,获取手指的关键点坐标。
根据手指关键点的坐标计算手指长度,并将手指长度映射到电脑音量和矩形显示上。
更新电脑音量。
在图像上标注手指和连接线,并显示矩形和帧率。
使用 OpenCV 的窗口显示图像,按 ESC 键退出循环。
- 关于音量控制:
使用 `pycaw` 库获取系统音量范围。
使用 `self.volume.SetMasterVolumeLevel` 方法设置电脑音量。
- 关于图像显示:
使用 OpenCV 在图像上绘制手指关键点、连接线、矩形和帧率。
使用 `cv2.imshow` 显示图像,按 ESC 键退出循环。
- 其他:
- 代码中对帧率、图像显示和音量设置进行了一些调整和控制。
总体来说,这段代码结合了手势检测和音量控制,通过摄像头捕捉手势并映射到系统音量上,以及通过图形界面展示手势和音量信息。
(2)再让它分析问题
然后我就告诉他我的问题,会闪退,他是这样分析的
如果程序在打开摄像头后立即退出,有几个可能的原因需要检查:
**摄像头连接问题:** 确保摄像头正常工作,可以尝试使用其他摄像头或检查摄像头的连接状态。
**摄像头读取问题:** 在代码中,有一行 `cap = cv2.VideoCapture(0)` 用于打开摄像头。确保摄像头设备的索引正确(通常是 0),或者尝试使用其他索引。
**错误处理:** 在循环内有一个 `cap.isOpened()` 的条件判断,如果摄像头未成功打开,会打印 "空帧" 并继续下一次循环。检查是否有打印出 "空帧",这可能是导致程序退出的原因之一。
**窗口名称检查:** 在退出条件中,有一行 `cv2.getWindowProperty('MediaPipe Hands', cv2.WND_PROP_VISIBLE) < 1 `,确保这个窗口名称与实际显示的窗口名称匹配。或者,可以直接使用 `cv2.waitKey(5) & 0xFF == 27` 作为退出条件。
**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.修改后的完美运行代码
python
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~