基于摄像头的简单手势控制系统
基于摄像头的简单手势控制系统系统,主要利用Pytnon语言和控制摄像头的opencv库,识别和获取手部信息的mediapipa库以及控制鼠标和键盘的pyautogui库。通过对手指竖起的数量和特定的手势来实现人机交互,从而来实现控制系统的所有功能。使用这个系统进行的人机交互可以实现电脑的一些常用操作。
大致功能如下:
a) 控制电脑音量大小。
b) 控制鼠标的移动,能大致完成鼠标的所有功能。比如鼠标的移动,单击左键或右键,拖动文件,画图,滑动等
c) 通过手的左右移动来实现键盘左右方向键的功能。比如PPT的翻页,文件和网页的浏览等.
安装:下载Python 3.8 和 PyCharm Community Edition 2021.3.3(安装路径尽量不要出现中文)
下载opencv-python,mediapiia,pyautogui,numpy,pycaw,comtypes库
作品思路:
首先,构建了手指、手指形状、手指个数与系统功能间的对应关系,确定交互系统的大致设计框架和方向;然后根据实际需求选择了Python作为本交互系统的设计语言;最后根据系统的需求,选取合适的Python库及其方法:
a) 根据作品的概念设计上使用opencv和mediapipa这两个库的方法并进行初步的测试了解后,开始设计大致的判断方法以及对信息的获取。比如对于手指的坐标,判断是否竖起,计算两指间的距离等。再设相应的实现代码测量并改进。
b) 开始查询并设计控制系统的功能。根据作品大致的框架和概念设计来进行控制系统的功能设计,并在网上查询对应的库和使用方法。开始设计代码
功能包括鼠标的移动,滑动,单击左右键,拖动,控制音量,键盘的左右方向键。
c) 测试代码的效果。
d) 完善系统的功能,提升执行效果。
这是Hand.py
import cv2 import math import mediapipe as md class HandDetector(): def __init__(self): self.hand_detector = md.solutions.hands.Hands() self.length = 0 self.label = "Right" def hand(self, img, draw=True): img_Rgb = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) #改变视屏色彩 self.hand_date = self.hand_detector.process(img_Rgb) if draw: if self.hand_date.multi_hand_landmarks: for handlms in self.hand_date.multi_hand_landmarks: md.solutions.drawing_utils.draw_landmarks(img, handlms, md.solutions.hands.HAND_CONNECTIONS) def hand_position(self, img): h, w, c = img.shape self.position = {'Left': {}, 'Right': {}} #定义一个字典 if self.hand_date.multi_hand_landmarks: i = 0 for point in self.hand_date.multi_handedness: score = point.classification[0].score if score > 0.8: #大于百分之八十是哪只手 self.label = point.classification[0].label hand_lms = self.hand_date.multi_hand_landmarks[i].landmark for id, lm in enumerate(hand_lms): x, y = int(lm.x * w), int(lm.y * h) self.position[self.label][id] = (x, y) #print(label) #输出是那只手 i =i + 1 return self.position, self.label def finger_up(self, hand='Left'): #获取竖起的手指数量和哪只手 self.tips = {4, 8, 12, 16, 20} self.tips_count = {4: 0, 8: 0, 12: 0, 16: 0, 20: 0} for tip in self.tips: self.tip1 = self.position[hand].get(tip, None) self.tip2 = self.position[hand].get(tip-2, None) if self.tip1 and self.tip2: if tip == 4: if self.tip1[0] > self.tip2[0]: if hand == 'Left': self.tips_count[tip] = 1 else: self.tips_count[tip] = 0 else: if hand == 'Left': self.tips_count[tip] = 0 else: self.tips_count[tip] = 1 else: if self.tip1[1] > self.tip2[1]: self.tips_count[tip] = 0 else: self.tips_count[tip] = 1 return list(self.tips_count.values()).count(1), self.tips_count def handtips_distance(self, img, rp1, rp2, hand = 'Right'): #计算两指间的距离 self.length = 0 #cx, cy = 0, 0 Right_finger1 = self.position[hand].get(rp1, None) Right_finger2 = self.position[hand].get(rp2, None) if Right_finger1 and Right_finger2: cv2.circle(img, (Right_finger1[0], Right_finger1[1]), 10, (255, 0, 0), cv2.FILLED) cv2.circle(img, (Right_finger2[0], Right_finger2[1]), 10, (255, 0, 0), cv2.FILLED) cv2.line(img, Right_finger1, Right_finger2, (144, 0, 255)) x1, y1 = Right_finger1[0], Right_finger1[1] x2, y2 = Right_finger2[0], Right_finger2[1] #cx, cy = (x1 + x2) // 2, (y1 + y2) // 2 self.length = math.hypot((x2 - x1), (y2 - y1)) return self.length
这是主函数
import cv2 import pyautogui as pb from HandWay import HandDetector from ctypes import cast, POINTER from comtypes import CLSCTX_ALL from pycaw.pycaw import AudioUtilities, IAudioEndpointVolume import numpy as nb import math, time camera = cv2.VideoCapture(1, cv2.CAP_DSHOW) #使用摄像头,视屏捕捉 camera.set(3, 1080) #设置摄像头屏幕的大小 camera.set(4, 720) hand_detector = HandDetector() devices = AudioUtilities.GetSpeakers() #初始化windows音频控制对象 interface = devices.Activate(IAudioEndpointVolume._iid_, CLSCTX_ALL, None) #调用系统的音频控制接口 volume = cast(interface, POINTER(IAudioEndpointVolume)) volRange = volume.GetVolumeRange() #获取电脑音量范围 frameR = 50 plocx, plocy = 0, 0 finger_count = {'Left': {}, 'Right': {}} tip = {'Left': {4: 0, 8: 0, 12: 0, 16: 0, 20: 0}, 'Right': {4: 0, 8: 0, 12: 0, 16: 0, 20: 0}} #获取电脑最大最小音量 minVol = volRange[0] maxVol = volRange[1] while True: success, img = camera.read() #获取是否读成功和视频帧 if success: h, w, c = img.shape #获取摄像头的屏幕大小 h1, w1 = pb.size() #获取电脑屏幕的大小 #print(h1, w1) x, y = pb.position() #print(x, y) img = cv2.flip(img, 1) hand_detector.hand(img, draw=False) position, label = hand_detector.hand_position(img) #获取手部的信息 center = hand_detector.position[label].get(0, None) finger_count[label], tip[label] = hand_detector.finger_up(label) #获取竖起的手指数量和哪只手指是否竖起 left_finger_count, tip['Left'] = hand_detector.finger_up('Left') # print(left_finger_count) cv2.putText(img, str(left_finger_count), (100, 150), cv2.FONT_HERSHEY_DUPLEX, 5, (0, 0, 255), 3) right_finger_count, tip['Right'] = hand_detector.finger_up('Right') # print(right_finger_count) cv2.putText(img, str(right_finger_count), (w - 200, 150), cv2.FONT_HERSHEY_DUPLEX, 5, (255, 0, 255), 3) hand_length1 = hand_detector.handtips_distance(img, 8, 12, label) # print(hand_length1) hand_length2 = hand_detector.handtips_distance(img, 4, 8, label) if tip[label][4] == 1 and tip[label][8] == 1 and finger_count[label] == 2: #当大拇指和食指竖起时,改变两指间的距离来调整音量大小 vol = nb.interp(hand_length2, [40, 250], [minVol, maxVol]) print(tip[label][4]) print(vol, hand_length2) volume.SetMasterVolumeLevel(vol, None) if finger_count[label] == 5: #当五指全部竖起时,掌心的横坐标向左或右超过某一值时,将执行向左或右的键盘功能 # time.sleep(2) if label == 'Right': if center[0] > w - 300: time.sleep(0.5) pb.press('right') print("Right") else: if center[0] < w - 460: time.sleep(0.5) pb.press('left') print("Left") else: print("center") else: if label == 'Left': if center[0] < 250: pb.press('left') print("Left") else: if center[0] > 360: pb.press('right') print("Right") else: print("center") Right_roll = hand_detector.position[label].get(8, None) #得到食指的相关信息 if Right_roll: rx, ry = Right_roll[0], Right_roll[1] if tip[label][8] == 0 and finger_count[label] == 0: #只有食指竖起时,执行鼠标向上滑动的功能,反之向下 pb.scroll(-50, x, y) print("down") else: if tip[label][8] == 1 and finger_count[label] == 1: pb.scroll(50, x, y) print("up") Right_finger1 = hand_detector.position[label].get(8, None) Right_finger2 = hand_detector.position[label].get(12, None) if Right_finger1 and Right_finger2: cx = (Right_finger1[0] + Right_finger2[0]) // 2 #得到食指和中指的中心坐标 cy = (Right_finger1[1] + Right_finger2[1]) // 2 tx = nb.interp(cx, (frameR, w - frameR), (0, w1)) #转化为屏幕坐标 ty = nb.interp(cy, (frameR, h - frameR), (0, h1)) if tip[label][8] == 1 and tip[label][12] == 1 and finger_count[label] == 2: #当左手或右手的食指和中指竖起时,通过两指的指尖中心的坐标来控制移动鼠标 pb.FAILSAFE = False tip_x = (plocx + (tx - plocx)) * 2 tip_y = plocy + (ty - plocy) time.sleep(0.01) pb.moveTo(tip_x, tip_y) plocx, plocy = tip_x, tip_y #记录上次的位置 if hand_length1 < 52: #当两指的指尖距离小于52mm时一直执行鼠标的左键单击功能 pb.mouseDown(button='left')#pb.dragTo(tip_x, tip_y, button='left') print("first") cv2.circle(img, (cx, cy), 15, (144, 144, 144), cv2.FILLED) else: pb.mouseUp(button='left') #当大拇指,食指以及中指竖起时,执行鼠标的左键点击: if tip[label][4] == 1 and tip[label][8] == 1 and tip[label][12] == 1 and finger_count[label] == 3: pb.leftClick() print("Left") #当食指和中指以及无名指和小拇指竖起时,执行鼠标的右键点击 if tip[label][8] == 1 and tip[label][12] == 1 and tip[label][16] == 1 and tip[label][20] == 1 and finger_count[label] == 4: pb.rightClick() print("Right") cv2.imshow('Hand', img) #视频窗口 if cv2.waitKey(1) & 0XFF == 27: #按下Esc按键退出 break camera.release() cv2.destroyAllWindows()
运行说明:打开摄像头(如果使用电脑默认的,在源代码中的camera = cv2.VideoCapture(1, cv2.CAP_DSHOW的1改为0)
使用方法:
根据作品的安装说明来进行安装相应的编译器,打开摄像头,进行功能执行时将手心对准摄像头。
详细的功能执行操作如下:
利用改变大拇指和食指间的距离来改变电脑音量的大小。
将食指和中指竖起时,两指间的中心坐标会作为鼠标的坐标来控制鼠标的移动。并且两指贴合时,执行鼠标一直单击左键的功能。
当大拇指,食指和中指同时竖起时,执行鼠标的单击左键的功能。
仅大拇指未竖起时,执行鼠标的单击右键的功能。
当只有食指竖起时,执行鼠标的滑轮向上的功能,反之,执行鼠标的滑轮向下的功能。
当五指全都竖起时,向左(右)移动一定距离时,每隔0.5秒执行的键盘的左(右)方向键的功能。