귀퉁이 서재

컴퓨터 비전 - 4. LBPH 알고리즘을 활용한 얼굴 인식 본문

컴퓨터 비전

컴퓨터 비전 - 4. LBPH 알고리즘을 활용한 얼굴 인식

Baek Kyun Shin 2023. 3. 9. 00:32

이전 글에서 LBPH 알고리즘 개념에 관해 알아봤습니다. 이번에는 LBPH 알고리즘을 활용해서 얼굴 인식을 하는 실습을 해보겠습니다.

코드 링크 : https://github.com/BaekKyunShin/Computer-Vision-Basic/blob/main/Project2-Face_Recognition/Face_Recognition_with_LBPH.ipynb


아래 코드는 구글 코랩(colab)을 바탕으로 설명합니다.

1. 구글 드라이브 마운트 & 이미지 데이터셋 불러오기

가장 먼저 구글 드라이브를 마운트합니다.

from google.colab import drive
drive.mount('/content/drive')

이어서 얼굴 인식에 사용할 이미지 데이터를 불러옵니다. 여기서는 Yale face라는 이미지 데이터를 사용해볼 겁니다. Yale Face란 15명의 얼굴 이미지 데이터셋입니다. 총 165개의 흑백 얼굴 이미지로 구성됐고, 데이터 크기는 6.4MB입니다. 파일 형태는 gif입니다. 한 사람당 11개 이미지가 있는 것이죠. 다양한 조명에 따른 정면 사진들, 안경을 낀 사진, 안경을 끼지 않은 사진, 슬픈 표정의 사진, 놀란 표정의 사진, 졸린 표정의 사진, 윙크한 표정의 사진 등으로 한 사람당 총 11개 사진으로 이루어져 있습니다.

Yale face를 압축한 zip 파일을 현재 디렉터리에 풀어서 사용하겠습니다.

import zipfile

yalefaces_path = '/content/drive/MyDrive/colab/Computer-Vision-Course/Data/Datasets/yalefaces.zip'

zip = zipfile.ZipFile(file=yalefaces_path, mode='r')
zip.extractall('./')  # extract all file in current directory
zip.close()

샘플로 이미지를 몇 개 열어보면 아래와 같습니다. 

왼쪽에서 조명이 비춘 정면 얼굴
놀란 표정
슬픈 표정

Yale face는 훈련 데이터와 테스트 데이터로 이루어져 있습니다. 훈련 데이터를 활용해 LBPH 얼굴 인식기를 훈련할 겁니다. 그러면 이 LBPH 인식기는 11명의 특징을 훈련한 상태가 되겠죠. 그다음 테스트 데이터로 11명을 각각 인식해볼 겁니다.

훈련 데이터명을 출력해보죠.

import os

train_image_path = '/content/yalefaces/train'

# print all train image file name
all_train_image_file_name = os.listdir(train_image_path)
print(all_train_image_file_name)

  ['subject15.leftlight.gif', 'subject10.leftlight.gif', 'subject05.happy.gif',... 'subject04.centerlight.gif']

정렬이 안 된 상태로 출력이 됐지만 subject01부터 subject15까지 총 15명이라는 점을 알 수 있습니다. leftlight, happy, centerlight 등의 표시가 있네요. 같은 사람이라도 다양한 조건의 사진이 있다는 뜻입니다.

전체 훈련 데이터 각각의 파일 경로를 all_train_image_paths 변수에 저장하겠습니다. 경로를 통해 훈련 데이터에 접근을 해야 하기 때문이죠.

all_train_image_paths = [os.path.join(train_image_path, f) for f in all_train_image_file_name]
print(all_train_image_paths)

  ['/content/yalefaces/train/subject15.leftlight.gif', '/content/yalefaces/train/subject10.leftlight.gif',... , 
   '/content/yalefaces/train/subject04.centerlight.gif']

2. 이미지 전처리

우리는 OpenCV를 활용해 LBPH 알고리즘을 써볼 겁니다. OpenCV의 LBPH 알고리즘을 사용하려면 간단한 전처리 작업을 먼저 해야 합니다.

이를 위해, 이미지 경로를 전달하면 전처리한 이미지를 가져오는 함수를 정의해보겠습니다.

import numpy as np
from PIL import Image

