본문 바로가기

인턴일지

얼굴의 표정으로 7가지 감정을 예측하는 CNN모델

서론

CNN 모델을 이용하여 얼굴 표정을 인식하는 모델입니다.
angry, disgusted, fearful, happy, sad, suprised, neutral 총 7개의 표정을 인식합니다.
Facial Expression Recognition(FER) 2013에서 65%의 accuracy를 기록했습니다.
https://github.com/xionghc/Facial-Expression-Recognition

 

xionghc/Facial-Expression-Recognition

Facial-Expression-Recognition in TensorFlow. Detecting faces in video and recognize the expression(emotion). - xionghc/Facial-Expression-Recognition

github.com

GitHub에서 찾아 표정 인식 프로젝트를 진행했습니다. 이 코드를 선택한 이유는, 회사에서 사용하는 임베디드가 rknn 라이브러리를 이용하기 때문에, 원할한 모델 convert를 위해 선택했습니다. ^^
pytorch, keras 등 최근에 더 좋은 모델도 많으니, github에서 좋은 코드를 검색해서 사용하시길 추천드립니다!

 

본론

원래 코드는 webcam을 띄워 스크린샷하여 그 때의 표정을 인식하는 코드입니다.
외부 서버에는 webcam을 연결할 수 없기 때문에 웹캠을 이미지로 바꾸어주었습니다.
동영상으로 계속 표정을 인식하도록 구현하려 했으나, rknn 변환 후 init 시간에 의해 Segmentation Fault가 발생하여 (원인을 파악하지 못함) 일단 이미지 처리로 프로젝트를 마무리했습니다!

https://github.com/Seojiyoung/Emotion-Recognition

 

Seojiyoung/Emotion-Recognition

[ICT인턴] 얼굴의 표정으로 7가지 감정을 예측하는 CNN모델 (using RKNN). Contribute to Seojiyoung/Emotion-Recognition development by creating an account on GitHub.

github.com

코드를 수정하고 rknn 모델로 바꾸어 임베디드에서 사용할 수 있는 예제를 마무리했습니다 :)

 

코드 분석 및 개념 정리

텐서(Tensor): 텐서플로우에서 다양한 수학식을 계산하기 위한 가장 기본적이고 중요한 자료형
- Rank, Shape 개념을 가짐

텐서플로우 프로그램은 그래프 생성 ⇒ 그래프 실행 으로 짜여짐
- 그래프: 텐서들의 연산 모음
- 그래프 실행은 sess.run()

def demo(modelPath, showBox=False):
  #데이터타입은 float32, shape=[None,2304]
  face_x = tf.placeholder(tf.float32, [None, 2304])

플레이스홀더는 그래프에 사용할 입력값을 나중에 받기 위해 사용하는 매개변수
>> (?, 2304) 을 가져올 것임을 알 수 있다.

  #deepnn 모델 적용
  y_conv = deepnn(face_x)

deepnn은 직접 만든 함수로, model.py에서 deepnn 함수를 import하여 사용
- 모델 output을 y_conv 변수에 저장한다.

  #softmax
  probs = tf.nn.softmax(y_conv)

y_conv를 softmax 활성화하여 probs에 저장한다.

 

* softmax 함수 식: tf.exp(logits) / tf.reduce_sum(tf.exp(logits), axis)

출처:  https://medium.com/@a.ydobon/ml-softmax-소프트맥스-함수-2f4740141bfe

https://medium.com/@a.ydobon/ml-softmax-소프트맥스-함수-2f4740141bfe

 

[ML]Softmax 소프트맥스 함수

소프트맥스란?

medium.com

자세한 설명은 위의 블로그를 참고했습니다. :) 이미지로는 이해가 되지 않으니 들어가서 설명을 읽어보세요!

  #변수 선언
  saver = tf.train.Saver()
  ckpt = tf.train.get_checkpoint_state(modelPath)
  sess = tf.Session()
  # ckpt 모델 불러와 모델구동
  if ckpt and ckpt.model_checkpoint_path:
    saver.restore(sess, ckpt.model_checkpoint_path)
    print('Restore model sucsses!!')

ckpt와 ckpt모델 path가 있으면, ckpt로부터 파라미터, 모델 구조를 그래프에 저장한다.

[ckpt와 pbtxt의 차이점]
1) .ckpt는 모델의 변수(가중치) 저장 되어 있고, .pbtxt는 모델 구조가 텍스트로 저장되어 있다.
⇒ 일반적으로 .ckpt=.ckpt.data이기 때문에 이렇게 설명할 수 있다.
2) .pb = .ckpt + .pbtxt

[체크포인트의 종류]
1) .ckpt.data : 모델을 제외한 학습한 가중치 (weight)만 있는 파일
2) .ckpt.meta : 모델(graph)만 있는 파일
3) .ckpt.index : 체크포인트에 대한 정보(index)

  result = None

  #이미지 읽기
  input_image = cv2.imread('input1111.jpg', cv2.IMREAD_COLOR)

  #resize한 이미지, 가장 큰 얼굴 object
  detected_face, face_coor = format_image(input_image)

  #탐지된 이미지가 있다면,
  if detected_face is not None:
    #image를tensor로 변환: tensor size는 (1,2304) // detected_face는 48X48
    tensor = image_to_tensor(detected_face)

입력 이미지를 전처리 하는 부분이다.
- result는 결과 값을 저장하기 위해 선언한 변수
-
이미지를 읽고 ⇒ resize & 얼굴 object 탐지
-
탐지된 이미지가 있다면, 이미지를 배열로 변환

    #probs는softmax
    #feed dictionary는 텐서를 매핑
    result = sess.run(probs, feed_dict={face_x: tensor})

