在先前的博客中已经实现过了手势追踪的基本功能,由于最近项目需要,开始学习封装操作,也为了更简洁的调用手势追踪模块,所以参照了Youtube上一位大佬的教程,把之前的追踪模块整理了一下,将代码封装到了类中,然后加了一些功能。
开发环境:Pycharm
所需软件包:Opencv-python,Mediapipe,math,pycaw,numpy
手势识别和追踪原理在之前的博客已经详述过了,对音量的控制是通过Mediapipe实时检测得出的拇指指尖和食指指尖的坐标,再通过坐标计算出两者距离,并将距离处理为参数调用pycaw中相关的音量控制函数实现手势对音量的实时控制,并且利用Opencv画出柱状图像以显示音量的实时大小。
手势跟踪模块
import cv2
import mediapipe as mp
import time
class handDetector():
def __init__(self,mode = False,maxHands = 2,comp = 1,detectionCon = 0.5,trackCon = 0.5):#这里由于函数库更新,所以多了一个复杂度参数,默认设为1
self.mode = mode
self.maxHands = maxHands
self.comp = comp
self.detectionCon = detectionCon
self.trackCon = trackCon
self.mpHands = mp.solutions.hands
self.hands = self.mpHands.Hands(self.mode,self.maxHands,self.comp,
self.detectionCon,self.trackCon)
self.mpDraw = mp.solutions.drawing_utils
self.handLmStyle = self.mpDraw.DrawingSpec(color=(0, 0, 255), thickness=5) # 点的样式,前一个参数是颜色,后一个是粗细
self.handConStyle = self.mpDraw.DrawingSpec(color=(0, 255, 0), thickness=3) # 线的样式BGR,前一个参数是颜色,后一个是粗细
def findHands(self,img,draw = True):
imgRGB = cv2.cvtColor(img,cv2.COLOR_BGR2RGB)#将图像转化为RGB图像
self.results = self.hands.process(imgRGB)
if self.results.multi_hand_landmarks:
for handLms in self.results.multi_hand_landmarks:
if draw:
#self.mpDraw.draw_landmarks(img, handLms,
# self.mpHands.HAND_CONNECTIONS)
self.mpDraw.draw_landmarks(img, handLms, self.mpHands.HAND_CONNECTIONS, self.handLmStyle, self.handConStyle) # 画出点和线
return img
def findPosition(self,img,handNo = 0,draw = True):
lmlist = []
if self.results.multi_hand_landmarks:
myHand = self.results.multi_hand_landmarks[handNo]
for id, lm in enumerate(myHand.landmark):
# print(id,lm)
h, w, c = img.shape # 得到图像的长宽以及通道数
cx, cy = int(lm.x * w), int(lm.y * h) # 计算出中心点位置
#print(id, cx, cy)
lmlist.append([id,cx,cy])
if id == 0:
cv2.circle(img, (cx, cy), 15, (0, 0, 255), cv2.FILLED)
return lmlist
def main():
pTime = 0
cTime = 0
cap = cv2.VideoCapture(0) # 捕获摄像头
detector = handDetector()
while True:
success, img = cap.read() # 读入每一帧图像
img = detector.findHands(img)
limist = detector.findPosition(img)
if len(limist) != 0:
print(limist[4])
cTime = time.time()#用于计算FPS
fps = 1/(cTime-pTime)
pTime = cTime
cv2.putText(img,f"FPS:{int(fps)}",(10,70),cv2.FONT_HERSHEY_PLAIN,3,
(255,0,0),3)#在图像上画出实时FPS
#print(results.multi_hand_landmarks)
cv2.imshow("Image",img)#展示图像
cv2.waitKey(1)#延迟1ms
if __name__ == "__main__":
main()
音量控制代码(需要调用手势跟踪模块)
import cv2
import mediapipe as mp
import numpy as np
import time#用于得知当前时间
import HandTrackingModule as htm
import math
from ctypes import cast,POINTER
from comtypes import CLSCTX_ALL
from pycaw.pycaw import AudioUtilities, IAudioEndpointVolume
#########################
wCam,hCam = 640,480
#########################
cTime = 0
pTime = 0
cap = cv2.VideoCapture(0)
cap.set(3,wCam)
cap.set(4,hCam)
detector = htm.handDetector(detectionCon=0.7)
devices = AudioUtilities.GetSpeakers()
interface = devices.Activate(
IAudioEndpointVolume._iid_,CLSCTX_ALL,None)
volume = cast(interface,POINTER(IAudioEndpointVolume))
#volume.GetMute()
#volume.GetMasterVolumeLevel()
volRange = volume.GetVolumeRange()
minVol = volRange[0]
maxVol = volRange[1]
vol = 0
volBar = 400
volPer = 0
while True:
success,img = cap.read()
img = detector.findHands(img)
lmList = detector.findPosition(img,draw=False)
if len(lmList) != 0:
print(lmList[4],lmList[8])
x1,y1 = lmList[4][1],lmList[4][2]
x2,y2 = lmList[8][1],lmList[8][2]
cx,cy = (x1+x2)//2,(y1+y2)//2
cv2.circle(img,(x1,y1),15,(255,0,255),cv2.FILLED)
cv2.circle(img,(x2,y2),15,(255,0,255), cv2.FILLED)
cv2.line(img,(x1,y1),(x2,y2),(255,0,255),3)
cv2.circle(img, (cx, cy), 15, (255, 0, 255), cv2.FILLED)
length = math.hypot(x2-x1,y2-y1)
#print(length)
#Hand Range 50 - 300
#Volume Range -65 - 0
vol = np.interp(length,[50,300],[minVol,maxVol])
volBar = np.interp(length, [50, 300], [400, 150])
volPer = np.interp(length, [50, 300], [0, 100])
print(int(length),vol)
volume.SetMasterVolumeLevel(vol,None)
if length<50:
cv2.circle(img, (cx, cy), 15, (0,255,0), cv2.FILLED)
cv2.rectangle(img, (50, 150), (85, 400), (255, 0, 0), 3)
cv2.rectangle(img, (50, int(volBar)), (85, 400), (255, 0, 0), cv2.FILLED)
cTime = time.time()
fps = 1/(cTime-pTime)
pTime = cTime
cv2.putText(img,f'{int(volPer)}%',(40,450),cv2.FONT_HERSHEY_PLAIN,
2,(255,0,0),2)
cv2.imshow("IMG",img)
cv2.waitKey(1)
代码效果
音量控制及显示
扬声器音量
手指关节坐标显示
项目不足:
1、项目所设置的音量控制手势太过简单,手势很有可能在无意状态下被识别并且改变音量,影响项目实用性,后面会设置更复杂一些的手势来改良。
2、由于代码构建时对音量控制参数的转化过于直接,在img图像显示音量为60%时,电脑实际音量只有25左右,两者并不是绝对的正比关系,需要进行参数修正。
3、由于Mediapipe所得到的坐标并不是绝对的空间坐标,会受到手势和摄像头之间远近的影响,因此对使用距离有一定的限制,这一点暂时没有想到什么好的解决方式,用深度摄像头的话实用性又不高,或许可以利用摄像头成像原理,给出手掌或者手指的大致长度,通过相似三角形大致计算出手掌和摄像头之间的距离来解决。
注意事项:
由于使用者版本可能跟作者不一样,在源码实际使用时手势跟踪模块有可能会报错,这是由于软件包版本不一致造成定义的某些函数的参数个数等不一致导致的,可以查看该软件包的官方注释来修改参数定义。当然,也可以直接将Opencv版本修改为4.5以上版本,Mediapipe修改为0.8.9.1,则不会出现上述报错。