Keras와 OpenCV를 활용하여 웹캠으로 실시간 나이, 성별, 감정 예측 (번역)

2021. 3. 26. 11:51노트/Python : 프로그래밍

출처 : towardsdatascience.com/real-time-age-gender-and-emotion-prediction-from-webcam-with-keras-and-opencv-bde6220d60a

 

Real-time Age, Gender and Emotion Prediction from Webcam with Keras and OpenCV

Find working codes and trained models here

towardsdatascience.com

Real-time Age, Gender and Emotion Prediction from Webcam with Keras and OpenCV

Find working codes and trained models here

개요 

Covid-19시대가 되면서, Zoom 미팅 / Team 채팅과 같은 가상 상호작용에 더욱 의존적이게 되었습니다. 이 실시간 웹캠 비디오는 탐구할 풍부한 데이터 소스가 되고 있습니다. 이번 글에서는 판매원이 그들의 고객을 더 잘 이해할 수 있도록하는 나이, 성별, 감정 예측의 use case를 탐구할 것입니다. 

 

데모 

real-time prediction from my webcam (gif by author)

 

전반적 구조 

Overall implementation structure (image by author)

위 그림에서 보여줬듯이, 실행 과정은 4가지 주요 스텝을 포함합니다 : 

  1.  웹캠으로 부터 입력 프레임을 받습니다. 
  2.  웹캠에서 얼굴을 인식하고, 3개의 딥러닝 모델(즉, 나이, 성별, 감정 예측 모델)을 위한 이미지를 준비합니다.
  3.  모델에 가공된 얼굴을 보내서 예측결과를 받습니다. 
  4.  화면에 경계 상자를 사용하여 예측 결과를 렌더링합니다. 

이번 실행에서는 step 2에서 최신의 얼굴 인식 모델인, MTCNN을 사용할 것입니다. 여기에 파이썬 케라스로 이용가능한 안정된 파이썬 코드가 있습니다. 

 

step 3 에서는, 우리의 커스터마이징된 모델을 트레이닝 할 것입니다. 하지만 적은 노력과 더 좋은 정확도를 위해서, 전이학습 기법을 고려하길 원할지도 모릅니다. VGG-face, FaceNet, GoogLeNet을 포함한 많은 pre-trained models이 이용가능합니다. 이 pre-trained models들은 아마 다른 입력 사이즈를 요구할지도 모른다는 사실을 주목하세요. 그래서 step 2 에서 인식된 얼굴이 이에 따라 가공될 필요가 있습니다.  

 

MTCNN을 활용한 얼굴 인식 

얼굴 인식은 최근 딥러닝의 많은 응용이 되고 있습니다. 많은 알고리즘은 이미지/ 비디오에서 더 빠르고, 정확한 얼굴 인식을 제안하고 있죠. MTCNN은 FaceNet에 기반한 알고리즘 중 하나입니다. 

 

파이썬 실행에서, 모델은 사전-학습및 최적화가 되었으며, 그에 따라 모델의 사용을 직접적으로 만들 수 있습니다. 그럼에도 불고하고, 모델의 출력값을 이해하는 것은 여전히 중요합니다. 

 

[
    {
        'box': [277, 90, 48, 63],
        'keypoints':
        {
            'nose': (303, 131),
            'mouth_right': (313, 141),
            'right_eye': (314, 114),
            'left_eye': (291, 117),
            'mouth_left': (296, 143)
        },
        'confidence': 0.99851983785629272
    }
]

모든 이미지에서 MTCNN은 사진에서 탐지된 얼굴을 나타내는 사전형 구조 리스트를 출력합니다. 모든 얼굴은 경계박스(얼굴 주변을 둘러싸는 직사각형)로 표현이 됩니다 

  • box : [x,y,width, height], x 와 y는 경계박스의 왼쪽 위쪽의 좌표가 됩니다. 
  • keypoints : 탐지된 얼굴 랜드마크의 사전형 구조 
  • confidence : 탐지된 얼굴에 대한 모델의 신뢰도 점수, 1이 가장 높은 신뢰도를 가짐. 

 

