机器视觉实现手势增大电脑音量

实现方案:基于mediapipe和opencv实现的手势控制电脑音量

使用场景:VR/AR场景中音量的调控,机器视觉控制音量,中程控制音量

  1. 具体分为两部分:初始化部分,机器视觉部分和音量控制部分
  • 初始化部分:
    1. 详见代码上方注释
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
  ```
  • 机器视觉部分:
    1. 和前面部分的手势识别博文中相似【没看过的可以去了解一下】,但对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

  • 音量控制部分
    1. 先判断系统声音是为静音模式,获取当前音量值并打印出音量范围,一般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

posted @ 2022-12-06 19:34  筝弈  阅读(87)  评论(0编辑  收藏  举报
2 3
4