728x90
반응형
1. Math 라이브러리를 활용한 입술 랜드마크 거리 측정
math 라이브러리 활용에 대한 핸드모델 실습 예)
https://swmakerjun.tistory.com/59
math 라이브러리 활용
1. math 라이브러리 파이썬의 math 라이브러리는 수학적인 연산을 수행하는 함수와 상수들을 제공하는 표준 라이브러리 모듈 중 하나 이 라이브러리를 사용하면 다양한 수학적인 작업을 수행할 수
swmakerjun.tistory.com
제곱 → 합 → 제곱근 과정 [두점의 좌표]
import math
# frame_bgr, h, w, face_landmarks 가 준비되어 있다고 가정
# 얼굴이 인식된 코드 안에 아래 코드 적용하기
# 윗입술(13), 아랫입술(14) 좌표 구하기
x_1, y_1 = int(face_landmarks.landmark[13].x * w), int(face_landmarks.landmark[13].y * h) # 윗입술
x_2, y_2 = int(face_landmarks.landmark[14].x * w), int(face_landmarks.landmark[14].y * h) # 아랫입술
# 피타고라스 정리를 활용한 거리 계산
dist = int(math.sqrt((x_1 - x_2)**2 + (y_1 - y_2)**2))
# 결과를 영상에 출력
cv2.putText(frame_bgr, f"Mouth dist: {dist}", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
2. 입술의 거리를 출력하는 전체 코드
![]() |
![]() |
import cv2
import mediapipe as mp
import math
# 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
)
# 이미지 크기
h, w, _ = frame_bgr.shape
# 윗입술(13), 아랫입술(14) 좌표 구하기
x_1, y_1 = int(face_landmarks.landmark[13].x * w), int(face_landmarks.landmark[13].y * h) # 윗입술
x_2, y_2 = int(face_landmarks.landmark[14].x * w), int(face_landmarks.landmark[14].y * h) # 아랫입술
# 피타고라스 정리를 활용한 거리 계산
dist = int(math.sqrt((x_1 - x_2)**2 + (y_1 - y_2)**2))
# 입술 중앙 좌표에 원 그리기
cv2.circle(frame_bgr, (x_1, y_1), 3, (0, 255, 0), -1)
cv2.circle(frame_bgr, (x_2, y_2), 3, (0, 255, 0), -1)
# 결과를 영상에 출력
cv2.putText(frame_bgr,
f"Mouth dist: {dist}",(10, 30),cv2.FONT_HERSHEY_SIMPLEX,0.7,(0, 255, 0),2)
# 결과 출력
cv2.imshow('Face Mesh White Lines', frame_bgr)
# ESC 키로 종료
if cv2.waitKey(1) & 0xFF == 27:
break
cap.release()
cv2.destroyAllWindows()
- 얼굴 메쉬가 흰색 선으로 표시
- 윗입술(13)과 아랫입술(14) 위치에 초록색 점 표시
- 두 점 사이 거리를 피타고라스 공식으로 계산해 "Mouth dist: N" 형태로 출력됩니다.
3. 선형 매핑
입술 거리 → 서보모터 각도(0~180°) 로 바꾸는 가장 깔끔한 방법 : 선형 매핑(linear mapping)
- 카메라로 윗입술(13번)과 아랫입술(14번) 사이 거리를 구한다.
- 이 거리는 픽셀 단위로 나오며, 입을 닫으면 값이 작고, 크게 벌리면 값이 커진다.
- 이 값을 서보모터의 제어 범위인 0° ~ 180° 사이 값으로 매핑하면,
입술 움직임 → 모터 각도 변환이 가능하다.
- 매핑 공식
- D_MIN : 입을 닫았을 때의 거리 (픽셀)
- D_MAX : 크게 벌렸을 때의 거리 (픽셀)
- np.interp 함수를 이용하면, D_MIN, D_MAX → 0, 180° 로 선형 매핑할 수 있다.
#상단에 변수 선언
D_MIN = 0 # 입을 다물었을 때 거리
D_MAX = 36 # 입을 벌렸을 때 거리
#얼굴이 인식되고 있을때의 문법안에 코드 작성
angle = np.interp(dist, [D_MIN, D_MAX], [0, 180]) # 거리 → 각도 변환 (비례식)
angle = int(np.clip(angle, 0, 180)) # 값이 0~180 범위에 들어오게 제한 + 정수 변환
np.interp(dist, [D_MIN, D_MAX], [0, 180])
- np.interp는 선형 보간 함수예요.
- 문법 : np.interp(값, [입력최소, 입력최대], [출력최소, 출력최대])
- 여기서는
- 입력 범위: [D_MIN, D_MAX] → 입술 거리의 최소·최대값 (픽셀 단위)
- 출력 범위: [0, 180] → 서보모터 각도 범위
- 즉,
- dist == D_MIN → angle = 0
- dist == D_MAX → angle = 180
- 그 사이 값은 비례해서 변환됩니다.
- 위 사진에서 보면 D_MIN은 0 / D_MAX는 36 인것을 확인 할 수 있다
※ 각자 값이 상이하므로 그전 실습으로 입술의 거리를 확인을 해야함.
비례식 " 0픽셀일 때 0°, 30픽셀일 때 180° → 그 사이 값은 선형으로 계산" 으로 풀어서 설명
중학교 수학식 비례식 풀이
4. 각도매핑 출력 코드
import cv2
import mediapipe as mp
import math
import numpy as np
# =========================
# 상단에 변수 선언
# =========================
D_MIN = 0 # 입을 다물었을 때 거리(픽셀)
D_MAX = 36 # 입을 벌렸을 때 거리(픽셀)
# 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)
# 추론용 RGB
rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
results = face_mesh.process(rgb)
if results.multi_face_landmarks:
h, w, _ = frame.shape
for face_landmarks in results.multi_face_landmarks:
# (선택) 얼굴 메쉬 흰색으로 그리기
mp_drawing.draw_landmarks(
image=frame,
landmark_list=face_landmarks,
connections=mp_face_mesh.FACEMESH_TESSELATION,
landmark_drawing_spec=None,
connection_drawing_spec=white_line
)
# 윗입술(13)·아랫입술(14) 좌표
x1 = int(face_landmarks.landmark[13].x * w)
y1 = int(face_landmarks.landmark[13].y * h)
x2 = int(face_landmarks.landmark[14].x * w)
y2 = int(face_landmarks.landmark[14].y * h)
# 피타고라스 정리로 거리 계산
dist = int(math.sqrt((x1 - x2)**2 + (y1 - y2)**2))
# 얼굴이 인식되고 있을 때의 코드 작성 (요청하신 매핑 2줄)
angle = np.interp(dist, [D_MIN, D_MAX], [0, 180]) # 거리 → 각도 변환 (비례식)
angle = int(np.clip(angle, 0, 180)) # 값이 0~180 범위에 들어오게 제한 + 정수 변환
# 시각화
cv2.circle(frame, (x1, y1), 3, (0, 255, 0), -1)
cv2.circle(frame, (x2, y2), 3, (0, 255, 0), -1)
cv2.putText(frame, f"Dist: {dist}", (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0,255,0), 2)
cv2.putText(frame, f"Servo angle: {angle}", (10, 60),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0,255,255), 2)
# (선택) 시리얼로 아두이노 전송 예시
# ser.write(f"{angle}\n".encode())
cv2.imshow("Lip Distance -> Servo Angle (0~180)", frame)
if cv2.waitKey(1) & 0xFF == 27: # ESC
break
cap.release()
cv2.destroyAllWindows()
![]() |
![]() |
- 실행 후 화면 왼쪽 상단에 Dist(픽셀)와 Servo angle이 표시됩니다.
- 환경에 따라 D_MAX가 더 클/작 수 있으니, 학생들이 직접 닫힘/최대 벌림 값을 관찰하고 D_MIN/D_MAX를 조정해보게 하면 학습 효과가 좋아요.
5. 각도를 시리얼통신으로 보내보자
import serial
import time
# 아두이노와 연결 (포트 이름과 보레이트 확인 필요)
# Windows 예: 'COM3', mac/Linux 예: '/dev/ttyUSB0'
ser = serial.Serial('COM3', 9600)
time.sleep(2) # 아두이노 초기화 대기 (필수)
while True:
# angle 값은 얼굴 인식 코드에서 계산된 결과라고 가정
angle = 90 # 예시: 90도로 고정
data = f"{angle}\n" # 문자열로 변환 후 줄바꿈 추가
ser.write(data.encode()) # 바이트로 인코딩해 전송
print("보낸 값:", angle)
time.sleep(1) # 1초마다 전송
728x90
반응형
'인공지능 기초 수업' 카테고리의 다른 글
Mediapip 메쉬(Mesh)모델 활용하기. (0) | 2025.09.03 |
---|---|
Roboflow #2. 이미지 라벨링, 데이터 셋 만들기. (3) | 2025.09.01 |
Roboflow #1. 활용 이미지 데이터 업로드 하기. (2) | 2025.09.01 |
kaggle 에서 이미지 데이터 받아오기. (2) | 2025.09.01 |
mediapipe 매쉬 모델활용 얼굴 트레킹봇 만들기. (4) | 2025.08.14 |