매직코드
article thumbnail

프로젝트를 하게된 이유

멀티모달에 관심이 생겨서 쉬운것부터 토이 프로젝트를 진행했는데 이미지, 오디오가 연속적으로 연결되어있는 비디오 데이터를 이용하는 것이야말로 멀티모달을 어느정도 공부했다고 할 수 있을 것 같아 노트북에 있는 웹캠으로 뭔가를 할 수 없을까 하다가 실시간 감정분석을 시도해보았다.

 

유튜브에 올라와있는 다른 멀티모달 비디오처럼 화자분석이나 마우스로 대상을 지정하면 지정된 대상의 소리만 나오는 방식의 멀티모달도 있었으나 실시간으로 내 얼굴을 통해 분석을 할 수 있다는 점에서 웹캠을 이용한 감정분석이 좀 더 흥미로웠다.

 

개요

감정분석이란?

감정분석은 얼굴표정, 음성, 자세 등의 다양한 신호를 분석하여 감정 상태를 이해하고 해석하는 기술로,

이번 프로젝트에서는 얼굴표정을 이용한 감정분석을 시도했다.

그동안 출시된 인공지능들은 텍스트를 있는 그대로 받기 때문에 비꼼이나 반어법 등에 대한 이해도가 떨어졌고

화자가 쓰는 단어나 음성의 높낮이 등을 파악하지 못해 화자의 감정을 알 수 없었지만, 감정분석 멀티모달을 통해 인공지능이 사용자의 감정을 파악하고 그에 맞는 상황대처능력 및 답변의 품질이 좋아질 수 있다.

 

데이터 다운로드

본 프로젝트에서 얼굴감지 모델을 만들기 위한 학습데이터는 캐글의 <Challenges in Representation Learning: Facial Expression Recognition Challenge> 에서 다운받았다. tar.gz 압축파일을 풀면 감정데이터와 이미지 pixel데이터를 포함한 fer2013.csv 파일을 얻을 수 있다.

 

모델 다운로드

얼굴감지를 위해서 openCV에서 제공하는 cascade pre-trained 모델을 사용했다.

사용 모델은 "haarcascade_frontalface_default.xml"로 이 포스팅에서도 다운받을 수 있도록 첨부했다.

haarcascade_frontalface_default.xml
0.89MB

 

문제 해결 방안

  1. 실시간으로 들어오는 비디오를 프레임 단위로 쪼개서 순간의 이미지로 만든다.
  2. 이미지 내에서 얼굴을 감지한다.
  3. 얼굴의 눈, 코, 입 등의 부위를 감지하여 감정을 분석한다.

 

실습코드

import warnings
warnings.filterwarnings('ignore')

# 데이터 확인
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

# Dataset 만들기
import keras
from keras.utils import to_categorical

# Detect Face
import cv2
from scipy.ndimage import zoom

# Model
from keras.models import Sequential
from keras.layers.convolutional import Conv2D, MaxPooling2D
from keras.layers.core import Dense, Dropout, Activation, Flatten
from keras.layers.normalization import batch_normalization
from keras.models import Model
from keras.preprocessing.image import ImageDataGenerator

 

변수설정

# gpu사용 변수
device = torch.device('cuda:3') if torch.cuda.is_available() else torch.device('cpu')

모델을 만들 때 학습은 GPU에서 진행하고 만든 모델을 저장하여 로컬로 옮겼다.

웹캠을 사용하려면 로컬에서 프로그램을 돌려야하기 때문이다.

 

데이터 확인

df = pd.read_csv('../../data/face_detect/fer2013.csv')

# 이미지 픽셀 list로 만들기
df['pixels'] = df['pixels'].apply(lambda pixel: np.fromstring(pixel, sep=' '))
df.head()

df.head()

 

# train test 나누기
train_df = df[df['Usage']=='Training']
test_df = df[df['Usage']=='PublicTest']
print(train_df.shape)
print(test_df.shape)

 

Dataset 만들기

shape_x = 48
shape_y = 48

# X_train, y_train, X_test, y_test split
X_train = train_df.iloc[:, 1].values # pixles
y_train = train_df.iloc[:, 0].values # emotion

