MediaPipe Face Landmarker — 학부생을 위한 학습 교재

07. Walkthrough와 결론 — 하나의 시나리오로 꿰기

Contents

00장부터 06장까지 배운 조각을 하나의 시나리오에 순서대로 흘려보낸다. 그다음 이 책 전체의 결론과 결정표, 체크리스트를 한곳에 모은다. 결론은 이 장에만 둔다.

7.1 시나리오 설정 — "웹캠 VTuber 앱 만들기"

학부생 지우가 노트북 웹캠 한 대로 웹 앱을 만든다고 하자. 자기 표정을 따라 하는 고양이 아바타를 실시간으로 움직이는 앱이다. 마커도, 깊이 센서도, 다중 카메라도 없다. 평범한 RGB 웹캠 영상뿐이다. 지우가 원하는 동작은 세 가지다.

  • 웹캠에 비친 자기 얼굴을 따라 고양이 아바타가 같은 표정을 짓는다. 웃으면 웃고, 눈 감으면 감는다.
  • 지우가 고개를 돌리면 아바타 머리도 같이 돌아간다.
  • 끊김 없이 실시간으로, 초당 수십 프레임으로 동작한다.

이 단순한 목표 하나에 이 책의 모든 장이 정확히 한 번씩 등장한다. 한 프레임이 흐르는 길을 따라가 보자.

webcam RGB frame

detect face

crop and align

478 landmarks

52 blendshapes

4x4 head pose

cat avatar: expression

cat avatar: head turn

rendered avatar on screen

웹캠 프레임 하나가 검출과 정렬을 거쳐 478점이 되고, 거기서 표정과 머리 자세가 갈라져 나와 고양이 아바타로 합쳐지는 전체 그림이다.

Advertisements

7.2 한 프레임이 흐르는 길 (전 챕터 순차 등장)

단계 0 — 도구를 고른다 (05장)

지우는 검색하다 두 라이브러리를 발견한다. 옛 튜토리얼이 쓰는 @mediapipe/face_mesh(레거시)와, 새로 권장되는 @mediapipe/tasks-vision(Tasks API)이다. 레거시는 2023-03-01에 지원이 끝났고, 표정 계수도 내주지 못한다. 아바타에 표정을 입히려면 52개 블렌드셰이프(표정 계수)가 필요하다. 그래서 지우는 Face Landmarker(Tasks API)를 고른다. @mediapipe/tasks-vision을 설치하고 face_landmarker.task 번들을 받는다. 이 번들은 float16으로 약 3.58 MiB다. 웹 실시간이니 delegate: "GPU"(WebGL 가속)로 설정한다.

단계 1 — 모드와 옵션을 정한다 (06장)

입력이 라이브 웹캠이다. 웹에서는 VIDEO 모드를 쓰고 매 프레임 detectForVideo(frame, timestamp)를 호출한다. 네이티브였다면 LIVE_STREAM 모드와 콜백을 썼을 것이다.

지우에게는 표정과 머리 자세가 둘 다 필요하다. 그런데 이 두 출력은 기본적으로 꺼져 있다. 그래서 둘을 켜고, 나머지는 기본값으로 둔다.

  • output_face_blendshapes = true — 52개 표정 계수 (기본 false)
  • output_facial_transformation_matrixes = true — 머리 자세 4×4 행렬 (기본 false)
  • num_faces = 1 — 지우 한 명. 이 값이 1일 때만 떨림 완화(smoothing)가 켜진다.
  • 세 confidence 임계값 — 검출·존재·추적 모두 기본 0.5로 둔다.

단계 2 — 첫 프레임: 얼굴을 찾는다 (02장)

웹캠 첫 프레임이 들어온다. RGB 픽셀 격자다. 직전 프레임 정보가 아직 없으니 처음부터 얼굴을 찾아야 한다. 이렇게 맨바닥에서 찾는 것을 재획득(reacquisition)이라 부른다.

내부에서 프레임을 192×192로 줄인 뒤 검출기 BlazeFace short-range가 돈다. 검출기는 지우 얼굴을 거칠게 찾아 세 가지를 낸다. 얼굴을 감싸는 바운딩 박스, 6개 키포인트, 그리고 검출 점수다. 6개 키포인트는 양 눈·코끝·입·양 귀의 tragion(귀 앞 연골 돌기)이다. 검출 점수가 min_face_detection_confidence(0.5) 이상이라 "검출 성공"으로 인정된다.

