AI 탐구노트

AI를 이용한 간단한 안면 식별 서비스 개발 테스트 본문

DIY 테스트

AI를 이용한 간단한 안면 식별 서비스 개발 테스트

42morrow 2025. 1. 7. 14:05

 

 

인공지능(AI) 기술은 일상 속에서 점점 더 많은 역할을 하고 있습니다. 그중에서도 안면 인식 기술은 보안, 스마트 기기 제어, 출석 체크 등 다양한 분야에서 유용하게 활용됩니다. 특히, 최근에는 고성능 하드웨어가 아닌 경량화된 임베디드 보드에서도 동작할 수 있는 AI 모델이 개발되면서, 보다 실용적인 서비스 구현이 가능해지고 있습니다. 이런 경량 AI 모델은 소규모 데이터로도 효과적인 학습이 가능하고, 에너지와 비용 면에서 경제적이어서 많은 주목을 받고 있습니다.

 

실생활에서 안면 인식 서비스는 어떻게 활용될 수 있을까요? 예를 들어, 학교에서 수업 참여자의 출석 체크를 AI로 간단히 처리하거나, 소규모 모임에서 손쉽게 개개인을 식별할 수 있다면 어떨까요? 이러한 아이디어는 복잡하고 대규모 시스템을 구축하지 않아도 경량 AI 모델을 활용해 작고 효율적인 서비스를 개발하는 것으로 현실화할 수 있습니다.

 

이번 글에서는 공개된 경량 AI 모델을 이용해 영상에서의 인물이 누구인지 판별하는 테스트를 진행하는 과정을 설명하겠습니다.


경량 모델을 사용하는 이유

기존의 안면 인식 서비스는 대규모 서버와 고성능 하드웨어를 필요로 했습니다. 이는 시스템 구축 비용이 높고, 유지보수가 어렵다는 단점이 있었습니다. 또한, 수백에서 수천 명에 이르는 데이터를 학습해야 하는 경우가 많아 소규모 데이터를 다루기에는 비효율적이었습니다. 

 

이런 이유 때문에 안면식별이 필요한 현장에서는, 안면식별 알고리즘을 칩 형태로 만들어서 모듈 형태로 제공하는 것을 이용하는 경우가 많았습니다. 인터넷이 연결되어 있지 않은 경우, 기기에 장착된 임베디드 보드 상에서 AI 추론이 이뤄져야 하기 때문이죠. 우리가 아는 대부분의 안면식별 도어락 같은 것들이 그렇게 동작하도록 되어 있습니다. 이 경우, 등록된 소수의 안면 이미지만 지원되며 모델 사이즈도 작고 저장할 수 있는 메모리 크기도 작기 때문에 모델의 성능이 아주 뛰어나지는 않습니다. 하지만, 한정된 인원만 사용하는 제한된 환경에서 실시간 처리가 되어야 하는 용도이므로 이 목적에는 아주 잘 맞는 적용 사례라고 할 수 있죠.

 

경량 안면 인식 모델의 적용 사례

  • 학교 출석 체크 : 대학 강의 출결 처리 (국내, 해외에서 이렇게 한 사례도 있다고 들었습니다)
  • 소규모 이벤트 : 초대된 참가자의 얼굴을 인식하여 명부를 작성하는 경우
  • 스마트 홈 : 특정 사용자를 인식하여 개인화된 환경 제공하는 경우
  • 병원, 회사 사무실 출입 통제 : 출입이 허용된 인원만 특정 공간에 출입할 수 있도록 하는 경우

 

경량 안면 인식 모델 예시

안면 인식 모델로 잘 알려진 것으로는 ArcFace, InsightFace 등이 있습니다. 하지만 이들은 다소 무거운 모델이고 경량 모델로는 MobileFaceNet, EdgeFace 등이 있습니다. 이 글에서는 가장 많이 알려진 MobileFaceNet을 이용한 간단한 테스트를 진행하겠습니다. (EdgeFace의 경우는 좀 더 내용을 알아보고 다음 번에 한번 소개하도록 하겠습니다)

 


