본문 바로가기
인공지능 기초 수업

Mediapip 메쉬(Mesh)모델 활용하기.

by SwMaker_Jun 2025. 9. 3.
728x90
반응형

미디어파이프 메쉬 모델 소개

1. 미디어파이프란?

미디어파이프(MediaPipe)는 구글에서 개발한 멀티모달 머신러닝 파이프라인 프레임워크예요.
영상·음성·센서 데이터를 빠르고 효율적으로 처리할 수 있도록 다양한 사전 학습된 AI 모델을 제공하죠.
특히 컴퓨터 비전 분야에서 강력하게 활용되며, 얼굴 인식·포즈 추정·손동작 추적 등 실시간 애플리케이션에 널리 쓰입니다.


2. 메쉬(Mesh) 모델이란?

메쉬 모델은 영상 속 객체를 단순히 사각형 박스로 인식하는 것이 아니라, 정밀한 점(landmark) 좌표를 기반으로 3D 형태를 예측하는 방식입니다.
예를 들어:

  • Face Mesh : 얼굴의 468개 랜드마크를 감지해 3D 형태로 분석
  • Hand Mesh : 손가락 관절 21개 포인트를 추적해 제스처 인식 가능
  • Body Pose Mesh : 전신 33개 관절 포인트를 기반으로 자세 분석

즉, 메쉬 모델은 단순한 ‘위치 감지’를 넘어서 세밀한 동작 이해와 3D 시뮬레이션을 가능하게 합니다.

 

 

3. 활용 사례

  • 🎭 증강현실(AR) : 얼굴 필터, 가상 화장, 캐릭터 마스크 적용
  • 🖐 제스처 인식 : 손가락 모양으로 컴퓨터나 로봇 제어
  • 🏃 스포츠 분석 : 선수의 동작 자세 분석 및 피드백
  • 🧑‍⚕️ 헬스케어 : 재활 훈련에서 환자의 동작 추적 및 평가
  • 🎮 게임 인터페이스 : 손과 몸을 활용한 모션 게임

 

4. 파이썬으로 코드

import cv2
import mediapipe as mp

# MediaPipe FaceMesh 초기화
mp_face_mesh = mp.solutions.face_mesh
mp_drawing = mp.solutions.drawing_utils

# 흰색 선 스타일 정의
white_line = mp_drawing.DrawingSpec(color=(255, 255, 255), thickness=1)

# 웹캠 열기
cap = cv2.VideoCapture(0)

with mp_face_mesh.FaceMesh(
    max_num_faces=1,              # 최대 감지 얼굴 수
    refine_landmarks=True,        # 눈/입술/홍채 정밀 검출
    min_detection_confidence=0.5,
    min_tracking_confidence=0.5
) as face_mesh:

    while cap.isOpened():
        success, frame = cap.read()
        if not success:
            print("웹캠을 불러올 수 없습니다.")
            break

        # --- 프레임 좌우 반전 (거울 모드) ---
        frame = cv2.flip(frame, 1)

        # OpenCV는 BGR, MediaPipe는 RGB
        frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)

        # 얼굴 메쉬 추론
        results = face_mesh.process(frame_rgb)

        # 출력용으로 다시 BGR 변환
        frame.flags.writeable = True
        frame_bgr = cv2.cvtColor(frame_rgb, cv2.COLOR_RGB2BGR)

        # 랜드마크 그리기
        if results.multi_face_landmarks:
            for face_landmarks in results.multi_face_landmarks:
                # 전체 메쉬 (흰색)
                mp_drawing.draw_landmarks(
                    image=frame_bgr,
                    landmark_list=face_landmarks,
                    connections=mp_face_mesh.FACEMESH_TESSELATION,
                    landmark_drawing_spec=None,
                    connection_drawing_spec=white_line
                )

        # 결과 출력
        cv2.imshow('Face Mesh White Lines', frame_bgr)

        # ESC로 종료
        if cv2.waitKey(1) & 0xFF == 27:
            break

cap.release()
cv2.destroyAllWindows()

 

5. 입술 랜드마크 활용하기

  • 윗입술과 아랫입술의 랜드마크 좌표를 활용하기
  • 두 랜드마크 간의 거리를 계산하기
  • 계산된 거리를 통해 입이 열렸는지 닫혔는지 판별하기
  • 윗입술(Upper Lip)
    • 외곽(upper outer): [61, 185, 40, 39, 37, 0, 267, 269, 270, 409, 291]
    • 안쪽(upper inner): [78, 191, 80, 81, 82, 13, 312, 311, 310, 415, 308]
  • 아랫입술(Lower Lip)
    • 외곽(lower outer): [61, 146, 91, 181, 84, 17, 314, 405, 321, 375, 291]
    • 안쪽(lower inner): [78, 95, 88, 178, 87, 14, 317, 402, 318, 324, 308]