단계 3 — 얼굴을 잘라 똑바로 세운다 (02장)

지우가 살짝 고개를 기울이고 있어도 다음 단계는 정면에 가까운 얼굴을 받아야 정확하다. 그 정리를 ROI crop & align이 맡는다. ROI는 관심 영역(Region of Interest), 즉 잘라낸 얼굴 패치다.

이 단계는 단계 2의 6개 키포인트로 두 눈을 잇는 각도를 계산한다. 그 각도만큼 패치를 회전시켜 두 눈이 수평이 되게 한다. 그리고 얼굴 둘레에 25% 마진을 두고 잘라 256×256 패치로 만든다. 이렇게 회전과 크기를 정규화해 주면, 다음 모델은 기울거나 작은 얼굴을 따로 외울 필요 없이 정밀 회귀에만 집중할 수 있다.

이때 "얼마나 돌리고 얼마나 잘랐는지"를 ROI 변환으로 따로 저장해 둔다. 나중에 점을 원본 좌표로 되돌리려면 이 기록이 필요하다.

단계 4 — 478점을 찍는다 (03장)

정렬된 256×256 패치를 Face Mesh V2가 받아 478개 랜드마크를 회귀한다. 좌표는 패치(ROI) 기준이다.

이 478은 03장에서 본 그 구성이다. 468개는 얼굴 표면 메시(canonical 정점, 인덱스 0–467)이고, 나머지 10개는 홍채다. 홍채는 눈당 5점씩, 인덱스 468–477에 들어간다. 눈·입술·홍채는 Attention 정제로 더 높은 해상도에서 다듬어 정밀하다. 동시에 이 모델은 얼굴 존재 확률(presence flag)도 내는데, 0.5를 넘어 "얼굴 있음"이 확인된다.

홍채 10점이 여기서 찍혔다는 점이 뒤에서 중요해진다. 시선 방향 계수가 이 홍채 점에서 나오기 때문이다.

단계 5 — 표정을 숫자로 (04장)

output_face_blendshapes=true라서 Blendshape V2가 깨어난다. 이 모델은 이미지를 다시 보지 않는다. 단계 4의 478점 중 146점 서브셋의 2D 좌표만 입력으로 받는다. 입력 텐서는 1×146×2다. 146점은 입·눈·눈썹·홍채·얼굴 외곽처럼 표정에 가장 중요한 부위만 골라낸 것이다. 이 점들로 모델은 52개 표정 계수를 회귀한다.

지우가 활짝 웃자 출력은 이렇게 나온다.

블렌드셰이프 계수
mouthSmileLeft 0.71
mouthSmileRight 0.68
eyeSquintLeft 0.40
eyeSquintRight 0.38
jawOpen 0.12
(나머지) ≈ 0

04장에서 본 대로 이 계수들은 합이 1이 아니다. 분류가 아니라 다중 회귀라서, 여러 계수가 동시에 큰 값을 가질 수 있다(웃으면서 눈을 가늘게 뜨는 것처럼). 그리고 이 52는 MediaPipe의 52다. _neutral 한 칸에 51개 표정 계수를 더한 집합이다. ARKit용 아바타에 매핑할 때는 두 가지만 챙기면 된다. _neutral은 무시하고, tongueOut이 기본에 없다는 점이다.

단계 6 — 머리 자세를 행렬로 (04장)

output_facial_transformation_matrixes=true라서 4×4 변환 행렬도 함께 나온다. 이 행렬은 표준(canonical) 얼굴 모델을 지금 검출된 얼굴의 위치·방향·크기로 옮기는 변환이다. 별도 신경망이 아니라 검출된 메시를 표준 메시에 정렬해 얻는 후처리 결과다.

지우가 고개를 왼쪽으로 15° 돌리면, 이 행렬의 좌상단 3×3(회전 부분)이 그 yaw(좌우 회전)를 담는다. 표정과 자세는 서로 다른 정보다. 블렌드셰이프는 얼굴 내부의 표정을 담고, 행렬은 머리 전체의 자세를 담는다. 아바타가 자연스러우려면 둘 다 있어야 한다. 행렬이 없으면 표정은 따라 해도 아바타 머리가 정면에 고정돼 어색하다.