나이/성별/감정 모델 학습을 위한 데이터 셋 

감정 모델은 CKPlus Facial Emotion dataset 으로 부터 학습됩니다. 이 데이터셋은 7개의 감정 : anger, contempt, disgust, fear, happy, sadness, surprise, 으로 분류된 981개의 이미지를 포함합니다. 각 이미지는 grey scale이며, 48*48의 고정된 크기를 가지고 있습니다. 

Image examples from CKPlus dataset (image by author)

 

나이와 성별 모델은 UTKface dataset 으로부터 학습됩니다. 데이터셋은 20k 이미지보다 더 많은 것을 포함하고 있습니다. 각 이미지는 나이, 성별, 인종으로 라벨링되있습니다. 전체 이미지와, 얼굴만 잘린 사진 둘다 다운로드 가능합니다. 이번 글에서는 전체 사진을 사용할 것이며, 얼굴 정렬 방법을 구현해서 정확도를 높일 것입니다. 

 

Image examples from UTKface dataset (image by author)

 

이미지 전처리 - UTKface dataset 

MTCNN이나 다른 얼굴 인식 모델을 사용하여 전체 사진으로부터 얼굴만 자를 필요가 있습니다. 그러나 대부분 알고리즘들은 탐지된 얼굴의 크기나 위치에 따라 다른 모양의 경계 박스를 가져옵니다. 

Example of MTCNN cropping faces with different sizes (image by author)

딥러닝 모델은 입력 이미지가 표준 크기를 가지도록 요구합니다. (주의사항: 완전한 컨볼루션 네트워크에는 해당되지 않으며, 본 문서에서는 범위를 벗어납니다) 그래서, 잘린 얼굴을 resizing하는 것이 필요합니다. 직접적인 resizing은 명백한 단점, - 즉 얼굴 변형- 을 포함한 가장 흔하고 간단한 방법입니다. 아래 그림에서 설명했듯, 얼굴은 직접적인 resizing 후에 상당히 넓혀집니다. 이는 우리 모델의 성능에 부정적인 영향을 줍니다. 

Example of face deformation as the result of direct resizing (image by author)

이상적인 잘린 얼굴 사진은 왜곡과 사이즈 변형을 요구하지 않도록 중앙에 위치된 얼굴을 가져야 합니다. 만약 요구되는 사이즈가 정사각형이라면, 다음 방법이 트릭이 될 수 있을것입니다. 

 

  1. MTCNN으로 부터 얼굴 경계 박스를 얻습니다. 
  2. 경계박스의 중심 점을 찾습니다. 
  3. 경계박스의 높이와 넓이 사이의 최대값을 찾습니다. 
  4. 중심 및 최대 측면 길이를 기준으로 새 경계박스를 그립니다. 
  5. 새 경계 박스에서 자른 얼굴의 크기를 필요한 크기로 조정합니다. 

만약 요구되는 크기가 정사각형이 아니라면, step 3 과 step 4는 필요한 측면과 동일한 비율을 유지하도록 조정해야합니다. 

 

Example of centered resizing (image by author)

 

이미지 전처리 - CKPlus Facila Emotion dataset 

이미지 형식이 grey scale이며, 작은 크기이기 때문에, 감정 예측을 하는데 이상적인 데이터셋이 아닙니다. 모든 이미지들이 잘 짤렸고, 정렬되어 빠른 프로토타이핑에 좋은 점이 장점입니다. 

 

이 데이터셋에서 한가지 주의할점은 : 각 감정 클래스에 대해, 개인의 얼굴이 3번 반복되었다는 점입니다. 그래서 만약 train/test split이 랜덤으로 된다면, 타겟 부족이 발생할 것입니다. 피실험자에 기반하여 나누거나 랜덤으로 나누기 전에 중복을 제거하는 것을 추천합니다. 

 

Repeated faces in the CKPlus dataset (image by author)

 