모델 테스트

1) 테스트 모델 

 

이번에 테스트 할 모델은 MovbileFaceNet (Github) 입니다. 5.2MB의 크기 모델로 입력은 112x112 이미지를 받습니다. 공개된 APK (안드로이드용 앱)을 이용할 경우, 꽤 괜찮은 속도와 성능을 보여 줍니다. 내부적으로는 안면감지는 MTCNN을, 식별은 MobileFaceNet을 이용하고 있습니다. 

 

해당 모델의 처리 절차는 다음과 같습니다. 

  • 모델 로딩
  • face_db 상의 이미지 파일에서 특징 추출 및 임베딩 DB 저장
  • 입려된 대상 이미지 특징 추출 및 입베딩 DB 상에서 거리 측정 통한 인물 식별 

 

2) 환경 구성

 

코드 다운로드

$ git clone https://github.com/yeyupiaoling/Pytorch-MobileFaceNet
$ cd Pytorch-MobileFaceNet

 

판정할 인원들의 안면부 포함 사진 등록

 

face_db 폴더에 안면부가 포함된 사진이 등록되어 있어야 합니다. 이때 파일명은 해당 인원의 이름으로 설정되어야 합니다. 제 경우, zum에서 이미지 검색에서 런닝맨 멤버들의 사진을 일부 다운받아서 테스트를 진행했습니다. 

그림 : 테스트를 위한 안면 이미지 파일 준비 예시

 

 

3) 테스트

 

공개 코드에서 추론을 하는 방식은 다음과 같습니다.

# 단일 이미지 이용하는 방법
$ python infer.py --image_pat=temp/test.jpg

# 카메라 입력을 이용하는 방법
$ python infer_camera.py --camera_id=0

 

하지만, 제가 하려는 것은 영상 입력에서 안면식별을 하는 테스트입니다. 그래서, infer_camera.py 코드를 약간 수정해서 다음의 기능을 추가했습니다. 

  • 한글 출력 : 기본적으로 중국어 출력 가능 폰트로 되어 있는 것을 한글 폰트로 변경
  • video 파일 입력 지원 : 원래 코드는 카메라 입력인데 이를 파일로 변경합니다.
  • face db 상에 사람별 이미지를 다수 등록하고 감지 후 표시할 때 이름만 표시토록 합니다. 
import argparse
import functools
import os
import time

import cv2
import numpy as np
import torch
from PIL import ImageDraw, ImageFont, Image

from detection.face_detect import MTCNN
from utils.utils import add_arguments, print_arguments


from types import SimpleNamespace
opt = SimpleNamespace(camera_id=0,face_db_path='face_db', threshold=0.5, 
                      mobilefacenet_model_path='save_model/mobilefacenet.pth', mtcnn_model_path='save_model/mtcnn')


