2.인공지능/MediaPipe

7.MediaPipe 손 동작 인식 (Gesture Recognition)

쿼드큐브 2025. 5. 16. 14:25
728x90

MediaPipe Gesture Recognizer는 손의 제스처를 실시간으로 인식하여 다양한 동작을 제어할 수 있는 강력한 비전 태스크입니다.

 

MediaPipe 손 동작 인식 (Gesture Recognition)

 

목차

1. Gesture Recognizer란 ?

2. 작동 방식 - 입력과 출력

3. 설정 옵션

4. HandGestureClassifier 구조 분석

5. 사용자 정의 제스쳐

6. 제스쳐 인식 테스트 코드

7. 제스쳐 테스트 결과

관련 글 링크

 

 

1. Gesture Recognizer란?

MediaPipe Gesture Recognizer는 손 제스처를 인식하는 머신러닝 태스크입니다.

손의 위치와 형태를 실시간으로 추적하여 사전 정의된 제스처 또는 사용자 정의 제스처를 분류하고,

이를 기반으로 UI 동작, 명령 실행 등 다양한 기능과 연동할 수 있습니다.

 

예시: 사용자가 "엄지척" 제스처를 하면, 시스템은 이를 Thumb_Up 제스처로 인식하고 관련 기능을 실행합니다.

 

2. 작동 방식 - 입력과 출력

  • 입력 이미지 처리 - 처리에는 이미지 회전, 크기 조절, 정규화, 색상 공간 변환이 포함됩니다.
  • 점수 기준점: 예측 점수를 기준으로 결과를 필터링합니다.
  • 라벨 허용 목록 및 차단 목록: 모델에서 인식하는 동작 카테고리를 지정합니다.
Task 입력 Task 출력
동작 감지기는 다음 데이터 유형 중 하나의 입력을 허용합니다.
 - 정지 이미지
 - 디코딩된 동영상 프레임
 - 라이브 동영상 피드
동작 감지기는 다음과 같은 결과를 출력합니다.
 - 손 동작 카테고리
 - 감지된 손의 손잡이
 - 이미지 좌표에서 감지된 손의 랜드마크
 - 감지된 손의 랜드마크(세계 좌표)

 

◆ GestureRecognizerResult 포맷 예시

🖐 Hand #1
──────────────────────────────────────────────
🔹 Gesture
  - category_name: "None"
  - score        : 0.8128
  - index        : -1

🔹 Handedness
  - category_name: "Left"
  - score        : 0.9974
  - index        : 1

🔹 Landmarks (Normalized - image space)
  - Total points: 21
  - Sample:
    - [0] x=0.6816, y=1.1222, z=+0.0000
    - [1] x=0.7000, y=1.0495, z=-0.0068
    - [2] x=0.6860, y=0.9710, z=-0.0172
    - ...
    - [20] x=0.4673, y=1.0535, z=-0.0371

🔹 Landmarks (World - 3D space)
  - Total points: 21
  - Sample:
    - [0] x=+0.0261, y=+0.0451, z=+0.0416
    - [1] x=+0.0260, y=+0.0208, z=+0.0350
    - [2] x=+0.0218, y=+0.0061, z=+0.0358
    - ...
    - [20] x=-0.0366, y=+0.0138, z=+0.0400
항목 설명
gestures 인식된 제스처 목록 (일반적으로 score가 가장 높은 top-1 사용)
handedness 해당 손이 Left/Right 중 어떤 것으로 분류되었는지, 추
hand_landmarks 0~1 사이 정규화된 좌표 (이미지 기준)
hand_world_landmarks 실제 세계 단위의 3D 좌표 (카메라 캘리브레이션 기준), m 단위로 추정됨

 

◆ Normalized vs. World Landmarks 상세 비교