모델의 구조 

3개의 타겟들에 대해서 나이는 가장 어려운 과업입니다. 때때로 사람들도 다른 사람의 나이를 추측하는데 실수를 하기도 합니다. 그래서 나이 예측에 대해 깊은 모델이 필요할 것입니다. 일반적으로, 전형적인 컨볼루젼 신경망일 것입니다. 이 모든 모델 구조가 조정되지도 않았고, 최적화되지도 않았다는 점을 주목해주세요. 글의 목적은 딥러닝 모델의 미세-조정을 포함하지 않았습니다. 

input = Input(shape=(224, 224, 3))

cnn1 = Conv2D(128, kernel_size=3, activation='relu')(input)
cnn1 = Conv2D(128, kernel_size=3, activation='relu')(cnn1)
cnn1 = Conv2D(128, kernel_size=3, activation='relu')(cnn1)
cnn1 = MaxPool2D(pool_size=3, strides=2)(cnn1)

cnn2 = Conv2D(128, kernel_size=3, activation='relu')(cnn1)
cnn2 = Conv2D(128, kernel_size=3, activation='relu')(cnn2)
cnn2 = Conv2D(128, kernel_size=3, activation='relu')(cnn2)
cnn2 = MaxPool2D(pool_size=3, strides=2)(cnn2)

cnn3 = Conv2D(256, kernel_size=3, activation='relu')(cnn2)
cnn3 = Conv2D(256, kernel_size=3, activation='relu')(cnn3)
cnn3 = Conv2D(256, kernel_size=3, activation='relu')(cnn3)
cnn3 = MaxPool2D(pool_size=3, strides=2)(cnn3)

cnn4 = Conv2D(512, kernel_size=3, activation='relu')(cnn3)
cnn4 = Conv2D(512, kernel_size=3, activation='relu')(cnn4)
cnn4 = Conv2D(512, kernel_size=3, activation='relu')(cnn4)
cnn4 = MaxPool2D(pool_size=3, strides=2)(cnn4)

dense = Flatten()(cnn4)
dense = Dropout(0.2)(dense)
dense = Dense(1024, activation='relu')(dense)
dense = Dense(1024, activation='relu')(dense)

output = Dense(1, activation='linear', name='age')(dense)

model = Model(input, output)
model.compile(optimizer=Adam(0.0001), loss='mse', metrics=['mae'])

Age model 

input = Input(shape=(224, 224, 3))
cnn1 = Conv2D(36, kernel_size=3, activation='relu')(input)
cnn1 = MaxPool2D(pool_size=3, strides=2)(cnn1)
cnn2 = Conv2D(64, kernel_size=3, activation='relu')(cnn1)
cnn2 = MaxPool2D(pool_size=3, strides=2)(cnn2)
cnn3 = Conv2D(128, kernel_size=3, activation='relu')(cnn2)
cnn3 = MaxPool2D(pool_size=3, strides=2)(cnn3)
cnn4 = Conv2D(256, kernel_size=3, activation='relu')(cnn3)
cnn4 = MaxPool2D(pool_size=3, strides=2)(cnn4)
cnn5 = Conv2D(512, kernel_size=3, activation='relu')(cnn4)
cnn5 = MaxPool2D(pool_size=3, strides=2)(cnn5)
dense = Flatten()(cnn5)
dense = Dropout(0.2)(dense)
dense = Dense(512, activation='relu')(dense)
dense = Dense(512, activation='relu')(dense)
output = Dense(1, activation='sigmoid', name='gender')(dense)
sex_model = Model(input, output)
sex_model.compile(optimizer=Adam(learning_rate=0.0001), loss='binary_crossentropy', metrics=['accuracy'])

Gender model

