Created
June 12, 2025 20:28
-
-
Save shuantsu/0e13a67a3a9c878a888cc15f828d49f8 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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