구분 Normalized Landmarks World Landmarks
좌표계 이미지 비율 기반 2D 좌표계(x, y ∈ [0, 1]) 3차원 실세계 공간 좌표계(단위: meter)
x/y/z 의미 - x, y: 이미지 너비/높이 기준 정규화 위치- z: 손 깊이 (카메라 기준 상대값, 보통 -이면 카메라 가까움) - x, y, z: 카메라 기준 실세계 위치 (왼쪽/위쪽/앞뒤 방향 포함)
z축 기준 이미지 기반 상대 거리 추정 (픽셀 기준 추정) 실제 거리로 추정된 깊이값(3D 공간 상 절대 거리 기반)
정밀도 이미지 왜곡, 포즈에 따라 왜곡 가능 (시각화 중심) 학습된 모델에 따라 깊이까지 추정하므로 상대적으로 더 정밀함
시점 의존성 이미지 크기와 위치에 의존 시점과 해상도에 독립적 (실제 위치 추정이 목적이므로)
좌표 활용성 - 이미지에 시각화 (draw_landmarks)- 바운딩박스, 손 위치 분류 등 - 손 간 거리 계산- 손의 3D 위치 추정- 물체 잡기, AR 제스처 인식
대표 사용처 - 제스처 분류- 이미지상 손 위치 판단- 웹 기반 인터페이스 - 3D 공간 추적- 거리 기반 판단 (예: 두 손 사이 거리)- AR/VR 제어
제공 구조체 NormalizedLandmark 객체 리스트 (landmarks) Landmark 객체 리스트 (world_landmarks)

 

 

3. 설정 옵션

MediaPipe는 세부 조정을 위한 다양한 옵션을 제공합니다:

옵션 설명 값 범위 기본 값
running_mode Gesture Recognizer의 실행 모드를 설정합니다.
- IMAGE: 단일 이미지 입력 모드
- VIDEO: 디코딩된 동영상 프레임 입력 모드
- LIVE_STREAM: 실시간 카메라 입력 모드
  (이 경우 result_callback 필수)
{"IMAGE", "VIDEO", "LIVE_STREAM"} "IMAGE"
num_hands 동시에 감지 가능한 손의 최대 수 정수 ≥ 1 1
min_hand_detection_confidence 손바닥 감지 성공으로 간주하기 위한 최소 신뢰도
(Palm Detection 단계에서 사용)
0.0 ~ 1.0 0.5
min_hand_presence_confidence 손 랜드마크 모델에서 손이 존재한다고 판단하기 위한 최소 신뢰도.
이 값보다 낮으면 palm detection을 다시 실행함
0.0 ~ 1.0 0.5
min_tracking_confidence 현재 프레임과 이전 프레임 간 손 추적이 성공으로 간주될 최소 신뢰도 (IoU 기반 추적) 0.0 ~ 1.0 0.5
result_callback LIVE_STREAM 모드에서 분류 결과를 비동기 방식으로 수신하는 리스너 함수 설정 ResultListener 객체 없음

MediaPipe Gesture Recognizer는 canned_gestures_classifier와 custom_gestures_classifier를 동시에 함께 사용할 수 있도록 설계되어 있습니다.

◆ canned_gestures_classifier_options

MediaPipe Gesture Recognizer는 손의 움직임을 분석하여 특정 제스처를 분류하는 기능을 제공합니다.

이때 사용하는 분류 모델은 기본적으로 8가지 제스처를 인식할 수 있도록 사전 학습되어 있습니다.

MediaPipe는 이 기본 제스처 분류기의 동작을 좀 더 유연하게 제어할 수 있도록 여러 가지 설정 옵션을 제공합니다.

옵션 설명 값 범위 기본 값
display_names_locale 라벨 표시 언어 코드 ("en", "ko" 등) 문자열 "en"
max_results 반환할 최대 분류 결과 수. 음수일 경우 모든 결과 반환 정수 -1
score_threshold 이 값보다 낮은 신뢰도 결과는 제외됨 0.0 ~ 1.0 0.0
category_allowlist 허용할 제스처 라벨 목록. 이 리스트에 없는 결과는 제외됨
(denylist와 함께 사용 불가)
문자열 리스트 빈 리스트
category_denylist 제외할 제스처 라벨 목록. 이 리스트에 있는 결과는 제외됨
(allowlist와 함께 사용 불가)
문자열 리스트 빈 리스트
기본 제공 제스처: ["None", "Closed_Fist", "Open_Palm", "Pointing_Up", "Thumb_Down", "Thumb_Up", "Victory", "ILoveYou"]
 