X_test = test_df.iloc[:, 1].values # pixles
y_test = test_df.iloc[:, 0].values # emotion

# 전체데이터
X = df.iloc[:, 1].values # pixles
y = df.iloc[:, 0].values # emotion

# array([array([....])]) 구조를 바꾸기 위한 np.vstack
X_train = np.vstack(X_train)
X_test = np.vstack(X_test)
X = np.vstack(X)
# 4차원 데이터셋 만들기 (데이터개수, x축, y축, rgb)
X_train_ds = np.reshape(X_train, (X_train.shape[0], shape_x, shape_y, 1))
y_train_ds = np.reshape(y_train, (y_train.shape[0], 1))

X_test_ds = np.reshape(X_test, (X_test.shape[0], shape_x, shape_y, 1))
y_test_ds = np.reshape(y_test, (y_test.shape[0], 1))

print(X_train_ds.shape, y_train_ds.shape)
print(X_test_ds.shape, y_test_ds.shape)

X_train_ds shape (28709, 48, 48, 1) y_train_ds shape (28709, 1)

X_test_ds shape (3589, 48, 48, 1) y_test_ds shape (3589, 1)

 

# 데이터타입 float로 변경
train_data = X_train_ds.astype('float32')
test_data = X_test_ds.astype('float32')

# 스케일링
train_data /= 225
test_data /= 225

# y데이터 원핫인코딩
train_labels_onehot = to_categorical(y_train_ds)
test_labels_onehot = to_categorical(y_test_ds)

# input_shape 설정
n_rows, n_cols, n_dims = X_train_ds.shape[1:]
input_shape = (n_rows, n_cols, n_dims)
print(input_shape)

inpuy_shape (48, 48, 1)

 

# 라벨 숫자를 문자로 변경
def get_label(argument):
    labels = {0:'angry', 1:'disgust', 2:'fear', 3:'happy', 4:'sad', 5:'surprise', 6:'neutral'}
    return(labels.get(argument, 'Invalid emotion'))
    
# 데이터 시각화
plt.figure(figsize=[10,5])

# Train data 중 100번째 이미지
n=100

plt.subplot(121)
plt.imshow(np.squeeze(X_train_ds[n,:,:], axis = 2), cmap='gray')
plt.title("Ground Truth : {}".format(get_label(int(y_train[n]))))

# Test data 중 100번째 이미지
plt.subplot(122)
plt.imshow(np.squeeze(X_test_ds[n,:,:], axis = 2), cmap='gray')
plt.title("Ground Truth : {}".format(get_label(int(y_test[n]))))

 

Detect Faces

  • 얼굴을 감지하는 모델은 openCV에서 제공하는 cascade를 사용
# 전체 이미지에서 얼굴을 찾아내는 함수
def detect_face(frame):
    
    # cascade pre-trained 모델 불러오기
    face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
    
    # RGB를 gray scale로 바꾸기
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    
    # cascade 멀티스케일 분류
    detected_faces = face_cascade.detectMultiScale(gray,
                                                   scaleFactor = 1.1,
                                                   minNeighbors = 6,
                                                   minSize = (shape_x, shape_y),
                                                   flags = cv2.CASCADE_SCALE_IMAGE
                                                  )
    
    coord = []
    for x, y, w, h in detected_faces:
        if w > 100:
            sub_img = frame[y:y+h, x:x+w]
            coord.append([x, y, w, h])
            
    return gray, detected_faces, coord
# 전체 이미지에서 찾아낸 얼굴을 추출하는 함수
def extract_face_features(gray, detected_faces, coord, offset_coefficients=(0.075, 0.05)):
    new_face = []
    for det in detected_faces:
        
        # 얼굴로 감지된 영역
        x, y, w, h = det
        
        # 이미지 경계값 받기
        horizontal_offset = np.int(np.floor(offset_coefficients[0] * w))
        vertical_offset = np.int(np.floor(offset_coefficients[1] * h))
        
        # gray scacle 에서 해당 위치 가져오기
        extracted_face = gray[y+vertical_offset:y+h, x+horizontal_offset:x-horizontal_offset+w]
        
        # 얼굴 이미지만 확대
        new_extracted_face = zoom(extracted_face, (shape_x/extracted_face.shape[0], shape_y/extracted_face.shape[1]))
        new_extracted_face = new_extracted_face.astype(np.float32)
        new_extracted_face /= float(new_extracted_face.max()) # sacled
        new_face.append(new_extracted_face)
        
    return new_face
