Assunto abordado: Visão Computacional
O reconhecimento de gestos ajuda os computadores a entender a linguagem do corpo humano. Isso ajuda a construir um vínculo mais potente entre humanos e máquinas, em vez de apenas as interfaces de usuário de texto básicas ou interfaces gráficas de usuário (GUIs). Neste projeto de reconhecimento de gestos, os movimentos do corpo humano são lidos pela câmera do computador. O computador então usa esses dados como entrada para lidar com o aplicativo.
- OpenCV - Biblioteca para visão computacional
- Pycaw - Biblioteca de controle de áudio Python
- Mediapipe - Mediapipe é uma biblioteca de aprendizado de máquina de código aberto do Google, que possui algumas soluções para reconhecimento facial e reconhecimento de gestos, e fornece encapsulamento de python, js e outras linguagens. O MediaPipe Hands é uma solução de rastreamento de mãos e dedos de alta fidelidade. Ele usa aprendizado de máquina (ML) para inferir 21 principais informações de mão 3D a partir de apenas um quadro. Podemos usá-lo para extrair as coordenadas dos pontos-chave da mão.
Documentação: Mediapipe
A imagem acima mostra os números dos pontos que o MediaPipe usa para se referir a diferentes pontos da mão.
Usaremos a distância entre os pontos 4 e 8 que será diretamente proporcional ao volume do dispositivo.
- Detectar pontos de referência da mão
- Detectar a localização das pontas dos dedos indicador e polegar
- Calcular a distância entre a ponta do polegar e a ponta do dedo indicador.
- Mapear a distância da ponta do polegar e da ponta do dedo indicador com a faixa de volume. Nesse caso, a distância entre a ponta do polegar e a ponta do dedo indicador estava na faixa de 30 a 350 e a faixa de volume foi de -63,5 a 0,0.
O objetivo deste projeto é desenvolver uma interface que capture dinamicamente o gesto da mão humana e controle o nível de volume.
Quando o dedo indicador se afasta do dedo polegar aumenta o volume e o quando o dedo polegar se aproxima do dedo indicador o volume diminue.
No desenvolvimento pode ser construído tanto no jupyter notebook quanto outros editores para python. No meu caso utilizei o PyCharm, conforme abaixo:
Criar um novo projeto
O projeto contém os arquivos abaixo:
- HandGestureVolumeController.py
- HandingTrackingModule.py
Abrir o terminal e efetuar as seguintes instalações:
pip install opencv-python
pip install mediapipe
pip install pycaw
import cv2
import time
import numpy as np
import HandTrackingModule as htm
import math
#control volume
from ctypes import cast, POINTER
from comtypes import CLSCTX_ALL
from pycaw.pycaw import AudioUtilities, IAudioEndpointVolume
############################################################
wCam, hCam = 640, 480 # definições do tamanho da tela
############################################################
camera = 0 #selecão da camera. Onde 0 é para camera principal, 1 para outra camera
cap = cv2.VideoCapture(camera)
cap.set(3, wCam) # aplica tamanho na horizontal
cap.set(4, hCam) # aplica tamanho na vertical
Acima obtemos a entrada de vídeo da câmera principal do nosso computador. Se estiver usando qualquer outra câmera, substitua o número 0 pelo da câmera que está usando.
detector = htm.handDetector(detectionCon=0.7, maxHands=1)
No código acima, estamos chamando htm.handDetector módulo para detectar as mãos da entrada de vídeo que recebemos de nossa câmera principal. As configurações do mediapipe e métodos estão separados no arquivo HandTrackingModule.py que está sendo importado no código.
devices = AudioUtilities.GetSpeakers()
interface = devices.Activate(
IAudioEndpointVolume._iid_, CLSCTX_ALL, None)
volume = cast(interface, POINTER(IAudioEndpointVolume))
Estas são as inicializações que precisamos para pycaw funcionar. Você pode encontrar a documentação da biblioteca aqui
volRange = volume.GetVolumeRange()
minVol = volRange[0]
maxVol = volRange[1]
Inicialização das variáveis de volume e barra de progresso:
vol = -20.0
volBar = 318 #360
volPerc = 20
while True:
success, img = cap.read()
img = detector.findHands(img)
O código acima verifica se a câmera que especificamos funciona. Se funcionar, vamos capturar para o processamento da imagem e detecção da mão.
lmList = detector.findPosition(img, draw=False)
Acima recebemos as coordenadas da mão.
if len(lmList) != 0:
x1, y1 = lmList[4][1], lmList[4][2]
x2, y2 = lmList[8][1], lmList[8][2]
Acima especificamos os pontos da mão que usaremos: indice 4 - Polegar e indice 8 o indicador.
Lá no início temos a imagem com o mapeamento desses pontos com a biblioteca mediapipe.
cv2.circle(img, (x1, y1), 15, (255,0,255), cv2.FILLED)
cv2.circle(img, (x2, y2), 15, (255, 0, 255), cv2.FILLED)
O código acima desenha um círculo na ponta do polegar e no dedo indicador com as coordenadas (x e y).
(x1, y1)especifica que vamos desenhar o círculo na ponta do polegar onde 15 é o raio do círculo e (255, 0, 0) é a cor do círculo, cv2.FILLED refere-se à espessura dos pixels que preencherão o círculo com a cor que especificamos. (x2, y2)especifica que vamos desenhar o círculo na ponta do indicador.
A cor está no padrão BGR (blue, green, red).
cv2.line(img, (x1, y1), (x2, y2), (255, 0, 255), 3)
No código acima, usamos a cv2.line função para desenhar uma linha entre o ponto quatro da mão e o ponto 8. A linha conectará ponto 4 (x1, y1), que é a ponta do polegar, e ponto 8 (x2, y2), que é a ponta do dedo indicador. (255, 0, 0)é a cor da linha e 3 é a sua espessura.
# lengh = math.hypot(x2 - x1, y2 - y1)
No código acima, encontramos a distância entre a ponta do polegar e o dedo indicador usando uma hipotenusa. Conseguimos isso chamando a hypot função matemática e passando a diferença entre x2 e x1 e a diferença entre y2 e y1.
vol = np.interp(lengh, [50, 300], [minVol, maxVol])
função NumPy np.interp, para converter o intervalo de mão para o intervalo de volume. Os argumentos usados são:
- length: Este é o valor que queremos converter.
- [50 - 300]: Este é o alcance da mão.
- [volMin, volMax]: Fornecendo o intervalo para o qual queremos converter.
configuração para barra do volume:
volBar = np.interp(lengh, [50, 300], [360, 150])
configuração para exibição do volume de 0 á 100:
volPerc = np.interp(lengh, [50, 300], [0, 100])
# volume.SetMasterVolumeLevel(vol, None)
volume.SetMasterVolumeLevelScalar(volPerc / 100, None)
No exemplo acima utilizamos volume.SetMasterVolumeLevelScalar.
cv2.rectangle(img, (50, 150), (85, 360), (255,0,0), 3)
cv2.rectangle(img, (50, int(volBar)), (85, 360), (0, 255, 0), cv2.FILLED)
O primeiro retangulo é para criar o contorno e segundo retangulo é criado para receber um preenchimento dinâmico, para simular uma barra de controle do volume.
cVol = round(volume.GetMasterVolumeLevelScalar() * 100)
cv2.putText(img, f'Vol: {cVol}', (40, 400), cv2.FONT_HERSHEY_COMPLEX, 1, (255, 0, 0), 3)
cv2.imshow("img", img)
k = cv2.waitKey(1) & 0xFF
if k == 27 or k == 13:
break
O código acima encerrará o programa quando o usuário pressionar a tecla ESC ou ENTER.
import cv2
import numpy as np
import HandTrackingModule as htm
import math
#control volume
from ctypes import cast, POINTER
from comtypes import CLSCTX_ALL
from pycaw.pycaw import AudioUtilities, IAudioEndpointVolume
############################################################
wCam, hCam = 640, 480
#wCam, hCam = 1200, 600
############################################################
camera = 0 #1
cap = cv2.VideoCapture(camera)
cap.set(3, wCam)
cap.set(4, hCam)
detector = htm.handDetector(detectionCon=0.7, maxHands=1)
devices = AudioUtilities.GetSpeakers()
interface = devices.Activate(
IAudioEndpointVolume._iid_, CLSCTX_ALL, None)
volume = cast(interface, POINTER(IAudioEndpointVolume))
#volume.GetMute()
#volume.GetMasterVolumeLevel()
print(volume.GetVolumeRange())
volRange = volume.GetVolumeRange()
minVol = volRange[0]
maxVol = volRange[1]
vol = -20.0
volBar = 318 #360
volPerc = 20
while True:
success, img = cap.read()
img = detector.findHands(img)
lmList = detector.findPosition(img, draw=False)
if len(lmList) != 0:
#print(lmList[2])
#print(lmList[4], lmList[8])
x1, y1 = lmList[4][1], lmList[4][2]
x2, y2 = lmList[8][1], lmList[8][2]
cv2.circle(img, (x1, y1), 15, (255,0,255), cv2.FILLED)
cv2.circle(img, (x2, y2), 15, (255, 0, 255), cv2.FILLED)
cv2.line(img, (x1, y1), (x2, y2), (255, 0, 255), 3)
lengh = math.hypot(x2 - x1, y2 - y1)
# Hand range 50 - 300
# Volume Range -65 -0
vol = np.interp(lengh, [50, 300], [minVol, maxVol])
volBar = np.interp(lengh, [50, 300], [360, 150])
volPerc = np.interp(lengh, [50, 300], [0, 100])
print("volBar: ", volBar)
print("volPerc: ", round(volPerc))
#volume.SetMasterVolumeLevel(vol, None)
volume.SetMasterVolumeLevelScalar(volPerc / 100, None)
print(int(lengh), vol)
else:
# seta um volume padrão é o ultimo volume feito pelo movimento das mãos
#volume.SetMasterVolumeLevel(vol, None)
volume.SetMasterVolumeLevelScalar(volPerc / 100, None)
cv2.rectangle(img, (50, 150), (85, 360), (255,0,0), 3)
cv2.rectangle(img, (50, int(volBar)), (85, 360), (0, 255, 0), cv2.FILLED)
cVol = round(volume.GetMasterVolumeLevelScalar() * 100) # mostra volume do windows
cv2.putText(img, f'Vol: {cVol}', (40, 400), cv2.FONT_HERSHEY_COMPLEX, 1, (255, 0, 0), 3)
cv2.imshow("img", img)
k = cv2.waitKey(1) & 0xFF
if k == 27 or k == 13:
break
Abaixo resultado da detecção da mão e controle do volume. Com o volume do windows aberto é possível ver em tempo real o ponteiro do volume mudar, conforme é feito o movimento entre os dedos polegar e indicador:
Se você estiver trabalhando enquanto ouve sua música favorita, com apenas um gesto de sua mão, você poderá controlar o nível de volume de sua música.