OpenCV 얼굴 각도 - OpenCV eolgul gagdo

OpenCV 얼굴 각도 - OpenCV eolgul gagdo

파이썬 OpenCV 얼굴, 눈 인식 함수(CascadeClassifier)

이번 포스트에서는 OpenCV에서 제공하는 CascadeClassifier 함수를 사용해서 얼굴과 눈을 인식해서 출력해보겠습니다. 흔히 구글 지도나 네이버 지도 거리뷰를 보면 사람들 얼굴이 모자이크 처리가 된것을 볼 수 있습니다. 하나 하나 모자이크를 한다면 얼마나 힘들겠습니까.... 

그래서 위와 같은 함수를 사용해서 사람 얼굴을 인식하고 모자이크 처리 해주는 것입니다. 

이번 포스트에서는 사람 얼굴 부분과 눈 부분을 인식하는 방법에 대해 알아보고 다음 장에서 인식한 부분을 모자이크 처리하는 방법에 대해 알아보겠습니다. 

CascadeClassifier는 Haar Cascades 라는 사람의 논문에 Face Detection 방법을 사용합니다. Face Detection이 어떤 로직으로 동작하는지는 저의 이해 범주 밖이므로 저는 예제 코드를 통해 어떻게 사용하는지에 대해 얘기하도록 하겠습니다. 

CascadeClassifier를 사용하기 위해서는 OpenCV에 내장된 아래의 DB를 이용해서 검출 합니다. 이번 시간에는 얼굴과 눈을 검색할 것이므로 haarcascade_frontalface_alt.xml 와 haarcascade_eye.xml를 불러 올것 입니다. 얼굴 이외의 다른 부분을 검출 하고 싶을 때는 아래 목록 중에 하나를 선택해서 구현 하면 됩니다. 

DB 리스트

haarcascade_eye_tree_eyeglasses.xml   

haarcascade_mcs_leftear.xml
haarcascade_eye.xml                   

haarcascade_mcs_lefteye.xml
haarcascade_frontalface_alt2.xml      

haarcascade_mcs_mouth.xml
haarcascade_frontalface_alt_tree.xml  

haarcascade_mcs_nose.xml
haarcascade_frontalface_alt.xml       

haarcascade_mcs_rightear.xml
haarcascade_frontalface_default.xml   

haarcascade_mcs_righteye.xml
haarcascade_fullbody.xml             

 haarcascade_mcs_upperbody.xml
haarcascade_lefteye_2splits.xml       

haarcascade_profileface.xml
haarcascade_lowerbody.xml             

haarcascade_righteye_2splits.xml
haarcascade_mcs_eyepair_big.xml       

haarcascade_smile.xml
haarcascade_mcs_eyepair_small.xml     

haarcascade_upperbody.xml

파이썬 OpenCV 얼굴, 눈 인식 함수(CascadeClassifier) 예제

코드>>

import cv2

cap = cv2.VideoCapture(0)
cap.set(cv2.CAP_PROP_FRAME_WIDTH,320)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT,240)    
if not cap.isOpened():    
    print("cap open failed")    
    exit()

face_xml = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
eye_xml = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_eye.xml')

while True:
    ret, img = cap.read()  
    if not ret:       
        print("Can't read cap")        
        break  

    img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    faces = face_xml.detectMultiScale(img_gray, 1.3, 5)
    for (x, y, w, h) in faces:
        cv2.rectangle(img, (x, y), (x + w, y + h), (255, 0, 0), 2)

        roi_color = img[y:y + h, x:x + w]
        roi_gray = img_gray[y:y + h, x:x + w]
        
        eyes = eye_xml.detectMultiScale(roi_gray)
        for (ex, ey, ew, eh) in eyes:
            cv2.rectangle(roi_color, (ex, ey), (ex+ew, ey+eh), (0, 255, 0), 2)

   
    cv2.imshow("Face Recognition", img)

    if cv2.waitKey(1) == ord('q'):
        break

cv2.destroyAllWindows()

라인1~8번: 컴퓨터의 카메라를 구동 합니다.

10~11번 라인: 얼굴 인식, 눈 인식 관련 XML을 불러옵니다.

19번 라인: 이미지를 회색으로 변경합니다. 회색 변경 이유는 Haar의 Face recognition 시 얼굴을 인식하기에 흑백의 명도를 이용하는 것이 유리하기 때문입니다.

21번 라인: detectMultiScale 함수 입력값으로 이미지와 scale값 minNeighbors 값을 입력 합니다. scale 은 확대해서 검출하는 것이고 minNeighbors는 간단하게 높은 값을 쓸수록 검출은 어렵지만 높은 출력으로 구분 됩니다. return 값으로 인식된 face 좌표(Coordinate)를 넘겨 줍니다.

22~30번 라인: 얼굴이 인식된 좌표를 사각형으로 표기하고 각 얼굴안에 눈이 있으면 사각형으로 표기 합니다.

결과>>

OpenCV 얼굴 각도 - OpenCV eolgul gagdo

눈과 얼굴이 인식됨을 알 수 있습니다.

다음 장에서는 해당 얼굴을 모자이크(Mosaic) 하는 방법에 대해 알아보겠습니다.

원본 링크

Face Alignment for Face Recognition in Python within OpenCV

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

OpenCV 얼굴 각도 - OpenCV eolgul gagdo
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()

OpenCV 얼굴 각도 - OpenCV eolgul gagdo
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)

OpenCV 얼굴 각도 - OpenCV eolgul gagdo
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

OpenCV 얼굴 각도 - OpenCV eolgul gagdo
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개의 값을 포함한다. 아래 그림은 이들 값을 보여준다.

OpenCV 얼굴 각도 - OpenCV eolgul gagdo
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)

OpenCV 얼굴 각도 - OpenCV eolgul gagdo
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)

OpenCV 얼굴 각도 - OpenCV eolgul gagdo
Triangle

약간의 삼각법(Little trigonometry)

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

OpenCV 얼굴 각도 - OpenCV eolgul gagdo
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도에서 찾은 각도를 뺀 각도로 이미지를 회전시킨다. 아래 그림으로 왜 그런지 이해할 수 있을 것이다.

OpenCV 얼굴 각도 - OpenCV eolgul gagdo
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

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

OpenCV 얼굴 각도 - OpenCV eolgul gagdo
Rotate from scratch

Conclusion

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

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

Python library

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

OpenCV 얼굴 각도 - OpenCV eolgul gagdo
Face alignment in deepface

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



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

  • haarcascade_eye.xml 다운로드

  • haarcascade_frontalface_default.xml 다운로드

  • 샘플 사진 - angelina.jpg 다운로드

  • 소스코드 다운로드 - jupyter