반응형

원본 링크



Face Alignment for Face Recognition in Python within OpenCV

얼굴 정렬(face alignment)은 얼굴인식 파이프라인의 앞단계이다. 구글은 얼굴정렬이 구글의 얼굴인식 모델인 FaceNet의 정확도를 98.89%에서 99.63%로 증가시킨다고 발표했다. 이는 거의 1%의 정확도 향상이다. 파이프라인에서 더 빠른 단계인 얼굴탐지와 유사하게 쉽게 파이썬에서 OpenCV로 2차원 얼굴 정렬을 적용할 수 있다.


Face alignment



Face detectors

이 글에서는 얼굴 탐지를 위해 OpenCV의 haar cascade 방법을 사용한다. 이 방법은 adaboost 알고리즘에 기초한 매우 전통적인 방법이다. 비록 고전적이지만 잘 동작한다.

문헌에는 더 많은 현대적인 접근 방법이 있다. OpenCV는 haar cascade와 SSD(Single Shot Multibox Detector)Dlib는 HoG(Histogram of Oriented Gradients)와 CNN기반 MMOD(Max-Margin Object Detection)을 제공하고 마지막으로 MTCNN(Multi-Task Cascaded Convolutional Networks)은 얼굴탐지를 위한 일반적인 솔루션이다. 아래 비디오에서 이들 방법의 탐지성능을 볼 수 있다. SSD와 MTCNN이 Haar와 Dlib HoG보다 더 강력해 보인다.


SSD는 초당 9.20프레임을 처리할 수 있는 반면 Haar Cascade는 6.50프레임, Dlib Hog는 1.57프레임, MTCNNdms 1.54프레임이다. 즉, SSD가 가장 빠르다.

여기서 단지 몇줄을 코드로 서로다른 얼굴 탐지 백엔드를 사용하는 방법을 볼 수 있다.




Initializing environment

OpenCV는 haarcascade 설정에 대한 정확한 경로가 필요하다. 여기서는 정면 얼굴과 눈 탐지 모듈을 모두 사용한다. 여기서 사용한 설정 xml파일은 여기에서 찾을 수 있다.


import cv2
face_detector = cv2.CascadeClassifier("haarcascade_frontalface_default.xml")
eye_detector = cv2.CascadeClassifier("haarcascade_eye.xml")



Reading image

이 글에서는 다음의 안젤리나 졸리(Angelina Jolie)의 이미지로 작업한다.


img = cv2.imread("angelina.jpg")
img_raw = img.copy()


Base Image



Face detection

여기서는 탐지된 얼굴에만 초점을 맞추고 이외 영역은 무시한다.


faces = face_detector.detectMultiScale(img, 1.3, 5)
face_x, face_y, face_w, face_h = faces[0]

img = img[int(face_y):int(face_y+face_h), int(face_x):int(face_x+face_w)]
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)


Detected face



Eye detection

OpenCV는 눈(eye) 탐지를 제공한다. 이 기능은 흑백(gray version) 이미지를 필요로 한다.


eyes = eye_detector.detectMultiScale(gray_img)

index = 0
for (eye_x, eye_y, eye_w, eye_h) in eyes:
    if index == 0:
        eye_1 = (eye_x, eye_y, eye_w, eye_h)
    elif index == 1:
        eye_2 = (eye_x, eye_y, eye_w, eye_h)

    cv2.rectangle(img,(eye_x, eye_y),(eye_x+eye_w, eye_y+eye_h), color, 2)
    index = index + 1


Eye detection

첫번째와 두번째 눈 변수에 눈의 위치를 저장했다. 이제 왼쪽과 오른쪽 눈을 결정해야 한다. X 좌표의 위치가 튜플의 첫번째 아이템으로 저장되어 있다. 따라서 작은 값이 외쪽 눈이 된다.


if eye_1[0] < eye_2[0]:
    left_eye = eye_1
    right_eye = eye_2
else:
    left_eye = eye_2
    right_eye = eye_1



Coordinates of eye

좌측 상단이 OpenCV에서 (0, 0) 좌표이다. 탐지된 눈은 4개의 값을 포함한다. 아래 그림은 이들 값을 보여준다.


Eye coordinates\

탐지된 눈의 중심이 필요하다. 0 인덱스는 x를, 1 인덱스는 y를, 2 인덱스는 w를, 3 인덱스는 h를 나타낸다. 두 눈의 중심간에 선을 그린다.


left_eye_center = (int(left_eye[0] + (left_eye[2] / 2)), int(left_eye[1] + (left_eye[3] / 2)))
left_eye_x = left_eye_center[0]
left_eye_y = left_eye_center[1]

