케라스의 FaceNet을 이용한 얼굴 인식 시스템 개발 방법

01 Oct 2019

원글 Jason Brownlee
번역 JKIsaacLee
최근 수정일 2019년 10월 2일

얼굴 인식은 얼굴 사진을 기반으로 사람을 식별하고 확인하는 컴퓨터 비전 작업입니다.

FaceNet은 2015년 Google 연구원이 개발한 얼굴 인식 시스템으로, 다양한 얼굴 인식 벤치마크 데이터셋에서 최첨단 결과를 달성했습니다. FaceNet 시스템은 모델의 여러 제3자 오픈 소스 구현물 또는 사전 훈련된 모델의 가용성 덕분에 광범위하게 사용됩니다.

FaceNet 시스템을 사용하여 얼굴 임베딩이라고 하는 얼굴에서 고품질의 특성을 추출한 다음 얼굴 식별 시스템을 훈련하는 데 사용 가능합니다.

이 실습에서는 FaceNet 및 SVM 분류기를 사용하여 사진에서 사람을 식별하는 얼굴 감지 시스템을 개발하는 방법에 대해 알아봅니다.

실습을 모두 수행하면, 당신은:

  • Google이 개발한 FaceNet 얼굴 인식 시스템과 오픈 소스 구현물 및 사전 훈련된 모델에 대해 알 수 있습니다.
  • 먼저 얼굴 감지 시스템을 통해 얼굴을 추출한 다음 얼굴 임베딩을 통해 얼굴 특성을 추출하는 것을 포함한 얼굴 감지 데이터셋을 준비하는 방법에 대해 알 수 있습니다.
  • 얼굴 임베딩에서 정체성을 예측하기 위해 SVM 모델을 적합시키고, 평가 및 시연하는 방법에 대해 알 수 있습니다.

30가지 단계별 자습서와 전체 소스 코드를 사용하여 새로운 컴퓨터 비전 책에서 사진 분류, 물체 감지, 얼굴 인식 등에 대한 모델을 구축하는 방법을 알아보십시오.

시작해 봅시다.

How to Develop a Face Recognition System Using FaceNet in Keras and an SVM Classifier Photo by Peter Valverde, some rights reserved.

실습 요약

이 실습은 다섯 개의 항목으로 나뉩니다. 각 항목은 다음으로 구성되어 있습니다:

  1. 얼굴 인식
  2. FaceNet 모델
  3. FaceNet 모델을 케라스에서 불러오는 방법
  4. 얼굴 인식으로 얼굴을 감지하는 방법
  5. 얼굴 분류 시스템을 개발하는 방법

얼굴 인식

얼굴 인식은 얼굴 사진에서 사람들을 식별하고 확인하는 일반적인 작업입니다.

얼굴 인식 안내서“라는 2011년 얼굴 인식에 관한 책은 다음의 두 가지 주요 얼굴 인식 과정을 소개합니다:

  • 얼굴 검사. 주어진 얼굴을 알려진 정체성에 대한 일대일 매핑 (예를 들어, 사람인가?).
  • 얼굴 식별. 주어진 얼굴을 알려진 얼굴형의 데이터베이스에 대한 일대다 매핑 (예를 들어, 이 사람은 누구인가?).

얼굴 인식 시스템은 이미지 및 비디오에 존재하는 얼굴을 자동으로 식별할 것으로 예상됩니다. 두 가지 모드 (1)얼굴 검사(또는 확인), 그리고 (2)얼굴 식별(또는 인지) 중 하나 혹은 둘 다에서 동작할 수 있습니다.

이 실습에서는 얼굴 식별 작업에 중점을 둘 것입니다.

FaceNet 모델

FaceNet은 플로리안 스크로프와 Google에서 2015년 “FaceNet: 얼굴 인식 및 클러스터링용 통합 임베딩.” 논문을 통해 제시한 얼굴 인식 시스템입니다.

얼굴 그림이 주어지면 얼굴에서 고품질의 특성을 추출하여 이러한 특성을 얼굴 임베딩이라고 하는 128개의 요소 벡터로 표현하는 방식을 통해 예측하는 시스템입니다.

FaceNet은 얼굴 이미지에서 얼굴 유사성 척도와 얼마나 직접적으로 일치하는지 거리를 측정하는 밀집 유클리드 공간으로의 매핑을 직접 학습합니다.

이 모델은 세 개의 손실 함수를 통해 훈련된 심층 합성곱 신경망으로, 같은 정체성에 대한 벡터가 더 비슷(더 가까운 거리)해지고, 다른 정체성에 대한 벡터는 덜 비슷(더 먼 거리)해질 것입니다. 모델의 중간 층에서 임베딩을 추출하는 대신 임베딩을 직접 생성하기 위해 모델을 훈련하는 데 중점을 두는 것이 이 작업의 주요 혁신이었습니다.

이 방법은 이전의 딥러닝 접근 방식에서와 같이 중간 병목 층이 아니라 임베딩 자체를 직접 최적화하도록 훈련된 심층 합성곱 신경망을 사용합니다.

그런 다음 이러한 얼굴 임베딩을 표준 얼굴 인식 벤치마크 데이터셋에 대한 분류기 시스템 훈련 기반으로 사용하여 최근의 결과를 얻었습니다.

Google 시스템은 이전의 최고 실적과 비교하여 오차 비율을 30% 정도 줄였습니다 …

이 논문은 또한 추출된 특성을 기반으로 같은 얼굴을 그룹화하기 위한 클러스터링과 같은 임베딩의 다른 용도도 살펴봅니다.

이는 강력하고 효과적인 얼굴 인식 시스템이며 추출된 얼굴 임베딩의 일반적인 특성은 다양한 응용 분야에 대한 접근 방식을 제공합니다.

