Creating a Real-Time Hand-Tracking Drawing Application with Python

Introduction

Welcome to Finger-Funk! In this blog post, we will build a real-time hand-tracking drawing application using Python, OpenCV, and MediaPipe. This application allows users to draw on the screen using hand gestures, change colors, adjust brush thickness, and erase drawings—all without touching the screen. By the end of this tutorial, you'll have a functional drawing tool that leverages computer vision for an interactive experience.

🎥 Demo

Check out this demo of Finger-Funk in action:

📂 GitHub Repository

You can find the complete source code for Finger-Funk on GitHub. Click the image below to visit the repository and get started:

Finger-Funk GitHub Repository

Finger-Funk: Your Hands, Your Canvas

Finger-Funk is a virtual canvas application that captures hand movements via your webcam to create digital art. Powered by OpenCV and MediaPipe, it transforms simple gestures into dynamic drawing actions.

🎨🖐️ Draw with Your Hands: A Gesture-Based Drawing App

This fun and interactive application allows you to draw on your screen using hand gestures detected by your webcam. Whether you're changing colors, adjusting brush thickness, or erasing drawings, Finger-Funk makes digital art intuitive and hands-free.

Features

Prerequisites

Before we begin, ensure you have the following installed on your Windows machine:

You can install the required libraries using pip:

pip install opencv-python mediapipe

Installation

Clone the repository:

git clone https://github.com/udaysinh-git/finger-funk.git
cd finger-funk

Create a virtual environment and activate it:

python -m venv .venv
.venv\Scripts\activate  # On Windows use `.venv\Scripts\activate`

Install the required packages:

pip install -r requirements.txt

Usage

Run the Jupyter Notebook:

jupyter notebook main.ipynb

Follow the instructions in the notebook to start the application.

Step-by-Step Guide

Step 1: Import Libraries

First, let's import the necessary libraries for our application.

import cv2
import mediapipe as mp
import time

Step 2: Initialize MediaPipe and OpenCV

We'll initialize MediaPipe for hand tracking and set up OpenCV for video capture.

# Initialize MediaPipe Hands
mp_hands = mp.solutions.hands
hands = mp_hands.Hands()
mp_drawing = mp.solutions.drawing_utils

# Initialize OpenCV
cap = cv2.VideoCapture(0)

Step 3: Set Up Drawing Settings

Define the initial settings for drawing, including colors, thickness, and other configurations.

# Drawing settings
draw_color = (0, 255, 0)  # Green color for drawing
draw_thickness = 5
erase_thickness = 50  # Thickness of the eraser
drawing_points = []  # List to store drawing points

# Color options
colors = [(0, 255, 0), (0, 0, 255), (255, 0, 0), (255, 255, 0)]
color_index = 0

# Color selection box settings
color_box_size = 50
color_box_positions = [(10, 10), (70, 10), (130, 10), (190, 10)]

# Time tracking for clearing delay
clear_time = None
clear_delay = 5 

# Track previous points for drawing lines
prev_point = None

Step 4: Define Helper Functions

We'll create a function to determine if the middle finger is up, which will be used to clear the drawings.

def is_middle_finger_up(thumb_tip_coords, index_tip_coords, middle_tip_coords, ring_tip_coords, pinky_tip_coords):
    # Check if middle finger is up and other fingers are down
    return (middle_tip_coords[1] < index_tip_coords[1] and
            middle_tip_coords[1] < ring_tip_coords[1] and
            middle_tip_coords[1] < thumb_tip_coords[1] and
            middle_tip_coords[1] < pinky_tip_coords[1] and
            index_tip_coords[1] > thumb_tip_coords[1] and
            ring_tip_coords[1] > thumb_tip_coords[1] and
            pinky_tip_coords[1] > thumb_tip_coords[1])

Step 5: Create a Resizable Window

Set up a named window that can be resized for better visibility.

# Create a named window with the ability to resize
cv2.namedWindow('Hand Drawing App', cv2.WINDOW_NORMAL)
# Set the desired window size
cv2.resizeWindow('Hand Drawing App', 1366, 768)

Step 6: Implement the Main Loop

The core of the application captures video frames, processes hand landmarks, and handles drawing based on user gestures.