right_eye_center = (int(right_eye[0] + (right_eye[2] / 2)), int(right_eye[1] + (right_eye[3] / 2)))
right_eye_x = right_eye_center[0]
right_eye_y = right_eye_center[1]

cv2.circle(img, left_eye_center, 2, (255, 0, 0), 2)
cv2.circle(img, right_eye_center, 2, (255, 0, 0), 2)
cv2.line(img, right_eye_center, left_eye_center, (67, 67,67), 2)


Line satisfying center of eyes

중심을 이은선과 수평선 사이의 정확한 각도가 필요하다. 왜냐하면 이 각도로 이미지를 회전시키기 때문이다. 아래 그림에서 왼쪽 눈이 오른쪽 눈보다 위에 있다. 그렇기 때문에 반시계 방향으로 이미지를 회전시킨다.


if left_eye_y < right_eye_y:
    point_3rd = (right_eye_x, left_eye_y)
    direction = -1 #rotate same direction to clock
    print("rotate to clock direction")
else:
    point_3rd = (left_eye_x, right_eye_y)
    direction = 1 #rotate inverse direction of clock
    print("rotate to inverse clock direction")

cv2.circle(img, point_3rd, 2, (255, 0, 0) , 2)

cv2.line(img,right_eye_center, left_eye_center,(67,67,67),2)
cv2.line(img,left_eye_center, point_3rd,(67,67,67),2)
cv2.line(img,right_eye_center, point_3rd,(67,67,67),2)


Triangle



약간의 삼각법(Little trigonometry)

유클리드 거리(euclidean distance)로 삼각형의 3변의 길이를 구할 수 있다.


Cosine rule

def euclidean_distance(a, b):
    x1 = a[0]
    y1 = a[1]
    x2 = b[0]
    y2 = b[1]

    return math.sqrt(((x2 - x1) * (x2 - x1)) + ((y2 - y1) * (y2 - y1)))

a = euclidean_distance(left_eye_center, point_3rd)
b = euclidean_distance(right_eye_center, left_eye_center)
c = euclidean_distance(right_eye_center, point_3rd)

또한 코사인 법칙(cosine rule)은 아래식으로 표현할 수도 있다.

$\cos(A) = (b^2 + c^2 - a^2) \div (2bc)$

이미 삼각형의 변의 길이를 계산했기 때문에 코사인 법칙을 적용할 수 있다. 코사인 값에서 각도를 찾으려면 역삼각함수를 호출해야 한다. 운좋게도 numpy가 이를 수행한다. 그러나 arc-cos 함수는 라디안(radian) 단위로 각도를 반환한다. 따라서 180 / pi를 곱하여 도(degree)로 변환한다.


cos_a = (b*b + c*c - a*a)/(2*b*c)
print("cos(a) = ", cos_a)

angle = np.arccos(cos_a)
print("angle: ", angle," in radian")

angle = (angle * 180) / math.pi
print("angle: ", angle," in degree")

우리는 직각 삼각형을 만들었다. 즉, 한쪽 각도가 90도이고 나머지 두각도의 합이 90이다. 만약 시계방향으로 이미지를 회전하려면 90도에서 찾은 각도를 뺀 각도로 이미지를 회전시킨다. 아래 그림으로 왜 그런지 이해할 수 있을 것이다.


Rotating to clock direction

if direction == -1:
    angle = 90 - angle

이제 회전시킬 각도를 알았다.


from PIL import Image
new_img = Image.fromarray(img_raw)
new_img = np.array(new_img.rotate(direction * angle))



Testing

이 글에서 설명한 로직을 아래 그림으로 테스트하면 결과는 매우 만족스러울 것이다.


Rotate from scratch



Conclusion

기초부터 OpenCV를 포함한 파이썬으로 얼굴을 정렬하는 법을 알아보았다. 이 작업을 위해 약간의 삼각법을 사용했다. 눈이 수평일때 까지 1도 회전까지 할 수 있지만 이는 솔루션을 복잡도를 증가시킬 것이다. 반면 이러한 접극은 얼굴을 정렬하기 위해 비레적인 시간(linear time)을 제공한다.

이 글에서 사용된 소스코드는 여기에서 찾을 수 있다.



Python library

여기서 deepface는 얼굴인식과 나이, 성별, 인종 그리고 감정 같은 인구 통계학(demography)을 포함하는 가벼운 얼굴 분석 프레임워크이다. 또한 한줄의 코드로 얼굴 탐지와 정렬을 제공한다. 이것은 완전 오픈소스이고 PyPI에서 사용가능하다.


Face alignment in deepface

아래는 실습 튜토리얼 비디오이다.





아래는 별도로 소스 확인을 위해 실행한 내용이다.

반응형

+ Recent posts