FaceNet 모델을 케라스에서 불러오는 방법

FaceNet 기반 모델을 훈련하고 사전 훈련된 모델을 사용하기 위한 도구를 제공하는 많은 프로젝트가 있습니다.

아마도 가장 유명한 것은 PyTorch 딥러닝 프레임워크를 사용하여 구현 및 훈련된 FaceNet 모델을 제공하는 OpenFace라는 프로젝트입니다. Keras OpenFace라는 케라스용 OpenFace 포트가 있지만 글 작성 시점에서의 모델은 파이썬 2가 필요한 것으로 보이며 상당히 제한적입니다.

또 다른 유명한 프로젝트는 텐서플로우로 구현하고 훈련한 FaceNet 모델을 제공하는 데이비드 샌드버그의 FaceNet입니다. 작성 당시 라이브러리 기반 설치나 정돈된 API를 제공하지는 않지만 프로젝트는 뛰어나 보입니다. 유용하게도 데이비드의 프로젝트는 여러 가지 사전 훈련된 FaceNet 모델을 제공하며 케라스에서 사용하기 위해 이러한 모델을 이식하거나 변환하는 많은 프로젝트가 있습니다.

주목할 만한 예시로는 케라스의 히로키 타니아이의 FaceNet입니다. 그의 프로젝트는 Inception ResNet v1 모델을 텐서플로에서 케라스로 변환하기 위한 스크립트를 제공합니다. 또한 그는 사용 준비가 된 사전 훈련된 케라스 모델을 제공합니다.

이번 실습에서는 히로키 타니아이가 제공한 사전 훈련된 케라스 FaceNet을 사용할 것입니다. 1M 크기의 MS-Celeb 데이터셋으로 훈련받았으며 입력 이미지가 컬러가 되고 픽셀 값을 흑백으로 하며(세 채널 모두에 대해 표준화됨) 160×160 픽셀의 정사각형 모양이 될 것으로 예상합니다.

여기에서 모델을 다운로드할 수 있습니다:

모델 파일을 다운로드하고 현재 작업 디렉토리에 파일명을 ‘facenet_keras.h5‘으로 하세요.

케라스의 load_model() 함수를 사용하여 직접 모델을 로드할 수 있습니다; 예를 들어:

# 실습 시작 전 필수 설치 항목
!pip3 install keras tensorflow==1.15.0rc1 mtcnn Pillow numpy opencv-python matplotlib sklearn
# 케라스 FaceNet 모델 로드 예시
from keras.models import load_model
# 모델 불러오기
model = load_model('facenet_keras.h5')
# 입력과 출력 배열 형태 요약
print(model.inputs)
print(model.outputs)

예제를 실행하면 모델을 가져오고 입력 및 출력 텐서 모델의 형태가 출력됩니다.

모델은 실제로 160x160 형태의 입력으로 정사각형 컬러 이미지를 예상하고 128개의 요소 벡터로 포함 된 얼굴을 출력함을 알 수 있습니다.

출력 예시:

[<tf.Tensor 'input_1:0' shape=(?, 160, 160, 3) dtype=float32>]
[<tf.Tensor 'Bottleneck_BatchNorm/cond/Merge:0' shape=(?, 128) dtype=float32>]

이제 FaceNet 모델을 가졌고, 이를 이용해 탐구해 볼 수 있습니다.

얼굴 인식으로 얼굴을 감지하는 방법

얼굴 인식을 실행하기 전에 우리는 얼굴을 감지해야 합니다.

얼굴 감지는 사진에서 얼굴을 자동으로 찾고 범위 주변에 경계 상자를 그려 특정화하는 과정입니다.

이 실습에서는, 얼굴 감지(예: 사진에서 얼굴을 찾고 추출하기)를 위해 멀티태스킹 합성곱 신경망(MTCNN)을 사용합니다. 이는 “멀티태스킹 합성곱 망을 이용한 얼굴 마디 감지 및 정렬.”이라는 2016년에 발행된 얼굴 인식을 위한 최첨단 딥러닝 모델 논문입니다.

우리는 ipazc/mtcnn project 내의 이반 데 파 센테노가 제공하는 구조를 사용할 것입니다. 다음과 같이 pip를 통해 설치할 수도 있습니다: (실습 시작 전 필수 설치 항목 참고)

라이브러리를 가져와 버전을 출력하여 라이브러리가 올바르게 설치되었는지 확인할 수 있습니다; 예를 들어:

# MTCNN이 올바르게 설치되었는지 확인
import mtcnn
# 버전 출력
print(mtcnn.__version__)

예제를 실행하면 현재 라이브러리의 버전이 출력됩니다.

출력 예시:

0.0.8

다음 섹션에서 mtcnn 라이브러리를 사용하여 얼굴 감지기를 만들고 FaceNet 얼굴 감지기 모델과 함께 사용할 얼굴을 추출할 수 있습니다.

첫 번째 단계는 PIL 라이브러리와 open() 함수를 사용하여 이미지를 넘파이 배열로 로드하는 것입니다. 또한 이미지에 알파 채널이 있거나 흑백인 경우를 대비하여 이미지를 RGB로 변환합니다.

# 파일로 이미지 불러오기
image = Image.open(filename)
# RGB로 변환, 필요시
image = image.convert('RGB')
# 배열로 변환
pixels = asarray(image)

다음으로, 불러온 사진에 있는 모든 얼굴들을 감지하기 위해 MTCNN 얼굴 감지기 클래스를 만들고 사용합니다.

# 감지기 생성, 기본 가중치 이용
detector = MTCNN()
# 이미지에서 얼굴 감지
results = detector.detect_faces(pixels)

결과는 경계 상자 리스트이며 각 경계 상자는 왼쪽 아래 모서리 위치와 너비 및 높이를 정의합니다.

