CastleJo의 개발일지

Face Recognition + Mask Detection

|

여태 한 것들

Face Recognition은 Dlib으로 얼굴을 인식하고 특징점을 찾은 다음, DB에 있는 사진들과 비교하는 방식이다.
여기서 가장 치명적인 문제점은 마스크를 쓰고 있으면 인식을 하지 못한다는점.
그래서 Mask Detection과 병합을 해보려고 한 결과… 성공했다!

전체적인 코드는 여기에 있다.

작동 방법은 꽤 간단하다.

  1. 마스크를 낀 얼굴을 찾을 수 있는 모델로 위치를 추출
  2. 마스크를 쓰고있는지 확인
  3. 마스크를 쓰고있지 않을 시 얼굴의 특징점을 추출한 후 비교

얼굴의 위치를 추출

class FaceDetector:
    __net = None

    def __init__(self, prototxtPath, modelPath):
        self.__net = cv2.dnn.readNet(prototxtPath, modelPath)
        if self.__net is None:
            print("Face Detector 클래스 생성 실패, 프로그램 종료")
            sys.exit()

        print("Face Detector 클래스 생성 완료") 

얼굴을 찾는 모델을 인자로 받은 뒤 cv2.dnn.readNet 함수를 이용하여 얼굴을 찾는 모델을 net 변수로 받는다.

    def GetDetectionsFromFrame(self, frame):
        blob = cv2.dnn.blobFromImage(frame, 1.0, (300,300), (104.0,177.0,123.0))
        self.__net.setInput(blob)
        detections = self.__net.forward()#순방향 추론
        return detections[0,0]
    

다음 프레임을 인자로 받은 뒤 프레임을 blob화 시키고 추론을 시작한 뒤, 결과값을 리턴한다.
얼굴들의 정확도와 x,y좌표값이 있는 배열을 리턴한다.

    @staticmethod
    def GetConfidence(detection):
        return detection[2]  

detection 중 정확도만 리턴하는 함수이다. 코드의 명료성를 위해 분리시켜놨다.

    @staticmethod
    def GetFaceLocation(detection, frame):
        (height,width) = np.shape(frame)[:2]
        faceLocation  = detection[3:7] * np.array([width,height,width,height])
        (left, top, right, bottom) = faceLocation.astype("int")
        top = max(0,top)
        bottom = min(height-1,bottom)
        left = max(0,left)
        right = min(width-1,right)        

        return (left,top,right,bottom)

detection에서 위치를 추출하는 코드이다.
frame에서 w,h를 구한 뒤 정제한 뒤, 튜플을 리턴한다.

 @staticmethod    
    def GetFaceLocationForMD(detection, frame):

        faceLocation = FaceDetector.GetFaceLocation(detection,frame)
        (left, top, right, bottom) = faceLocation
        face = frame[top:bottom,left:right]
        face = cv2.cvtColor(face,cv2.COLOR_BGR2RGB)
        face = cv2.resize(face, (224, 224))

        return face

Mask Detection 라이브러리를 위해 추가 정체를 하는 과정이다.
Mask Detection의 모델을 사용할땐 해당 정제과정을 거쳐야 한다.

얼굴에서 마스크 유무를 판별

from tensorflow.keras.models import load_model
from tensorflow.keras.preprocessing.image import img_to_array
from tensorflow.keras.applications.mobilenet_v2 import preprocess_input
import sys
import numpy as np

class MaskDetector:
    def __init__(self, modelPath):
        self._model = load_model(modelPath)
        if self._model is None:
            print("Mask Detector 클래스 생성 실패, 프로그램 종료")
            sys.exit()
        print("Mask Detector 클래스 생성 완료")
        
    def MaskDetection(self, face):
        face = img_to_array(face)
        face = preprocess_input(face)
        face = np.expand_dims(face, axis = 0)

        #return (mask, withoutMask)
        return self._model.predict(face)[0]