def fetch_preprocessed_image_data(image_paths):
    faces_of_image = []
    ids_of_image = []

    for image_path in image_paths:
        image = Image.open(image_path).convert('L')  # convert to grayscale
        # convert the image type to a numpy array type (with integer format)
        image_np = np.array(image, 'uint8')  
        image_file_name = os.path.split(image_path)[1]
        id_of_image = int(image_file_name.split('.')[0].replace('subject', ''))
        ids_of_image.append(id_of_image)
        faces_of_image.append(image_np)

    return np.array(ids_of_image), faces_of_image

이미지 전체 경로를 전달하면, 전체 경로를 순회하면서 1) 이미지를 불러온 뒤 흑백이미지로 변환합니다(이미 흑백 이미지라서 바꿀 필요는 없습니다. 그런데, 색상 이미지인 경우에 대비해서 흑백 이미지로 바꾸는 convert('L') 메서드를 넣었습니다). 2) 이어서 이미지를 넘파이 배열 형태로 변환합니다. 배열을 구성하는 값도 정수(uint8)로 바꾸고요. 3) 문자열 조작을 통해 subject의 ID 값을 가져옵니다. 4) 마지막으로 ids_of_image와 faces_of_image 리스트에 저장합니다.

위 함수에서 os.path.split(image_path)는 예를 들어 ('/content/yalefaces/train', 'subject15.leftlight.gif')를 반환합니다. 따라서 os.path.split(image_path)[1]은 'subject15.leftlight.gif'가 되겠죠. '.'을 기준으로 split()을 한 뒤, 첫 글자를 가져오면 'subject15'가 됩니다. 여기서 subject를 빈 문자('')로 치환하면 15가 반환됩니다. 전체 경로에서 ID 값만 추출한 것입니다.

위에서 정의한 함수로 훈련 데이터의 전체 ID 값과 이미지 데이터를 가져오겠습니다. 물론 전처리가 된 데이터가 불러와집니다.

ids_of_train_image, faces_of_train_image = fetch_preprocessed_image_data(all_train_image_paths)

전처리된 데이터를 출력해보죠. ID 값부터 출력해보겠습니다.

ids_of_train_image

array([15, 10,  5,  8,  9,  7,  7,  8,  9, 14, 14,  3, 11, 15,  6,  1,  4, 5, 12,  3,  5, 11, 12,  9,  8, 11,  1,  5, 14, 15,  9, 10,  1, 13, 5, 14, 11, 12,  8,  1,  3, 13, 12, 13,  3,  1, 13,  3, 10, 14,  1, 11, 14,  7, 15,  8,  7,  3, 15, 10, 13,  2,  5,  6,  2,  6, 12,  3, 7, 13,  4,  7,  9, 10, 12,  2,  7,  4,  8, 12,  9,  6, 15, 15,  8, 12, 10,  6,  2, 10,  3,  6,  5,  7,  2,  9, 15,  5,  8,  1,  2, 15, 13, 14, 12, 13,  4,  1, 10,  2, 11,  1,  8,  9,  4,  7, 10,  4,  6, 11,  6,  5,  9,  6, 14, 13, 14,  4,  3, 11,  4,  2,  2, 11,  4])

ID 값이 1부터 15까지 있네요.

이어서 얼굴 이미지 데이터 샘플을 하나 출력해보겠습니다. 각 픽셀값의 크기가 출력됩니다.

faces_of_train_image[0]

array([[108, 116, 117, ..., 156, 147, 167], 
             [237, 248, 244, ..., 163, 152, 171], 
              ...,
             [241, 227, 230, ..., 124, 114, 122],
             [  68,   68,   68,  ...,   68,   68,   68]], dtype=uint8)

len(ids_of_train_image), len(faces_of_train_image)

   (135, 135)

데이터 개수는 총 135개입니다.

3. 원본 이미지에서 LBP 이미지 구해보기

LBPH 모델을 훈련하기 전에, 원본 이미지에서 LBP 이미지를 구하는 방법을 알아보겠습니다. 우리가 가진 훈련 이미지 데이터는 넘파이 배열 형태입니다. 전처리할 때 넘파이 배열 형태로 바꿨기 때문이죠. 이를 바탕으로 LBP를 구해보겠습니다. LBP(Local Binary Patterns)란 한국말로 국지 이진 패턴을 뜻합니다. 간단히 텍스처의 특징을 나타낸다고 보면 됩니다. 

from skimage.feature import local_binary_pattern

# Original image as a numpy array
original_image = faces_of_train_image[5]

