使用场景:VR/AR场景中音量的调控,机器视觉控制音量,中程控制音量
- 具体分为两部分:初始化部分,机器视觉部分和音量控制部分
import cv2
import mediapipe as mp
import time
import numpy as np
from ctypes import cast, POINTER
from comtypes import CLSCTX_ALL
from pycaw.pycaw import AudioUtilities, IAudioEndpointVolume
devices = AudioUtilities.GetSpeakers()
interface = devices.Activate(
IAudioEndpointVolume._iid_, CLSCTX_ALL, None)
volume = cast(interface, POINTER(IAudioEndpointVolume))
# 开启摄像头
cap = cv2.VideoCapture(0)
# 设置手部 跟踪
mpHands = mp.solutions.hands
# 设置手部 跟踪参数,Hands括号内可填入精准度等
hands = mpHands.Hands()
# 使用画出手部的跟踪工具包,读取出里面的21个点
mpDraw = mp.solutions.drawing_utils
# 设置手部 关节样式
handLmsStyle = mpDraw.DrawingSpec(color=(0, 0, 255), thickness=5)
# 设置手部 连接样式
handConStyle = mpDraw.DrawingSpec(color=(0, 255, 255), thickness=3)
# 进行时间帧率的计算变量
pTime = 0
cTime = 0
# 设置四个变量装4号节点和8号节点的位置
x1, y1 = 0, 0
x2, y2 = 0, 0
```
- 机器视觉部分:
- 和前面部分的手势识别博文中相似【没看过的可以去了解一下】,但对8号关节位和4号关节位进行了加粗和变色,并将连个关节位给进行了连线,并在这根线的中点位置画了一个圆,再根据勾股定理去得出这根线的长度
if __name__ == '__main__':
while True:
ret, img = cap.read()
# 读取到了,和没有读取到分别做出的配置
if ret:
# 如果读取到了进行灰度转换
imgRGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# 将读取到的灰度图片调用手部的hands对象里面的process函数进行处理
result = hands.process(imgRGB)
# print(result.multi_hand_landmarks)
# 将读取到的手部坐标进行处理,将比例转化为数字
imgHeight = img.shape[0]
imgWidth = img.shape[1]
# 如果读取到了手部的坐标
if result.multi_hand_landmarks:
# 遍历里面的这些点
for handLms in result.multi_hand_landmarks:
# 画出所有节点和连线
mpDraw.draw_landmarks(img, handLms, mpHands.HAND_CONNECTIONS, handLmsStyle, handConStyle)
# 遍历手部节点的坐标
for i, lm in enumerate(handLms.landmark):
# 对坐标进行转换
xPos = int(lm.x * imgWidth)
yPos = int(lm.y * imgHeight)
# 打印出来位置
cv2.putText(img, str(i), (xPos - 25, yPos + 5), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0, 0, 250), 2)
# 关节点为4和8的编号加大变蓝
if i == 4:
x1 = xPos
y1 = yPos
cv2.circle(img, (xPos, yPos), 10, (255, 0, 0), cv2.FILLED)
if i == 8:
x2 = xPos
y2 = yPos
cv2.circle(img, (xPos, yPos), 10, (255, 0, 0), cv2.FILLED)
# 连接4和8的两个关节点,并计算长度,达到控制音量的效果
cv2.line(img, (x1, y1), (x2, y2), (255, 0, 255), 3)
# 拇指和食指的中点,像素坐标是整数要用//
cx, cy = (x1 + x2) // 2, (y1 + y2) // 2
# 在中点画一个圈
cv2.circle(img, (cx, cy), 12, (255, 0, 0), cv2.FILLED)
# 基于长度控制音量
# 计算线段之间的长度,勾股定理计算平方和再开根
length = math.hypot(x2 - x1, y2 - y1)
print("线段长度为", int(length))
# 得到两点之间线段长度,判定线段长度来控制音量,调用声音控制函数去通过长度控制音量
voice_controller(length)
# 打印输出位置
print(i, xPos, yPos)
# 计算出fps,并打印出来
cTime = time.time()
fps = 1 / (cTime - pTime)
pTime = cTime
cv2.putText(img, f"FPS:{int(fps)}", (30, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (250, 0, 0))
cv2.imshow('img', img)
# 按空格退出
if cv2.waitKey(10) == ord(' '):
break
- 音量控制部分
- 先判断系统声音是为静音模式,获取当前音量值并打印出音量范围,一般0.0表示最大,具体范围可通过函数获取,接下来设置最小音量和最大音量,把长度范围和音量范围进行转换,并用虚拟音量映射,再去设置电脑主音量,并设置号虚拟音量值位置和显示
def voice_controller(length):
# 判断是否静音,mute为1 代表是静音,为0 代表不是静音
mute = volume.GetMute()
if mute == 1:
print('当前是静音状态')
else:
print('当前是非静音状态')
# 获取音量值,0.0代表最大,-96.0代表最小
vl = volume.GetMasterVolumeLevel()
print('当前音量值为%s' % vl)
# 获取音量范围,我的电脑经测试是(-96.0, 0.0, 0.125),
# 第一个应该代表最小值,第二个代表最大值,第三个不知道是干嘛的。也就是音量从大到小是0.0到-96.0这个范围
vr = volume.GetVolumeRange()
# print('当前音量值为%s' % str(vr))
# 设置最小音量
minVol = -96 # 元素:-96.0
maxVol = 0 # 元素:0
# 设置音量, 比如-96.0代表音量是0,0.0代表音量是100
# voice = int(length * 0.3) - 96.0
# print("voice:", voice)
# volume.SetMasterVolumeLevel(voice, None)
############
# 线段长度最大300,最小50,转换到音量范围,最小-65,最大0
# 将线段长度变量length从[50,300]转变成[-65,0]
vol = np.interp(length, [50, 300], [minVol, maxVol])
print('vol:', vol, 'length:', length)
# 虚拟音量调的映射,如果和vol一样音量调填充不满
volBar = np.interp(length, [50, 300], [400, 150]) # 映射到150-400
# print('volbar',volBar)
# 设置电脑主音量
volume.SetMasterVolumeLevel(vol, None)
if length < 50: # 距离小于50改变中心圆颜色绿色
cv2.circle(img, (cx, cy), 12, (0, 255, 0), cv2.FILLED)
# (5)画出矩形音量条,img画板,起点和终点坐标,颜色,线宽
cv2.rectangle(img, (50, 150), (85, 400), (0, 0, 255), 3)
# 用音量的幅度作为填充矩形条的高度,像素坐标是整数
cv2.rectangle(img, (50, int(volBar)), (85, 400), (0, 0, 255), cv2.FILLED)
# 把音量值写上去,坐标(50-5,150-10)避免数字遮挡框
text_vol = 100 * (volBar - 150) / (400 - 150) # 音量归一化再变成百分数
cv2.putText(img, f'{str(int(100 - text_vol))}%', (50 - 5, 150 - 10), cv2.FONT_HERSHEY_COMPLEX, 1,
(255, 0, 0), 2)
总结:
代码全览
"""
教育机构:乐博乐博
讲 师:胡晨曦
开发时间:2022/12/1 17:51
"""
import math
import cv2
import mediapipe as mp
import time
import numpy as np
from ctypes import cast, POINTER
from comtypes import CLSCTX_ALL
from pycaw.pycaw import AudioUtilities, IAudioEndpointVolume
devices = AudioUtilities.GetSpeakers()
interface = devices.Activate(
IAudioEndpointVolume._iid_, CLSCTX_ALL, None)
volume = cast(interface, POINTER(IAudioEndpointVolume))
# 开启摄像头
cap = cv2.VideoCapture(0)
# 设置手部 跟踪
mpHands = mp.solutions.hands
# 设置手部 跟踪参数,Hands括号内可填入精准度等
hands = mpHands.Hands()
# 使用画出手部的跟踪工具包,读取出里面的21个点
mpDraw = mp.solutions.drawing_utils
# 设置手部 关节样式
handLmsStyle = mpDraw.DrawingSpec(color=(0, 0, 255), thickness=5)
# 设置手部 连接样式
handConStyle = mpDraw.DrawingSpec(color=(0, 255, 255), thickness=3)
# 进行时间帧率的计算变量
pTime = 0
cTime = 0
# 设置四个变量装4号节点和8号节点的位置
x1, y1 = 0, 0
x2, y2 = 0, 0
def voice_controller(length):
# 判断是否静音,mute为1 代表是静音,为0 代表不是静音
mute = volume.GetMute()
if mute == 1:
print('当前是静音状态')
else:
print('当前是非静音状态')
# 获取音量值,0.0代表最大,-96.0代表最小
vl = volume.GetMasterVolumeLevel()
print('当前音量值为%s' % vl)
# 获取音量范围,我的电脑经测试是(-96.0, 0.0, 0.125),
# 第一个应该代表最小值,第二个代表最大值,第三个不知道是干嘛的。也就是音量从大到小是0.0到-96.0这个范围
# vr = volume.GetVolumeRange()
# print('当前音量值为%s' % str(vr))
# 设置最小音量
minVol = -96 # 元素:-96.0
maxVol = 0 # 元素:0
# 设置音量, 比如-96.0代表音量是0,0.0代表音量是100
# voice = int(length * 0.3) - 96.0
# print("voice:", voice)
# volume.SetMasterVolumeLevel(voice, None)
############
# 线段长度最大300,最小50,转换到音量范围,最小-65,最大0
# 将线段长度变量length从[50,300]转变成[-65,0]
vol = np.interp(length, [50, 300], [minVol, maxVol])
print('vol:', vol, 'length:', length)
# 虚拟音量调的映射,如果和vol一样音量调填充不满
volBar = np.interp(length, [50, 300], [400, 150]) # 映射到150-400
# print('volbar',volBar)
# 设置电脑主音量
volume.SetMasterVolumeLevel(vol, None)
if length < 50: # 距离小于50改变中心圆颜色绿色
cv2.circle(img, (cx, cy), 12, (0, 255, 0), cv2.FILLED)
# (5)画出矩形音量条,img画板,起点和终点坐标,颜色,线宽
cv2.rectangle(img, (50, 150), (85, 400), (0, 0, 255), 3)
# 用音量的幅度作为填充矩形条的高度,像素坐标是整数
cv2.rectangle(img, (50, int(volBar)), (85, 400), (0, 0, 255), cv2.FILLED)
# 把音量值写上去,坐标(50-5,150-10)避免数字遮挡框
text_vol = 100 * (volBar - 150) / (400 - 150) # 音量归一化再变成百分数
cv2.putText(img, f'{str(int(100 - text_vol))}%', (50 - 5, 150 - 10), cv2.FONT_HERSHEY_COMPLEX, 1,
(255, 0, 0), 2)
#########################
if __name__ == '__main__':
# 开启摄像头读取视频,并对读取到的图像进行处理
while True:
ret, img = cap.read()
# 读取到了,和没有读取到分别做出的配置
if ret:
# 如果读取到了进行灰度转换
imgRGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# 将读取到的灰度图片调用手部的hands对象里面的process函数进行处理
result = hands.process(imgRGB)
# print(result.multi_hand_landmarks)
# 将读取到的手部坐标进行处理,将比例转化为数字
imgHeight = img.shape[0]
imgWidth = img.shape[1]
# 如果读取到了手部的坐标
if result.multi_hand_landmarks:
# 遍历里面的这些点
for handLms in result.multi_hand_landmarks:
# 画出所有节点和连线
mpDraw.draw_landmarks(img, handLms, mpHands.HAND_CONNECTIONS, handLmsStyle, handConStyle)
# 遍历手部节点的坐标
for i, lm in enumerate(handLms.landmark):
# 对坐标进行转换
xPos = int(lm.x * imgWidth)
yPos = int(lm.y * imgHeight)
# 打印出来位置
cv2.putText(img, str(i), (xPos - 25, yPos + 5), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0, 0, 250), 2)
# 关节点为4和8的编号加大变蓝
if i == 4:
x1 = xPos
y1 = yPos
cv2.circle(img, (xPos, yPos), 10, (255, 0, 0), cv2.FILLED)
if i == 8:
x2 = xPos
y2 = yPos
cv2.circle(img, (xPos, yPos), 10, (255, 0, 0), cv2.FILLED)
# 连接4和8的两个关节点,并计算长度,达到控制音量的效果
cv2.line(img, (x1, y1), (x2, y2), (255, 0, 255), 3)
# 拇指和食指的中点,像素坐标是整数要用//
cx, cy = (x1 + x2) // 2, (y1 + y2) // 2
# 在中点画一个圈
cv2.circle(img, (cx, cy), 12, (255, 0, 0), cv2.FILLED)
# 基于长度控制音量
# 计算线段之间的长度,勾股定理计算平方和再开根
length = math.hypot(x2 - x1, y2 - y1)
print("线段长度为", int(length))
# 得到两点之间线段长度,判定线段长度来控制音量
voice_controller(length)
# 打印输出位置
print(i, xPos, yPos)
# 计算出fps,并打印出来
cTime = time.time()
fps = 1 / (cTime - pTime)
pTime = cTime
cv2.putText(img, f"FPS:{int(fps)}", (30, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (250, 0, 0))
cv2.imshow('img', img)
# 按空格退出
if cv2.waitKey(10) == ord(' '):
break