Download app.py from GLITCH-08/PAINTER: direct link, hf CLI and curl.
- Browser
- Download file 7.78 kB
-
https://huggingface.co/spaces/GLITCH-08/PAINTER/resolve/main/app.py
- Command line
-
hf download hf://spaces/GLITCH-08/PAINTER/app.py
-
curl -L -o app.py https://huggingface.co/spaces/GLITCH-08/PAINTER/resolve/main/app.py
7.78 kB
| import cv2 | |
| import mediapipe as mp | |
| import numpy as np | |
| import streamlit as st | |
| import os | |
| import math | |
| # Streamlit app setup | |
| st.title("AI Virtual Painter") | |
| st.text("Interact with the AI Virtual Painter using hand gestures!") | |
| # Mediapipe and OpenCV setup | |
| mp_hands = mp.solutions.hands | |
| # Configuration | |
| width, height = 640, 480 | |
| draw_color = (0, 0, 255) # Default color: Red | |
| thickness = 15 | |
| tipIds = [4, 8, 12, 16, 20] # Fingertips indexes | |
| xp, yp = 0, 0 # Previous points for drawing | |
| # Load header images for color options | |
| header_images = [f"Header/{img}" for img in os.listdir("Header") if img.endswith((".png", ".jpg", ".jpeg"))] | |
| header_list = [cv2.imread(img) for img in header_images] | |
| header = header_list[0] # Default header | |
| # Sidebar for color selection | |
| # st.sidebar.title("Select Drawing Color") | |
| color_map = { | |
| "Red": (0, 0, 255), | |
| "Blue": (255, 0, 0), | |
| "Green": (0, 255, 0), | |
| "Eraser": (0, 0, 0) | |
| } | |
| selected_color = list(color_map.keys())[0] | |
| draw_color = color_map[selected_color] | |
| # Canvas for drawing | |
| canvas = np.zeros((height, width, 3), dtype=np.uint8) | |
| # Start capturing video | |
| cap = cv2.VideoCapture(0) | |
| # Create a placeholder for the live video feed | |
| video_placeholder = st.empty() | |
| # Process the video feed | |
| with mp_hands.Hands(min_detection_confidence=0.85, min_tracking_confidence=0.5, max_num_hands=1) as hands: | |
| while cap.isOpened(): | |
| ret, frame = cap.read() | |
| if not ret: | |
| st.error("Failed to capture video.") | |
| break | |
| frame = cv2.flip(frame, 1) # Flip the frame horizontally | |
| frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) | |
| # Process hand landmarks | |
| results = hands.process(frame_rgb) | |
| if results.multi_hand_landmarks: | |
| for hand_landmarks in results.multi_hand_landmarks: | |
| points = [ | |
| (int(lm.x * width), int(lm.y * height)) | |
| for lm in hand_landmarks.landmark | |
| ] | |
| if points: | |
| x1, y1 = points[8] # Index finger | |
| x2, y2 = points[12] # Middle finger | |
| x3, y3 = points[4] # Thumb | |
| x4, y4 = points[20] # Pinky finger | |
| ## Checking which fingers are up | |
| fingers = [] | |
| # Checking the thumb | |
| if points[tipIds[0]][0] < points[tipIds[0] - 1][0]: | |
| fingers.append(1) | |
| else: | |
| fingers.append(0) | |
| # The rest of the fingers | |
| for id in range(1, 5): | |
| if points[tipIds[id]][1] < points[tipIds[id] - 2][1]: | |
| fingers.append(1) | |
| else: | |
| fingers.append(0) | |
| # Check if in selection mode (two fingers up) | |
| nonSel = [0, 3, 4] # indexes of the fingers that need to be down in the Selection Mode | |
| if (fingers[1] and fingers[2]) and all(fingers[i] == 0 for i in nonSel): | |
| # Selection mode (change color) | |
| xp, yp =[x1, y1] # Reset drawing position | |
| # Selecting the colors and the eraser on the screen | |
| for i, img in enumerate(header_list): | |
| if (10 < y1 < 100): | |
| if (100 < x1 < 120): | |
| header = header_list[0] | |
| draw_color = color_map[list(color_map.keys())[0]] | |
| elif (230 < x1 < 250): | |
| header = header_list[1] | |
| draw_color = color_map[list(color_map.keys())[1]] | |
| elif (370 < x1 < 390): | |
| header = header_list[2] | |
| draw_color = color_map[list(color_map.keys())[2]] | |
| elif (510 < x1 < 540): | |
| header = header_list[3] | |
| draw_color = color_map[list(color_map.keys())[3]] | |
| break | |
| cv2.rectangle(frame, (x1-10, y1-15), (x2+10, y2+23), draw_color, -1) | |
| # Adjust line thickness using index finger and thumb | |
| selecting = [1, 1, 0, 0, 0] # Thumb and index finger up | |
| setting = [1, 1, 0, 0, 1] # Thumb, index, and pinky up | |
| if all(fingers[i] == j for i, j in zip(range(0,5), selecting)) or \ | |
| all(fingers[i] == j for i, j in zip(range(0,5), setting)): | |
| # Calculate radius based on distance between thumb and index finger | |
| r = int(math.sqrt((x1 - x3) ** 2 + (y1 - y3) ** 2) / 3) | |
| # Find midpoint between thumb and index finger | |
| x0, y0 = [(x1 + x3) / 2, (y1 + y3) / 2] | |
| # Orthogonal vector for alignment | |
| v1, v2 = [x1 - x3, y1 - y3] | |
| v1, v2 = [-v2, v1] | |
| # Normalize vector | |
| mod_v = math.sqrt(v1 ** 2 + v2 ** 2) | |
| v1, v2 = [v1 / mod_v, v2 / mod_v] | |
| # Draw circle indicating thickness selection | |
| c = 3 + r | |
| x0, y0 = [int(x0 - v1 * c), int(y0 - v2 * c)] | |
| cv2.circle(frame, (x0, y0), int(r / 2), draw_color, -1) | |
| # Confirm thickness when pinky is up | |
| if fingers[4]: | |
| thickness = r | |
| cv2.putText(frame, "Thickness Set", (x4 - 25, y4 - 8), | |
| cv2.FONT_HERSHEY_TRIPLEX, 0.8, (0, 0, 0), 1) | |
| xp, yp = [x1, y1] | |
| # Check if in drawing mode (only index finger up) | |
| nonDraw = [0, 2, 3, 4] | |
| if fingers[1] and all(fingers[i] == 0 for i in nonDraw): | |
| cv2.circle(frame, (x1, y1), thickness // 2, draw_color, -1) | |
| if xp == 0 and yp == 0: | |
| xp, yp = [x1, y1] | |
| cv2.line(canvas, (xp, yp), (x1, y1), draw_color, thickness) | |
| xp, yp = [x1, y1] | |
| # Check if erasing (hand closed - no fingers up) | |
| if not any(fingers): | |
| canvas = np.zeros((height, width, 3), dtype=np.uint8) | |
| xp, yp = 0, 0 | |
| # Overlay the canvas onto the video feed | |
| canvas_resized = cv2.resize(canvas, (frame.shape[1], frame.shape[0])) | |
| # Create an inverted binary mask | |
| img_gray = cv2.cvtColor(canvas_resized, cv2.COLOR_BGR2GRAY) | |
| _, img_inv = cv2.threshold(img_gray, 50, 255, cv2.THRESH_BINARY_INV) | |
| img_inv = cv2.cvtColor(img_inv, cv2.COLOR_GRAY2BGR) | |
| # Combine the frame and the canvas | |
| frame = cv2.bitwise_and(frame, img_inv) | |
| frame = cv2.bitwise_or(frame, canvas_resized) | |
| # Add the header image to the frame | |
| frame_height, frame_width = frame.shape[:2] | |
| header_resized = cv2.resize(header, (frame_width, 100)) | |
| frame[0:100, 0:frame_width] = header_resized | |
| # Update the live video feed in Streamlit | |
| video_placeholder.image(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB), channels="RGB") | |
| # Release the video capture | |
| cap.release() | |