◆ custom_gestures_classifier_options

MediaPipe Gesture Recognizer는 기본적으로 8가지 손 제스처를 인식할 수 있도록 설정되어 있지만, 직접 정의한 새로운 제스처를 인식하고 싶을 때는 사용자 정의 제스처 분류기(Custom Gesture Classifier)를 사용할 수 있습니다.

옵션 설명  값 범위  기본 값
display_names_locale 표시할 라벨의 언어 코드 문자열 "en"
max_results 최대 결과 수 정수 -1
score_threshold 신뢰도 기준 0.0 ~ 1.0 0.0
category_allowlist 허용할 사용자 제스처 라벨 리스트 문자열 리스트 빈 리스트
category_denylist 제외할 사용자 제스처 라벨 리스트 문자열 리스트 빈 리스트

 

 

4. HandGestureClassifier 구조 분석

모델 이름 입력 셰이프 양자화 유형 모델 카드 링크 버전

모델 입력 세이프 양자화
HandGestureClassifier 192×192, 224×224 (지원) float16 (부동 소수점 16비트)

 

MediaPipe Gesture Recognizer의 핵심은 손의 구조를 정확히 감지하고, 그 구조를 기반으로 손 제스처를 인식하는 두 개의 주요 컴포넌트로 이루어져 있습니다:

  1. 손 관절을 감지하는 손 랜드마크 모델
  2. 손의 모양을 분류하는 제스처 분류기 모델

이 두 모델은 연속적으로 작동하며, 사용자가 손을 움직이기만 해도 실시간으로 동작을 감지하고 해석할 수 있게 합니다.

 

◆ 손 랜드마크 모델 (Hand Landmark Model)

손 랜드마크 모델 번들은 감지된 손 영역 내에서 21개의 손가락 관절 좌표의 주요 지점 위치를 감지합니다. 이 모델은 약 30, 000개의 실제 이미지와 여러 배경에 적용된 여러 개의 렌더링된 합성 손 모델을 사용하여 학습되었습니다. 

MediaPipe Gesture Recognizer
출처:https://ai.google.dev/edge/mediapipe/solutions/vision/gesture_recognizer

손 랜드마커 모델 번들에는 손바닥 감지 모델과 손 랜드마커 감지 모델이 포함되어 있습니다.

손바닥 감지 모델은 전체 입력 이미지에서 손의 영역을 찾고, 손 랜드마크 감지 모델은 손바닥 감지 모델에 의해 정의된 잘린 손 이미지에서 랜드마크를 찾습니다.

단계 설명
Palm Detection 전체 이미지에서 손바닥의 위치(ROI)를 찾아냅니다.
정확도는 높지만 계산량이 많기 때문에 반복 실행은 비효율적입니다.
Hand Landmark Detection palm detection으로 잘라낸 손 영역에서 21개 손 관절 위치를 정밀하게 추출합니다.

 

◆ 제스처 분류 모델 (Gesture Classifier)

손 랜드마크(관절 좌표)를 기반으로 손의 전체 모양을 분석하고, 이를 특정 제스처로 분류합니다.

단계 설명
Gesture Embedding Model 관절 위치를 압축된 벡터 형태의 특징(feature vector)로 변환
Gesture Classification Model 이 벡터를 입력으로 받아, 가장 가능성 높은 제스처를 예측
ID 라벨 이름 설명
0 None 인식된 제스처 없음 또는 불확실함
1 Closed_Fist 주먹 쥔 상태
2 Open_Palm 손바닥 펼친 상태
3 Pointing_Up 손가락 하나를 위로 가리킴
4 Thumb_Down 엄지를 아래로
5 Thumb_Up 엄지를 위로 (👍)
6 Victory 브이(V) 제스처 (✌️)
7 ILoveYou 수어(SL)의 사랑해요 제스처

 