실험을 위해 사진에 얼굴이 하나만 있다고 가정하면 다음과 같이 경계 상자의 픽셀 좌표를 결정할 수 있습니다. 때로는 라이브러리가 음의 픽셀 인덱스를 반환하며 이것이 버그라고 생각합니다. 좌표값에 절대값을 취하여 이 문제를 해결할 수 있습니다.

# 첫번째 얼굴에서 경계 상자 추출
x1, y1, width, height = results[0]['box']
# 버그 수정
x1, y1 = abs(x1), abs(y1)
x2, y2 = x1 + width, y1 + height

이 좌표값을 얼굴을 추출하는 데 사용할 수 있습니다.

# 얼굴 추출
face = pixels[y1:y2, x1:x2]

그런 다음 PIL 라이브러리를 사용하여 이 작은 얼굴 이미지의 크기를 원하는 크기로 조정할 수 있습니다. 특히, 모델은 얼굴을 160×160 형태의 사각형 입력을 예상합니다.

# 모델 사이즈로 픽셀 재조정
image = Image.fromarray(face)
image = image.resize((160, 160))
face_array = asarray(image)

모든 작업을 함께 묶어, extract_face() 함수는 파일에서 사진을 불러오고 추출된 얼굴을 반환합니다. 사진에 하나의 얼굴이 포함되어 있고 처음 감지된 얼굴을 반환한다고 가정합니다.

# mtcnn으로 얼굴을 감지하는 함수
from PIL import Image
from numpy import asarray
from mtcnn.mtcnn import MTCNN

# 주어진 사진에서 하나의 얼굴 추출
def extract_face(filename, required_size=(160, 160)):
	# 파일에서 이미지 불러오기
	image = Image.open(filename)
	# RGB로 변환, 필요시
	image = image.convert('RGB')
	# 배열로 변환
	pixels = asarray(image)
	# 감지기 생성, 기본 가중치 이용
	detector = MTCNN()
	# 이미지에서 얼굴 감지
	results = detector.detect_faces(pixels)
	# 첫 번째 얼굴에서 경계 상자 추출
	x1, y1, width, height = results[0]['boxes']
	# 버그 수정
	x1, y1 = abs(x1), abs(y1)
	x2, y2 = x1 + width, y1 + height
	# 얼굴 추출
	face = pixels[y1:y2, x1:x2]
	# 모델 사이즈로 픽셀 재조정
	image = Image.fromarray(face)
	image = image.resize(required_size)
	face_array = asarray(image)
	return face_array

# 사진을 불러오고 얼굴 추출
pixels = extract_face('...')

이 함수를 사용하여 FaceNet 모델에 입력으로 제공할 수 있는 다음 부분에서 필요하다면 얼굴을 추출할 수 있습니다.

얼굴 분류 시스템을 개발하는 방법

이 부분에서는 주어진 얼굴의 정체성을 예측하는 얼굴 감지 시스템을 개발할 것입니다.

이 모델은 5명의 유명인의 사진이 많이 포함된 5명의 유명인사 얼굴 데이터셋을 사용하여 훈련하고 테스트할 것입니다.

얼굴 감지에는 MTCNN 모델을 사용하고, FaceNet 모델을 사용하여 감지된 각 얼굴에 대한 얼굴 임베딩을 생성한 다음 지정된 얼굴의 정체성을 예측하는 선형 서포트 벡터 머신(SVM) 분류기 모델을 개발합니다.

5명의 유명인사 얼굴 데이터셋

5명의 유명인사 얼굴 데이터셋은 유명인사들의 사진을 포함한 작은 데이터셋입니다.

이 데이터셋은 다음 사진들을 포함합니다: 벤 애플렉, 엘튼 존, 제리 세인펠드, 마돈나, 민디 칼링.

댄 벡커가 이 데이터셋을 준비하여 사용 가능하며 Kaggle에서 무료 다운로드를 제공합니다. 주의, 데이터셋을 다운로드할 시 Kaggle 계정이 요구됩니다.

이제 다음 구조로 된 디렉토리가 있어야 합니다(일부 디렉토리 이름에는 철자가 틀리며 이 예시에서는 그대로 남았습니다).

출력 예시:

5-celebrity-faces-dataset
 ├── train
 │   ├── ben_afflek
 │   ├── elton_john
 │   ├── jerry_seinfeld
 │   ├── madonna
 │   └── mindy_kaling
 └── val
     ├── ben_afflek
     ├── elton_john
     ├── jerry_seinfeld
     ├── madonna
     └── mindy_kaling

훈련 데이터셋과 검증 및 테스트 데이터셋이 있음을 알 수 있습니다.

디렉토리의 일부 사진을 보면 사진이 다양한 방향과 조명, 크기를 가진 얼굴을 제공함을 알 수 있습니다. 중요한 것은, 각 사진은 한 명의 얼굴 사진을 포함합니다.

우리는 이 데이터셋을 분류기의 시작점으로 사용하고, train 데이터셋만 훈련하고 val 데이터셋에서 얼굴을 분류합니다. 동일한 구조를 사용하여 자신의 사진으로 분류기를 개발할 수 있습니다.

얼굴 감지하기

첫 번째 단계는 각 사진에서 얼굴을 감지하고 일련의 얼굴로만 데이터셋을 줄이는 것입니다.

이전 부분에서 정의했던 얼굴 감지기 함수인 extract_face() 함수를 테스트해 봅시다.

5-celebrity-faces-dataset/train/ben_afflek/ 디렉토리를 살펴보면, 우리는 훈련 데이터셋에 벤 애플렉의 사진 14장이 있다는 것을 알 수 있습니다. 각 사진에서 얼굴을 감지하고 각각 7개의 이미지로 2행, 총 14개의 얼굴로 플롯을 만들 수 있습니다.

