mate 프로그램이 이번주에 끝나서 그동안 배웠던 여러 활동들을 쭉 보는데
mediapipe를 활용했던 활동이 조금 아쉬웠었다.
뭔가를 더 많이 할 수 있을 것 같고 같아서 관련 자료들을 찾아보다
검지손가락을 굽었다 피어 Click이 가능하게끔 하는 VirtualMouseProject를 계획하였다.
pip install pyautogui
pip install mediapipe
pip install numpy
pip install opencv-python
import cv2
import numpy as np
import mediapipe as mp
import time
# MediaPipe hands 모델 초기화
mp_hands = mp.solutions.hands
hands = mp_hands.Hands()
mp_drawing = mp.solutions.drawing_utils
# 웹캠 캡처 시작
cap = cv2.VideoCapture(0)
while cap.isOpened():
success, image = cap.read()
if not success:
print("Ignoring empty camera frame.")
continue
# 성능 향상을 위해 이미지 작성을 방지하고 RGB로 변환
image.flags.writeable = False
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
results = hands.process(image)
# 이미지에 손 랜드마크 그리기
image.flags.writeable = True
image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
if results.multi_hand_landmarks:
for hand_landmarks in results.multi_hand_landmarks:
mp_drawing.draw_landmarks(
image, hand_landmarks, mp_hands.HAND_CONNECTIONS)
# 결과 보여주기
cv2.imshow('MediaPipe Hands', image)
if cv2.waitKey(5) & 0xFF == 27:
break
cap.release()
cv2.destroyAllWindows()

image.flags.writeable = False
'image' 배열의 속성을 False로 하여 내용을 수정할 수 없게 만들었다.
이는 이미지배열이 읽기전용이 되어 연산이 내부적으로 빠르게 수행되게 하여 성능향상을 돕는다.
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
OpenCV는 기본적으로 이미지를 BGR 형식으로 읽는다. 반면 Mediapipe는 RGB로 형식을 받아들이기 때문에 이미지를 Mediapipe로 처리하기 전에 BGR에서 RGB로 변환하여 제공하는 것이다.
이는 정확하게 손 랜드마크를 추출할 수 있게 도와준다.
Mediapipe Hands Solution을 사용
for id, lm in enumerate(hand_landmarks.landmark):
h, w, c = image.shape
cx, cy = int(lm.x * w), int(lm.y * h)
손가락별로 구분하여 각 랜드마크 좌표 출력하고 랜드마크 좌표를 이미지 크게 맞게 조정한다.
Mediapipe에서 각 손가락의 랜드마크는 정해져있다.
엄지손가락 끝: ID 4
검지손가락 끝: ID 8
중지손가락 끝: ID 12
약지손가락 끝: ID 16
새끼손가락 끝: ID 20
우선 손가락을 굽혔다는 것을 알기 위해 손가락의 각도를 계산하는 함수를 만들어야한다.
def calculate_angle(a, b, c):
a = np.array(a) # 첫 번째 점
b = np.array(b) # 중앙 점
c = np.array(c) # 마지막 점
# 벡터 계산
ba = a - b
bc = c - b
# 코사인 법칙을 사용하여 각도 계산
cosine_angle = np.dot(ba, bc) / (np.linalg.norm(ba) * np.linalg.norm(bc))
angle = np.arccos(cosine_angle)
return np.degrees(angle)
그리고 나서 손가락의 끝과 중간마디 그리고 기저부의 위치의 좌표를 추출하여 calculate_angle() 함수를 통해 손가락이 굽혀진 각도를 구한다.
# 각 손가락의 각도 계산 (엄지손가락을 제외한 네 손가락)
if id == 8:
# 손가락 끝 (Tip)
tip = [hand_landmarks.landmark[id].x, hand_landmarks.landmark[id].y]
# 손가락 중간 마디 (PIP)
pip = [hand_landmarks.landmark[id - 2].x, hand_landmarks.landmark[id - 2].y]
# 손가락 기저부 (MCP)
mcp = [hand_landmarks.landmark[id - 3].x, hand_landmarks.landmark[id - 3].y]
angle = calculate_angle(mcp, pip, tip)
그리고 pyautogui 라이브러리를 통해 검지손가락 끝의 좌표로 마우스 포인터를 이동하고 클릭하는 작업을 마지막으로 수행하면 끝이다.
(좌표 -> 좌표 아주 간단하게 해결가능)
나중에 매크로를 만들어보는 것도 해봐야겠다.

[사진 출처] https://mechacave.tistory.com/15
# 각도 출력 (예를 들어 160도 이상이면 손가락을 폈다고 간주)
if id == 8 and angle > 160:
pyautogui.position(cx, cy)
print('현재 검지손가락 위치 : ', pyautogui.position())
elif id == 8 and angle < 160:
pyautogui.click(cx, cy)
print("클릭한 검지손가락 위치 : ", cx, cy)


이렇게 되면 완성 손가락 끝을 인식하여서 노트북에서 클릭이 가능해졌다ㅏ
(조작이 어색해서 자꾸 vscode 다른 이상한 파일을 누르고 그랬지만)
조금 아쉬운걸 뽑자면 캠과 좌우가 반대여서 내가 왼쪽으로 손을 움직여야 마우스 포인터가 오른쪽으로 이동한 것이 조금 마음에 안들었지만 그래도 성공적으로 마쳤다.