◆ 성능

Pixel 6 기준으로 측정된 평균 처리 시간은 다음과 같습니다:

실행 환경 평균 지연 시작
CPU 16.76ms
GPU 20.87ms

 

5. 사용자 정의 제스쳐

MediaPipe Gesture Recognizer는 기본적으로 8가지 손동작(Thumb_Up, Victory 등)을 인식할 수 있도록 사전 학습된 제스처 분류기를 제공합니다.
하지만 실제 응용에서는 다음과 같은 추가적인 제스처가 필요할 수 있습니다:

  • “OK 사인”
  • “Call Me” 손동작
  • 특정 수어(수화) 제스처
  • 산업별 특화 제스처 (예: 수신호, 원격 제어 동작 등)

1. 학습 데이터 준비

  • 여러 각도와 손 모양으로 촬영한 이미지 또는 랜드마크 데이터를 수집
  • 클래스(예: OK, Stop, Call_Me) 별로 구분된 라벨 구성
  • 가능하면 직접 손 랜드마크 좌표를 추출하여 CSV로 저장하는 방식이 효과적임 (이미지보다는 랜드마크 기반 분류가 일반적)

2. Model Maker로 학습

  • TensorFlow Lite의 Model Maker 라이브러리를 사용
  • 손 랜드마크 → 특징 벡터 → 분류기를 학습하는 간단한 구조
# 예시 코드 (pseudo)
model = model_maker.train(
    data=gesture_landmark_data,
    labels=["OK", "Call_Me", "Stop"]
)

 

3. .task 파일로 내보내기

  • MediaPipe Task API가 인식 가능한 .task 형식으로 모델 번들 생성
# 내보낸 모델을 task 파일로 변환
model_maker.export_to_task("custom_gesture.task")

 

4. GestureRecognizer에 적용 방법

from mediapipe.tasks.python.vision import GestureRecognizer
from mediapipe.tasks import base

options = GestureRecognizer.GestureRecognizerOptions(
    base_options=base.BaseOptions(model_asset_path='custom_gesture.task'),
    running_mode=vision.RunningMode.LIVE_STREAM,
    result_callback=your_result_callback_fn
)

recognizer = GestureRecognizer.create_from_options(options)

 

5. 주의 사항

항목 설명
기본 제스처와 동시 사용 가능? 가능 (단, .task 파일에 두 분류기 모두 포함되어야 함)
학습 시 기존 제스처가 사라지나? 사용자 정의 모델만 포함된 .task를 쓸 경우, 기본 제스처는 인식되지 않음
권장 방식 기존 모델을 기반으로 사용자 정의 분류기를 추가 구성하여 병합하는 방식
최소 데이터셋 제스처당 수백 개 이상의 랜드마크 샘플이 있을수록 일반화 성능 향상
입력 형식 손 이미지가 아닌 21개 손 랜드마크 좌표(x, y, z) 기반 학습이 일반적

 

 

6. 제스쳐 인식 테스트 코드

이 예제는 MediaPipe Tasks API를 이용해 실시간 손 제스처를 인식하고, OpenCV를 통해 손 랜드마크 및 인식된 제스처 결과를 시각화하는 구현한 코드예시 입니다.

 

1. 모델 로드 및 옵션 설정

  • MediaPipe에서 제공하는 .task 모델 파일 경로를 지정하고 로드합니다.
  • GestureRecognizerOptions 객체를 통해 최대 감지 손 수, 신뢰도 임계값, 스트리밍 모드, 콜백 함수 등을 지정합니다.
import mediapipe as mp
from mediapipe.tasks import python
from mediapipe.tasks.python import vision
import os
import cv2  # OpenCV 라이브러리
import numpy as np  # NumPy 라이브러리