class Predictor:
    def __init__(self, mtcnn_model_path, mobilefacenet_model_path, face_db_path, threshold=0.7):
        self.threshold = threshold
        self.mtcnn = MTCNN(model_path=mtcnn_model_path)
        self.device = torch.device("cuda")

        self.model = torch.jit.load(mobilefacenet_model_path)
        self.model.to(self.device)
        self.model.eval()

        self.faces_db = self.load_face_db(face_db_path)

    def load_face_db(self, face_db_path):
        faces_db = {}
        for path in os.listdir(face_db_path):
            name = os.path.basename(path).split('.')[0]
            image_path = os.path.join(face_db_path, path)
            img = cv2.imdecode(np.fromfile(image_path, dtype=np.uint8), -1)
            imgs, _ = self.mtcnn.infer_image(img)
            if imgs is None or len(imgs) > 1:
                print('얼굴 DB의 %s 사진에 포함되어 있지 않은 사진은 자동으로 건너뜁니다' % image_path)
                continue
            imgs = self.process(imgs)
            feature = self.infer(imgs[0])
            faces_db[name] = feature[0][0]
        return faces_db

    @staticmethod
    def process(imgs):
        imgs1 = []
        for img in imgs:
            img = img.transpose((2, 0, 1))
            img = (img - 127.5) / 127.5
            imgs1.append(img)
        return imgs1

    # 예측된 그림
    def infer(self, imgs):
        assert len(imgs.shape) == 3 or len(imgs.shape) == 4
        if len(imgs.shape) == 3:
            imgs = imgs[np.newaxis, :]
        features = []
        for i in range(imgs.shape[0]):
            img = imgs[i][np.newaxis, :]
            img = torch.tensor(img, dtype=torch.float32, device=self.device)
            # 예측 실행
            feature = self.model(img)
            feature = feature.detach().cpu().numpy()
            features.append(feature)
        return features

    def recognition(self, img):
        imgs, boxes = self.mtcnn.infer_image(img)
        if imgs is None:
            return None, None
        imgs = self.process(imgs)
        imgs = np.array(imgs, dtype='float32')
        features = self.infer(imgs)
        names = []
        probs = []
        for i in range(len(features)):
            feature = features[i][0]
            results_dict = {}
            for name in self.faces_db.keys():
                feature1 = self.faces_db[name]
                prob = np.dot(feature, feature1) / (np.linalg.norm(feature) * np.linalg.norm(feature1))
                results_dict[name] = prob
            results = sorted(results_dict.items(), key=lambda d: d[1], reverse=True)
            print('얼굴비교결과 : ', results)
            result = results[0]
            prob = float(result[1])
            probs.append(prob)
            if prob > self.threshold:
                name = result[0]
                names.append(name)
            else:
                names.append('unknow')
        return boxes, names

    def add_text(self, img, text, left, top, color=(0, 0, 0), size=20):
        if isinstance(img, np.ndarray):
            img = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
        draw = ImageDraw.Draw(img)
        font = ImageFont.truetype('fonts/NanumGothic.ttf', size)
        draw.text((left, top), text, color, font=font)
        return cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR)

    # 얼굴 프레임 및 키 포인트 그리기
    def draw_face(self, img, boxes_c, names):
        if boxes_c is not None:
            for i in range(boxes_c.shape[0]):
                bbox = boxes_c[i, :4]
                name = names[i]
                corpbbox = [int(bbox[0]), int(bbox[1]), int(bbox[2]), int(bbox[3])]
                cv2.rectangle(img, (corpbbox[0], corpbbox[1]),
                              (corpbbox[2], corpbbox[3]), (255, 0, 0), 2)
                img = self.add_text(img, name.split('_')[0], corpbbox[0], corpbbox[1] -15, color=(0, 255, 0), size=30)
        cv2.imshow("result", img)
        key = cv2.waitKey(1)
        if key==ord('q'): exit
        elif key == ord('p') : cv2.waitKey(-1)


if __name__ == '__main__':
    predictor = Predictor(opt.mtcnn_model_path, opt.mobilefacenet_model_path, 
                          opt.face_db_path, threshold=opt.threshold)
    cap = cv2.VideoCapture('video_1.mp4')
    while True:
        ret, img = cap.read()
        if ret:
            start = time.time()
            boxes, names = predictor.recognition(img)
            if boxes is not None:
                predictor.draw_face(img, boxes, names)
                print('얼굴위치:', boxes.astype('int32').tolist())
                print('인식인물:', names)
                print('인식소요시간:%dms' % int((time.time() - start) * 1000))
            else:
                cv2.imshow("result", img)
                cv2.waitKey(1)

 

 

5) 테스트 결과 확인

 

