Eye-Remocon Tech Blog Home Edge Project

Select music and play

감정에 맞는 음악선정 및 재생

Emotion Detection Model-3

Tensorflow 활용 행동인식 모델 학습

1️⃣ Teachable Machine의 PoseNetModel 이용

  • 링크 에서 3가지 행동(쓰러짐, 식사, 박수)에 대해 인식할 수 있도록 학습
  • 링크 AI Hub에서 학습 시 사람동작 영상 데이터 활용 K-002 K-003 K-004


2️⃣ 이슈 발생

  • K-005
  • Teachable Machine으로 학습한 모델을 API하기 위해 다른 코드 구현 필요
  • 솔루션
    • 모델 불러서 인식 결과얻는 javascript를 node.js 사용해서 API화 하기

3️⃣ node.js 활용해서 Pose detection API 구현

  1. 개발환경 구축
    • 개발 환경 세팅
      • node.js 설치 참고
      • 패키지 설치 1) cd 해당프로젝트파일 2) npm init -y 3)
         $ npm i @teachablemachine/pose@0.8.6 --save
         $ npm i @tensorflow/tfjs@1.3.1 --save
         $ npm i canvas --save
         $ npm i express --save
        
  2. python client code
    • python requests 모듈 설치
      $ pip install requests
      
    • python code
import requests
import base64
import time


start = time.time()
with open('이미지파일', 'rb') as f:
    im_b64 = base64.b64encode(f.read()).decode('utf8')

payload = {'img_base64':im_b64}
headers = {}
url = 'http://localhost:3000/pose_detection'

r = requests.post(url, json=payload, headers=headers)

if r.ok:
    pose = r.json()
    print(pose)
    print("time :", time.time() - start)
  • 해당 이미지 파일을 테스트하려는 파이썬 프로젝트 파일경로에 넣어주시고 위에 코드를 알맞게 바꿔주시길 바랍니다.
  • main.js 실행시킨 후 python 코드 실행시키면 다음과 같은 predict 된 json 파일을 얻으실 수 있습니다. image
  1. main.js 코드 설명
    1) predict function
    • teachablemacine에서 미리 학습된 pose 모델을 구글 drive에서 load
    • client로부터 받은 img에 대한 base64코드를 buffer로 변환
    • 변환된 buffer를 canvas 모듈을 사용하여 Image를 형성
    • 해당 Canvas Image로 부터 posenet 패키지를 사용하여 17개의 skeleton 추출하여 posenetOutput 얻기
    • posenetOutput으로 부터 estimatePose하여 3가지 행동(응급, 식사, 박수)에 각각 확률값 json형태로 반환 2) express api 실행 부분
    • client post request를 받아 pose 예측값들을 response

Emotion Detection Model-2

AWS Rekognition 서비스와 결합

1️⃣ 단순 AWS Rekognition 서비스 이용

  • 가이드 참조하여 AWS Rekognition 서비스의 감정인식 테스트
    • Issue 발생 => AWS InvalidS3ObjectException 문제
    • Issue 해결 => AWS Rekognition 서비스 이용할 때 AWS S3 버킷에 이미지 파일 저장해야함
  • AWS Credentials 설정한 후 가이드 대로 따라하면 AWS Rekognition 서비스 간편하게 이용 가능


2️⃣ 감정인식 기능별 구현 함수 상세화

  • Main : 시작점

  • 서비스
    • 이미지 정보 수신 (Python flask 서버 코드 활용)
    • 감정인식 (Local 측면)
    • 감정인식 (AWS Rekognition 측면)
  • Local Emotion Detection
    • 크롭된 안면 이미지 파일 변환 (그레이화, 크기)
    • 미리 학습된 감정인식 모델 불러와서 해당 이미지에 인식된 감정인식 및 결과 반환
  • AWS Rekognition
    • 크롭된 안면 이미지 파일의 정보를 담은 요청구문을 통해 AWS Rekognition API 요청
    • 응답된 JSON 파일의 Emotions 정보를 반환