# 현재 파일의 디렉토리 경로를 설정합니다.
BASE_DIR = os.path.dirname(os.path.abspath(__file__))

# 제스처 인식 모델 파일 경로를 지정합니다.
model_path = os.path.join(BASE_DIR, './models/gesture_recognizer.task')

# 모델 파일이 존재하지 않으면 예외를 발생시킵니다.
if not os.path.exists(model_path):
    raise FileNotFoundError(f"Model file not found at path: {model_path}")

# MediaPipe BaseOptions 객체를 생성합니다. (CPU 사용)
# NotImplementedError: GPU Delegate is not yet supported for Windows
base_options = python.BaseOptions(model_asset_path=model_path, 
                                  delegate=python.BaseOptions.Delegate.CPU)

# MediaPipe에서 정의한 21개 손 관절 연결 구조입니다.
HAND_CONNECTIONS = [
    (0, 1), (1, 2), (2, 3), (3, 4),       # 엄지
    (0, 5), (5, 6), (6, 7), (7, 8),       # 검지
    (5, 9), (9, 10), (10, 11), (11, 12),  # 중지
    (9, 13), (13, 14), (14, 15), (15, 16),# 약지
    (13, 17), (17, 18), (18, 19), (19, 20),# 새끼
    (0, 17)                               # 손목 → 새끼 기저 연결
]

# 콜백에서 처리된 결과 이미지를 일시적으로 저장하는 큐입니다.
frame_queue = []

# GestureRecognizer 옵션을 설정합니다.
options = vision.GestureRecognizerOptions(
    base_options=base_options,  # 기본 옵션
    running_mode=vision.RunningMode.LIVE_STREAM,  # 실시간 스트리밍 모드
    num_hands=1,  # 최대 손 개수
    min_hand_detection_confidence=0.5,  # 손 감지 신뢰도 임계값
    min_hand_presence_confidence=0.5,  # 손 존재 신뢰도 임계값
    min_tracking_confidence=0.5,  # 손 추적 신뢰도 임계값
    result_callback=gesture_callback,  # 결과 콜백 함수
)

 

2. Cam 연결 및 실시간 제스쳐 인식

  • OpenCV를 통해 기본 웹캠을 활성화하고 프레임을 읽습니다.
  • MediaPipe의 GestureRecognizer 객체로부터 recognize_async() 메서드를 호출하여 비동기 인식 수행.
  • 프레임마다 처리된 결과가 있을 경우 시각화된 프레임을 frame_queue에서 꺼내 cv2.imshow()로 화면에 출력합니다.
# 웹캠에서 비디오 캡처를 시작합니다.
cap = cv2.VideoCapture(0)
if not cap.isOpened():
    raise RuntimeError("Could not open video device")

last_processed_time = 0  # 마지막 처리 시점 (ms 단위)

# MediaPipe Gesture Recognizer 객체를 생성하고, 실시간으로 프레임을 처리합니다.
try:
    with vision.GestureRecognizer.create_from_options(options) as recognizer:
        while cap.isOpened():
            ret, frame = cap.read()  # 프레임 캡처
            if not ret:
                print("Failed to capture image")
                break
            # OpenCV 이미지를 RGB로 변환
            frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
            # OpenCV 이미지를 MediaPipe 포맷으로 변환
            image = mp.Image(image_format=mp.ImageFormat.SRGB, data=frame_rgb)
            # 현재 비디오의 경과 시간(ms)
            elapsed_time_ms = int(cap.get(cv2.CAP_PROP_POS_MSEC))
            # 실시간 제스처 인식 (비동기)
            recognizer.recognize_async(image, timestamp_ms=elapsed_time_ms)
            last_processed_time = elapsed_time_ms
            # 콜백에서 처리된 프레임이 있으면 시각화하여 출력
            if frame_queue:
                visualized_frame = frame_queue[0]
                bgr_frame = cv2.cvtColor(visualized_frame, cv2.COLOR_RGB2BGR)
                cv2.imshow('LandMarks', bgr_frame)
                frame_queue.clear()
            # 처리 결과가 없으면 원본 프레임 출력
            cv2.imshow('Gesture', frame)
            # 'q' 키를 누르면 루프 종료
            if cv2.waitKey(1) & 0xFF == ord('q'):
                break