완성된 예시는 아래에 있습니다.

# 5명의 유명인사 얼굴 데이터셋으로 얼굴 감지 표시하기
from os import listdir
from PIL import Image
from numpy import asarray
from matplotlib import pyplot
from mtcnn.mtcnn import MTCNN

# 주어진 사진에서 하나의 얼굴 추출
def extract_face(filename, required_size=(160, 160)):
	# 파일에서 이미지 불러오기
	image = Image.open(filename)
	# RGB로 변환, 필요시
	image = image.convert('RGB')
	# 배열로 변환
	pixels = asarray(image)
	# 감지기 생성, 기본 가중치 이용
	detector = MTCNN()
	# 이미지에서 얼굴 감지
	results = detector.detect_faces(pixels)
	# 첫 번째 얼굴에서 경계 상자 추출
	x1, y1, width, height = results[0]['boxes']
	# 버그 수정
	x1, y1 = abs(x1), abs(y1)
	x2, y2 = x1 + width, y1 + height
	# 얼굴 추출
	face = pixels[y1:y2, x1:x2]
	# 모델 사이즈로 픽셀 재조정
	image = Image.fromarray(face)
	image = image.resize(required_size)
	face_array = asarray(image)
	return face_array

# 폴더를 플롯으로 구체화하기
folder = '5-celebrity-faces-dataset/train/ben_afflek/'
i = 1
# 파일 열거
for filename in listdir(folder):
	# 경로
	path = folder + filename
	# 얼굴 추출
	face = extract_face(path)
	print(i, face.shape)
	# 플롯
	pyplot.subplot(2, 7, i)
	pyplot.axis('off')
	pyplot.imshow(face)
	i += 1
pyplot.show()

예제를 실행하면 시간이 조금 걸리고 얼굴 픽셀 데이터를 포함하는 넘파이 배열 형태와 이에 따라 불러온 각 사진의 진행 상황을 보고합니다.

출력 예시:

1 (160, 160, 3)
2 (160, 160, 3)
3 (160, 160, 3)
4 (160, 160, 3)
5 (160, 160, 3)
6 (160, 160, 3)
7 (160, 160, 3)
8 (160, 160, 3)
9 (160, 160, 3)
10 (160, 160, 3)
11 (160, 160, 3)
12 (160, 160, 3)
13 (160, 160, 3)
14 (160, 160, 3)

Ben Affleck 디렉토리에서 감지된 얼굴이 포함된 그림이 생성됩니다.

각 얼굴이 올바르게 감지되었으며 감지된 얼굴에는 다양한 조명이나 피부톤, 방향이 있음을 알 수 있습니다.

Plot of 14 Faces of Ben Affleck Detected From the Training Dataset of the 5 Celebrity Faces Dataset

지금까지는 좋군요, 순조롭습니다.

다음은, 이 예제를 확장하여 지정된 데이터셋(예: train이나 val)에 대한 각 하위 디렉토리를 단계별로 살펴보고, 얼굴을 추출하고, 감지된 각 얼굴의 소유자 이름으로 된 출력 레이블로 데이터셋을 준비 할 수 있습니다.

아래의 load_faces() 함수는 지정된 디렉토리 목록(예: 5-celebrity-faces-dataset/train/ben_afflek/) 안에 있는 모든 얼굴들을 불러옵니다.

# 디렉토리 안의 모든 이미지를 불러오고 이미지에서 얼굴 추출
def load_faces(directory):
	faces = list()
	# 파일 열거
	for filename in listdir(directory):
		# 경로
		path = directory + filename
		# 얼굴 추출
		face = extract_face(path)
		# 저장
		faces.append(face)
	return faces

train이나 val폴더의 각 하위 디렉토리에 대해 load_faces() 함수를 호출할 수 있습니다. 각 얼굴에는 유명인의 이름인 하나의 레이블이 있으며 디렉토리명에서 가져올 수 있습니다.

아래의 load_dataset() 함수는 디렉토리명으로 5-celebrity-faces-dataset/train/을 가지고 각 하위 디렉토리(유명인사)에서 얼굴을 감지하고, 각 감지된 얼굴에 레이블을 할당합니다.

데이터셋의 X와 y 요소를 넘파이 배열로 반환합니다.

# 이미지를 포함하는 각 클래스에 대해 하나의 하위 디렉토리가 포함된 데이터셋을 불러오기
def load_dataset(directory):
	X, y = list(), list()
	# 클래스별로 폴더 열거
	for subdir in listdir(directory):
		# 경로
		path = directory + subdir + '/'
		# 디렉토리에 있을 수 있는 파일을 건너뛰기(디렉토리가 아닌 파일)
		if not isdir(path):
			continue
		# 하위 디렉토리의 모든 얼굴 불러오기
		faces = load_faces(path)
		# 레이블 생성
		labels = [subdir for _ in range(len(faces))]
		# 진행 상황 요약
		print('>%d개의 예제를 불러왔습니다. 클래스명: %s' % (len(faces), subdir))
		# 저장
		X.extend(faces)
		y.extend(labels)
	return asarray(X), asarray(y)

그런 다음 trainval폴더에 대해 이 함수를 호출하여 모든 데이터를 불러온 후 savez_compressed() 함수를 통해 넘파이 배열 단일 압축 파일에 결과를 저장할 수 있습니다.

# 훈련 데이터셋 불러오기
trainX, trainy = load_dataset('5-celebrity-faces-dataset/train/')
print(trainX.shape, trainy.shape)
# 테스트 데이터셋 불러오기
testX, testy = load_dataset('5-celebrity-faces-dataset/val/')
print(testX.shape, testy.shape)
# 배열을 단일 압축 포맷 파일로 저장
savez_compressed('5-celebrity-faces-dataset.npz', trainX, trainy, testX, testy)

