간단한 모션으로 이북 조작하기

Tieck

·

2021. 9. 28. 01:54

 

유튜브 채널 - 빵형의 개발도상국 의 <손 제스처 인식 딥러닝 인공지능 학습시키기>에서 배포된 코드를 변형하여 토이프로젝트를 진행하였습니다.

 

 


 

 

 

 

책을 읽을 때 주로 전자책을 읽는다.

전자책의 장점은 시간과 장소에 관계 없이 읽을 수 있다는 점과 종이책보다 가격이 낮게 책정되는 점이다.

 

전자책을 읽을 때 주로 리디북스 앱을 이용한다.

핸드폰, 아이패드, 노트북, 데스크탑...

기기를 가리지 않고 상황에 따라 적절하게 선택한다.

 

다만 데스크탑으로 볼 때, 27인치 + 4k의 조합에 의해 화면상에 글자가 폭이 넓고 빽빽하게 채워진다.

가까이에서 보면 붉은 벽돌 담의 벽돌을 보는 것 같아서 모니터에서 어느정도 거리를 두고 본다.

그러다 보니 다음 장으로 넘기기 위해 키보드나 마우스로 조작하는 데 번거로움이 있다.

 

그래서 배운 내용을 활용해 간단 토이프로젝트를 만들었다.

 

 

간단한 모션으로 이북 조작하기

import cv2
import mediapipe as mp
import numpy as np
import time, os

actions = ['next', 'previous']

 

위와 같은 라이브러리를 사용하여 데이터셋을 만들었다.

다음 페이지로 넘기는 기능을 할 'next'와 이전 페이지로 넘기는 기능을 할 'previous'라는 라벨을 만들고  cv2의 Videocapture()를 통해 dataset을 생성하였다.

웹캠을 이용한 data set 생성 (좌) previous / (우) next

 

'previous'는 손을 쥐었다 피는 동작이고, 'next'는 엄지를 어깨넘어로 가르키는 동작이다.

 

 

 

LSTM과 Dense layer를 통해 단순한 모델을 생성하였다.

 

model layers

 

 

 

그후 200 epoch 만큼 train하였다.

model의 train 과정

20회도 안되는 사이에 validation accuracy가 1에 도달했고, validation loss도 100회가 넘어가는 순간부터 0에 거의 근접하였다.  callback 시에 ReduceLROnPlateau()과  ModelCheckpoint()를 호출하였다. 다만, ReduceLROnPlateau()의 patience를 50으로 설정했기 때문에 early stop 없이 200 epoch 동안 학습을 수행하였다.

 

 

model의 학습 결과
confusion matrix : (상) 'next' / (하) 'previous'

간단한 모델이니 만큼 cousion matrix는 all perfect이다. model의 학습결과는 epoch 115 때, val_loss: 0.0061 - val_acc: 1.0000으로 안정된 모습을 보인다.

 

하나의 dataset으로 훈련해서 과적합이 강하게 의심된다. 110번부터 validation loss는 거의 0으로 수렴하고, validation accuracy는 1에 도달한다.

 

dataset과 동일한 환경에서 훈련 dataset을 알고 있는 사용자가 사용하기 때문에 목적한 기능을 수행하는 데 큰 문제가 없었다. 또한 손과 손가락의 움직임을 훈련하기 때문에, 훈련 데이터 셋에 없는 다양한 모션을 의도대로 인식하였다.

 

또한, 훈련 데이터의 동작 선정시 유사성이 낮은 동작을 선정하여 잘못 인식될 가능성을 낮췄다.

 

 

 

의도 대로 동작하는 모습을 확인 할 수 있다.

label에 따라 왼쪽 화살표 / 오른쪽 화살표를 입력해서 페이지를 앞으로 / 뒤로 넘기는 기능을 구현하였다.

pyautogui 라이브러리를 활용하였다.

 

 

 

 


 

하지만 정말 과적합된 상태로 두면, 사용에는 문제가 없지만 기분이 좋지 않았다.

게다가 과적합된 이유를 찾은 결과, 초당 24장의 이미지를 수집하는 동영상인데 사진처럼 같은 포즈를 취하고 있었기 때문에 결과적으로 24장/sec * 30 sec = 720장 이라는 "동일"한 이미지를 학습에 사용된 것이다. 720장의 이미지에서 서 추출한 손의 모션 데이터를 학습한다.

 

따라서 정지된 손의 모양을 인식하는 것이 아닌, 손의 움직임이 큰 동작(또는 모션)을 인식하는 모델을 만들기 위해 데이터 셋을 다시 수집했다.

 

 

 

 

드디어!! 익숙한 성능그래프가 보였다. 하지만 성능 (acc와 loss)의 분산이 크고, 불규칙적이다.

epoch를 지날 수록 accuracy는 증가하는 양상을 보이고, loss는 감소하는 양상을 보인다.

 

제대로 학습이 됐는지 모르겠다. 아무런 근거없이 epoch를 늘리면 결과적으로 그래프의 분산이 감소하고, 안정되겠지만 다시 과적합이 될지 우려된다.

실제 사용을 통해 검증을 할 필요가 있다.

검증 결과에 따라서 epoch를 200회에서 증가시키거나, layer를 추가하고 parametor를 튜닝해야겠다.

 

 

 

try1. 뭔가 잘못됐다.

 

어째서...?

 

 

model = Sequential([
    LSTM(256, activation='relu', input_shape=x_train.shape[1:3]),
    Dense(64,activation='relu'),
    Dropout(0.5),
    Dense(32, activation='relu'),
    Dense(len(actions), activation='softmax')
])