모델 구동 부분입니다.
- tensor를 입력으로 deepnn 모델 ⇒ softmax 함수를 통해 얻은 결과 배열을 result에 저장합니다.

    if result is not None:

      max_result=result.max()

      for i in range(7):
        if result[0,i] == max_result:
          print(EMOTIONS[i])

모델 출력 후처리 부분입니다.
-
출력의 max값 (1)을 max_result에 저장
-
result 배열 중 가장 큰 값을 가지는 index를 통해, EMOTION 배열의 결과 값을 출력합니다.


모델 네트워크 구조

deepnn모델 구조 (CNN 모델)을 분석한 내용입니다.

def deepnn(x):
  x_image = tf.reshape(x, [-1, 48, 48, 1])

[1, 2304] 배열을 4차원 [-1, 48, 48, 1] 배열로 변환
: -1은 갯수가 정해지지 않아서 모르는 경우에 사용한다.
x_image : (?, 48, 48, 1)

  # conv1
  W_conv1 = weight_variables([5, 5, 1, 64])
  b_conv1 = bias_variable([64])
  h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)

- random한 값으로 채워진 [5, 5, 1, 64] 모양의 배열을 w_conv1 에 저장
  5X5X1 필터를 64개 만드는 것임
  (자르기 전 정규 분포의 표준편차 = 0.1)
- bias는 0.1로 텐서의 shape와 동일한 배열을 b_conv1 에 저장

h_conv1: (?, 48, 48, 64)

- strides = [1, stride, stride, 1]
- 출력 채널(activation maps)은 padding=same이므로 원본 사이즈인 48X48과 동일한 출력 값을 얻는다.
-
ReLU 활성화 함수 이용

  # pool1
  h_pool1 = maxpool(h_conv1)

- ksize가 [1, 3, 3, 1]이라는 뜻은 3칸씩 이동하면서 출력 결과를 1개 만들어 낸다는 것이다.
- stride가 [1, 2, 2, 1]이므로 1/4로 resize됨

h_pool1: (?, 24, 24, 64)

  # conv2
  W_conv2 = weight_variables([3, 3, 64, 64])
  b_conv2 = bias_variable([64])
  h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
  norm2 = tf.nn.lrn(h_conv2, 4, bias=1.0, alpha=0.001 / 9.0, beta=0.75)
  h_pool2 = maxpool(norm2)

conv1은 conv → maxpool 순이었다면,
conv2는 conv → lrn → maxpool 순이다.
- random한 값으로 채워진 [3, 3, 64, 64] 모양의 배열을 w_conv2 에 저장
  3X3X64 필터를 64개 만드는 것임
- bias는 0.1가 64개 존재
-
convolution 연산 후 relu 함수 이용
-
lrn으로 정규화
-
maxpooling 진행 (9개 중 1개의 max값 뽑아냄) & 1/4크기로 resize

* tf.nn.local_response_normalize 함수
: 인접한 커널의 결과들끼리 연산을 함으로써 이웃에 대해 균일하게 큰 응답을 감소시키고 이웃 내에서 큰 활성화를 더욱 두드러지게 함
⇒ 활성화 할 때 더 높은 대비가 생성되게 함
⇒ 요즘에는 dropout 또는 batch normalization을 사용

h_pool2: (?, 12, 12, 64)

  # Fully connected layer
  W_fc1 = weight_variables([12 * 12 * 64, 384])
  b_fc1 = bias_variable([384])
  h_conv3_flat = tf.reshape(h_pool2, [-1, 12 * 12 * 64])
  h_fc1 = tf.nn.relu(tf.matmul(h_conv3_flat, W_fc1) + b_fc1)

- fully connected layer으로 출력을 7로 맞추어 주어야 한다! 여기 코드부터 끝까지 fully connected layer라고 보면 된다.
- matmul과 add를 이용한다.

h_conv3_flat: (?, 9216) #12X12X64
h_fc1: (?, 384) #[?, 9216]X[9216, 384] = [?, 384]

  # Fully connected layer
  W_fc2 = weight_variables([384, 192])
  b_fc2 = bias_variable([192])
  h_fc2 = tf.matmul(h_fc1, W_fc2) + b_fc2

h_fc2: (?, 192) #[?, 384]X[384, 192] = [?, 192]

  # linear
  W_fc3 = weight_variables([192, 7])
  b_fc3 = bias_variable([7])
  y_conv = tf.add(tf.matmul(h_fc2, W_fc3), b_fc3)

  return y_conv

y_conv: (?, 7) #[?, 192]X[192, 7] = [?, 7]

결론

인터넷에서 이미지를 다운 받아 실행한 결과를 print해줍니다. 얼굴 위치를 알고 있으니, 각 감정에 맞는 이모지를 얼굴 위에 올리는 프로젝트를 진행할 수도 있겠네요! (두근두근)



딥러닝에 대해 전혀 모르던 상태로 시작한 인턴인데, 그래도 이제는 어느정도 대화할 수준(?)은 되는 것 같아 뿌듯합니다. 저는 이제 CNN에서 벗어나 최근 논문 모델을 구현해보려고 합니다. ^-^
부족하지만 봐주신 분들 감사합니다 :)

'인턴일지' 카테고리의 다른 글

Semantic Segmentation 강의 정리  (0) 2020.05.08
Revisiting Single Image Depth Estimation: 실시간 Depth map  (0) 2020.04.27
[7,8주차] 주간프레젠테이션  (0) 2020.04.24
Git 문법 정리  (0) 2020.04.24
인턴 일지 작성  (2) 2020.04.21