finally:
    # 자원 해제 및 모든 OpenCV 창 닫기
    cap.release()
    cv2.destroyAllWindows()

 

3. 랜트마크 및 제스쳐 인식 결과 그리기

  • MediaPipe가 추론 결과를 비동기적으로 전달하면 호출되는 콜백 함수입니다.
# GestureRecognizerResult 타입을 직접 import하여 타입 힌트에 사용합니다.
from mediapipe.tasks.python.vision.gesture_recognizer import GestureRecognizerResult

def gesture_callback(result: GestureRecognizerResult, output_image: mp.Image, timestamp_ms: int):
    # MediaPipe 이미지 객체를 numpy 배열로 변환 (RGB)
    frame = output_image.numpy_view().copy()
    height, width, _ = frame.shape

    # 손이 감지되지 않은 경우, 원본 이미지를 큐에 추가하고 종료
    if not result.hand_landmarks:
        frame_queue.append(frame)
        return

    # 감지된 각 손에 대해 랜드마크와 제스처 정보를 시각화
    for i, hand_landmarks in enumerate(result.hand_landmarks):
        # 각 랜드마크(관절)에 원을 그림
        for j, landmark in enumerate(hand_landmarks):
            x = int(landmark.x * width)
            y = int(landmark.y * height)
            cv2.circle(frame, (x, y), 5, (0, 255, 0), -1)
        # 관절 연결선 그리기
        for start_idx, end_idx in HAND_CONNECTIONS:
            start = hand_landmarks[start_idx]
            end = hand_landmarks[end_idx]
            x0, y0 = int(start.x * width), int(start.y * height)
            x1, y1 = int(end.x * width), int(end.y * height)
            cv2.line(frame, (x0, y0), (x1, y1), (255, 255, 255), 2)
        # 인식된 제스처 라벨 및 신뢰도, 손 좌우 정보 표시
        if result.gestures and result.gestures[i]:
            gesture_label = result.gestures[i][0].category_name
            confidence = result.gestures[i][0].score
            hand_label = result.handedness[i][0].category_name
            text = f"{hand_label}: {gesture_label} ({confidence:.2f})"
            x0, y0 = int(hand_landmarks[0].x * width), int(hand_landmarks[0].y * height)
            cv2.putText(frame, text, (x0, y0 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 255), 2)
    # 시각화된 프레임을 큐에 추가
    frame_queue.append(frame)

 

 

7. 제스쳐 테스트 결과

MediaPipe Gesture Recognizer는 손바닥이 카메라를 정면으로 향하고 손가락이 위쪽을 향할 때 가장 안정적으로 제스처를 인식했습니다.

MediaPipe Gesture Recognizer

 

반면, 손가락이 측면 또는 아래쪽을 향하거나 손등이 카메라를 향할 경우에는 손 감지가 불안정해지고 제스처 분류가 실패하는 경우가 자주 발생했습니다

MediaPipe Gesture Recognizer

 

t1_gesture_stream.py
0.01MB


 

 

관련 글 링크

https://ai.google.dev/edge/mediapipe/solutions/vision/gesture_recognizer?hl=ko

 

동작 인식 작업 가이드  |  Google AI Edge  |  Google AI for Developers

LiteRT 소개: 온디바이스 AI를 위한 Google의 고성능 런타임(이전 명칭: TensorFlow Lite)입니다. 이 페이지는 Cloud Translation API를 통해 번역되었습니다. 의견 보내기 동작 인식 작업 가이드 MediaPipe 동작 인

ai.google.dev

 

728x90