단계 7 — 좌표를 원본 기준으로 되돌리고 결과를 조립한다 (02장)

단계 4의 478점은 ROI 패치 기준 좌표였다. 화면에 점을 찍으려면 원본 이미지 기준으로 되돌려야 한다. 그 일을 역정규화가 한다. 단계 3에서 저장해 둔 ROI 변환을 거꾸로 적용해, 478점을 원본 이미지 기준 정규화 좌표 x,y ∈ [0,1]로 환산한다.

num_faces=1이라 시간축 smoothing이 적용돼 프레임 간 떨림이 준다. 마지막으로 결과 객체 FaceLandmarkerResult가 조립된다. 여기에는 face_landmarks(478점), face_blendshapes(52계수), facial_transformation_matrixes(4×4 행렬)가 담겨 지우의 앱으로 돌아온다.

단계 8 — 아바타에 입힌다 (04장)

이제 결과를 고양이에 입힐 차례다. 지우의 앱은 04장의 리타게팅(다른 캐릭터로 옮겨 적용하기)을 따른다.

  • 52개 계수를 고양이 아바타의 같은 이름 morph target 가중치로 연결한다. 이름을 ARKit 52와 맞춰 두면 매핑이 거의 자동이다.
  • 4×4 행렬에서 회전을 뽑아 고양이의 head 본에 적용한다.
  • 프레임마다 값이 약간 떨리면 One-Euro filter로 평활화한다. 이 필터는 MediaPipe 기능이 아니라 응용 단의 관행이다.

결과는 지우가 바라던 그대로다. 지우가 웃으면 고양이가 웃고, 고개를 돌리면 고양이도 돌린다.

단계 9 — 둘째 프레임부터: 추적으로 빨라진다 (02장, 06장)

여기가 실시간의 비밀이다. 얼굴은 프레임 사이에 거의 움직이지 않는다. 30 FPS면 프레임 간격이 33ms인데, 그사이 얼굴은 거의 같은 자리에 있다. 그러니 매 프레임 화면 전체를 새로 뒤질 이유가 없다.

그래서 둘째 프레임부터는 직전 프레임의 랜드마크로 ROI를 바로 만든다. 검출기(BlazeFace)를 건너뛰고 메시 모델만 돌린다. 이것을 detect-once-then-track이라 부른다. 가장 비싼 단계 중 하나인 검출기가 빠지니 프레임당 비용이 줄어 초당 수십 프레임이 나온다.

추적 모드는 빠르기만 한 게 아니라 더 정확하기까지 하다. Face Mesh V2 모델 카드 기준으로 추적 모드의 위치 오차(IOD MAE)는 2.62%이고, 재획득 모드는 3.24%다. 직전 프레임으로 잘 정렬된 ROI를 쓰니 맨바닥에서 다시 찾는 것보다 정밀하다.

그렇다고 추적이 영원하지는 않다. 지우가 갑자기 화면 밖으로 나가면 추적이 깨진다. 추적 점수나 존재 확률이 0.5 아래로 떨어지는 순간이다. 그러면 다음 프레임에 검출기를 다시 불러 재획득한다.

Cat avatarBlendshape V2Face Mesh V2Crop and alignBlazeFaceWebcamCat avatarBlendshape V2Face Mesh V2Crop and alignBlazeFaceWebcamFrame 1 — reacquisitionFrame 2..N — tracking (detector skipped)full framebbox + 6 keypoints256x256 patch146 subset52 coeffs + 4x4 matrix (de-normalized)reuse prev landmarks as ROI256x256 patch146 subset52 coeffs + 4x4 matrix

첫 프레임만 검출기를 거치고, 이후 프레임은 메시 모델만 도는 짧은 루프가 반복되다가, 신뢰도가 떨어질 때만 검출기로 되돌아간다. 한 시나리오 안에 모든 장이 한 번씩 등장했다.

Advertisements

7.3 결론 — 다섯 핵심 질문에 대한 단일 답