Local Emotion detection model과 AWS Rekognition 서비스를 나눠서 실행
main에서 제일 먼저 Local Emotion detection model에서 감정인식된 결과값을 받기
감정인식되지 않으면 “None”값을 반환하여 none값이면 AWS Rekognition 서비스 이용해서 감정인식 수행하도록 구현


3️⃣ Flask 이용하여 emotion detection API 구현

  1. 개발환경 구축
    • 필요 패키지 설치(Linux & Mac)
      • Pytorch 공식 홈페이지에서 조건에 해당하는 pytorch 관련 라이브러리 설치
      •  $ pip install flask
         $ pip install boto3
        
    • AWS 설정
      • 공식문서 P.13 참고하여 access key, password 설정
      • 지역코드는 ap-northeast-2 로 입력
  2. 소스코드
  • main.py
    • flask 서버가 실행되는 메인 소스코드
    • 클라이언트로부터 이미지파일을 POST 요청을 받아서, 해당 이미지의 BASE64코드를 이미지로 변환 수행
    • 처음으로 HOME Emotion Detection을 수행하기 위해 detection() 함수 호출
    • HOME Emotion Detection 수행 시, 감정인식이 되지 않는다면 아무값도 반환되지 않는다면, AWS Emotion Detection 에서 aws_main() 호출
    • 반환되는 감정을 담은 json 파일을 클라이언트에게 다시 반환.
import base64
import io
import numpy
import aws_emotion_detection
import home_emotion_detection
from PIL import Image
from flask import Flask, request, jsonify


app = Flask(__name__)
            
@app.route('/main', methods=['POST'])
def main():
    payload = request.form.to_dict(flat=False)
    im_b64 = payload['image'][0]
    im_binary = base64.b64decode(im_b64)
    buf = io.BytesIO(im_binary)
    face_img = Image.open(buf).convert('RGB')
    open_cv_face_image = numpy.array(face_img)

    detection_result = home_emotion_detection.detection(open_cv_face_image)
    if detection_result == None:
        detection_result = aws_emotion_detection.aws_main(im_binary)

    return jsonify({"emotion":detection_result})

if __name__ == "__main__":
    app.run(debug=True, host='0.0.0.0', port=9900)



  • home_emotion_detection.py
    • Home Emotion Detection을 위해 존재하는 소스코드입니다.
    • 미리 학습된 ResNet9 감정인식모델과 얼굴을 인식하는 모델을 불러옴
    • 사진의 얼굴을 인식하여 크롭하고, 감정인식모델에 적용될 수 있도록 이미지 변환 시키고 만약 얼굴이 인식되지 않았다면 No Face 반환
    • 학습된 모델을 forward 시켜 7가지 감정의 분류를 할 수 있도록 구현
    • 감정이 7가지로 분류된다면 해당 Label를 반환, 감정분류가 안된다면 공백 반환
import cv2
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
import torchvision.transforms as tt

def conv_block(in_channels, out_channels, pool=False):
    layers = [nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1),
              nn.BatchNorm2d(out_channels),
              nn.ELU(inplace=True)]
    if pool: layers.append(nn.MaxPool2d(2))
    return nn.Sequential(*layers)

class ResNet(nn.Module):
    def __init__(self, in_channels, num_classes):
        super().__init__()

        self.conv1 = conv_block(in_channels, 128)
        self.conv2 = conv_block(128, 128, pool=True)
        self.res1 = nn.Sequential(conv_block(128, 128), conv_block(128, 128))
        self.drop1 = nn.Dropout(0.5)

        self.conv3 = conv_block(128, 256)
        self.conv4 = conv_block(256, 256, pool=True)
        self.res2 = nn.Sequential(conv_block(256, 256), conv_block(256, 256))
        self.drop2 = nn.Dropout(0.5)

        self.conv5 = conv_block(256, 512)
        self.conv6 = conv_block(512, 512, pool=True)
        self.res3 = nn.Sequential(conv_block(512, 512), conv_block(512, 512))
        self.drop3 = nn.Dropout(0.5)

        self.classifier = nn.Sequential(nn.MaxPool2d(6),
                                        nn.Flatten(),
                                        nn.Linear(512, num_classes))

    def forward(self, xb):
        out = self.conv1(xb)
        out = self.conv2(out)
        out = self.res1(out) + out
        out = self.drop1(out)

        out = self.conv3(out)
        out = self.conv4(out)
        out = self.res2(out) + out
        out = self.drop2(out)

        out = self.conv5(out)
        out = self.conv6(out)
        out = self.res3(out) + out
        out = self.drop3(out)

        out = self.classifier(out)
        return out