suzy = cv2.imread('../../data/face_detect/e_neutral.jpeg')
plt.imshow(cv2.cvtColor(suzy, cv2.COLOR_BGR2RGB))

이 때 나는 재미를 위해서 인터넷에 돌아다니는 연예인들의 얼굴을 별도로 저장해 불러왔다.

 

# 얼굴 찾기
gray, detected_faces, coord = detect_face(suzy)

# 찾은 얼굴 추출하기
face_zoom = extract_face_features(gray, detected_faces, coord)

# 시각화
plt.imshow(face_zoom[0])

 

지금까지 데이터를 확인하고, 얼굴 추출 과정이 잘 동작하는지 확인하는 과정이었다.

다음단계부터가 진짜 멀티모달 모델 학습이다.

1. 초반에 받았던 fer2013.csv 파일 안에 있는 이미지 픽셀들로부터 얼굴을 추출하고

2. 추출한 얼굴들을 input data로 넣어서 output으로 감정 분석을 할 수 있도록 학습시킨다.

3. 학습한 모델을 로컬에 저장해 웹캠 실행 시 들어오는 비디오 데이터를 모델의 input으로 받을 수 있게 하여 웹캠에서 실시간으로 감정분석을 할 수 있도록 한다.

 

 

 

감정분석 단순모델 구축

 

def simple_model():
    model = Sequential()
    
    # Input layer
    model.add(Conv2D(32, (3, 3), padding='same', activation='relu', input_shape=input_shape))
    
    # Add layers
    model.add(Conv2D(32, (3, 3), activation='relu'))
    model.add(MaxPooling2D(pool_size=(2, 2)))
    model.add(Dropout(0.2))
    
    model.add(Conv2D(64, (3, 3), padding='same', activation='relu'))
    model.add(Conv2D(64, (3, 3), activation='relu'))
    model.add(MaxPooling2D(pool_size=(2, 2)))
    model.add(Dropout(0.2))
    
    model.add(Conv2D(64, (3, 3), padding='same', activation='relu'))
    model.add(Conv2D(64, (3, 3), activation='relu'))
    model.add(MaxPooling2D(pool_size=(2, 2)))
    model.add(Dropout(0.2))
    
    # Flatten
    model.add(Flatten())
    
    # Fully connected layer
    model.add(Dense(512, activation='relu'))
    
    # Output layer : n_classes=7
    model.add(Dense(7, activation='softmax'))
    
    return model
model = simple_model()
model.summary()

 

# 각 레이어마다 어떻게 변화하는지 시각화

# 마지막 Dense 레이어 2개를 제외한 레이어별 out 담기 
layer_outputs = [layer.output for layer in model.layers[:12]]
activation_model = Model(inputs=model.input, outputs=layer_outputs)

# 시각화 할 때 어떤 레이어인지 확인할 수 있도록 레이어 이름 담기
layer_names = []
for layer in model.layers[:12]:
    layer_names.append(layer.name)
    
# 수지 얼굴 이미지 가져오기
suzy = cv2.imread('../../data/face_detect/e_neutral.jpeg')
gray, detected_faces, coord = detect_face(suzy)
face_zoom = extract_face_features(gray, detected_faces, coord)
face = face_zoom[0]

plt.subplot(211)
plt.title("Original Face")
plt.imshow(suzy)

plt.subplot(212)
plt.title("Extracted Face")
plt.imshow(face)

plt.show()

 

# 수지 얼굴 모델에 넣어서 모델이 어떻게 작동하는지 시각화
to_predict = np.reshape(face.flatten(), (1,48,48,1))
res = model.predict(to_predict)
activations = activation_model.predict(to_predict)

images_per_row = 16