📌 참고: 중앙 기준으로 보면

  • 윗입술 중앙(inner)13번
  • 아랫입술 중앙(inner)14번

 

6. 랜드마크 좌표 가져오기

h, w, _ = frame_bgr.shape
landmark = face_landmarks.landmark[13]                  
x = int(landmark.x * w)
y = int(landmark.y * h)

 

h, w, _ = frame_bgr.shape

frame_bgr는 OpenCV로 읽어온 
현재 프레임(이미지)

.shape는 이미지 크기를 알려줍니다.

h → 이미지 높이(height, 세로 픽셀 수)
w → 이미지 너비(width, 가로 픽셀 수)
_ → 채널 수(색상, BGR이므로 3)

즉, 이 코드로 현재 프레임의 너비와 높이를 가져옵니다.






landmark = face_landmarks.landmark[13]  

face_landmarks.landmark는 얼굴의
468개 랜드마크 리스트입니다.


그 중 13번 인덱스를 선택 → 윗입술 특정 지점에 해당하는 좌표.

landmark는 다음 값을 갖습니다:

landmark.x : 0~1 사이의 가로 위치
(정규화된 비율)


landmark.y : 0~1 사이의 세로 위치
(정규화된 비율)


landmark.z : 카메라와의 깊이 상대값
(주로 3D 계산할 때 사용)

 x = int(landmark.x * w)
 y = int(landmark.y * h)

landmark.x와 landmark.y는
정규화된 좌표라서 화면 크기와는
무관한 0~1 값입니다.

따라서 실제 픽셀 단위 좌표
변환하려면 width와 height를
곱해야 합니다.


x = landmark.x * w → 가로 픽셀 위치

y = landmark.y * h → 세로 픽셀 위치

int()로 변환 → 정수 픽셀 좌표로 변환




 

 

7. puttext() 함수를 활용하여 좌표값 출력하기.

text = f"13: ({x}, {y})"
cv2.putText(frame_bgr,text, (10,30), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)

 

“63번 랜드마크 좌표 (x, y)”라는 텍스트를 초록색 글씨로 영상 위에 표시

 

 

8. 최종 코드

import cv2
import mediapipe as mp

# MediaPipe FaceMesh 초기화
mp_face_mesh = mp.solutions.face_mesh
mp_drawing = mp.solutions.drawing_utils

# 흰색 선 스타일 정의
white_line = mp_drawing.DrawingSpec(color=(255, 255, 255), thickness=1)

# 웹캠 열기
cap = cv2.VideoCapture(0)

with mp_face_mesh.FaceMesh(
    max_num_faces=1,              # 최대 감지 얼굴 수
    refine_landmarks=True,        # 눈/입술/홍채 정밀 검출
    min_detection_confidence=0.5,
    min_tracking_confidence=0.5
) as face_mesh:

    while cap.isOpened():
        success, frame = cap.read()
        if not success:
            print("웹캠을 불러올 수 없습니다.")
            break

        # --- 프레임 좌우 반전 (거울 모드) ---
        frame = cv2.flip(frame, 1)

        # OpenCV는 BGR, MediaPipe는 RGB
        frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)

        # 얼굴 메쉬 추론
        results = face_mesh.process(frame_rgb)

        # 출력용으로 다시 BGR 변환
        frame.flags.writeable = True
        frame_bgr = cv2.cvtColor(frame_rgb, cv2.COLOR_RGB2BGR)

        # 랜드마크 그리기
        if results.multi_face_landmarks:
            for face_landmarks in results.multi_face_landmarks:
                # 전체 메쉬 (흰색)
                mp_drawing.draw_landmarks(
                    image=frame_bgr,
                    landmark_list=face_landmarks,
                    connections=mp_face_mesh.FACEMESH_TESSELATION,
                    landmark_drawing_spec=None,
                    connection_drawing_spec=white_line
                )

                # ---- 추가 부분: 윗입술 13번 좌표 출력 ----
                h, w, _ = frame_bgr.shape
                landmark = face_landmarks.landmark[13]  # 윗입술 중앙
                x, y = int(landmark.x * w), int(landmark.y * h)

                # 좌표에 원 표시
                cv2.circle(frame_bgr, (x, y), 3, (0, 255, 0), 1)

                # 좌표 텍스트 출력
                coord_text = f"13: ({x}, {y})"
                cv2.putText(frame_bgr, coord_text, (10, 30),
                            cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)

        # 결과 출력
        cv2.imshow('Face Mesh White Lines', frame_bgr)

        # ESC로 종료
        if cv2.waitKey(1) & 0xFF == 27:
            break

cap.release()
cv2.destroyAllWindows()

 

728x90
반응형