def detection(face_picture):
    face_classifier = cv2.CascadeClassifier('./models/haarcascade_frontalface_default.xml')
    model_state = torch.load('./models/emotion_detection_model_state.pth')
    class_labels = ['ANGRY', 'DISGUS', 'FEAR', 'HAPPY', 'NEUTRAL', 'SAD', 'SURPRISE']
    model = ResNet(1, len(class_labels))
    model.load_state_dict(model_state)

    gray = cv2.cvtColor(np.ascontiguousarray(face_picture), cv2.COLOR_BGR2GRAY)
    faces = face_classifier.detectMultiScale(gray, 1.3, 5)
    for (x, y, w, h) in faces:
        cv2.rectangle(face_picture, (x, y), (x + w, y + h), (255, 0, 0), 2)
        roi_gray = gray[y:y + h, x:x + w]
        roi_gray = cv2.resize(roi_gray, (48, 48), interpolation=cv2.INTER_AREA)

        if np.sum([roi_gray]) != 0:
            roi = tt.functional.to_pil_image(roi_gray)
            roi = tt.functional.to_grayscale(roi)
            roi = tt.ToTensor()(roi).unsqueeze(0)

            # make a prediction on the ROI
            tensor = model(roi)
            pred = torch.max(tensor, dim=1)[1].tolist()
            label = class_labels[pred[0]]
            return label

        else:
            return 'No Face'



  • aws_emotion_detection.py
    • aws 감정인식 요청 시에, 이미지의 base64코드를 aws 로컬 이미지로 요청
    • 기존 S3 클라우드 서비스에 이미지 저장하는 것을 변경함
    • 여러 FaceDetails 중에 가장 Confidence 높은 Emotions 특징값만 반환
#Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#PDX-License-Identifier: MIT-0 (For details, see https://github.com/awsdocs/amazonrekognition-developer-guide/blob/master/LICENSE-SAMPLECODE.)
import boto3


def detect_faces(photo):
    client = boto3.client('rekognition')
    response = client.detect_faces(Image={'Bytes': photo}, Attributes=['ALL'])
    for faceDetail in response['FaceDetails']:
        return faceDetail['Emotions'][0]

def aws_main(face_image):
    photo = face_image
    face_emotion = detect_faces(photo)
    return str(face_emotion['Type'])



4️⃣ 값진 코드 리뷰

  • Flask 이용한 API 구현
    • 개발 편의로 인한 host 주소 0.0.0.0 사용
    • 자주 사용되는 포트(5000, 8080 등) 사용시 충돌이 날 수 있음으로 다른 포트 번호 사용
if __name__ == "__main__":
    app.run(debug=True, host='0.0.0.0', port=9900)


  • Local Emotion Detection에서 인식 결과 없을 때 ““로 반환이 아닌 “None”으로 반환
    detection_result = home_emotion_detection.detection(open_cv_face_image)
    if detection_result == None:
        detection_result = aws_emotion_detection.aws_main(im_binary)

Modify storagehandler.gofile

Handler logger 수정

1️⃣ Replace handler.logger in storagehandler.go file with logmgr

K-010

  • Description : Since DataStorage uses two types of logger, storagehandler.go file is modified. so, the handler.logger part of the storagehandler.go file was replaced wtih logmgr.
  • Type of change : Code cleanup/refactoring
  • PR
  • Related Issue : #173


2️⃣ 컨트리뷰션을 위한 노력

  • 이슈 1: 테스트 시 build failed 오류
  • image

  • 해결책
    • 원인 : 중복으로 선언되어 에러 발생, 다른 package storagedriver에서 이미 선언됨
    • 해결 방법 : logmgr import 부분을 제거하면 log redeclared as imported package name 에러 해결 가능

