Skip to content

Instantly share code, notes, and snippets.

@shuantsu
Created June 12, 2025 20:28
Show Gist options
  • Save shuantsu/0e13a67a3a9c878a888cc15f828d49f8 to your computer and use it in GitHub Desktop.
Save shuantsu/0e13a67a3a9c878a888cc15f828d49f8 to your computer and use it in GitHub Desktop.
import cv2
import mediapipe as mp
import numpy as np
from PIL import Image, ImageDraw, ImageFont
import screeninfo
import pyautogui # Importa a biblioteca para controle do mouse
# Configurações do PyAutoGUI
pyautogui.FAILSAFE = False # Desabilita o fail-safe para evitar interrupções por movimentação do mouse para os cantos
pyautogui.PAUSE = 0.001 # Define um pequeno atraso entre as ações do mouse
# Inicializa a solução MediaPipe Hands
mp_hands = mp.solutions.hands
hands = mp_hands.Hands(
static_image_mode=False,
max_num_hands=1,
min_detection_confidence=0.7,
min_tracking_confidence=0.5
)
mp_drawing = mp.solutions.drawing_utils
# Nome da janela
window_name = 'Detecção de Mão e Coordenadas'
# Abre a câmera padrão
cap = cv2.VideoCapture(0)
if not cap.isOpened():
print("Erro: Não foi possível abrir a câmera.")
exit()
# Obtém as dimensões da tela principal
screen_width = screeninfo.get_monitors()[0].width
screen_height = screeninfo.get_monitors()[0].height
# Cria uma janela nomeada e a define como redimensionável
cv2.namedWindow(window_name, cv2.WINDOW_NORMAL)
# Define a propriedade da janela para maximizar e ocupar a tela cheia
#cv2.setWindowProperty(window_name, cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)
# --- Configuração da fonte para a Pillow ---
try:
font_path = "arial.ttf"
font_size = 30
font = ImageFont.truetype(font_path, font_size)
except IOError:
print(f"Aviso: Não foi possível carregar a fonte {font_path}. Usando a fonte padrão da Pillow.")
font = ImageFont.load_default()
# --- Fim da configuração da fonte ---
print("Pressione 'q' para sair ou feche a janela diretamente.")
# Variável para controlar o estado do clique
# 0: Nenhum clique (movendo o mouse)
# 1: Clique simples
# 2: Arrastando
mouse_mode = 0 # Inicialmente no modo de movimento
while cap.isOpened():
ret, frame = cap.read()
if not ret:
print("Erro: Não foi possível ler o frame.")
break
# Inverte o frame horizontalmente para uma visualização mais natural (como um espelho)
frame = cv2.flip(frame, 1)
# Redimensiona o frame para as dimensões da tela
frame = cv2.resize(frame, (screen_width, screen_height))
# Converte o frame do OpenCV (NumPy array) para uma imagem Pillow
pil_img = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
draw = ImageDraw.Draw(pil_img)
# Converte o frame BGR para RGB para o MediaPipe Hands
rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
# Processa a imagem com o MediaPipe Hands
results = hands.process(rgb_frame)
hand_gesture = "Nenhuma mão detectada"
wrist_z = 0.0
if results.multi_hand_landmarks:
for hand_landmarks in results.multi_hand_landmarks:
mp_drawing.draw_landmarks(frame, hand_landmarks, mp_hands.HAND_CONNECTIONS,
mp_drawing.DrawingSpec(color=(0, 0, 255), thickness=2, circle_radius=2),
mp_drawing.DrawingSpec(color=(0, 255, 0), thickness=2))
# Coordenadas da ponta do dedo indicador (landmark 8)
index_finger_tip = hand_landmarks.landmark[mp_hands.HandLandmark.INDEX_FINGER_TIP]
# Mapeia as coordenadas do dedo indicador para as coordenadas da tela
# As coordenadas do MediaPipe são normalizadas (0 a 1)
# screen_x e screen_y são as coordenadas do mouse na tela
screen_x = int(index_finger_tip.x * screen_width)
screen_y = int(index_finger_tip.y * screen_height)
# Define um "campo de sensibilidade" para mover o mouse, evitando movimentos bruscos
# Você pode ajustar esses valores para a sensibilidade desejada
smoothed_x = screen_x
smoothed_y = screen_y
pyautogui.moveTo(smoothed_x, smoothed_y)
# Calcula a bounding box para a mão (para visualização no frame)
h, w, c = frame.shape
x_min, y_min = w, h
x_max, y_max = 0, 0
for lm in hand_landmarks.landmark:
cx, cy = int(lm.x * w), int(lm.y * h)
x_min = min(x_min, cx)
y_min = min(y_min, cy)
x_max = max(x_max, cx)
y_max = max(y_max, cy)
padding = 20
x_min = max(0, x_min - padding)
y_min = max(0, y_min - padding)
x_max = min(w, x_max + padding)
y_max = min(h, y_max + padding)
rectangle_color = (0, 255, 255)
draw.rectangle([(x_min, y_min), (x_max, y_max)], outline=rectangle_color, width=2)
center_x = (x_min + x_max) // 2
center_y = (y_min + y_max) // 2
wrist_z = hand_landmarks.landmark[mp_hands.HandLandmark.WRIST].z
# --- Lógica para detectar mão aberta vs. mão fechada e controlar o clique ---
tip_ids = [8, 12, 16, 20] # Pontas dos dedos: indicador, meio, anelar, mínimo
mcp_ids = [5, 9, 13, 17] # Bases dos dedos
fingers_up = 0
# Verifica o polegar (landmark 4) em relação ao landmark 2 (base do polegar)
# Se a ponta do polegar estiver à direita do ponto de articulação inferior do polegar (para mão direita espelhada)
# Ou à esquerda para a mão esquerda.
# O sinal '<' ou '>' pode variar dependendo da mão e se o frame está espelhado.
# Uma abordagem mais robusta seria verificar a distância entre a ponta e a base ou a orientação.
# Para simplificar, vou usar uma heurística de Y como você fez para os outros dedos, mas pode precisar de ajuste.
if hand_landmarks.landmark[mp_hands.HandLandmark.THUMB_TIP].y < hand_landmarks.landmark[mp_hands.HandLandmark.THUMB_IP].y - 0.02:
fingers_up += 1
for i in range(4): # Para indicador, meio, anelar, mínimo
tip_y = hand_landmarks.landmark[tip_ids[i]].y
mcp_y = hand_landmarks.landmark[mcp_ids[i]].y
if tip_y < mcp_y - 0.05: # A ponta do dedo está mais "para cima" (Y menor) que a base
fingers_up += 1
# Lógica de controle do mouse
if fingers_up >= 4: # Mão aberta (todos ou quase todos os dedos para cima) - MOVER
hand_gesture = "Mão Aberta (Movendo)"
if mouse_mode == 1: # Se estava clicando, libera o clique
pyautogui.mouseUp()
mouse_mode = 0
elif mouse_mode == 2: # Se estava arrastando, libera o arraste
pyautogui.mouseUp()
mouse_mode = 0
elif fingers_up == 1 and hand_landmarks.landmark[mp_hands.HandLandmark.INDEX_FINGER_TIP].y < hand_landmarks.landmark[mp_hands.HandLandmark.INDEX_FINGER_PIP].y:
# Se apenas o dedo indicador está levantado (e outros estão para baixo) - CLIQUE ÚNICO OU ARRRASTAR
hand_gesture = "Indicador para Cima (Clicar/Arrastar)"
# Se ainda não estamos clicando/arrastando
if mouse_mode == 0:
pyautogui.mouseDown() # Pressiona o botão do mouse
mouse_mode = 2 # Entra no modo de arrastar
print("Mouse Down / Arrastando")
# Se já estamos arrastando, apenas continua o movimento (já handled por pyautogui.moveTo)
else: # Mão fechada (ou outros gestos sem dedos levantados) - CLIQUE OU OUTRA AÇÃO
hand_gesture = "Mão Fechada (Clicar)"
if mouse_mode == 0: # Se ainda não clicamos
pyautogui.click() # Executa um clique
mouse_mode = 1 # Entra no modo de clique simples
print("Clique detectado")
elif mouse_mode == 2: # Se estava arrastando, libera o arraste
pyautogui.mouseUp()
mouse_mode = 0
coords_text = f'Mouse X: {smoothed_x}, Y: {smoothed_y}'
gesture_text = f'Gesto: {hand_gesture}'
z_text = f'Z (Relativa): {wrist_z:.2f}'
text_color = (0, 255, 255)
draw.text((x_min, y_min - 50), coords_text, font=font, fill=text_color)
draw.text((x_min, y_min - 30), gesture_text, font=font, fill=text_color)
draw.text((x_min, y_min - 10), z_text, font=font, fill=text_color)
# Converte a imagem Pillow de volta para o formato OpenCV
frame = cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR)
cv2.imshow(window_name, frame)
if cv2.getWindowProperty(window_name, cv2.WND_PROP_VISIBLE) < 1:
break
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
print("Câmera liberada e janelas fechadas.")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment