반응형

원본 링크

** 원문의 소스에서 deepface.commons.functions는 사용할 수 없어 소스를 수정하여 반영하였다. 수정된 소스는 이글 맨 마지막에 추가하였다.



A Gentle Introduction to Face Recognition in Deep Learning

현대의 얼굴 인식 파이프라인은 보통 '탐지(detection)', '정렬(alignment)', '표현(representation)', '검증(verification)'의 4단계로 구성된다. 이것은 처음접하는 사람을 혼란스럽게 할 수 있다. 이 글에서는 되돌아가서 개념적으로 얼굴 인식 파이프라인(pipeline)을 다룬다.


[Nosedine](https://www.imdb.com/title/tt5497778/) in Black Mirror



Vlog

다음 비디오는 파이썬으로 설계부터 직접 얼굴인식 워크샵을 다룬다.




DeepFace

이 글에서는 deepface 프레임워크를 사용한다. 아래 명령으로 간단히 설치할 수 있다.


pip install deepface



Stage 1 and 2: Detection and Alignment

몇가지 얼굴 탐지 솔루션이 있다. OpenCV는 haar cascade와 SSD(Single Shot Multibox Detector)를 지원한다. Dlib는 HOG(Histogram of Oriented Gradients)와 MMOD(Max-Margin Object Detection) 기반 CNN을 지원한다. 마지막으로 MTCNN(Multi-task Cascaded Convolutional Networks)은 얼굴 탐지를 위한 일반적인 솔루션이다.


여기서 파이썬에서 다른 얼굴탐지기를 어떻게 사용하는지를 볼 수 있다.


Alignment는 얼굴과 눈이 이미 탐지되었다면 쉽다. 실험은 얼굴 정렬(face alignment)을 적용하는 것이 1%이상 모델의 정확도를 증가시킨다는 것을 보여준다. 불행하게도 opencv나 dlib 모두 바로 사용할 수 있는 함수로 얼굴 정렬을 제공하지 않는다. 여기서 얼굴을 정렬하기 위해 몇가지 삼각법을 사용해야 한다.


Detect and align

여기서 딥페이스(deepface)는 함수로 얼굴탐지와 얼굴 정렬 모두를 제공한다. 딥페이스는 opencv의 haar cascade, SSD, dlib HoG와 MTCNN을 포함한다. 또한 얼굴을 정렬하기 위해 몇가지 수학과 삼각법을 할 수 있다. 여러분은 단지 이미지의 경로를 전달하면 된다. 만약 'detector_bacend' 인자를 사용하지 않으면 기본 설정인 opencv의 haar cascade를 사용한다.


from deepface import DeepFace
backends = ['opencv', 'ssd', 'dlib', 'mtcnn']
img1 = DeepFace.detectFace("img1.jpg", detector_backend = backends[3])
img2 = DeepFace.detectFace("img2.jpg", detector_backend = backends[3])



Stage 3: Representation

딥러능은 오직 이 표현 단계에서만 나타난다. 얼굴 이미지를 CNN(Convolutional Neural Networks) 모델로 제공하지만 여기서의 작업은 분류(classification)가 아니다. 여기서 CNN 모델은 autoencoder와 유사한 임베딩(embedding)을 찾기 위해 사용한다.


VGG-Face model

가장 유명한 얼굴 인식 모델은 VGG-Face, Google FaceNet, OpenFace 그리고 Facebook DeepFace이다. 운좋게도 이 모델들은 모두 딥페이스 프레임웍에서 지원하기 때문에 아래처럼 모델을 모델을 만들 수 있다.


from deepface.basemodels import VGGFace, OpenFace, Facenet, FbDeepFace
model = VGGFace.loadModel()
#model = Facenet.loadModel()
#model = OpenFace.loadModel()
#model = FbDeepFace.loadModel()

이 모델들은 다른 입력과 출력 모양을 갖는다. 예를 들어, VGG-Face는 (224, 224, 3) 모양으로 입력을 받고 2622차원 벡터로 출력한다. 반면 Google FaceNet은 (160, 160, 3)으로 입력하여 128차원 배열로 출력한다. 우리는 탐지와 정렬 단계에서 detectFace함수에 입력 모양을 전달해야 한다는 것을 기억하자. 아래와 같이 만들어진 모델에 필요한 입력 모양을 얻을 ㅅ수 있다. 따라서 입력 모양을 검색한 후 detectFace 명령을 넣어야 한다.


input_shape = model.layers[0].input_shape[1:3]



Question: how those models trained?

이 얼굴인식 모델들은 이전에 대규모 데이터세셍서 얼굴 이미지의 신분을 구분하기 위해 만들어졌다. 1000명의 1M 이미지를 포함하는 데이터셋이 있다고 해보자. CNN 모델의 출력 레이어는 이런경우 1000이 되고 모델은 제공된 이미지의 신분을 찾기 위해 훈련된다. 훈련이 끝나면 출력 레이가 버려지고(drop) 출력의 앞쪽 레이어가 새로운 출력 에리어가 된다. 이제 새로운 모델은 신분을 분류하지 못하지만 얼굴의 표현(representation: 얼굴 이미지의 벡터 제목 추출 - extracting vector heading of face images, 첫번째 비디오 내용중에 나옴.)을 반환한다. 이제 훈련 데이터셋에 없는 새로운 이미지를 제공할 수 있고 모델은 여전히 표현을 찾는다.

마지막 레이어에서 점선은 Facebook DeppFace 아키텍쳐에서 정확하게 이를 의미한다.


Facebook DeepFace architecture



Representations

지금까지 얼굴 이미지를 탐지하고 정렬하여 얼굴 인식 모델에 전달했다. 이제는 각 이미지에 대한 벡터 표현(vector representation)을 갖는다. 이것은 추상적인 개념으로 이를 견고하게 하기 위해 시각화하여 설명할 것이다.

벡터 스스로를 추가하여 1차원 벡터를 2차원 행렬로 변환한다. 이런 방법으로 행렬의 각 열(line)은 동일한 정보를 갖게 된다.


img1_graph = []; img2_graph = []

for i in range(0, 200):
    img1_graph.append(img1_representation)
    img2_graph.append(img2_representation)

img1_graph = np.array(img1_graph)
img2_graph = np.array(img2_graph)

이것은 바코드와 유사하다. 바코드는 오로지 수평으로만 데이터를 저장한다. 바코드가 수평으로 손상되어도 여전히 읽을 수 있다. 그러나 수직으로 손상되면 데이터를 잃는다.


Barcode

아래 코드로 이 표현을 시각화해 보자.


fig = plt.figure()

ax1 = fig.add_subplot(3,2,1)
plt.imshow(img1[0][:,:,::-1])
plt.axis('off')

ax2 = fig.add_subplot(3,2,2)
im = plt.imshow(img1_graph, interpolation='nearest', cmap=plt.cm.ocean)
plt.colorbar()

ax3 = fig.add_subplot(3,2,3)
plt.imshow(img2[0][:,:,::-1])
plt.axis('off')

ax4 = fig.add_subplot(3,2,4)
im = plt.imshow(img2_graph, interpolation='nearest', cmap=plt.cm.ocean)
plt.colorbar()

plt.show()

VGG-Face 표현은 수평으로 2622슬롯(slot)이다. 각 슬롯은 다른 색상으로 표현되고 색상의 의미는 오른쪽 컬러바(colorbar)에서 표시된다.


VGG-Face representation

얼굴인식 모델로 Google FaceNet을 설정했다면, 표현은 다른 모양와 컨텐츠로 나타난다. FaceNet은 128차원이다.


Google FaceNet representation

이제 얼굴 이미지 자체가 이닌 이 벡터 표현으로 이 두이미지가 동일 인물인지 아닌지를 결정해 보자.



Question: which single face recognition model is the best

얼굴의 표현을 찾기 위해 VGG-Face, FaceNet, OpenFace, DeepFace를 사용할 수 있다. 이 모두 최신 얼굴 인식 모델이다. 몇몇은 Google과 Facebook같이 기술 거인들에 의해 설계되었지만 몇몇은 University of Oxford 또는 Carnegie Mellon University 같은 상위 대학에서 설계되었다. 그러면 어떤 모델이 더 나을까? 다음은 이에 대한 짧은 비디오이다.




Stage 3: Verification

이번에는 이미지의 벡터 표현을 비교한다. 두 벡터를 비교하는 가장 쉬운 방법은 유클리드 거리(euclidean distance)를 구하는 것이다. 이미 고등학교때 피타고라스 정리로 이것을 기억하고 있다. 어떻게 되든 이것은 2차원 방정식이다. 여기서는 표현으로 n차원 벡터를 사용한다.


Euclidean distance from [dataaspirant](https://dataaspirant.com/2016/12/30/k-nearest-neighbor-implementation-scikit-learn/)

n차원 공간에 피타고라스 정리를 적용하려면 표현에서 각 슬롯의 차이에 제곱을 알아야 한다. 이 새로운 벡터는 거리벡터(distance vector)를 나타낸다. 따라서 각 슬롭의 합에 대한 제곱근이 거리가 된다.


distance_vector = np.square(img1_representation - img2_representation)
distance = np.sqrt(distance_vector.sum())

이 벡터 거리를 시각화해보자.


distance_graph = []
for i in range(0, 200):
    distance_graph.append(distance_vector)
    distance_graph = np.array(distance_graph)

ax6 = fig.add_subplot(3,2,6)
im = plt.imshow(distance_graph, interpolation='nearest', cmap=plt.cm.ocean)
plt.colorbar()

거리벡터는 3번째줄에서 나타난다. 보이는 것과 같이 거리벡터의 슬롯은 대부분 0에 가까운 값을 나타내는 녹색이다.


Ture positive example

다른 사진의 쌍을 보자.


False positive example



Decision

만약 동일 이미지를 제공하면 거리가 0이 된다는 것을 알았다. 왜냐하면 표현이 같고 각 슬롯의 거리가 0이기 때문이다.

게다가 동일 인물의 이미지를 제공할때 거리값이 더 작다는 것을 보았다. 다른 사람의 이미지를 제공하면 거리는 증가한다. 여기서 임계치(threshold)보다 작은 거리 값을 점검한다.



Threshold

그러나 동일인물로 이미지 쌍을 구분하기 위해 충분한 거리를 결정하기 위한 임계치는 무엇인가?

이는 매우 깊은 주제이다. 얼굴인식 파이프라인에서 임계치에 대한 결정에 관한 깊고 자세한 설명은 여기에서 찾을 수 있다. 게다가 이후 vlog는 얼굴인식 파이프라인에서 어떻게 임계치를 세부튜닝하는지도 포함한다.


요약해보면, VGG-Face 모델에 대한 유클리안 거리값은 아래처럼 보일 것이다.


if distance = 0.55:
    return True
else:
    return False

임계치는 다른 얼굴 인식 모델에 따라 달라야한다. (필자의) 실험에서는 임계치가 아래처럼 튜닝되었다.


def findThreshold(model_name):
    threshold = 0
    if model_name == 'VGG-Face':
        threshold = 0.55
    elif model_name == 'OpenFace':
        threshold = 0.55
    elif model_name == 'Facenet':
        threshold = 10
    elif model_name == 'DeepFace':
        threshold = 64
    return threshold

또한 벡터 비교를 위해 코사인 유사도(cosine similarity)도 있다. 코사인 값에 대한 임계치는 여기에서 볼 수 있다.



Testing

다음 비디오에서 실시간으로 Facebook DeepFace 모델을 적용하였다. 결과는 정확도와 속도 모두 만적스럽다.




Namesakes(이름이 같은 사람)

보이는 것과 같이 얼굴인식은 주로 2개의 이미지를 비교하는 것을 기본으로 한다. 신분을 나타내는 사진 여러장으로 CNN 모델을 훈련하지 않는다. 단지 이미지를 전달한다. 그렇기 때문에 이 개념 또한 one shot learning으로 불린다. 게다가 몇몇 리소스는 이 기술을 얼굴 인식(face recognition) 대신 얼굴 검증(face verification)으로 부른다.



DeepFace itself

DeepFace는 백그라운드에서 이 글에서 언급한 모든 파이프라인 단계를 다루기 때문에 단지 몇줄의 코드로 얼굴인식 테스트를 적용할 수 있다. 우리는 단지 얼굴인식 시스템을 이해하기 위해 파이프라인 단계에 집중했을 뿐이다.




대규모 얼굴인식(Large scale face recognition)

이 글에서는 실제 어떻게 얼굴검증을 적용하는가를 언급했다. 얼굴검증은 O표기법으로 O(1)복잡도를 갖는다. 얼굴 인식은 데이터셋에서 얼구을 찾아야 한다. 이는 O표기법으로 O(n) 복잡도가 된다. 여기서 n은 데이터셋내 인스턴스의 수이다.

hacking method에서 대규모 얼굴인식 속도를 극적으로 높이는 방법을 볼 수 있다.


얼굴인식이 O(n) 복잡도를 갖고 이것은 수백만 또는 수십억 수준의 데이터에서 문제일 수 있다는 것을 기억하자. 여기서 a-nn(approximate nearest neighbor) 알고지즘은 시간 복잡도를 극적으로 줄여준다. Spotify Annoy, Facebook Faiss 그리고 NMSLIB는 놀라운 a-nn 라이브러리이다. Elasticsearch는 NMSLIB를 포함하여 높은 확장성이 있다. 실제 대규모 데이터베이스라면 이 a-nn 라이버리로 deepface를 실행해야 한다.




실시간(real time) 얼굴 인식




앙상블(ensemble method)

지금까지는 단지 얼굴인신 모델만을 설명했다. : VGG-Face, Google FaceNet, OpenFace, Facebook DeepFace and DeepID 같은 여러가지 최신 모델이 있다. 이 모델 전부가 잘 동작하지만 절대적으로 더 나은 모델은 없다. 여전히 그랜드마스터(grandmaster) 모델을 만들기 위해 앙상블을 적용할 수 있다. 이러한 접근에서는 얼굴인식 모델의 예측을 boosting 모델로 제공한다. 정밀도(precision), 재현률(recall) 그리고 f1 점수를 포함하는 정확도 지표(accuracy metrics)는 앙상블에서 극적으로 증가하지만 실행시간이 더 길어진다.


** GitHub에 여기에서 사용한 소스코드가 있다.




수정한 소스는 아래와 같다.


from deepface import DeepFace
from deepface.basemodels import VGGFace, OpenFace, Facenet, FbDeepFace

import matplotlib.pyplot as plt
import numpy as np

model = VGGFace.loadModel()
#model = Facenet.loadModel()
#model = OpenFace.loadModel()
#model = FbDeepFace.loadModel()

input_size = model.layers[0].input_shape[1:3]

backends = ['opencv', 'ssd', 'dlib', 'mtcnn']

img1 = DeepFace.detectFace("img1.jpg", detector_backend = backends[3])
img2 = DeepFace.detectFace("img2.jpg", detector_backend = backends[3])

img1 = np.expand_dims(img1, axis=0)
img2 = np.expand_dims(img2, axis=0)
img1_representation = model.predict(img1)[0,:]
img2_representation = model.predict(img2)[0,:]

distance_vector = np.square(img1_representation - img2_representation)
distance = np.sqrt(distance_vector.sum())
print("Euclidean distance : ", distance)

img1_graph = [] 
img2_graph = [] 
distance_graph = []

for i in range(0, 200):
    img1_graph.append(img1_representation)
    img2_graph.append(img2_representation)
    distance_graph.append(distance_vector)

img1_graph = np.array(img1_graph)
img2_graph = np.array(img2_graph)
distance_graph = np.array(distance_graph)

fig = plt.figure()

ax1 = fig.add_subplot(3,2,1)
#plt.imshow(img1[0][:,:,::-1]) # 이건 BGR일 경우, RGB로 바꿀때 사용한다.
plt.imshow(img1[0])
plt.axis('off')

ax2 = fig.add_subplot(3,2,2)
im = plt.imshow(img1_graph, interpolation='nearest', cmap=plt.cm.ocean)
plt.colorbar()

ax3 = fig.add_subplot(3,2,3)
#plt.imshow(img2[0][:,:,::-1]) # 이건 BGR일 경우, RGB로 바꿀때 사용한다.
plt.imshow(img2[0])
plt.axis('off')

ax4 = fig.add_subplot(3,2,4)
im = plt.imshow(img2_graph, interpolation='nearest', cmap=plt.cm.ocean)
plt.colorbar()

ax5 = fig.add_subplot(3,2,5)
plt.text(0.35, 0, "Distance: %s" % (distance))
plt.axis('off')

ax6 = fig.add_subplot(3,2,6)
im = plt.imshow(distance_graph, interpolation='nearest', cmap=plt.cm.ocean)
plt.colorbar()

plt.show()

반응형

+ Recent posts