while cap.isOpened():
    ret, frame = cap.read()
    if not ret:
        break

    # Flip the frame horizontally for a selfie-view display
    frame = cv2.flip(frame, 1)
    frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)

    # Draw color selection boxes
    for i, color in enumerate(colors):
        cv2.rectangle(frame, color_box_positions[i], 
                      (color_box_positions[i][0] + color_box_size, color_box_positions[i][1] + color_box_size), 
                      color, -1)

    # Process the frame and detect hands
    results = hands.process(frame_rgb)

    right_hand_middle_up = False
    left_hand_middle_up = False

    if results.multi_hand_landmarks:
        for hand_landmarks, handedness in zip(results.multi_hand_landmarks, results.multi_handedness):
            # Draw hand landmarks
            mp_drawing.draw_landmarks(frame, hand_landmarks, mp_hands.HAND_CONNECTIONS)

            # Get hand label (Left or Right)
            hand_label = handedness.classification[0].label

            # Get coordinates of finger tips
            thumb_tip = hand_landmarks.landmark[mp_hands.HandLandmark.THUMB_TIP]
            index_tip = hand_landmarks.landmark[mp_hands.HandLandmark.INDEX_FINGER_TIP]
            middle_tip = hand_landmarks.landmark[mp_hands.HandLandmark.MIDDLE_FINGER_TIP]
            ring_tip = hand_landmarks.landmark[mp_hands.HandLandmark.RING_FINGER_TIP]
            pinky_tip = hand_landmarks.landmark[mp_hands.HandLandmark.PINKY_TIP]

            # Convert normalized coordinates to pixel coordinates
            h, w, _ = frame.shape
            thumb_tip_coords = (int(thumb_tip.x * w), int(thumb_tip.y * h))
            index_tip_coords = (int(index_tip.x * w), int(index_tip.y * h))
            middle_tip_coords = (int(middle_tip.x * w), int(middle_tip.y * h))
            ring_tip_coords = (int(ring_tip.x * w), int(ring_tip.y * h))
            pinky_tip_coords = (int(pinky_tip.x * w), int(pinky_tip.y * h))

            if hand_label == 'Right':
                # Check if thumb and index finger are close to each other
                distance_thumb_index = ((thumb_tip_coords[0] - index_tip_coords[0]) ** 2 + 
                                        (thumb_tip_coords[1] - index_tip_coords[1]) ** 2) ** 0.5
                if distance_thumb_index < 30 and (clear_time is None or time.time() - clear_time > clear_delay):
                    if prev_point is not None:
                        drawing_points.append((prev_point, index_tip_coords, draw_color, draw_thickness))
                    prev_point = index_tip_coords
                else:
                    prev_point = None

                # Check if index finger is over a color box
                for i, (x, y) in enumerate(color_box_positions):
                    if x < index_tip_coords[0] < x + color_box_size and y < index_tip_coords[1] < y + color_box_size:
                        draw_color = colors[i]

                # Adjust thickness based on the number of fingers touching the thumb
                touching_fingers = 0
                for tip_coords in [index_tip_coords, middle_tip_coords, ring_tip_coords, pinky_tip_coords]:
                    distance = ((thumb_tip_coords[0] - tip_coords[0]) ** 2 + 
                                (thumb_tip_coords[1] - tip_coords[1]) ** 2) ** 0.5
                    if distance < 30:
                        touching_fingers += 1

                draw_thickness = 5 + touching_fingers * 5

                # Check if middle finger is up
                if is_middle_finger_up(thumb_tip_coords, index_tip_coords, middle_tip_coords, ring_tip_coords, pinky_tip_coords):
                    right_hand_middle_up = True

            elif hand_label == 'Left':
                # Check if hand is wide open (thumb and pinky are far apart)
                distance_thumb_pinky = ((thumb_tip_coords[0] - pinky_tip_coords[0]) ** 2 + 
                                        (thumb_tip_coords[1] - pinky_tip_coords[1]) ** 2) ** 0.5
                if distance_thumb_pinky > 100:
                    # Erase points near the hand
                    margin = 10
                    drawing_points = [point for point in drawing_points if 
                                      ((point[0][0] - index_tip_coords[0]) ** 2 + 
                                       (point[0][1] - index_tip_coords[1]) ** 2) ** 0.5 > erase_thickness or
                                      point[0][0] < margin or point[0][0] > w - margin or 
                                      point[0][1] < margin or point[0][1] > h - margin]

                # Check if middle finger is up
                if is_middle_finger_up(thumb_tip_coords, index_tip_coords, middle_tip_coords, ring_tip_coords, pinky_tip_coords):
                    left_hand_middle_up = True

    # Clear all drawings if middle finger is up on both hands
    if right_hand_middle_up and left_hand_middle_up:
        drawing_points.clear()
        clear_time = time.time()  # Record the time when the drawing is cleared

    # Draw all lines in drawing_points
    for start_point, end_point, color, thickness in drawing_points:
        cv2.line(frame, start_point, end_point, color, thickness)

    cv2.imshow('Hand Drawing App', frame)

    if cv2.waitKey(1) & 0xFF == 27:
        break

Step 7: Release Resources

After exiting the main loop, release the video capture and close all OpenCV windows.

cap.release()
cv2.destroyAllWindows()

How It Works

Dependencies

Contributing

Feel free to fork this repository and contribute by submitting pull requests. Any improvements or new features are welcome!

License

This project is licensed under the MIT License. See the LICENSE file for details.

References

Acknowledgements

Enjoy drawing with your hands! 🎨🖐️