이 책이 답한 다섯 가지 핵심 질문을, 각 장으로 링크하며 한곳에 모은다.

질문 한 줄 답 깊이
왜 이 기술이 이렇게 생겼나? (히스토리) BlazeFace(위치)→Face Mesh(표면)→Attention Mesh(정밀)→Iris(눈·거리)→Tasks/Landmarker(통합)→Blendshapes(표정)로, 각 단계가 앞 단계의 한계를 하나씩 풀며 쌓인 합본이다. 01장
한 장이 어떻게 처리되나? (파이프라인) 검출(b)→ROI 정렬(c)→478점 회귀(d)→(옵션) 52계수(e)·4×4 행렬(f)→역정규화(g). 영상은 detect-once-then-track으로 검출기를 대부분 생략한다. 02장
왜 478개인가? (랜드마크) 478 = 468(표면 메시, canonical 정점, 0–467) + 10(홍채, 눈당 5점, 468–477). 곡면·표정 복원과 고정 토폴로지를 위해 조밀해야 하되, 모바일 실시간을 위해 478이 균형점이다. 03장
블렌드셰이프란? (표정) 478점을 입력받는 별도 2차 모델이 표정을 ARKit 호환 52계수(0~1)로 회귀한다. 머리 자세는 별개의 4×4 행렬이 담는다. 04장
무엇이 달라졌고, 어떻게 돌리나? (차이·성능) 레거시 Face Mesh와 달리 Landmarker는 52계수·4×4 행렬을 표준 출력으로 더하고 Tasks API로 통일됐다. 번들 ≈3.58 MiB, 3모드, CPU/GPU delegate로 돌린다. 05장, 06장

한 문장 결론. MediaPipe Face Landmarker는 4년에 걸쳐 쌓인 경량 모델들(BlazeFace·Face Mesh V2·Blendshape V2)을 하나의 .task 번들로 묶은 도구다. 평범한 RGB 카메라 한 대로 온디바이스에서 실시간으로 얼굴을 찾아 478점을 찍고, 52개 표정 계수와 머리 자세 행렬까지 내준다.

7.4 핵심 수치 결정표 (한곳에 모음)

자주 쓰는 숫자를 한 표로 묶는다. 각 수치가 무엇의 무슨 값인지를 함께 적어 혼동을 막았다.

항목 무엇의 값인가 출처 챕터
랜드마크 수 478 = 468 + 10 메시 468 + 홍채 10 03장
홍채 인덱스 우안 468(중심)·469–472, 좌안 473(중심)·474–477 피사체 기준 좌우 03장
블렌드셰이프 수 52 (MediaPipe = _neutral + 51; ARKit = 51 + tongueOut) "두 개의 52"는 다른 집합 04장
블렌드셰이프 입력 146점 × 2D (1×146×2) 478 중 표정 핵심 서브셋 04장
번들 크기 3,758,596 B ≈ 3.58 MiB (float16) .task 전체(3모델) 06장
검출기 입력 192×192 (번들 기준) BlazeFace short-range 02장, 06장
메시 입력 256×256 (25% 마진 크롭) Face Mesh V2 02장, 06장
검출기 latency Pixel 6 CPU 2.94 ms / GPU 7.41 ms 검출 단계만(전체 아님) 06장
메시 정확도 IOD MAE 2.62%(track) / 3.24%(reacq) 메시(랜드마크) 모델 위치 오차 06장
블렌드셰이프 정확도 MNE 3.88% 블렌드셰이프 모델 계수 오차(메시와 별개 지표) 04장, 06장
기본 설정 num_faces=1, conf 0.5×3, blendshape/matrix=false Tasks API 기본값 06장
플랫폼 최소 Android 24 / iOS 12.0 / Python 3.9+ 각 setup 가이드 06장
홍채 깊이 상수 11.7 ± 0.5 mm (거리 오차 ≈4.3%) MediaPipe Iris 01장, 03장

지표 분리 재강조. 표의 IOD MAE 2.62%/3.24%는 메시 모델의 위치 오차이고, MNE 3.88%는 블렌드셰이프 모델의 계수 오차다. 둘은 다른 모델·다른 지표·다른 출처다. "정확도는 한 숫자"로 섞어 말하면 안 된다.