이 모든 과정을 합쳐 5명의 유명인사 얼굴 데이터셋에서 모든 얼굴을 감지하는 완전한 예제가 아래에 나열되어 있습니다.

# 5명의 유명인사 데이터셋으로 얼굴 감지하기
from os import listdir
from os.path import isdir
from PIL import Image
from matplotlib import pyplot
from numpy import savez_compressed
from numpy import asarray
from mtcnn.mtcnn import MTCNN

# 주어진 사진에서 하나의 얼굴 추출
def extract_face(filename, required_size=(160, 160)):
	# 파일에서 이미지 불러오기
	image = Image.open(filename)
	# RGB로 변환, 필요시
	image = image.convert('RGB')
	# 배열로 변환
	pixels = asarray(image)
	# 감지기 생성, 기본 가중치 이용
	detector = MTCNN()
	# 이미지에서 얼굴 감지
	results = detector.detect_faces(pixels)
	# 첫 번째 얼굴에서 경계 상자 추출
	x1, y1, width, height = results[0]['boxes']
	# 버그 수정
	x1, y1 = abs(x1), abs(y1)
	x2, y2 = x1 + width, y1 + height
	# 얼굴 추출
	face = pixels[y1:y2, x1:x2]
	# 모델 사이즈로 픽셀 재조정
	image = Image.fromarray(face)
	image = image.resize(required_size)
	face_array = asarray(image)
	return face_array

# 디렉토리 안의 모든 이미지를 불러오고 이미지에서 얼굴 추출
def load_faces(directory):
	faces = list()
	# 파일 열거
	for filename in listdir(directory):
		# 경로
		path = directory + filename
		# 얼굴 추출
		face = extract_face(path)
		# 저장
		faces.append(face)
	return faces

# 이미지를 포함하는 각 클래스에 대해 하나의 하위 디렉토리가 포함된 데이터셋을 불러오기
def load_dataset(directory):
	X, y = list(), list()
	# 클래스별로 폴더 열거
	for subdir in listdir(directory):
		# 경로
		path = directory + subdir + '/'
		# 디렉토리에 있을 수 있는 파일을 건너뛰기(디렉토리가 아닌 파일)
		if not isdir(path):
			continue
		# 하위 디렉토리의 모든 얼굴 불러오기
		faces = load_faces(path)
		# 레이블 생성
		labels = [subdir for _ in range(len(faces))]
		# 진행 상황 요약
		print('>%d개의 예제를 불러왔습니다. 클래스명: %s' % (len(faces), subdir))
		# 저장
		X.extend(faces)
		y.extend(labels)
	return asarray(X), asarray(y)

# 훈련 데이터셋 불러오기
trainX, trainy = load_dataset('5-celebrity-faces-dataset/train/')
print(trainX.shape, trainy.shape)
# 테스트 데이터셋 불러오기
testX, testy = load_dataset('5-celebrity-faces-dataset/val/')
print(testX.shape, testy.shape)
# 배열을 단일 압축 포맷 파일로 저장
savez_compressed('5-celebrity-faces-dataset.npz', trainX, trainy, testX, testy)

예제를 실행하면 시간이 약간 걸립니다.

먼저, train 데이터셋의 모든 사진을 불러온 다음 얼굴이 추출되어 얼굴의 정사각형 입력과 클래스 레이블 문자열이 출력된 93개의 샘플이 생성됩니다. 그런 다음 val 데이터셋을 불러와 테스트 데이터셋으로 사용할 수 있는 25개의 샘플이 제공됩니다.

그런 다음 두 데이터셋은 약 3MB의 5-celebrity-faces-dataset.npz라는 압축된 넘파이 배열 파일에 저장되며 현재 작업중인 디렉토리에 저장됩니다.

출력 예시:

>14개의 예제를 불러왔습니다. 클래스명: ben_afflek
>19개의 예제를 불러왔습니다. 클래스명: madonna
>17개의 예제를 불러왔습니다. 클래스명: elton_john
>22개의 예제를 불러왔습니다. 클래스명: mindy_kaling
>21개의 예제를 불러왔습니다. 클래스명: jerry_seinfeld
(93, 160, 160, 3) (93,)
>5개의 예제를 불러왔습니다. 클래스명: ben_afflek
>5개의 예제를 불러왔습니다. 클래스명: madonna
>5개의 예제를 불러왔습니다. 클래스명: elton_john
>5개의 예제를 불러왔습니다. 클래스명: mindy_kaling
>5개의 예제를 불러왔습니다. 클래스명: jerry_seinfeld
(25, 160, 160, 3) (25,)

이 데이터셋은 얼굴 감지 모델로 제공될 준비가 되었습니다.

얼굴 임베딩 생성

다음 단계는 얼굴 임베딩을 생성하는 것입니다.

얼굴 임베딩은 얼굴에서 추출된 특성을 표현하는 벡터입니다. 이는 다른 얼굴에서 생성된 벡터와 비교될 수 있습니다. 예를 들어, (일부 측정에 의해) 가까운 다른 벡터는 동일한 사람일 수 있는 반면, (일부 측정에 의해) 다른 벡터는 다른 사람일 수 있습니다.

우리가 개발하려는 분류기 모델은 얼굴 임베딩을 입력으로 받아 얼굴의 정체성을 예측합니다. FaceNet 모델은 제공된 얼굴 이미지에서 임베딩을 생성할 것입니다.

FaceNet 모델은 분류기 자체의 일부로 사용되거나 얼굴을 전처리하기 위한 FaceNet 모델을 사용하여 분류기 모델의 입력으로 저장 및 사용 가능한 얼굴 임베딩을 생성할 수 있습니다. 얼굴 임베딩을 생성하기 위한 FaceNet 모델은 크고 느리기 때문에 후자의 접근 방식이 선호됩니다.

