iBetter Books
수정

PART 08의 마무리는 웹캠으로 표정을 실시간 표시하는 대시보드입니다. 빠른 처리를 위해 MediaPipe 블렌드셰이프(4장)를 쓰고, 6장의 주의를 코드에 그대로 반영합니다. 단일 표정으로 단정하지 않고, 표정 단위의 강도를 막대로 함께 보여 주어 "추정"임을 화면에서부터 드러냅니다.

무엇을 만드는가

  • 웹캠 프레임마다 FaceLandmarker로 블렌드셰이프 52계수를 읽는다.
  • 미소 등 핵심 계수를 골라 막대 그래프로 실시간 표시한다.
  • 단일 감정 라벨로 단정하지 않고 강도를 그대로 보여 준다(6장의 태도).

블렌드셰이프를 고른 이유는 실시간에 빠르고, 신원 정보를 쓰지 않아 개인정보 부담이 적으며, 계수가 그대로 보여 해석이 투명하기 때문입니다.

전체 코드

face_landmarker.task 모델이 작업 폴더에 있다고 가정합니다. 다음 내용을 emotion_dashboard.py로 저장합니다.

# 파일: emotion_dashboard.py"""웹캠 블렌드셰이프를 막대로 실시간 표시한다. q 키로 종료."""import cv2import mediapipe as mpfrom mediapipe.tasks import pythonfrom mediapipe.tasks.python import visionbase = python.BaseOptions(model_asset_path="face_landmarker.task")options = vision.FaceLandmarkerOptions(    base_options=base, output_face_blendshapes=True, num_faces=1)landmarker = vision.FaceLandmarker.create_from_options(options)# 화면에 보여 줄 핵심 표정 단위KEYS = ["mouthSmileLeft", "mouthSmileRight", "browInnerUp",        "eyeBlinkLeft", "jawOpen"]def draw_bars(frame, scores):    for i, key in enumerate(KEYS):        val = scores.get(key, 0.0)        y = 30 + i * 30        cv2.putText(frame, f"{key:16s}", (10, y),                    cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1, cv2.LINE_AA)        cv2.rectangle(frame, (200, y - 12), (200 + int(val * 200), y), (0, 255, 0), -1)cap = cv2.VideoCapture(0)while True:    ok, frame = cap.read()    if not ok:        break    rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)    mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=rgb)    result = landmarker.detect(mp_image)    if result.face_blendshapes:        scores = {b.category_name: b.score for b in result.face_blendshapes[0]}        draw_bars(frame, scores)    cv2.imshow("emotion dashboard - press q to quit", frame)    if cv2.waitKey(1) & 0xFF == ord("q"):        breakcap.release()cv2.destroyAllWindows()

매 프레임 블렌드셰이프를 읽어 핵심 계수(KEYS)만 골라 막대로 그립니다. 미소를 지으면 mouthSmileLeft·mouthSmileRight 막대가 길어지고, 입을 벌리면 jawOpen이 올라갑니다. 표정을 "행복"이라고 단정하는 대신 강도를 그대로 보여 주는 것이 6장의 주의를 실천하는 방식입니다.

감정 라벨이 꼭 필요하다면

라벨이 필요한 경우에도 단정하지 않는 설계를 권합니다. 계수를 조합해 부드럽게 표현합니다.

# 파일: soft_label.py (개념)smile = (scores.get("mouthSmileLeft", 0) + scores.get("mouthSmileRight", 0)) / 2if smile > 0.6:    label = "밝은 표정"      # "행복"이라 단정하지 않음elif smile > 0.3:    label = "옅은 미소"else:    label = "중립적 표정"

"행복" 같은 내면 단정 대신 "밝은 표정"처럼 관찰된 사실에 가깝게 표현하면, 6장에서 말한 "표정≠감정"의 간극으로 인한 오해를 줄일 수 있습니다.

책임 있는 대시보드를 위한 점검

  • 개인 판정 금지: 이 대시보드로 개인을 평가·선별하지 않습니다(6장).
  • 표시는 강도로: 단일 라벨보다 계수·분포를 보여 줍니다.
  • 저장 최소화: 표정 데이터를 굳이 저장하지 않거나, 저장 시 목적·기간을 명확히 합니다.
  • 고지: 표정을 분석한다는 사실을 사용자에게 알립니다.

실무 팁. 실시간 대시보드는 프레임마다 값이 미세하게 떨리기 쉽습니다. 막대가 깜빡이며 보기 어렵다면, 최근 몇 프레임의 값을 평균 내어(이동 평균) 부드럽게 표시하세요. 졸음 감지(PART 03)에서 연속 프레임을 본 것과 같은 원리로, 순간의 흔들림을 누그러뜨리면 훨씬 안정적인 화면이 됩니다.

이 장에서 기억할 것

실시간 감정 대시보드는 MediaPipe 블렌드셰이프로 매 프레임 표정 단위를 읽어 막대로 표시하며, 단일 감정으로 단정하지 않고 강도를 그대로 보여 6장의 주의를 실천합니다. 라벨이 필요하면 "밝은 표정"처럼 관찰에 가깝게 표현하고, 개인 판정 금지·표시는 강도로·저장 최소화·고지를 지킵니다. 이로써 PART 08이 끝납니다. 다음 PART 09에서는 사진·영상으로 시스템을 속이는 위조를 막는 안티스푸핑과 라이브니스를 다룹니다.