테스트 결과 중에 제대로 나온 부분을 추려봤습니다. 아무래도 영상에서 안면 이미지 크기가 작은 경우에는 제대로 인식하지 못하는 경우가 많았습니다. 모델이 요구하는 최소 이미지 사이즈(112ㅌ112) 보다 작아서 그런 듯 합니다. 그보다 조금 더 큰 안면 이미지도 성능이 좀 떨어지는 경우가 있는데 이는 아무래도 모델 크기가 작아 인식을 위한 특징 추출에 다소의 제약이 있기 때문으로 생각됩니다. 그렇더라도 더 다양한 크기와 포즈의 사진이 등록되면 어느 정도 커버가 될 것 같다는 생각이 들었습니다. 

그림 : 영상파일에서 안면식별 테스트 예시

 


 

정리하며

 

이번에는 MTCNN을 이용해 안면감지를 하고 MobileFaceNet을 이용해 안면 특징 추출과 비교를 해서 영상 속의 인물 식별을 해 봤습니다. 훨씬 성능 좋은 모델들도 있겠지만 경량으로 임베디드 보드 상에서도 동작할 수 있는 수준의 공개 모델을 선정했었습니다. 이처럼 경량 AI 모델을 이용한 간단한 안면 식별 서비스는 경제적이면서도 실용적인 솔루션을 제공할 수 있습니다.  소규모 데이터로도 효과적인 학습과 추론이 가능하기 때문이죠. 게다가 가정, 학교, 커뮤니티 모임 등 다양한 상황에서 활용할 수 있기 때문입니다. 

 

아무쪼록 이렇게 손쉽게 활용할 수 있는 AI 서비스는 앞으로도 계속 등장할 겁니다. 어떻게 써먹느냐... 장난감(?!) 만드는 것을 좋아하는 호기심 많은 사람들에겐 그런 것이 즐거운 숙제겠죠. :-)

 


참고자료

 

Pytorch-MobileFaceNet

 

MobileFaceNet을 Pytorch를 이용해 구현한 사례입니다. 안면감지는 mtcnn, 식별은 mobilefacenet을 이용합니다. 

 

GitHub - yeyupiaoling/Pytorch-MobileFaceNet: Pytorch实现的人脸识别明细MobileFaceNet模型,在预测使用MTCNN检测

Pytorch实现的人脸识别明细MobileFaceNet模型,在预测使用MTCNN检测人脸,然后使用MobileFaceNet模型识别。 - yeyupiaoling/Pytorch-MobileFaceNet

github.com

 

 

EdgeFace

에지 디바이스를 위한 효율적인 얼굴 인식 모델

 

 

GitHub - otroshi/edgeface: EdgeFace: Efficient Face Recognition Model for Edge Devices [TBIOM 2024] the winner of compact track

EdgeFace: Efficient Face Recognition Model for Edge Devices [TBIOM 2024] the winner of compact track of IJCB 2023 Efficient Face Recognition Competition - otroshi/edgeface

github.com

 

FaceRecognitionAuth

  • Tensorflow Lite와 Google ML Kit 라이브러리를 이용해 Flutter로 만든 얼굴 인식 인증 

 

 

GitHub - MCarlomagno/FaceRecognitionAuth: 😀🤳 Simple face recognition authentication (Sign up + Sign in) written in Flutter

😀🤳 Simple face recognition authentication (Sign up + Sign in) written in Flutter using Tensorflow Lite and Firebase ML vision library. - MCarlomagno/FaceRecognitionAuth

github.com

 

그림 : Face 인증을 스마트폰에서 실행한 예시

 


5. Q&A

Q. 경량 AI 모델을 사용하는 이유는 무엇인가요?
경량 AI 모델은 비용 절감, 낮은 전력 소모, 소규모 데이터 학습이 가능해 소형 기기에서 실행하기 적합합니다.

 

Q. 어떤 임베디드 보드를 사용하는 것이 좋은가요?
Raspberry Pi와 NVIDIA Jetson Nano는 저렴하면서도 AI 연산이 가능해 많이 사용됩니다.

 

Q. 얼굴 데이터는 어떻게 보호할 수 있나요?
데이터는 암호화하여 저장하고, 접근 권한을 제한하며, 법적 규제를 준수하는 방법으로 보호할 수 있습니다.