iBetter Books
수정

랜드마크를 뽑는 가장 큰 이유는 정렬입니다. 정렬은 얼굴을 항상 비슷한 자세와 크기로 맞춰, 인식 모델이 같은 사람을 같은 사람으로 더 잘 알아보게 만듭니다. 이 장에서는 정렬의 원리를 코드로 구현하고, 정렬을 했을 때와 안 했을 때 인식이 어떻게 달라지는지 직접 비교합니다.

정렬이 정확도를 끌어올리는 이유

얼굴 인식 모델은 얼굴 이미지를 숫자 벡터(임베딩, PART 04에서 자세히)로 바꾼 뒤, 두 벡터가 비슷하면 같은 사람으로 판단합니다. 그런데 같은 사람이라도 고개가 기울어져 있으면 픽셀 배치가 달라져 벡터도 달라집니다. 정렬은 이 변동을 미리 제거합니다. 두 눈을 수평으로 맞추고 얼굴 크기를 일정하게 만들면, 모델은 자세 차이가 아니라 사람 고유의 특징에만 집중할 수 있습니다.

비유하자면, 증명사진을 찍을 때 "정면을 보고 턱을 당기세요"라고 안내하는 것과 같습니다. 같은 규격으로 맞춰 두면 비교가 훨씬 쉬워집니다.

두 눈을 기준으로 회전시키기

정렬의 핵심은 두 눈을 잇는 선을 수평으로 만드는 것입니다. 두 눈 좌표만 있으면 기울어진 각도를 계산해 그만큼 이미지를 회전시킬 수 있습니다. 68점에서 왼쪽 눈(36~41)과 오른쪽 눈(42~47)의 중심을 구해 각도를 재고 회전합니다.

# 파일: align_manual.py"""두 눈 좌표로 각도를 구해 얼굴을 수평으로 회전시킨다."""import cv2import dlibimport numpy as npdetector = dlib.get_frontal_face_detector()predictor = dlib.shape_predictor("shape_predictor_68_face_landmarks.dat")img = cv2.imread("sample.jpg")gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)rect = detector(gray, 1)[0]shape = predictor(gray, rect)pts = np.array([[shape.part(i).x, shape.part(i).y] for i in range(68)])left_eye = pts[36:42].mean(axis=0)    # 왼쪽 눈 중심right_eye = pts[42:48].mean(axis=0)   # 오른쪽 눈 중심# 두 눈을 잇는 선의 기울기(각도)dy = right_eye[1] - left_eye[1]dx = right_eye[0] - left_eye[0]angle = np.degrees(np.arctan2(dy, dx))# 두 눈의 중점을 회전 중심으로 그 각도만큼 회전center = tuple(((left_eye + right_eye) / 2).astype(int))M = cv2.getRotationMatrix2D(center, angle, 1.0)aligned = cv2.warpAffine(img, M, (img.shape[1], img.shape[0]))cv2.imwrite("aligned_result.jpg", aligned)

np.arctan2로 두 눈 선의 각도를 구하고, getRotationMatrix2DwarpAffine으로 그 각도만큼 이미지를 돌립니다. 결과는 두 눈이 수평이 된 얼굴입니다.

더 간단한 길 — dlib의 get_face_chip

위 과정을 직접 짜 봤지만, 실무에서는 dlib이 제공하는 정렬 함수를 쓰면 회전·크기 정규화·잘라내기를 한 번에 끝낼 수 있습니다.

# 파일: align_chip.py (위 shape를 구한 뒤)import dlibrgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)# 정렬 + 150x150으로 정규화된 얼굴 칩을 바로 얻는다chip = dlib.get_face_chip(rgb, shape, size=150, padding=0.25)chip_bgr = cv2.cvtColor(chip, cv2.COLOR_RGB2BGR)cv2.imwrite("chip_result.jpg", chip_bgr)

정렬 전(왼쪽) vs 정렬 후(오른쪽)

get_face_chip은 68점 shape를 받아 정렬과 크기 정규화를 한 번에 처리해, 인식 모델에 바로 넣기 좋은 정사각형 얼굴을 돌려줍니다. 대부분의 인식 라이브러리(PART 05~07)는 내부에서 이런 정렬을 자동으로 수행합니다.

정렬 전후, 무엇이 달라지나

정렬의 효과는 기울어진 얼굴에서 두드러집니다. 같은 두 얼굴을 비교할 때, 정렬 전에는 자세 차이 때문에 임베딩 거리가 멀어 "다른 사람"으로 오판할 수 있지만, 정렬 후에는 자세가 통일되어 같은 사람으로 더 안정적으로 묶입니다.

상황 정렬 전 정렬 후
정면 얼굴 큰 차이 없음 큰 차이 없음
기울어진 얼굴 임베딩 흔들림, 오판 가능 안정적, 정확도 상승
다양한 각도 데이터셋 전반적으로 불안정 전반적으로 향상

즉 정면 위주의 깔끔한 데이터라면 정렬의 이득이 작지만, 실제 환경(다양한 각도·기울기)에서는 정렬이 인식 정확도를 떠받치는 토대가 됩니다.

실무 팁. PART 05~07의 인식 라이브러리는 대부분 정렬을 내부에서 자동으로 합니다. 그래서 직접 정렬 코드를 매번 짤 필요는 없습니다. 다만 "왜 정렬이 필요한가"를 이해하고 있으면, 인식 정확도가 낮을 때 "혹시 정렬이 안 된 입력을 넣고 있나?"를 점검할 수 있습니다. 원리를 알면 디버깅이 쉬워집니다.

이 장에서 기억할 것

정렬은 두 눈을 수평으로 맞추고 얼굴 크기를 정규화해, 인식 모델이 자세 차이가 아닌 사람 고유 특징에 집중하게 합니다. 두 눈 좌표로 각도를 구해 warpAffine으로 직접 회전시킬 수도 있고, dlib의 get_face_chip으로 한 번에 처리할 수도 있습니다. 정면 데이터에서는 이득이 작지만 다양한 각도가 섞인 실전에서는 정확도의 토대가 됩니다. 다음 장에서는 랜드마크를 정렬 외의 응용, 곧 깜빡임·졸음·머리 자세 추정에 써 봅니다.