input = Input(shape=(48, 48, 1))
cnn1 = Conv2D(36, kernel_size=3, activation='relu')(input)
cnn1 = MaxPool2D(pool_size=3, strides=2)(cnn1)
cnn2 = Conv2D(64, kernel_size=3, activation='relu')(cnn1)
cnn2 = MaxPool2D(pool_size=3, strides=2)(cnn2)
cnn3 = Conv2D(128, kernel_size=3, activation='relu')(cnn2)
cnn3 = MaxPool2D(pool_size=3, strides=2)(cnn3)
dense = Flatten()(cnn3)
dense = Dropout(0.3)(dense)
dense = Dense(256, activation='relu')(dense)
output = Dense(7, activation='softmax', name='race', kernel_regularizer=l1(1))(dense)
emotion_model = Model(input, output)
emotion_model.compile(optimizer=Adam(learning_rate=0.0001), loss='categorical_crossentropy', metrics=['categorical_accuracy'])

Emotion model

 

OpenCV와의 통합 설명 

기본적으로, openCV는 당신의 웹캠으로부터 비디오를 캡쳐합니다 (line2). 매 프레임마다 캡쳐된 것을 RGB포멧으로 바꿀 것입니다. (line 19) 이 RGB프레임은 MTCNN을 사용해서 프레임 속에 모든 얼굴들을 탐지하는 detect_face function 으로 보내질 것입니다. (line22) 그리고 모든 얼굴마다, 3개의 학습된 모델을 사용하여 결과를 생성하기 위해 학습할 것입니다. 이 결과는 얼굴 경계 상자 위치(top, right, bottom, left)와 함께 출력됩니다. 

 

OpenCV는 그리고나서 경계 상자의 위치를 사용하여 프레임에 직사각형을 그립니다. (line 27) 그리고 예측 결과를 텍스트로 나타냅니다(line 29 - line32). 

 

detect_face function의 실행은 소스코드에서 확인할 수 있습니다. 감정 모델이 grey-scale images로부터 학습됬기 때문에 감정 모델로 예측되기 전에 RGB 이미지는 grey-scale로 바뀔 필요가 있습니다. 

 

# Get a reference to webcam 
video_capture = cv2.VideoCapture(0)

emotion_dict = {
    0: 'Surprise',
    1: 'Happy', 
    2: 'Disgust',
    3: 'Anger',
    4: 'Sadness',
    5: 'Fear',
    6: 'Contempt'
}

while True:
    # Grab a single frame of video
    ret, frame = video_capture.read()

    # Convert the image from BGR color (which OpenCV uses) to RGB color 
    rgb_frame = frame[:, :, ::-1]

    # Find all the faces in the current frame of video
    face_locations = detect_face(rgb_frame)

    # Display the results
    for top, right, bottom, left, sex_preds, age_preds, emotion_preds in face_locations:
        # Draw a box around the face
        cv2.rectangle(frame, (left, top), (right, bottom), (0, 0, 255), 2)
        
        sex_text = 'Female' if sex_preds > 0.5 else 'Male'
        cv2.putText(frame, 'Sex: {}({:.3f})'.format(sex_text, sex_preds), (left, top-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (36,255,12), 1)
        cv2.putText(frame, 'Age: {:.3f}'.format(age_preds), (left, top-25), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (36,255,12), 1)
        cv2.putText(frame, 'Emotion: {}({:.3f})'.format(emotion_dict[np.argmax(emotion_preds)], np.max(emotion_preds)), (left, top-40), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (36,255,12), 1)
        
    # Display the resulting image
    cv2.imshow('Video', frame)

    # Hit 'q' on the keyboard to quit!
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

# Release handle to the webcam
video_capture.release()
cv2.destroyAllWindows()

OpenCV main script 

 

향상을 위한 가능한 활동 

  • 감정 예측을 위한 더 나은 데이터셋 
  • 컴퓨터적인 리소스 제한으로 인해, UTKface dataset으로 부터 오직 5k 이미지만 age/gender 모델 학습에 사용되었다는 점. 더 많은 이미지를 사용함으로써 모델 성능이 향상될 수 있다. 
  • 이미지 증식 
  • 전이 학습/ 모델 구조 조정 

 

읽어주셔서 감사합니다!