따라서, 5명의 유명인사 얼굴 데이터셋의 훈련 및 테스트(공식적으로는 val)셋 내 모든 얼굴에 대한 얼굴 임베딩을 사전 계산할 수 있습니다.

먼저, load() 넘파이 함수를 이용해 우리의 감지된 얼굴 데이터셋을 불러올 수 있습니다.

# 얼굴 데이터셋 불러오기
data = load('5-celebrity-faces-dataset.npz')
trainX, trainy, testX, testy = data['arr_0'], data['arr_1'], data['arr_2'], data['arr_3']
print('불러오기: ', trainX.shape, trainy.shape, testX.shape, testy.shape)

다음으로, 얼굴을 얼굴 임베딩으로 변환할 준비가 된 FaceNet 모델을 불러올 수 있습니다.

# facenet 모델 불러오기
model = load_model('facenet_keras.h5')
print('모델 불러오기')

그런 다음 임베딩을 예측하기 위한 훈련 및 테스트 데이터셋의 각각의 얼굴을 열거할 수 있습니다.

임베딩을 예측하려면, 먼저 FaceNet 모델의 기대치를 충족시키기 위해 이미지의 적당한 픽셀 값이 준비되어야 합니다. FaceNet 모델의 구체적 구현물은 표준화된 픽셀값을 기대합니다.

# 픽셀 값의 척도
face_pixels = face_pixels.astype('int32')
# 채널 간 픽셀값 표준화(전역에 걸쳐)
mean, std = face_pixels.mean(), face_pixels.std()
face_pixels = (face_pixels - mean) / std

케라스의 한 가지 예시에 대한 예측을 생성하려면 얼굴 배열이 하나의 샘플이 되도록 차원을 확장해야 합니다.

# 얼굴을 하나의 샘플로 변환
samples = expand_dims(face_pixels, axis=0)

그런 다음 예측을 생성하기 위한 모델을 사용하여 임베딩 벡터를 추출할 수 있습니다.

# 임베딩을 갖기 위한 예측 생성
yhat = model.predict(samples)
# 임베딩 얻기
embedding = yhat[0]

아래에 정의된 get_embedding() 함수는 이 명령을 구현하고 주어진 얼굴 단일 이미지의 얼굴 임베딩과 불러온 FaceNet 모델을 반환합니다.

# 하나의 얼굴의 얼굴 임베딩 얻기
def get_embedding(model, face_pixels):
	# 픽셀 값의 척도
	face_pixels = face_pixels.astype('int32')
	# 채널 간 픽셀값 표준화(전역에 걸쳐)
	mean, std = face_pixels.mean(), face_pixels.std()
	face_pixels = (face_pixels - mean) / std
	# 얼굴을 하나의 샘플로 변환
	samples = expand_dims(face_pixels, axis=0)
	# 임베딩을 갖기 위한 예측 생성
	yhat = model.predict(samples)
	return yhat[0]

이 모든 과정을 합쳐, 훈련 및 테스트 데이터셋의 각각의 얼굴을 얼굴 임베딩으로 변환하는 완전한 예제가 아래에 나열되어 있습니다.

# facenet을 이용해 데이터셋 내 각 얼굴에 대한 얼굴 임베딩 계산
from numpy import load
from numpy import expand_dims
from numpy import asarray
from numpy import savez_compressed
from keras.models import load_model

# 하나의 얼굴의 얼굴 임베딩 얻기
def get_embedding(model, face_pixels):
	# 픽셀 값의 척도
	face_pixels = face_pixels.astype('int32')
	# 채널 간 픽셀값 표준화(전역에 걸쳐)
	mean, std = face_pixels.mean(), face_pixels.std()
	face_pixels = (face_pixels - mean) / std
	# 얼굴을 하나의 샘플로 변환
	samples = expand_dims(face_pixels, axis=0)
	# 임베딩을 갖기 위한 예측 생성
	yhat = model.predict(samples)
	return yhat[0]

# 얼굴 데이터셋 불러오기
data = load('5-celebrity-faces-dataset.npz')
trainX, trainy, testX, testy = data['arr_0'], data['arr_1'], data['arr_2'], data['arr_3']
print('불러오기: ', trainX.shape, trainy.shape, testX.shape, testy.shape)
# facenet 모델 불러오기
model = load_model('facenet_keras.h5')
print('모델 불러오기')
# 훈련 셋에서 각 얼굴을 임베딩으로 변환하기
newTrainX = list()
for face_pixels in trainX:
	embedding = get_embedding(model, face_pixels)
	newTrainX.append(embedding)
newTrainX = asarray(newTrainX)
print(newTrainX.shape)
# 테스트 셋에서 각 얼굴을 임베딩으로 변환하기
newTestX = list()
for face_pixels in testX:
	embedding = get_embedding(model, face_pixels)
	newTestX.append(embedding)
newTestX = asarray(newTestX)
print(newTestX.shape)
# 배열을 하나의 압축 포맷 파일로 저장
savez_compressed('5-celebrity-faces-embeddings.npz', newTrainX, trainy, newTestX, testy)

예제를 실행하면 진행 상황을 보고합니다.

얼굴 데이터셋을 올바르게 불러왔고 모델도 준비되었음을 알 수 있습니다. 그런 다음 훈련 데이터 셋은 각각 93개의 요소 벡터로 구성된 93개의 얼굴 임베딩으로 변환되었습니다. 테스트 데이터셋의 25개 예제도 적절하게 얼굴 임베딩으로 변환되었습니다.

