Primeiros passos com visão computacional usando Python + OpenCV + MediaPipe
Por que usar OpenCV e MediaPipe?
O OpenCV é uma biblioteca poderosa para processamento de imagens, e o MediaPipe (desenvolvido pelo Google) oferece soluções prontas para tarefas como detecção de mãos. Juntos, eles simplificam o desenvolvimento, já que grande parte do trabalho pesado (como detectar landmarks das mãos) já está implementada. Vamos explorar como configurar e usar essas ferramentas para criar um sistema de controle por gestos.
Pré-requisitos
Antes de começar, instale as dependências necessárias. Execute no terminal:
pip install opencv-python mediapipe numpy screeninfo pydirectinput
- opencv-python: Para processar vídeo da câmera.
- mediapipe: Para detecção de mãos.
- numpy: Para cálculos numéricos.
- screeninfo: Para obter as dimensões da tela.
- pydirectinput: Para controlar o mouse.
Certifique-se de ter uma webcam conectada. No código, usaremos a câmera com índice 1 (cv2.VideoCapture(1)), mas você pode ajustar para 0 se for a câmera padrão.
Passo 1: Configuração Inicial
Vamos começar configurando as bibliotecas e inicializando a câmera. O código a seguir importa as bibliotecas necessárias, configura o MediaPipe para rastrear até duas mãos e define as dimensões da tela.
import cv2
import mediapipe as mp
import numpy as np
import screeninfo
import pydirectinput
# Configuração do pydirectinput
pydirectinput.FAILSAFE = False
pydirectinput.PAUSE = 0.001
# Configuração do MediaPipe Hands
mp_hands = mp.solutions.hands
hands = mp_hands.Hands(
static_image_mode=False,
max_num_hands=2,
min_detection_confidence=0.7,
min_tracking_confidence=0.5
)
mp_drawing = mp.solutions.drawing_utils
# Dimensões da tela
monitors = screeninfo.get_monitors()
if monitors:
screen_width = monitors[0].width
screen_height = monitors[0].height
else:
screen_width = 1920
screen_height = 1080
print("Aviso: Não foi possível detectar as dimensões da tela, usando 1920x1080.")
# Configuração da câmera
cap = cv2.VideoCapture(1)
if not cap.isOpened():
print("Erro: Não foi possível abrir a câmera.")
exit()
camera_width = 1280
camera_height = 720
cap.set(cv2.CAP_PROP_FRAME_WIDTH, camera_width)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, camera_height)
O que está acontecendo?
- Importações: Importamos
cv2(OpenCV),mediapipe,numpy,screeninfoepydirectinput. - pydirectinput: Desativamos o failsafe e definimos um pequeno atraso para ações do mouse.
- MediaPipe Hands: Configuramos o módulo de detecção de mãos para rastrear até duas mãos com confiança mínima de 0.7 para detecção e 0.5 para rastreamento.
- Tela: Usamos
screeninfopara obter as dimensões da tela, com um fallback para 1920x1080. - Câmera: Abrimos a webcam e definimos a resolução para 1280x720.
O MediaPipe já faz a detecção de mãos automaticamente, então não precisamos nos preocupar com algoritmos complexos!
Passo 2: Processando o Vídeo e Detectando Mãos
Agora, vamos criar o loop principal para capturar frames da câmera e processá-los com o MediaPipe. Também desenharemos os landmarks das mãos no frame para visualização.
print("Pressione 'q' para sair.")
cv2.namedWindow('Hand Gesture Control', cv2.WINDOW_NORMAL)
while True:
ret, frame = cap.read()
if not ret:
print("Erro: Não foi possível ler o frame.")
break
# Inverte o frame horizontalmente para uma visão espelhada
frame = cv2.flip(frame, 1)
# Converte BGR para RGB (MediaPipe usa RGB)
rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
# Processa com MediaPipe Hands
results = hands.process(rgb_frame)
if results.multi_hand_landmarks:
for hand_idx, hand_landmarks in enumerate(results.multi_hand_landmarks):
mp_drawing.draw_landmarks(
frame, hand_landmarks, mp_hands.HAND_CONNECTIONS,
mp_drawing.DrawingSpec(color=(255, 0, 0), thickness=2, circle_radius=2),
mp_drawing.DrawingSpec(color=(255, 100, 0), thickness=2)
)
cv2.imshow('Hand Gesture Control', frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
O que está acontecendo?
- Loop de vídeo: Capturamos frames com
cap.read()e verificamos se a captura foi bem-sucedida. - Espelhamento: Usamos
cv2.flip(frame, 1)para criar uma visão espelhada, mais natural para o usuário. - Conversão de cor: Convertemos o frame de BGR (padrão do OpenCV) para RGB (necessário para o MediaPipe).
- Detecção de mãos: O MediaPipe processa o frame e retorna os landmarks das mãos.
- Desenho: Usamos
mp_drawing.draw_landmarkspara desenhar os pontos e conexões das mãos no frame.
Execute esse código e você já verá os landmarks das mãos desenhados na tela! O MediaPipe faz todo o trabalho de detecção, tornando o processo incrivelmente simples.
Passo 3: Identificando Mãos e Controlando o Cursor
Vamos adicionar lógica para identificar se a mão detectada é a direita ou esquerda e usar a mão direita para mover o cursor do mouse.
# Variáveis para rastrear mãos
tracked_right_hand_landmarks = None
tracked_left_hand_landmarks = None
def get_hand_centroid(hand_landmarks, frame_width, frame_height):
x_coords = [lm.x * frame_width for lm in hand_landmarks.landmark]
y_coords = [lm.y * frame_height for lm in hand_landmarks.landmark]
return np.mean(x_coords), np.mean(y_coords)
# Dentro do loop while True, após results = hands.process(rgb_frame):
detected_hands_info = []
if results.multi_hand_landmarks:
for hand_idx, hand_landmarks in enumerate(results.multi_hand_landmarks):
handedness = results.multi_handedness[hand_idx].classification[0].label
centroid_x, centroid_y = get_hand_centroid(hand_landmarks, frame.shape[1], frame.shape[0])
detected_hands_info.append((handedness, hand_landmarks, (centroid_x, centroid_y)))
# Atribuir mãos
new_right_hand_lm = None
new_left_hand_lm = None
if len(detected_hands_info) == 1:
h_type, h_lm, _ = detected_hands_info[0]
if h_type == 'Right':
new_right_hand_lm = h_lm
else:
new_left_hand_lm = h_lm
elif len(detected_hands_info) == 2:
detected_hands_info.sort(key=lambda x: x[2][0])
new_left_hand_lm = detected_hands_info[0][1]
new_right_hand_lm = detected_hands_info[1][1]
tracked_right_hand_landmarks = new_right_hand_lm
tracked_left_hand_landmarks = new_left_hand_lm
else:
tracked_right_hand_landmarks = None
tracked_left_hand_landmarks = None
# Processar mão direita para mover o cursor
if tracked_right_hand_landmarks:
index_finger_tip = tracked_right_hand_landmarks.landmark[mp_hands.HandLandmark.INDEX_FINGER_TIP]
frame_x = int(index_finger_tip.x * frame.shape[1])
frame_y = int(index_finger_tip.y * frame.shape[0])
screen_x = int(np.interp(frame_x, [0, frame.shape[1]], [0, screen_width]))
screen_y = int(np.interp(frame_y, [0, frame.shape[0]], [0, screen_height]))
pydirectinput.moveTo(screen_x, screen_y)
cv2.putText(frame, f'Mao Direita: Movendo Cursor (X: {screen_x}, Y: {screen_y})',
(frame_x - 50, frame_y - 50), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 0, 0), 2)
O que está acontecendo?
- Rastreamento de mãos: Usamos variáveis para rastrear os landmarks da mão direita e esquerda entre frames.
- Centróide: A função
get_hand_centroidcalcula o centro da mão para ajudar na identificação. - Atribuição de mãos: Quando uma ou duas mãos são detectadas, usamos a posição no frame (após espelhamento) para determinar qual é a mão direita e esquerda.
- Controle do cursor: A posição da ponta do dedo indicador da mão direita é mapeada para as coordenadas da tela usando
np.interp, epydirectinput.moveTomove o cursor.
Agora, a mão direita controla o cursor do mouse! Experimente mover a mão na frente da câmera.
Passo 4: Detectando Gestos com a Mão Esquerda
Vamos adicionar a funcionalidade de detectar se a mão esquerda está aberta ou fechada para simular cliques e arrastos.
# Variável para o estado da mão esquerda
left_hand_action_state = "None"
def is_finger_up(landmark_list, finger_tip_id, finger_pip_id):
return landmark_list.landmark[finger_tip_id].y < landmark_list.landmark[finger_pip_id].y - 0.03
# Dentro do loop while True, após processar a mão direita:
if tracked_left_hand_landmarks:
frame_x_left, frame_y_left = get_hand_centroid(tracked_left_hand_landmarks, frame.shape[1], frame.shape[0])
frame_x_left = int(frame_x_left)
frame_y_left = int(frame_y_left)
fingers_up_left = 0
if is_finger_up(tracked_left_hand_landmarks, mp_hands.HandLandmark.THUMB_TIP, mp_hands.HandLandmark.THUMB_IP):
fingers_up_left += 1
tip_ids = [mp_hands.HandLandmark.INDEX_FINGER_TIP, mp_hands.HandLandmark.MIDDLE_FINGER_TIP,
mp_hands.HandLandmark.RING_FINGER_TIP, mp_hands.HandLandmark.PINKY_TIP]
pip_ids = [mp_hands.HandLandmark.INDEX_FINGER_PIP, mp_hands.HandLandmark.MIDDLE_FINGER_PIP,
mp_hands.HandLandmark.RING_FINGER_PIP, mp_hands.HandLandmark.PINKY_PIP]
for i in range(4):
if is_finger_up(tracked_left_hand_landmarks, tip_ids[i], pip_ids[i]):
fingers_up_left += 1
is_closed_fist = (fingers_up_left <= 1)
if is_closed_fist:
hand_gesture_left = "Mao Esquerda: Mao Fechada (Arrastando)"
if left_hand_action_state != "Closed":
pydirectinput.mouseDown()
print("Mao Esquerda: Mouse Down / Iniciando Arraste")
left_hand_action_state = "Closed"
else:
hand_gesture_left = "Mao Esquerda: Mao Aberta (Liberando)"
if left_hand_action_state == "Closed":
pydirectinput.mouseUp()
print("Mao Esquerda: Mouse Up / Arraste Finalizado")
left_hand_action_state = "Open"
cv2.putText(frame, f'Gesto: {hand_gesture_left}', (frame_x_left - 50, frame_y_left - 50),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
# Lidar com a saída da mão esquerda
if not tracked_left_hand_landmarks and left_hand_action_state == "Closed":
pydirectinput.mouseUp()
print("Mao Esquerda: Mouse Up / Arraste Finalizado (Mao saiu do frame)")
left_hand_action_state = "None"
elif not tracked_left_hand_landmarks:
left_hand_action_state = "None"
O que está acontecendo?
- Detecção de dedos: A função
is_finger_upverifica se um dedo está levantado comparando as coordenadas Y da ponta e da articulação proximal. - Mão fechada: Consideramos a mão fechada se no máximo um dedo estiver levantado.
- Ações do mouse: Uma mão fechada inicia um arraste (
mouseDown), e uma mão aberta libera o clique (mouseUp). Se a mão sai do frame durante um arraste, liberamos o clique.
Agora, a mão esquerda pode arrastar objetos quando fechada e soltar quando aberta!
Passo 5: Feedback Visual e Finalização
Por fim, adicionamos feedback visual para informar o usuário sobre o estado das mãos e liberamos os recursos ao encerrar.
# Dentro do loop while True, antes de cv2.imshow:
if not tracked_left_hand_landmarks and not tracked_right_hand_landmarks:
cv2.putText(frame, "Nenhuma mao detectada", (50, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 255), 2)
elif not tracked_left_hand_landmarks:
cv2.putText(frame, "Mao Esquerda NAO detectada", (50, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 255), 2)
elif not tracked_right_hand_landmarks:
cv2.putText(frame, "Mao Direita NAO detectada", (50, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 255), 2)
# Após o loop while True:
cap.release()
cv2.destroyAllWindows()
print("Camera liberada e janelas fechadas.")
O que está acontecendo?
- Feedback: Exibimos mensagens na tela indicando quais mãos estão sendo detectadas.
- Limpeza: Liberamos a câmera e fechamos as janelas do OpenCV ao sair.
Código Completo
Aqui está o código completo para você copiar e executar. Salve-o como hand_tracking.py e execute com python hand_tracking.py.
import cv2
import mediapipe as mp
import numpy as np
import screeninfo
import pydirectinput
# --- pydirectinput config ---
pydirectinput.FAILSAFE = False
pydirectinput.PAUSE = 0.001
# --- MediaPipe Hands setup ---
mp_hands = mp.solutions.hands
hands = mp_hands.Hands(
static_image_mode=False,
max_num_hands=2,
min_detection_confidence=0.7,
min_tracking_confidence=0.5
)
mp_drawing = mp.solutions.drawing_utils
# --- Screen dimensions ---
monitors = screeninfo.get_monitors()
if monitors:
screen_width = monitors[0].width
screen_height = monitors[0].height
else:
screen_width = 1920
screen_height = 1080
print("Warning: Could not detect screen dimensions, using default 1920x1080.")
# --- Camera setup ---
cap = cv2.VideoCapture(1)
if not cap.isOpened():
print("Erro: Não foi possível abrir a câmera.")
exit()
camera_width = 1280
camera_height = 720
cap.set(cv2.CAP_PROP_FRAME_WIDTH, camera_width)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, camera_height)
# --- Variables for hand states ---
left_hand_action_state = "None"
tracked_right_hand_landmarks = None
tracked_left_hand_landmarks = None
# --- Functions for hand detection ---
def is_finger_up(landmark_list, finger_tip_id, finger_pip_id):
return landmark_list.landmark[finger_tip_id].y < landmark_list.landmark[finger_pip_id].y - 0.03
def get_hand_centroid(hand_landmarks, frame_width, frame_height):
x_coords = [lm.x * frame_width for lm in hand_landmarks.landmark]
y_coords = [lm.y * frame_height for lm in hand_landmarks.landmark]
return np.mean(x_coords), np.mean(y_coords)
# --- Main loop for video processing ---
print("Pressione 'q' para sair.")
cv2.namedWindow('Hand Gesture Control', cv2.WINDOW_NORMAL)
while True:
ret, frame = cap.read()
if not ret:
print("Erro: Não foi possível ler o frame.")
break
frame = cv2.flip(frame, 1)
rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
results = hands.process(rgb_frame)
current_frame_left_hand_detected = False
current_frame_right_hand_detected = False
detected_hands_info = []
if results.multi_hand_landmarks:
for hand_idx, hand_landmarks in enumerate(results.multi_hand_landmarks):
handedness = results.multi_handedness[hand_idx].classification[0].label
centroid_x, centroid_y = get_hand_centroid(hand_landmarks, frame.shape[1], frame.shape[0])
detected_hands_info.append((handedness, hand_landmarks, (centroid_x, centroid_y)))
new_right_hand_lm = None
new_left_hand_lm = None
if len(detected_hands_info) == 1:
h_type, h_lm, _ = detected_hands_info[0]
if h_type == 'Right':
new_right_hand_lm = h_lm
else:
new_left_hand_lm = h_lm
elif len(detected_hands_info) == 2:
detected_hands_info.sort(key=lambda x: x[2][0])
new_left_hand_lm = detected_hands_info[0][1]
new_right_hand_lm = detected_hands_info[1][1]
tracked_right_hand_landmarks = new_right_hand_lm
tracked_left_hand_landmarks = new_left_hand_lm
else:
tracked_right_hand_landmarks = None
tracked_left_hand_landmarks = None
if tracked_right_hand_landmarks:
current_frame_right_hand_detected = True
mp_drawing.draw_landmarks(frame, tracked_right_hand_landmarks, mp_hands.HAND_CONNECTIONS,
mp_drawing.DrawingSpec(color=(255, 0, 0), thickness=2, circle_radius=2),
mp_drawing.DrawingSpec(color=(255, 100, 0), thickness=2))
index_finger_tip = tracked_right_hand_landmarks.landmark[mp_hands.HandLandmark.INDEX_FINGER_TIP]
frame_x = int(index_finger_tip.x * frame.shape[1])
frame_y = int(index_finger_tip.y * frame.shape[0])
screen_x = int(np.interp(frame_x, [0, frame.shape[1]], [0, screen_width]))
screen_y = int(np.interp(frame_y, [0, frame.shape[0]], [0, screen_height]))
pydirectinput.moveTo(screen_x, screen_y)
cv2.putText(frame, f'Mao Direita: Movendo Cursor (X: {screen_x}, Y: {screen_y})',
(frame_x - 50, frame_y - 50), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 0, 0), 2)
if tracked_left_hand_landmarks:
current_frame_left_hand_detected = True
mp_drawing.draw_landmarks(frame, tracked_left_hand_landmarks, mp_hands.HAND_CONNECTIONS,
mp_drawing.DrawingSpec(color=(0, 255, 0), thickness=2, circle_radius=2),
mp_drawing.DrawingSpec(color=(0, 255, 100), thickness=2))
frame_x_left, frame_y_left = get_hand_centroid(tracked_left_hand_landmarks, frame.shape[1], frame.shape[0])
frame_x_left = int(frame_x_left)
frame_y_left = int(frame_y_left)
fingers_up_left = 0
if is_finger_up(tracked_left_hand_landmarks, mp_hands.HandLandmark.THUMB_TIP, mp_hands.HandLandmark.THUMB_IP):
fingers_up_left += 1
tip_ids = [mp_hands.HandLandmark.INDEX_FINGER_TIP, mp_hands.HandLandmark.MIDDLE_FINGER_TIP,
mp_hands.HandLandmark.RING_FINGER_TIP, mp_hands.HandLandmark.PINKY_TIP]
pip_ids = [mp_hands.HandLandmark.INDEX_FINGER_PIP, mp_hands.HandLandmark.MIDDLE_FINGER_PIP,
mp_hands.HandLandmark.RING_FINGER_PIP, mp_hands.HandLandmark.PINKY_PIP]
for i in range(4):
if is_finger_up(tracked_left_hand_landmarks, tip_ids[i], pip_ids[i]):
fingers_up_left += 1
is_closed_fist = (fingers_up_left <= 1)
if is_closed_fist:
hand_gesture_left = "Mao Esquerda: Mao Fechada (Arrastando)"
if left_hand_action_state != "Closed":
pydirectinput.mouseDown()
print("Mao Esquerda: Mouse Down / Iniciando Arraste")
left_hand_action_state = "Closed"
else:
hand_gesture_left = "Mao Esquerda: Mao Aberta (Liberando)"
if left_hand_action_state == "Closed":
pydirectinput.mouseUp()
print("Mao Esquerda: Mouse Up / Arraste Finalizado")
left_hand_action_state = "Open"
cv2.putText(frame, f'Gesto: {hand_gesture_left}', (frame_x_left - 50, frame_y_left - 50),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
if not current_frame_left_hand_detected and left_hand_action_state == "Closed":
pydirectinput.mouseUp()
print("Mao Esquerda: Mouse Up / Arraste Finalizado (Mao saiu do frame)")
left_hand_action_state = "None"
elif not current_frame_left_hand_detected:
left_hand_action_state = "None"
if not current_frame_left_hand_detected and not current_frame_right_hand_detected:
cv2.putText(frame, "Nenhuma mao detectada", (50, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 255), 2)
elif not current_frame_left_hand_detected:
cv2.putText(frame, "Mao Esquerda NAO detectada", (50, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 255), 2)
elif not current_frame_right_hand_detected:
cv2.putText(frame, "Mao Direita NAO detectada", (50, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 255), 2)
cv2.imshow('Hand Gesture Control', frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
print("Camera liberada e janelas fechadas.")
Conclusão
Com poucas linhas de código, criamos uma aplicação de hand tracking que controla o cursor do mouse e simula cliques/arrastos. O segredo está em usar bibliotecas como OpenCV e MediaPipe, que já implementam as partes mais difíceis. Experimente ajustar os parâmetros (como min_detection_confidence) ou adicionar novos gestos para personalizar a aplicação. Hand tracking não é tão complicado quanto parece — comece pequeno e explore as possibilidades!
Veja outros projetos legais que as pessoas estão criando para se inspirar e aprender mais!