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

mediapipe 매쉬 모델활용 얼굴 트레킹봇 만들기.

by SwMaker_Jun 2025. 8. 14.
728x90
반응형

 

 

1. 아두이노 코드 

아두이노 코드

#include <Servo.h>  
Servo neck;
Servo mouth;      

void setup() {
  Serial.begin(9600);
  neck.attach(6);   // 목 서보모터 연결 핀 번호 6번
  mouth.attach(7);  // 입 서보모터 연결 핀 번호 7번 
  neck.write(90);   // 정면을 90도로 셋팅
  mouth.write(0);   // 입은 0도로 셋팅
}

void loop() {
  while (Serial.available() > 0) {
    char data = Serial.read();
    if (data == 'a') {
      neck.write(50);
    }
    if (data == 'b') {
      neck.write(130);
    }
    if (data == 'c') {
      neck.write(90);
    }
    if (data == 'd') {
      mouth.write(90);
    }
    if (data == 'e') {
      mouth.write(0);
    }
  }
    
}

 

 

 

 

2. 파이썬 코드 (mediapipe 매쉬)

 

 

파이썬 5번째 줄 시리얼 포트 설정은 아두이노 보드 포트 번호와 속도는 꼭 확인하여 바꿔야 함.

import cv2
import mediapipe as mp
import math
import serial

# 시리얼 포트 설정 (포트 이름과 속도는 환경에 맞게 변경)
ser = serial.Serial('COM9', 9600)

mp_drawing = mp.solutions.drawing_utils
mp_face_mesh = mp.solutions.face_mesh

# 랜드마크 인덱스
NOSE_TIP = 4
LEFT_FACE = 234
RIGHT_FACE = 454
MOUTH_LEFT = 61
MOUTH_RIGHT = 291
MOUTH_UP_INNER = 13
MOUTH_DOWN_INNER = 14

def px(pt, w, h):
    return (pt.x * w, pt.y * h)

def dist_xy(a, b):
    return math.hypot(a[0] - b[0], a[1] - b[1])

cap = cv2.VideoCapture(0)

# 입 벌림 임계값
OPEN_TH = 0.065
CLOSE_TH = 0.055
mouth_is_open = False

with mp_face_mesh.FaceMesh(
    static_image_mode=False,
    refine_landmarks=True,
    max_num_faces=1,
    min_detection_confidence=0.5,
    min_tracking_confidence=0.5
) as face_mesh:

    while cap.isOpened():
        ret, frame = cap.read()
        if not ret:
            break

        frame = cv2.flip(frame, 1)
        rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        results = face_mesh.process(rgb)
        h, w = frame.shape[:2]

        direction_text = ""
        mouth_text = ""

        if results.multi_face_landmarks:
            for face_landmarks in results.multi_face_landmarks:
                lm = face_landmarks.landmark

                # 방향 판별
                nose_x = lm[NOSE_TIP].x * w
                left_face_x = lm[LEFT_FACE].x * w
                right_face_x = lm[RIGHT_FACE].x * w
                face_center_x = (left_face_x + right_face_x) / 2
                diff = nose_x - face_center_x

                if diff < -10:
                    direction_text = "LEFT"
                    ser.write(b'a')
                elif diff > 10:
                    direction_text = "RIGHT"
                    ser.write(b'b')
                else:
                    direction_text = "CENTER"
                    ser.write(b'c')

                # 입 벌림 판별
                p_left = px(lm[MOUTH_LEFT], w, h)
                p_right = px(lm[MOUTH_RIGHT], w, h)
                p_up = px(lm[MOUTH_UP_INNER], w, h)
                p_down = px(lm[MOUTH_DOWN_INNER], w, h)

                mouth_width = dist_xy(p_left, p_right) + 1e-6
                mouth_opening = dist_xy(p_up, p_down)
                mar = mouth_opening / mouth_width

                if not mouth_is_open and mar > OPEN_TH:
                    mouth_is_open = True
                elif mouth_is_open and mar < CLOSE_TH:
                    mouth_is_open = False

                if mouth_is_open:
                    mouth_text = "MOUTH OPEN"
                    ser.write(b'd')
                else:
                    mouth_text = "MOUTH CLOSE"
                    ser.write(b'e')

                # 흰색 얼굴 메쉬
                mp_drawing.draw_landmarks(
                    image=frame,
                    landmark_list=face_landmarks,
                    connections=mp_face_mesh.FACEMESH_TESSELATION,
                    landmark_drawing_spec=None,
                    connection_drawing_spec=mp_drawing.DrawingSpec(color=(255, 255, 255), thickness=1, circle_radius=0)
                )

        # 화면 표시
        if direction_text:
            cv2.putText(frame, direction_text, (30, 50),
                        cv2.FONT_HERSHEY_SIMPLEX, 1.5, (0, 255, 0), 3, cv2.LINE_AA)

        if mouth_text:
            (tw, th), _ = cv2.getTextSize(mouth_text, cv2.FONT_HERSHEY_SIMPLEX, 1.0, 3)
            x = w - tw - 30
            y = 50
            cv2.putText(frame, mouth_text, (x, y),
                        cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 0, 255), 3, cv2.LINE_AA)

        cv2.imshow("Face Direction + Mouth Open/Close", frame)
        if cv2.waitKey(1) & 0xFF == 27:
            break

cap.release()
cv2.destroyAllWindows()
ser.close()

KakaoTalk_20250814_175148280.mp4
3.91MB

 

 

 

728x90
반응형