그 다음 결과 데이터셋은 현재 작업 디렉토리에 약 50KB의 압축된 넘파이 배열이 5-celebrity-faces-embeddings.npz로 저장되었습니다.

# 불러오기:  (93, 160, 160, 3) (93,) (25, 160, 160, 3) (25,)
# 모델 불러오기
# (93, 128)
# (25, 128)

이제 얼굴 분류기 시스템을 개발할 준비가 되었습니다.

얼굴 분류하기

이 부분에서는, 5명의 유명인사 얼굴 데이터셋에서 알려진 유명인 중 하나로 얼굴 임베딩을 분류하는 모델을 개발할 것입니다.

먼저, 얼굴 임베딩 데이터셋을 불러와야 합니다.

# 데이터셋 불러오기
data = load('5-celebrity-faces-embeddings.npz')
trainX, trainy, testX, testy = data['arr_0'], data['arr_1'], data['arr_2'], data['arr_3']
print('데이터셋: 훈련=%d, 테스트=%d' % (trainX.shape[0], testX.shape[0]))

다음으로, 데이터는 모델링하기 전에 약간의 준비가 필요합니다.

먼저, 얼굴 임베딩 벡터를 일반화하는 데 좋은 실습입니다. 벡터는 거리 측정을 이용하여 다른 벡터를 자주 비교하기에 이는 좋은 실습입니다.

이 내용에서, 벡터 일반화란 벡터의 길이 또는 크기가 1이나 단위 길이가 될 때까지 값을 스케일링하는 것을 의미합니다. 이는 scikit-learn의 Normalizer 클래스를 사용하여 수행할 수 있습니다. 이전 단계에서 얼굴 임베딩이 생성되었을 때 이 단계를 수행하는 것이 더 편리할 수 있습니다.

# 입력 벡터 일반화
in_encoder = Normalizer(norm='l2')
trainX = in_encoder.transform(trainX)
testX = in_encoder.transform(testX)

다음으로, 각 유명인 이름 변수 문자열은 정수로 변환해야 합니다.

이는 scikit-learn의 LabelEncoder 클래스를 사용하여 수행할 수 있습니다.

# 목표 레이블 암호화
out_encoder = LabelEncoder()
out_encoder.fit(trainy)
trainy = out_encoder.transform(trainy)
testy = out_encoder.transform(testy)

이제, 모델을 적합시킬 수 있습니다.

일반화된 얼굴 임베딩 입력과 동작하는 데에는 선형 서포트 벡터 머신(SVM)을 사용하는 것이 일반적입니다. 이 방법이 얼굴 임베딩 벡터를 분리하는 데 매우 효과적이기 때문입니다. scikit-learn의 SVC 클래스를 사용하고 kernel 속성을 linear로 설정해 선형 SVM을 훈련 데이터에 적합시킬 수 있습니다. 추측을 하고 나서의 확률을 나중에 원할 수도 있는데, probabilityTrue로 설정하여 구성할 수 있습니다.

# 모델 맞추기(적합시키기)
model = SVC(kernel='linear')
model.fit(trainX, trainy)

이제, 모델을 평가할 수 있습니다.

이는 적합한 모델을 사용하여 훈련 및 테스트 데이터셋의 각 예시를 추측하고 분류 정확도를 계산하여 수행할 수 있습니다.

# 예측
yhat_train = model.predict(trainX)
yhat_test = model.predict(testX)
# 정확도 점수
score_train = accuracy_score(trainy, yhat_train)
score_test = accuracy_score(testy, yhat_test)
# 요약
print('정확도: 훈련=%.3f, 테스트=%.3f' % (score_train*100, score_test*100))

이 과정을 모두 합쳐, 5명의 유명인사 얼굴 데이터셋의 얼굴 임베딩으로 선형 SVM을 적합하는 완전한 예제가 아래에 나열되어 있습니다.

# 5명의 유명인사 얼굴 데이터셋으로 분류기 개발
from numpy import load
from sklearn.metrics import accuracy_score
from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import Normalizer
from sklearn.svm import SVC
# 데이터셋 불러오기
data = load('5-celebrity-faces-embeddings.npz')
trainX, trainy, testX, testy = data['arr_0'], data['arr_1'], data['arr_2'], data['arr_3']
print('데이터셋: 훈련 %d개, 테스트 %d개' % (trainX.shape[0], testX.shape[0]))
# 입력 벡터 일반화
in_encoder = Normalizer(norm='l2')
trainX = in_encoder.transform(trainX)
testX = in_encoder.transform(testX)
# 목표 레이블 암호화
out_encoder = LabelEncoder()
out_encoder.fit(trainy)
trainy = out_encoder.transform(trainy)
testy = out_encoder.transform(testy)
# 모델 맞추기(적합시키기)
model = SVC(kernel='linear')
model.fit(trainX, trainy)
# 추측
yhat_train = model.predict(trainX)
yhat_test = model.predict(testX)
# 정확도 점수
score_train = accuracy_score(trainy, yhat_train)
score_test = accuracy_score(testy, yhat_test)
# 요약
print('정확도: 훈련=%.3f, 테스트=%.3f' % (score_train*100, score_test*100))

예제를 먼저 실행하면 훈련 및 테스트 데이터셋의 샘플 수가 예상한 대로 확인됩니다.

다음으로 모델은 훈련 및 테스트 데이터셋에 대한 평가는 완벽한 분류 정확도를 보여줍니다. 데이터셋의 크기와 사용된 얼굴 인식 및 얼굴 인식 모델의 능력을 고려할 때 이것은 놀라운 일이 아닙니다.

출력 예시:

데이터셋: 훈련 93개, 테스트 25개
정확도: 훈련=100.000, 테스트=100.000

원래 얼굴과 추측값을 출력해 더 재미있게 만들 수 있습니다.