7.5 실무 체크리스트

Face Landmarker로 무언가를 만들 때 점검할 항목이다.

설계 단계

  • [ ] 표정 계수(52)가 필요한가? 필요하면 output_face_blendshapes=true로 켠다 (기본 꺼짐).
  • [ ] 머리 자세(4×4)가 필요한가? 필요하면 output_facial_transformation_matrixes=true로 켠다 (기본 꺼짐).
  • [ ] 입력이 단일 이미지인가, 비디오인가, 라이브 카메라인가? 각각 IMAGE / VIDEO / LIVE_STREAM을 고른다.
  • [ ] 동시에 볼 얼굴이 몇 개인가? num_faces로 정한다. 단 2개 이상이면 smoothing이 빠져 지터가 는다.

구현 단계

  • [ ] .task 번들 파일(≈3.58 MiB)을 앱에 동봉하거나 받아 두고, model_asset_path로 지정했는가?
  • [ ] 좌표가 정규화 [0,1]임을 코드에 반영했는가? 픽셀은 x*width, y*height로 환산하고, z는 절대 거리가 아니라 상대 깊이다.
  • [ ] 478을 가정하는가? 레거시 468 가정 코드를 옮긴다면 홍채 인덱스 468–477을 추가로 점검한다.
  • [ ] LIVE_STREAM이면 타임스탬프를 단조 증가로 관리하고 결과를 콜백으로 받는가?
  • [ ] 블렌드셰이프 좌우(Left/Right)가 피사체 기준임을 아바타 매핑에 반영했는가?

성능·품질 단계

  • [ ] delegate(CPU/GPU)를 목표 기기에서 직접 실측했는가? "GPU가 무조건 빠르다"는 틀린 일반화다.
  • [ ] 입력 한계를 사용자 안내나 필터링에 반영했는가? 시선 회피 80° 초과, roll 8° 초과, 가림 50% 초과, 너무 작은 얼굴은 부적합하다.
  • [ ] 프레임 간 지터가 거슬리면 One-Euro 같은 시간축 평활화를 적용했는가?
  • [ ] 생체인증이나 생명 위급 의사결정 용도가 아님을 전제로 설계했는가? 모델 카드의 out-of-scope 항목이다.

7.6 더 배우려면 (다음 발걸음)

이 책은 개념·구조·근거에 집중했다. 실제 코드로 손을 움직이려면 다음으로 간다.

  • 플랫폼별 빠른 시작: Face Landmarker 가이드의 Python/Android/iOS/Web 페이지 (06장 Sources).
  • 코드 없이 체험: MediaPipe Studio의 Face Landmarker Instant Demo(브라우저에서 바로 실행).
  • 478점을 실제 얼굴 위에서 보기: 03장이 안내한 외부 시각화 자료(canonical UV, "All 478 Landmark Points").
  • 블렌드셰이프 이론을 더 깊이: Blendshapes GHUM 논문(arXiv:2309.05782, 04장 Sources).

여기까지 읽었다면 "왜 478개인가?"부터 "어떻게 실시간 아바타가 되는가?"까지 한 줄기로 꿰뚫은 것이다. 처음의 그 의문들, 즉 468 대 478, 두 개의 52, Solutions 대 Tasks, 블렌드셰이프가 왜 따로인가가 이제 막힘없이 풀릴 것이다.

처음으로 돌아가려면 README로 간다.


Sources

이 장은 새 1차 사실을 도입하지 않고, 각 장의 결론을 한 시나리오로 통합한다. 모든 수치·인용의 원 출처는 해당 장의 Sources를 따른다.

  • 히스토리·계보: 01장
  • 파이프라인·검출/추적: 02장
  • 478 랜드마크: 03장
  • 블렌드셰이프·4×4 행렬: 04장
  • Face Mesh와의 차이: 05장
  • 성능·요구사항·플랫폼: 06장

대표 공식 출처(통합 인용): Face landmark detection guide (https://ai.google.dev/edge/mediapipe/solutions/vision/face_landmarker), Model Card MediaPipe Face Mesh V2, Blendshapes GHUM (arXiv:2309.05782), MediaPipe Iris (research.google blog).