Mask Detector 클래스의 생성자에서는 model 경로를 인자로 받는다.
다음 얼굴 이미지를 인자로 받은 뒤 모델을 적용한다.
그 다음 예측률을 리턴하는 간단한 클래스이다.
keras를 이용하였다.

Face Recognition

import face_recognition
import glob
import numpy as np

class FaceTool():
    def __init__(self):
        self.known_face = []
        path = "server/resources/classes/"
        classes = glob.glob(path+"*")
        for label in classes:
            imgs = glob.glob(label+"/*")
            label = label.split("\\")[-1]
            print(label)
            for img in imgs:
                self.registerImage(label, img)

    def registerImage(self, label, imgPath):
        try:
            image = face_recognition.load_image_file(imgPath)
            face_encoding = face_recognition.face_encodings(image)[0]
            self.known_face.append((label, face_encoding))
            print("reg img: %s(%s)"%(label, imgPath))
        except IndexError:
            print("failed to reg img: %s(%s)"%(label, imgPath))

Face Recognition에서 얼굴을 비교하기 위해 이미지에서 특징을 추출하는 과정이다.
지금은 테스트용으로 매 시행마다 받게 해놨지만, 나중엔 DB에서 검색하게 바꿔놔야 겠다.

def match(self, face_encoding):
        name = "Unknown"
        face_distances = face_recognition.face_distance(list(map(lambda x: x[1], self.known_face)), face_encoding)
        print(face_distances)
        best_match_index = np.argmin(face_distances)
        if face_distances[best_match_index] <= 0.5:
            name = self.known_face[best_match_index][0]
        
        return name, float(face_distances[best_match_index])

encoding 된 특징점을 가지고 저장된 얼굴들과 비교하는 과정이다.

Main 함수

Flask 서버를 만들었다.

from face_recognition.api import face_encodings
import numpy as np
import cv2
from flask import request, Blueprint
from server.tools.face_tool import FaceTool
from server.tools.face_detector import FaceDetector
from server.tools.mask_detector import MaskDetector

ft = FaceTool()
fd = FaceDetector('server/tools/face_detector/deploy.prototxt',
    'server/tools/face_detector/res10_300x300_ssd_iter_140000.caffemodel')
md = MaskDetector('server/tools/mask_detector.model')

bp = Blueprint('match', __name__, url_prefix='/match')

@bp.route('/', methods=['POST'], strict_slashes=False)

def match():
    result = []
    nparr = np.frombuffer(request.data, np.uint8) # 리퀘스트로 버퍼 읽기
    frame = cv2.imdecode(nparr, cv2.IMREAD_COLOR) # 버퍼 -> Mat

    for detection in fd.GetDetectionsFromFrame(frame):
    
        if fd.GetConfidence(detection) < 0.5: #임계점 이하일시 continue
            continue
        
        (mask,withoutMask) = md.MaskDetection(fd.GetFaceLocationForMD(detection, frame))
        
        if mask>withoutMask:
            print("Mask")
            result.append((1, None, None))

        else:
            print("No Mask")
            (left,top,right,bottom) = fd.GetFaceLocation(detection,frame)
            faceLocation = []
            faceLocation.append((top,right,bottom,left))
            faceEncoding = face_encodings(frame,faceLocation)[0]
            
            (name, face_distance) = ft.match(faceEncoding)
            #print(name, face_distance)                      
            result.append(( 0,
                            name,
                            (top.item(),right.item(),bottom.item(),left.item())
                          ))
            
    return {'data': result}, 200

match 함수에서 여태 소개한 함수들을 전부 사용하고 있다.
얼굴 위치와 프레임을 가지고 face recognition의 face_encodings 함수를 사용해 특징점을 추출하고, 그 것을 FaceTool.match 함수로 결과물을 만든 뒤 result 배열에 삽입한다.

이를 Requests로 전송한다.

결론

클라이언트는 8부능선을 지나온 것 같다.
앞으로 할 일은 적외선센서를 접목시키는 것 뿐!!