Dense(64) 레이어와 Dropout(0.5) 레이어를 추가하고, LSTM의 필터 수를 64개에서 256개로 변경했다.

 

history = model.fit(
    x_train,
    y_train,
    validation_data=(x_val, y_val),
    epochs=1000,
    callbacks=[
        ModelCheckpoint('models/model_4rd.h5', monitor='val_acc', verbose=1, save_best_only=True, mode='auto'),
        ReduceLROnPlateau(monitor='val_acc', factor=0.5, patience=50, verbose=1, mode='auto'),
        EarlyStopping( monitor='val_acc', min_delta=0, patience=50, verbose=1, mode='auto', baseline=None, restore_best_weights=False )
    ]
)

 

epoch를 200에서 1000으로 변경하고, callbacks에 EarlyStopping을 추가했다.

 

학습 종료 히스토리

문제점 1

 

첫번째 문제는 validation accuracy와 validation loss가 낮다는 점이다.

모니터링하는 성능 지표의 변화가 멈췄다.

val_loss와 val_acc, loss와 acc 모두  79번째 epoch를 기점으로 변화를 멈췄다.

 

가설 1

LSTM의 채널 수가 증가해서 과거의 정보를 지나치게 잊어버렸다.
-> 지금 지식 수준에서는 가장 가능성이 있다고 생각된다.

 

 

가설 2

많지 않은 수의 파라미터(약 80만개)의 절반을 제외해서 노드의 수가 부족해져 학습에 악영향을 미쳤다.

-> 기각. 데이터 셋의 규모가 작기 때문에 80만개의 파라미터도 충분히 많은 것 같다.

 

하지만 근거가 없기 때문에 가설 1과 가설 2를 모두 검증해야한다

 


 

 

 

문제점 2

 

또한 실제 사용해보니 불편한 점을 추가로 발견했다.

두번째 문제는 행동의 인식 속도가 너무 빨라서, 책을 한장 넘기자 마자 바로 다음장으로 연속해서 넘어가는 점이다.

본래 코드는 로봇의 움직임을 손의 움직임으로 제어하기 위한 코드로 손으로 연속적인 input을 주면 같은 동작 (앞으로 이동)을 취하다가 input이 끊기면 멈추는 코드였다.

 

하지만 내게 필요한 동작은 동작을 인식했을 때 딱 1회만 작동하는 코드였다.

이 차이 때문에 연속된 input에 의해 책장이 십 수 페이지가 넘어간 것이다.

 

 

 

 

3번 동안 같은 라벨로 인식되었을 때 다음 장 혹은 이전 장으로 넘어가도록 설정했다.

그런데 한번 인식되면 'next' 'next' 'next' 실행 된 이후에도 array로 저장된 label들이 다음 동작 인식에 영향을 미쳤다. 로봇의 경우 input이 로봇이 원하는 위치에 도달할 때까지 지속되므로 일종의 관성(모멘텀)을 받도록 설계되었다.

관성이 존재하는 기존 코드 개념도

 

'next' 'next 'next' 'next'
0 1 2 3

 

 

관성이 존재하기 때문에 time.sleep()코드로 멈춰도, 다음번에는 의도한 대로 3회 같은 라벨을 받아야 동작하는 것이 아니라, 1회의 인식으로 지정된 동작이 실행되는 것이다.

 

 

수정된 코드 개념도

따라서 관성을 끊어버렸다.

자체 count를 도입하여 count > 2 일 때 동작이 실행된다.

3회 동안 같은 라벨이 인식되면 count가 1만큼 증가한다.

또한 count가 증가하면, action array(인식된 라벨이 저장된 array)에서 최근 3회의 라벨을 '?'로 초기화 하도록 변경하였다.

 

 

 

 

 

 

 

 

 

시연 영상

 

 

 

 

 

 

 

 


 

최종 모델

 

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import LSTM, Dense, Dropout

model = Sequential([
    LSTM(64, activation='relu', input_shape=x_train.shape[1:3]),
    Dense(32, activation='relu'),
    Dense(len(actions), activation='softmax')
])

model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['acc'])
model.summary()

 

초기 모델의 layer와 동일한 구성으로 설정했다.

다만 epoch를 500으로 증가시켜 과적합시켰다.

통제된 환경이고 2가지 동작이므로 정확도를 높이는 것이 효과적라고 판단했다.

 

 

 

action = actions[i_pred]
action_seq.append(action)
action = '?'

if len(action_seq) < 3:
	continue

this_action = '?'


if action_seq[-1] == action_seq[-2]== action_seq[-3]:
  this_action = action_seq[-1]
  action_seq[-1] = '?'
  action_seq[-2] = '?'
  action_seq[-3] = '?'



if this_action == 'next':
  print("next")
  next_cnt += 1
    if next_cnt > 2 :
      next_cnt = 0
      #pag.click(x=3697, y=1073) 
      pag.press('right')


  elif this_action == 'previous':
    print("previous")
    previous_cnt += 1
      if previous_cnt > 2 :
      previous_cnt = 0
      #pag.click(x=140, y=1079)
      pag.press('left')


else:
	continue

 

라벨 간의 연속성을 끊고, 동작 실행까지의 단계를 증가시키자 동작이 분절되어 작동한다.

책을 앞 또는 뒤로 넘길 때 의도대로 1회씩 동작하고, 다음 동작까지 간격이 있는 모델을 만들었다.

 

 

 

 

+ LSTM, Dropout 에 대한 자세한 학습도 함께!

다른 포스팅으로

 

 

 

 

 

 

 


 

Reference

손 제스처 인식 딥러닝 인공지능 학습시키기 - YouTube