3️⃣ 값진 코드 리뷰

  • 프로젝트 내 테스트 위해서 이미 테스트하는 모듈 존재
  • 테스트 파일을 활용해 테스트 후, 결과를 PR에 반영
    • edge-home-orchestration-go 폴더
      • $ go test -v ./internal/controller/storagemgr/storagedriver/
    • edge-home-orchestration-go/internal/controller/storagemgr/storagedriver 폴더
      • $ go test -v .

Emotion Detection Model-1

1️⃣ CNN ResNet9 감정인식 모델 구축

출처

  1. 데이터 준비
    • kaggle, Emotion DataSet
    • Emotion : Angry, Disgust, Fear, Happy, Neutral, Sad, Surprise K-008
  2. 데이터 변환 작업
    • torch에서 dataset모듈로 데이터 읽어 오고, transforms 모듈로 불러온 이미지를 필요에 따라 변환
    • Grayscale(이미지 회색 변환), 50%의 확률(랜덤)으로 이미지 가로로 돌리기
    • RandomRotation으로 이미지 30도(왼쪽, 오른쪽 랜덤)으로 돌리기
    • 위 랜덤으로 이미지 변환을 통해 과적합을 피함, 과적합(overfitting)이란 학습 데이터셋 안에서 일정 수준 이상의 예측 정확도를 보이지만, 새로운 데이터에 적용하면 잘 맞지 않는 것을 의미
    • ToTensor => 이미지 데이터를 파이토치 텐서로 변환함, Pytorch layers는 오직 tensor 데이터 단위로 동작

K-010

  1. Loader data to GPU
    • cuda, cudnn 라이브러리를 이용해서 GPU를 이용해서 모델 학습
  2. ResNet9 Model Base Lines
def conv_block(in_channels, out_channels, pool=False):
    layers = [nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1), 
              nn.BatchNorm2d(out_channels), 
              nn.ELU(inplace=True)]
    if pool: layers.append(nn.MaxPool2d(2))
    return nn.Sequential(*layers)

class ResNet(ImageClassificationBase):
    def __init__(self, in_channels, num_classes):
        super().__init__()
        
        self.conv1 = conv_block(in_channels, 128)
        self.conv2 = conv_block(128, 128, pool=True)
        self.res1 = nn.Sequential(conv_block(128, 128), conv_block(128, 128))
        self.drop1 = nn.Dropout(0.5)
        
        self.conv3 = conv_block(128, 256)
        self.conv4 = conv_block(256, 256, pool=True)
        self.res2 = nn.Sequential(conv_block(256, 256), conv_block(256, 256))
        self.drop2 = nn.Dropout(0.5)
        
        self.conv5 = conv_block(256, 512)
        self.conv6 = conv_block(512, 512, pool=True)
        self.res3 = nn.Sequential(conv_block(512, 512), conv_block(512, 512))
        self.drop3 = nn.Dropout(0.5)
        
        self.classifier = nn.Sequential(nn.MaxPool2d(6), 
                                        nn.Flatten(),
                                        nn.Linear(512, num_classes))
        
    def forward(self, xb):
        out = self.conv1(xb)
        out = self.conv2(out)
        out = self.res1(out) + out
        out = self.drop1(out)
        
        out = self.conv3(out)
        out = self.conv4(out)
        out = self.res2(out) + out
        out = self.drop2(out)
        
        out = self.conv5(out)
        out = self.conv6(out)
        out = self.res3(out) + out
        out = self.drop3(out)
        
        out = self.classifier(out)
        return out
  1. Hyperparameters
epochs = 140
max_lr = 0.008
grad_clip = 0.1
weight_decay = 1e-4
opt_func = torch.optim.Adam
  1. 학습결과

K-005

2️⃣ CNN ResNet9 감정인식 모델 테스트

  • face 폴더에 감정을 인식할 사진들 저장함
    image

  • 테스트 결과 4번사람 제외하고 정확하게 감정인식된 결과 확인 가능
    image

3️⃣ 결론

  • 라이센스 문제와, 정확도 54% ~ 55% 정도로 다소 낮은 문제로 정확도를 높일 수 있는 방법 찾아 모델 발전시키로 결정. 또한 직접 학습하는 방법 이외에, 3개 회사 API 사용하기로 결정