2
\$\begingroup\$

This program uses webcam to track hand movements / gestures and sends corresponding mouse / keyboard events to the computer. I use it to switch spaces, move active window between desktops, scroll vertically and horizontally and control the cursor. My previous post here had some obvious improvements needed which a user suggested I implement before posting here. So here's version 2.

import cv2
from mediapipe import solutions
from math import sqrt
import pyautogui
import time

current_pose = ""
previous_pose = ""
zone = ""

previous_positions = []

index_image_pos = 0, 0
smoothing_factor = 2
framerate = 30

delay = 1 / framerate
screen_width, screen_height = pyautogui.size()

mp_drawing = solutions.drawing_utils
mp_drawing_styles = solutions.drawing_styles
mp_hands = solutions.hands

cap = cv2.VideoCapture(0)

with mp_hands.Hands(model_complexity=0, min_detection_confidence=0.5, min_tracking_confidence=0.5, max_num_hands=1) as hands:
  while cap.isOpened():
    success, image = cap.read()

    if not success:
        break

    image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
    results = hands.process(image)
    image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)

    image_height, image_width, _ = image.shape

    if results.multi_hand_landmarks:
        for hand_landmarks in results.multi_hand_landmarks:
            mp_drawing.draw_landmarks(
                image,
                hand_landmarks,
                mp_hands.HAND_CONNECTIONS,
                mp_drawing_styles.get_default_hand_landmarks_style(),
                mp_drawing_styles.get_default_hand_connections_style())

            # Get the positions of the hand keypoints
            palm_pos = hand_landmarks.landmark[0].x, hand_landmarks.landmark[0].y

            thumb_pos = hand_landmarks.landmark[4].x, hand_landmarks.landmark[4].y
            index_pos = hand_landmarks.landmark[8].x, hand_landmarks.landmark[8].y
            middle_pos = hand_landmarks.landmark[12].x, hand_landmarks.landmark[12].y
            ring_pos = hand_landmarks.landmark[16].x, hand_landmarks.landmark[16].y
            pinky_pos = hand_landmarks.landmark[20].x, hand_landmarks.landmark[20].y

            thumb_joint = hand_landmarks.landmark[3].x, hand_landmarks.landmark[3].y
            index_joint = hand_landmarks.landmark[7].x, hand_landmarks.landmark[7].y
            middle_joint = hand_landmarks.landmark[11].x, hand_landmarks.landmark[11].y
            ring_joint = hand_landmarks.landmark[15].x, hand_landmarks.landmark[15].y
            pinky_joint = hand_landmarks.landmark[19].x, hand_landmarks.landmark[19].y

            thumb_base = hand_landmarks.landmark[1].x, hand_landmarks.landmark[1].y
            index_base = hand_landmarks.landmark[5].x, hand_landmarks.landmark[5].y
            middle_base = hand_landmarks.landmark[9].x, hand_landmarks.landmark[9].y
            ring_base = hand_landmarks.landmark[13].x, hand_landmarks.landmark[13].y
            pinky_base = hand_landmarks.landmark[17].x, hand_landmarks.landmark[17].y

            # Calculate the distances between the fingertips and palm (wrist)
            thumb_dist = sqrt(pow(palm_pos[0] - thumb_pos[0], 2) + pow(palm_pos[1] - thumb_pos[1], 2))
            index_dist = sqrt(pow(palm_pos[0] - index_pos[0], 2) + pow(palm_pos[1] - index_pos[1], 2))
            middle_dist = sqrt(pow(palm_pos[0] - middle_pos[0], 2) + pow(palm_pos[1] - middle_pos[1], 2))
            ring_dist = sqrt(pow(palm_pos[0] - ring_pos[0], 2) + pow(palm_pos[1] - ring_pos[1], 2))
            pinky_dist = sqrt(pow(palm_pos[0] - pinky_pos[0], 2) + pow(palm_pos[1] - pinky_pos[1], 2))

            # Calculate distance between index base and thumb tip
            index_thumb_base_dist = sqrt(pow(index_base[0] - thumb_pos[0], 2) + pow(index_base[1] - thumb_pos[1], 2))

            previous_pose = current_pose

            # Identify hand poses
            if index_joint[1] > index_pos[1] and index_dist > thumb_dist and middle_pos[1] > middle_base[1] and middle_dist < 0.3:
                hand_pose = "index finger"
            elif middle_pos[1] < middle_base[1] and index_joint[1] > index_pos[1] and ring_joint[1] < ring_pos[1] and pinky_joint[1] < pinky_pos[1] and ring_dist < 0.3:
                hand_pose = "two fingers v"
            elif ring_pos[1] < ring_base[1] and index_joint[1] > index_pos[1] and ring_joint[1] > ring_pos[1] and pinky_joint[1] < pinky_pos[1] and pinky_dist < 0.3:
                hand_pose = "three fingers v"
            elif pinky_pos[1] < pinky_base[1] and index_joint[1] > index_pos[1] and ring_joint[1] > ring_pos[1] and pinky_joint[1] > pinky_pos[1] and pinky_dist > 0.3:
                hand_pose = "four fingers v"
            else:
                hand_pose = "unknown"

            current_pose = hand_pose

            # Set value on first occurrence of pose
            if current_pose != previous_pose and hand_pose != "unknown":
                index_image_pos = index_pos

            # Identify zones
            if index_image_pos[0] < 0.75 * index_pos[0]:
                zone = "left"
            elif index_image_pos[0] > 1.25 * index_pos[0]:
                zone = "right"
            elif index_image_pos[1] > 1.3 * index_pos[1]:
                zone = "top"
            elif index_image_pos[1] < 0.8 * index_pos[1]:
                zone = "bottom"
            else:
                zone = "unknown"

            # Actions
            if hand_pose == "index finger":
                subarea_width = 640
                subarea_height = 480

                normalized_x = index_pos[0] * image_width - subarea_width / 2
                normalized_y = index_pos[1] * image_height - subarea_height / 2

                screen_x = screen_width - normalized_x * screen_width / subarea_width
                screen_y = normalized_y * screen_height / subarea_height

                previous_positions.append((screen_x, screen_y))

                if len(previous_positions) > smoothing_factor:
                    previous_positions.pop(0)

                average_x = sum([pos[0] for pos in previous_positions]) / len(previous_positions)
                average_y = sum([pos[1] for pos in previous_positions]) / len(previous_positions)

                pyautogui.moveTo(average_x, average_y)

                if index_thumb_base_dist < 0.1:
                    pyautogui.click()

            elif hand_pose == "two fingers v":
                if zone == "top":
                    pyautogui.scroll(15)
                elif zone == "bottom":
                    pyautogui.scroll(-15)
                elif zone == "left":
                    pyautogui.hscroll(5)
                elif zone == "right":
                    pyautogui.hscroll(-5)

            elif hand_pose == "three fingers v":
                if zone == "left":
                    pyautogui.hotkey('ctrl', 'option', 'shift', 'left')
                    time.sleep(1)
                elif zone == "right":
                    pyautogui.hotkey('ctrl', 'option', 'shift', 'right')
                    time.sleep(1)

            elif hand_pose == "four fingers v":
                if zone == "left":
                    pyautogui.hotkey('ctrl', 'left')
                    time.sleep(1)
                elif zone == "right":
                    pyautogui.hotkey('ctrl', 'right')
                    time.sleep(1)

    cv2.imshow('Gesture Window', cv2.flip(image, 1))

    time.sleep(delay)

    key = cv2.waitKey(1)
    if key == 27:
        break
cap.release()

MacOS (and maybe Windows and Linux too) has an accessibility feature that tracks face but I would rather use hands so I ended up making this. Learnt a lot while making this so I would appreciate your suggestions and help in improving this further.

Edit: Please also let me know if the approach itself is completely wrong. While making this, I came across a lot of projects using mediapipe but training their own dataset. I couldn't understand the need for it but I feel it might have its benefits.

\$\endgroup\$
0

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Browse other questions tagged or ask your own question.