基于摄像头的简单手势控制系统

基于摄像头的简单手势控制系统系统,主要利用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秒执行的键盘的左(右)方向键的功能。

 

posted @ 2022-09-07 17:20  狗尾巴草word  阅读(361)  评论(0编辑  收藏  举报