먼저 얼굴 데이터셋, 특히 테스트 데이터셋의 얼굴을 불러와야 합니다. 또한 원본 사진을 불러와 더 재미있게 만들 수도 있습니다.

# 얼굴 불러오기
data = load('5-celebrity-faces-dataset.npz')
testX_faces = data['arr_2']

나머지 예제는 모델을 적합시킬 때와 동일합니다.

먼저 테스트 세트에서 임의의 예제를 선택한 다음 임베딩, 얼굴 픽셀, 기대 클래스 추측 및 해당 클래스의 이름을 가져옵니다.

# 테스트 데이터셋에서 임의의 예제에 대한 테스트 모델
selection = choice([i for i in range(testX.shape[0])])
random_face_pixels = testX_faces[selection]
random_face_emb = testX[selection]
random_face_class = testy[selection]
random_face_name = out_encoder.inverse_transform([random_face_class])

다음으로, 얼굴 임베딩을 입력으로 사용하여 적합한 모델로 단일 추측을 수행할 수 있습니다.

클래스 정수 번호와 추측 정확도를 모두 예상할 수 있습니다.

# 얼굴 예측
samples = expand_dims(random_face_emb, axis=0)
yhat_class = model.predict(samples)
yhat_prob = model.predict_proba(samples)

예측한 클래스의 정수 번호를 통해 이름을 얻을 수 있고, 이 추측의 정확도를 얻을 수 있습니다.

# 이름 얻기
class_index = yhat_class[0]
class_probability = yhat_prob[0,class_index] * 100
predict_names = out_encoder.inverse_transform(yhat_class)

그리고 이 정보를 출력할 수 있습니다.

print('예상: %s (%.3f)' % (predict_names[0], class_probability))
print('추측: %s' % random_face_name[0])

추측된 이름 및 정확도와 함께 얼굴 픽셀을 그릴 수도 있습니다.

# 재미삼아 그리기
pyplot.imshow(random_face_pixels)
title = '%s (%.3f)' % (predict_names[0], class_probability)
pyplot.title(title)
pyplot.show()

이 과정을 모두 합쳐, 테스트 데이터셋에서 주어진 본 적 없는 사진의 정체성을 예측하는 완전한 예제가 아래에 나열되어 있습니다.

# 5명의 유명인사 얼굴 데이터셋의 분류기 개발
from random import choice
from numpy import load
from numpy import expand_dims
from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import Normalizer
from sklearn.svm import SVC
from matplotlib import pyplot
# 얼굴 불러오기
data = load('5-celebrity-faces-dataset.npz')
testX_faces = data['arr_2']
# 얼굴 임베딩 불러오기
data = load('5-celebrity-faces-embeddings.npz')
trainX, trainy, testX, testy = data['arr_0'], data['arr_1'], data['arr_2'], data['arr_3']
# 입력 벡터 일반화
in_encoder = Normalizer(norm='l0')
trainX = in_encoder.transform(trainX)
testX = in_encoder.transform(testX)
# 목표 레이블 암호화
out_encoder = LabelEncoder()
out_encoder.fit(trainy)
trainy = out_encoder.transform(trainy)
testy = out_encoder.transform(testy)
# 모델 적합
model = SVC(kernel='linear', probability=True)
model.fit(trainX, trainy)
# 테스트 데이터셋에서 임의의 예제에 대한 테스트 모델
selection = choice([i for i in range(testX.shape[0])])
random_face_pixels = testX_faces[selection]
random_face_emb = testX[selection]
random_face_class = testy[selection]
random_face_name = out_encoder.inverse_transform([random_face_class])
# 얼굴 예측
samples = expand_dims(random_face_emb, axis=0)
yhat_class = model.predict(samples)
yhat_prob = model.predict_proba(samples)
# 이름 얻기
class_index = yhat_class[0]
class_probability = yhat_prob[0,class_index] * 100
predict_names = out_encoder.inverse_transform(yhat_class)
print('예상: %s (%.3f)' % (predict_names[0], class_probability))
print('추측: %s' % random_face_name[0])
# 재미삼아 그리기
pyplot.imshow(random_face_pixels)
title = '%s (%.3f)' % (predict_names[0], class_probability)
pyplot.title(title)
pyplot.show()

코드가 실행될 때마다 테스트 데이터셋과 다른 임의의 예시가 선택됩니다.

실행하는 데에는 약간의 시간이 걸립니다.

예시의 경우 제리 세인펠드의 사진이 선택되어 올바르게 예측됩니다.

출력 예시:

예상: jerry_seinfeld (88.476)
추측: jerry_seinfeld

이미지 제목에는 예측된 이름과 정확도를 보여주고, 선택된 얼굴 그림도 생성됩니다.

Detected Face of Jerry Seinfeld, Correctly Identified by the SVM Classifier

추가 자료

이 부분에서는 이 주제에 대한 심화 공부를 원할 경우에 대한 더 많은 자료를 제공합니다.

논문

서적

프로젝트

API

요약

이 실습에서 당신은 FaceNet 및 SVM 분류기를 사용하여 사진에서 사람을 식별하는 얼굴 감지 시스템을 개발하는 방법을 알아보았습니다.

구체적으로, 우리는:

  • Google이 개발한 FaceNet 얼굴 인식 시스템 및 오픈 소스 구현물 및 사전 훈련된 모델에 대해 배웠습니다.
  • 얼굴 감지 시스템을 통해 먼저 얼굴을 추출한 다음 얼굴 임베딩을 통해 얼굴 특징을 추출하는 것을 포함하여 얼굴 감지 데이터셋을 준비하는 방법에 대해 배웠습니다.
  • 얼굴 임베딩에서 정체성을 예측하기 위해 SVM 모델을 적합, 평가 및 시연하는 방법에 대해 배웠습니다.