for layer_name, layer_activation in zip(layer_names, activations): # Displays the feature maps
    n_features = layer_activation.shape[-1] # Number of features in the feature map
    size = layer_activation.shape[1] #The feature map has shape (1, size, size, n_features).
    n_cols = n_features // images_per_row # Tiles the activation channels in this matrix
    display_grid = np.zeros((size * n_cols, images_per_row * size))
    for col in range(n_cols): # Tiles each filter into a big horizontal grid
        for row in range(images_per_row):
            channel_image = layer_activation[0,:, :,col * images_per_row + row]
            channel_image -= channel_image.mean() # Post-processes the feature to make it visually palatable
            channel_image /= channel_image.std()
            channel_image *= 64
            channel_image += 128
            channel_image = np.clip(channel_image, 0, 255).astype('uint8')
            display_grid[col * size : (col + 1) * size, # Displays the grid
                         row * size : (row + 1) * size] = channel_image
    scale = 1. / size
    plt.figure(figsize=(scale * display_grid.shape[1],
                        scale * display_grid.shape[0]))
    plt.title(layer_name)
    plt.grid(False)
    plt.imshow(display_grid, aspect='auto', cmap='viridis')

.

.

.

모델이 작동하는 모습을 시각화

 

모델 학습

# 이미지 데이터 증강
datagen = ImageDataGenerator(zoom_range=0.2,          # 랜덤하게 이미지 줌 하는 비율
                             rotation_range=10,       # 램덤하게 이미지 회전하는 비율 (0도~180도)
                             width_shift_range=0.1,   # 랜덤하게 이미지 가로로 이동하는 비율
                             height_shift_range=0.1,  # 랜덤하게 이미지 세로로 이동하는 비율
                             horizontal_flip=True,    # 랜덤하게 이미지 수평 뒤집기
                             vertical_flip=False)     # 랜덤하게 이미지 수직 뒤집기
                             
# 모델 학습을 위한 파라미터 설정
batch_size = 256
n_epochs = 100
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
history = model.fit_generator(datagen.flow(train_data, train_labels_onehot, batch_size=batch_size),
                              steps_per_epoch=int(np.ceil(train_data.shape[0]/float(batch_size))),
                              epochs=n_epochs,
                              validation_data=(test_data, test_labels_onehot)
                             )

 

모델 평가 Evaluate

# Loss Curves
plt.figure(figsize=[8,6])
plt.plot(history.history['loss'],'r',linewidth=2.0)
plt.plot(history.history['val_loss'],'b',linewidth=2.0)
plt.legend(['Training loss', 'Validation Loss'],fontsize=18)
plt.xlabel('Epochs ',fontsize=16)
plt.ylabel('Loss',fontsize=16)
plt.title('Loss Curves',fontsize=16)
 
# Accuracy Curves
plt.figure(figsize=[8,6])
plt.plot(history.history['accuracy'],'r',linewidth=2.0)
plt.plot(history.history['val_accuracy'],'b',linewidth=2.0)
plt.legend(['Training Accuracy', 'Validation Accuracy'],fontsize=18)
plt.xlabel('Epochs ',fontsize=16)
plt.ylabel('Accuracy',fontsize=16)
plt.title('Accuracy Curves',fontsize=16)

 

감정분석 모델 결과 확인

결과 확인을 위해서 인터넷에서 돌아다니는 연예인 사진을 모아서 별도로 저장한 뒤

face 변수에 넣어 모델 결과를 확인했다.

 

# 원본이미지 확인
face = cv2.imread('../../data/face_detect/e_happy.png')

# 얼굴 추출
gray, detected_faces, coord = detect_face(face)
face_zoom = extract_face_features(gray, detected_faces, coord)

# 모델 추론
input_data = np.reshape(face_zoom[0].flatten(), (1, 48, 48, 1))
output_data = model.predict(input_data)
result = np.argmax(output_data)

# 결과 문자로 변환
if result == 0:
    emotion = 'angry'
elif result == 1:
    emotion = 'disgust'
elif result == 2:
    emotion = 'fear'
elif result == 3:
    emotion = 'happy'
elif result == 4:
    emotion = 'sad'
elif result == 5:
    emotion = 'surprise'
elif result == 6:
    emotion = 'neutral'
    