# Compute LBP image with radius=3 and n_points=8*3
lbp_image = local_binary_pattern(original_image, 8*3, 3)

# Normalize the LBP image to the range [0, 255]
lbp_image = ((lbp_image - lbp_image.min()) / (lbp_image.max() - lbp_image.min())) * 255

# Convert the LBP image to integer data type
lbp_image = lbp_image.astype(np.uint8)

이제 원본 이미지와 LBP 추출 이미지를 각각 출력해보겠습니다. 먼저 원본 이미지를 보시죠.

cv2_imshow(original_image)

다음은 LBP 이미지입니다. 얼굴의 주요 특징을 추출한 겁니다.

cv2_imshow(lbp_image)

4.  LBPH 얼굴 인식기 훈련하기

이제 훈련할 차례입니다. cv2.face.LBPHFaceRecognizer_create() 메서드로 LBPH 얼굴 인식기를 생성할 수 있습니다. 파라미터로는 4가지가 있습니다. 파라미터에 관해서는 이전 글에서 설명했으니 참고해주세요. 생성한 LBPH 인식기를 train() 메서드로 훈련합니다. 이미지 데이터와 타깃 ID값을 파라미터로 전달해서요.

import cv2

lbph_recognizer = cv2.face.LBPHFaceRecognizer_create(radius=5, neighbors=15, grid_x=10, grid_y=10)

lbph_recognizer.train(faces_of_train_image, ids_of_train_image)  # Train the lbph recognizer

5. LBPH 얼굴 인식기로 예측하기

훈련을 마쳤으니 이제 예측을 해야겠죠? 예측을 위해서 테스트 데이터를 불러오겠습니다. 훈련 데이터를 불러온 방식과 동일합니다.

test_image_path = '/content/yalefaces/test'

all_test_image_file_name = os.listdir(test_image_path)

all_test_image_paths = [os.path.join(test_image_path, f) for f in all_test_image_file_name]

ids_of_test_image, faces_of_test_image = fetch_preprocessed_image_data(all_test_image_paths)

 이어서 테스트 데이터 하나만 활용해 예측을 수행해보겠습니다. predict() 메서드로 예측을 합니다.

id_pred, confidence_pred = lbph_recognizer.predict(faces_of_test_image[0])

id_pred, confidence_pred

(11, 88.48699892288407)

결괏값을 두 개 반환합니다. 첫 번째는 예측한 타깃 ID값이고, 두 번째는 예측 신뢰도입니다. ID를 11로 예측을 했는데, 잘 예측한 건지 실제 이미지와 함께 출력해보겠습니다.

cv2.putText(faces_of_test_image[0], f'Pred: {str(id_pred)}', (5, 30), cv2.FONT_HERSHEY_PLAIN, 1.1, (0, 255, 0))
cv2.putText(faces_of_test_image[0], f'True: {str(ids_of_test_image[0])}', (5, 60), cv2.FONT_HERSHEY_PLAIN, 1.1, (0,255,0))

cv2_imshow(faces_of_test_image[0])

실제값도 11이니 잘 예측했네요!

6. 성능 평가

앞서 샘플 하나로만 예측을 해봤습니다. 이번에는 전체 테스트 데이터를 활용해 예측을 해보고, 성능까지 평가해보겠습니다.

ids_pred = []

# Prediction
for face_of_test_image in faces_of_test_image:
    id_pred, _ = lbph_recognizer.predict(face_of_test_image)
    ids_pred.append(id_pred)

예측을 했으니, 마지막으로 정확도 점수를 구해보죠.

from sklearn.metrics import accuracy_score

accuracy_score(ids_of_test_image, ids_pred)

0.7

70%입니다. 썩 좋은 결과는 아닙니다. 아무래도 LBPH가 과거 알고리즘이라 그다지 성능이 좋지 못한 겁니다. LBPH 알고리즘 자체가 나온지 오래됐기 때문이죠. CNN을 활용하면 성능이 훨씬 좋아집니다. 다음과 같이 얼굴의 주요 특징점을 찾아서 얼굴을 훨씬 정확하게 인식합니다. 

CNN을 활용해 얼굴을 인식하는 코드는 제 깃헙에서 보실 수 있습니다. CNN을 사용했다는 점을 빼면 절차가 비슷하니 설명은 생략하겠습니다.


참고 자료

Yale Face Database

Jones Granatyr(Udemy) - "Computer Vision: Master Class"

Comments