본문 바로가기

TIL

Face Blendshapes — 52개 숫자로 표정을 기술하는 법

MediaPipe의 FaceLandmarker는 얼굴에서 478개 랜드마크 좌표를 뽑는다. 이 좌표만으로도 눈코입의 위치를 알 수 있지만, "눈이 얼마나 감겨 있는가"나 "입이 얼마나 열려 있는가"를 바로 쓸 수 있는 숫자로 내주지는 않는다. 랜드마크에서 그 값을 직접 계산하려면 구체적인 점 번호를 알고 거리를 재야 한다.

FaceLandmarker는 이와 별개로 블렌드쉐이프(blendshape) 계수를 함께 출력한다. 이미 의미가 붙어 있는 숫자다. eyeBlinkLeft: 0.84라면 "왼쪽 눈이 84% 감겨 있다"로 바로 읽힌다.

배경 — Apple ARKit에서 온 표준

블렌드쉐이프는 3D 캐릭터 애니메이션에서 오래된 개념이다. 기본 형태(base mesh)와 여러 변형 형태(blend targets)를 정의해두고, 각 변형 형태의 혼합 비율(blendshape weight)로 표정을 만든다. 눈을 감는 형태에 0.8을 주면 80% 감긴 눈이 된다. 여러 변형 형태를 동시에 섞으면 복잡한 표정도 연출된다.

이 개념을 실시간 얼굴 추적에 체계적으로 적용한 것이 Apple의 ARKit Face Tracking이다. iPhone X에서 트루뎁스(TrueDepth) 전면 카메라로 얼굴을 추적하면서 52개의 블렌드쉐이프 계수를 표준화해 공개했다. 이름, 개수, 의미 체계가 정해졌다.

MediaPipe의 FaceLandmarker는 이 ARKit 52개 계수 체계를 그대로 채택했다. 이름이 같다. mouthSmileLeft, eyeWideRight, jawOpen 같은 이름들이 ARKit의 그것과 일치한다. 덕분에 ARKit 문서에서 각 블렌드쉐이프의 의미를 찾아볼 수 있고, ARKit용으로 만들어진 얼굴 애니메이션 에셋이 MediaPipe 출력값과 호환된다.

52개의 구성

52개 블렌드쉐이프는 얼굴 부위별로 묶어볼 수 있다.

눈 영역은 가장 많다. eyeBlinkLeft/Right(눈 감김), eyeLookUpLeft/Right, eyeLookDownLeft/Right, eyeLookInLeft/Right, eyeLookOutLeft/Right(시선 방향), eyeSquintLeft/Right(눈 찡그림), eyeWideLeft/Right(눈 크게 뜸)가 있다. 시선 방향은 4방향을 각각의 계수로 표현하므로, 조합으로 대각선 방향도 나타낼 수 있다.

눈썹 영역은 browDownLeft/Right(눈썹 내림), browInnerUp(미간 올림), browOuterUpLeft/Right(눈썹 바깥쪽 올림)다.

코는 noseSneerLeft/Right(코 찡그림) 두 개다.

입은 가장 다양하다. jawForward(아래턱 앞으로), jawLeft/Right(턱 좌우), jawOpen(입 열기)부터 mouthSmileLeft/Right, mouthFrownLeft/Right, mouthStretchLeft/Right, mouthPressLeft/Right, mouthDimpleLeft/Right 등 입꼬리와 입술의 세밀한 움직임을 각각 담는다. 혀 관련은 tongueOut 하나다.

볼과 턱은 cheekPuff(볼 부풀리기), cheekSquintLeft/Right(볼 찡그림), mouthRollLower/Upper(입술 말기), mouthShrugLower/Upper(입술 움츠리기) 등이다.

어떻게 출력되나

FaceLandmarker.createFromOptionsoutputFaceBlendshapes: true를 넣으면 활성화된다. detectForVideo 결과에서 faceBlendshapes[0].categories를 읽으면 각 블렌드쉐이프의 이름(categoryName)과 0에서 1 사이의 점수(score)가 배열로 나온다.

배열을 이름을 키로 한 맵으로 바꾸면 쓰기 편하다. blendshapes["eyeBlinkLeft"]처럼 이름으로 바로 꺼낼 수 있다. 52개 중 어떤 것은 0에 가깝고 어떤 것은 뚜렷한 값이 나온다. 무표정 상태에서 대부분은 0에 가깝다.

실제 활용

눈 감김 감지. eyeBlinkLefteyeBlinkRight 중 큰 값이 특정 임계(예: 0.3)를 넘으면 눈이 감겨 있다고 판단한다. 양쪽 중 하나라도 높으면 실제로 눈을 감은 상황에 가깝다. 이 값을 1에서 빼면 "눈 열림 비율"이 된다. 이 값이 낮게 유지되면 졸음 감지에 쓸 수 있다.

입 열기 감지. jawOpen 값이 높으면 입이 열려 있다. 말을 할 때, 하품할 때, 놀랐을 때 올라간다.

혀 감지. tongueOut 하나다. 값이 높으면 혀가 입 밖으로 나와 있다. 표준 ARKit 계수에는 혀의 좌우 방향 계수가 없다. 따라서 "혀를 내밀었는가"는 알 수 있지만 "왼쪽으로 내밀었는가"는 블렌드쉐이프만으로는 알기 어렵다.

집중 감지. 블렌드쉐이프는 표정 동작의 계수지 머리 방향이 아니다. 사람이 어디를 보는지는 별도의 머리 자세(head pose) 계산이 필요하다. 그러나 시선 방향 블렌드쉐이프(eyeLookUp/Down/In/Out)는 안구가 어느 쪽을 향하는지 알려준다. 이를 머리 방향과 조합하면 더 정확한 시선 추정이 가능하다.

접근성 입력. 손을 쓸 수 없는 사용자를 위한 대안 입력으로 활용할 수 있다. 혀를 내밀면 선택, 입을 열면 확인 같은 매핑이다. 소수 블렌드쉐이프만 쓰므로 FaceLandmarker 하나로 여러 입력 신호를 동시에 제공할 수 있다.

이점과 트레이드오프

이점은 의미가 붙어 있다는 점이다. 랜드마크 좌표에서 "눈이 얼마나 감겼나"를 재려면 위아래 눈꺼풀 랜드마크 거리를 눈 크기로 정규화해야 한다. 블렌드쉐이프는 이 계산을 모델이 이미 해준 결과다. 코드가 단순해진다.

표준화된 이름 체계도 이점이다. ARKit 기반이라 문서가 풍부하고, 다른 플랫폼과 호환된다.

감수하는 것이 있다. 정밀도가 낮다. tongueOut은 혀를 내밀었는지 여부는 알 수 있지만 방향은 알 수 없다. 미묘한 표정 차이는 잘 구분하지 못할 수 있다. 또 outputFaceBlendshapes: true로 활성화하면 랜드마크만 쓸 때보다 연산이 늘어난다. 필요한 경우에만 켜는 것이 좋다.

개인차도 있다. 같은 동작을 해도 얼굴 구조나 카메라 각도에 따라 블렌드쉐이프 값이 달리 나온다. 임계값은 고정해두기보다 사용 환경에서 직접 확인하고 조정하는 편이 안정적이다.