# 시각화
plt.subplot(121)
plt.title("Original Face")
plt.imshow(cv2.cvtColor(face, cv2.COLOR_BGR2RGB))

plt.subplot(122)
plt.title(f"Extracted Face : {emotion}")
plt.imshow(face_zoom[0])

happy를 예측하면 성공!
angry를 예측하면 성공!
무표정(neutral)을 예측하면 성공!
sad를 예측하면 성공!
surprise를 예측하면 성공!

 

Inference 코드 - 웹캠적용

위에서 이미지를 넣어서 감정분석을 하는것 까지만 해도 충분히 멀티모달을 잘 활용한 것이다.

모델학습을 위해서 이미지데이터 + 감정텍스트 데이터 2가지 종류의 데이터를 input으로 넣어주었기 때문이다.

여기서 한발 더 나아가 웹캠을 통해 실시간으로 내 얼굴 감정을 분석해보자.

 

inference코드는 실습코드와 다르기 때문에 새로운 파일을 하나 만들어준다.

 

import warnings
warnings.filterwarnings('ignore')

# 데이터 확인
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

# Dataset 만들기
import keras
from keras.utils import to_categorical

# Detect Face
import cv2
from scipy.ndimage import zoom

# Model
import torch
from keras.models import Sequential
from keras.layers.convolutional import Conv2D, MaxPooling2D
from keras.layers.core import Dense, Dropout, Activation, Flatten
from keras.layers.normalization import batch_normalization
from keras.models import Model
from keras.preprocessing.image import ImageDataGenerator

 

필요한 함수

inference를 할 때는 저장된 모델에 학습데이터와 동일한 형태의 input을 넣어주어야 하기 때문에 동일한 전처리 과정을 거쳐 동일한 형태의 input shape이 나오도록 처리한다.

 

shape_x = 48
shape_y = 48

# 전체 이미지에서 얼굴을 찾아내는 함수
def detect_face(frame):
    
    # cascade pre-trained 모델 불러오기
    face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
    
    # RGB를 gray scale로 바꾸기
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    
    # cascade 멀티스케일 분류
    detected_faces = face_cascade.detectMultiScale(gray,
                                                   scaleFactor = 1.1,
                                                   minNeighbors = 6,
                                                   minSize = (shape_x, shape_y),
                                                   flags = cv2.CASCADE_SCALE_IMAGE
                                                  )
    
    coord = []
    for x, y, w, h in detected_faces:
        if w > 100:
            sub_img = frame[y:y+h, x:x+w]
            coord.append([x, y, w, h])
            
    return gray, detected_faces, coord
# 전체 이미지에서 찾아낸 얼굴을 추출하는 함수
def extract_face_features(gray, detected_faces, coord, offset_coefficients=(0.075, 0.05)):
    new_face = []
    for det in detected_faces:
        
        # 얼굴로 감지된 영역
        x, y, w, h = det
        
        # 이미지 경계값 받기
        horizontal_offset = np.int(np.floor(offset_coefficients[0] * w))
        vertical_offset = np.int(np.floor(offset_coefficients[1] * h))
        
        # gray scacle 에서 해당 위치 가져오기
        extracted_face = gray[y+vertical_offset:y+h, x+horizontal_offset:x-horizontal_offset+w]
        
        # 얼굴 이미지만 확대
        new_extracted_face = zoom(extracted_face, (shape_x/extracted_face.shape[0], shape_y/extracted_face.shape[1]))
        new_extracted_face = new_extracted_face.astype(np.float32)
        new_extracted_face /= float(new_extracted_face.max()) # sacled
        new_face.append(new_extracted_face)
        
    return new_face

 

웹캠 영상 실시간 예측

# 모델 불러오기
model = keras.models.load_model('./face_emotion.h5')

# 인덱스번호로 웹캠연결 대부분 시스템적으로 0번부터 부여됨
video_capture = cv2.VideoCapture(0)
# 프레임 단위로 영상 캡쳐
while True:
    ret, frame = video_capture.read()
    # ret: 비디오를 성공적으로 읽어왔는지 확인 True/False
    # frame: 각 픽셀의 색상을 포함한 프레임 정보 Numpy
    
    face_index = 0
    gray, detected_faces, coord = detect_face(frame)
    
    try:
        face_zoom = extract_face_features(gray, detected_faces, coord)
        face_zoom = np.reshape(face_zoom[0].flatten(), (1, 48, 48, 1))
        x, y, w, h = coord[face_index]
        
        # 머리 둘레에 직사각형 그리기: (0, 255, 0)을 통해 녹색으로 선두께는 2
        cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2)
        
        # 감정 예측
        pred = model.predict(face_zoom)
        pred_result = np.argmax(pred)
        
        # 각 라벨별 예측 정도 표시
        cv2.putText(frame,                                   # 텍스트를 표시할 프레임
                    "Angry: " + str(round(pred[0][0], 3)),   # 텍스트 표시 "감정: 예측 probablity", 소수점 아래 3자리
                    (10, 50),                                # 텍스트 위치
                    cv2.FONT_HERSHEY_SIMPLEX,                # 폰트 종류
                    1,                                       # 폰트 사이즈
                    (0, 0, 255),                             # 폰트 색상
                    2                                        # 폰트 두께
                   )
        cv2.putText(frame, "Disgust: " + str(round(pred[0][1], 3)), (10, 90), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
        cv2.putText(frame, "Fear: " + str(round(pred[0][2], 3)), (10, 130), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
        cv2.putText(frame, "Happy: " + str(round(pred[0][3], 3)), (10, 170), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
        cv2.putText(frame, "Sad: " + str(round(pred[0][4], 3)), (10, 210), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
        cv2.putText(frame, "Surprise: " + str(round(pred[0][5], 3)), (10, 250), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
        cv2.putText(frame, "Neutral: " + str(round(pred[0][6], 3)), (10, 290), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
        
        # 예측값이 높은 라벨 하나만 프레임 옆에 표시
        if pred_result == 0:
            cv2.putText(frame, "Angry " + str(round(pred[0][0], 2)), (x, y), cv2.FONT_HERSHEY_SIMPLEX, 2, (0, 255, 0), 5)
        elif pred_result == 1:
            cv2.putText(frame, "Disgust " + str(round(pred[0][1], 2)), (x, y), cv2.FONT_HERSHEY_SIMPLEX, 2, (0, 255, 0), 5)
        elif pred_result == 2:
            cv2.putText(frame, "Fear " + str(round(pred[0][2], 2)), (x, y), cv2.FONT_HERSHEY_SIMPLEX, 2, (0, 255, 0), 5)
        elif pred_result == 3:
            cv2.putText(frame, "Happy " + str(round(pred[0][3], 2)), (x, y), cv2.FONT_HERSHEY_SIMPLEX, 2, (0, 255, 0), 5)
        elif pred_result == 4:
            cv2.putText(frame, "Sad " + str(round(pred[0][4], 2)), (x, y), cv2.FONT_HERSHEY_SIMPLEX, 2, (0, 255, 0), 5)
        elif pred_result == 5:
            cv2.putText(frame, "Surprise " + str(round(pred[0][5], 2)), (x, y), cv2.FONT_HERSHEY_SIMPLEX, 2, (0, 255, 0), 5)
        else:
            cv2.putText(frame, "Neutral " + str(round(pred[0][6], 2)), (x, y), cv2.FONT_HERSHEY_SIMPLEX, 2, (0, 255, 0), 5)
    
    except:
        continue
        
    # 결과 표시
    cv2.imshow('Video', frame)
    
    # 사용자가 q 키를 누르면 종료
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break
        
# 웹캠 해지
video_capture.release()

# 창 닫기: 창이 안닫히는 경우 쥬피터 닫기
cv2.destroyAllWindows()

 

내 얼굴을 공개하기 좀 그래서 핸드폰으로 얼굴사진을 보여주고 감정을 맞추게끔 해봤다.

 

 

이렇게 웹캠을 통해 실시간으로 감정을 예측하게 된다면 어딘가에 상용화 해서 사용할 수 있지 않을까??

 

 

참고: https://github.com/maelfabien/Multimodal-Emotion-Recognition/tree/master/03-Video

profile

매직코드

@개발법사

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!