웅재의 코딩세상
RNN기반 수어 번역 시스템 - 번역 프로그램 구현 본문
Mediapipe
알파벳 하나당 1분씩의 데이터 수집
actions = ['a','b','c','d','e','f',
'g','h','i','j','k','l',
'm','n','o','p','q','r','s',
't','u','v','w','x','y','z']
secs_for_action = 60 #데이터 녹화 시간
seq_length = 30 #윈도우의 사이즈
Mediapipe를 사용해 영상 데이터 수집
# MediaPipe hands model 미디어 파이프 initialize 시키는 것
mp_hands = mp.solutions.hands
mp_drawing = mp.solutions.drawing_utils
hands = mp_hands.Hands(
max_num_hands=1,
min_detection_confidence=0.5,
min_tracking_confidence=0.5)
cap = cv2.VideoCapture(1)
created_time = int(time.time())
os.makedirs('dataset', exist_ok=True) #데이터셋 저장할 폴더 생성
while cap.isOpened():
for idx, action in enumerate(actions):
data = []
ret, img = cap.read()
img = cv2.flip(img, 1) #웹캠의 영상을 읽고 플립 한번 시켜주기 -> 거울처럼 변환
#어떠한 액션을 표현하는지 7초 동안 기다리기
cv2.putText(img, f'Waiting for collecting {action.upper()} action...', org=(10, 30), fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=1, color=(255, 255, 255), thickness=2)
cv2.imshow('img', img)
cv2.waitKey(10000)
start_time = time.time()
#30초동안 반복을 하는데 하나씩 프레임을 읽어서 미디어파이프에 넣어준다.
while time.time() - start_time < secs_for_action:
ret, img = cap.read()
img = cv2.flip(img, 1)
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
result = hands.process(img)
img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
#손의 각도를 뽑아내는 코드
if result.multi_hand_landmarks is not None:
for res in result.multi_hand_landmarks:
#x,y,z, visibility가 있기 때문에 21,4로 설정
joint = np.zeros((21, 4))
#각 조인트마다 랜드마크를 저장한다.
for j, lm in enumerate(res.landmark):
#visibility는 손의 노드 랜드마크가 보이는지 안보이는지 파악
joint[j] = [lm.x, lm.y, lm.z, lm.visibility]
# Compute angles between joints 손 관절 사이의 코드 구현
v1 = joint[[0,1,2,3,0,5,6,7,0,9,10,11,0,13,14,15,0,17,18,19], :3] # Parent joint
v2 = joint[[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20], :3] # Child joint
#백터를 계산해서 관절 사이를 계산한다.
v = v2 - v1 # [20, 3]
# Normalize v 각 백터의 길이를 구한다. 각 백터의 길이로 길이를 나눠준다. -> 크기 일짜리 기본 백터가 나온다.
v = v / np.linalg.norm(v, axis=1)[:, np.newaxis]
# Get angle using arcos of dot product
# dot product를 arcos을 사용해 각도를 구해준다.
# einsum은 내적, 외적, 행렬곱 등을 해준다.
angle = np.arccos(np.einsum('nt,nt->n',
v[[0,1,2,4,5,6,8,9,10,12,13,14,16,17,18],:],
v[[1,2,3,5,6,7,9,10,11,13,14,15,17,18,19],:])) # [15,]
#앵글이 라디안 값으로 나오기 때문에 디그리 값으로 바꿔준다.
angle = np.degrees(angle) # Convert radian to degree
angle_label = np.array([angle], dtype=np.float32)
#마지막으로 라벨을 넣어주는데 idx를 통해 0인지 1인지 2인지 넣어준다.
angle_label = np.append(angle_label, idx)
#x와 y와 y와 비저빌리티가 들어있는 행렬을 concatenate시키면 100개의 행렬로 펼쳐준다.
d = np.concatenate([joint.flatten(), angle_label])
#data라는 변수에 전부 추가해준다.
data.append(d)
#손의 랜드마크를 그리는 코드
mp_drawing.draw_landmarks(img, res, mp_hands.HAND_CONNECTIONS)
cv2.imshow('img', img)
if cv2.waitKey(1) == ord('q'):
break
수집한 영상 데이터를 Sequence data의 파일로 저장한다.
data = np.array(data) #데이터를 30초동안 모았으면 numpy배열 형태로 변환시켜준다.
print(action, data.shape)
#npy형태로 저장
np.save(os.path.join('dataset', f'raw_31_{action}_{created_time}'), data)
# Create sequence data
# 입력 x1이 들어가면 출력 y1이 나오는 데이터
full_seq_data = []
for seq in range(len(data) - seq_length):
full_seq_data.append(data[seq:seq + seq_length])
full_seq_data = np.array(full_seq_data)
print(action, full_seq_data.shape)
#seq라 붙은 이름으로 저장해줄것이다.
np.save(os.path.join('dataset', f'seq_31_{action}_{created_time}'), full_seq_data)
Training model
저장된 sequence 파일을 배열을 축을 기준으로 묶어준다.
data.shape # (20021, 30, 100)
#마지막에 label 데이터가 저장되어 있기 때문에 -1을 해준다.
x_data = data[:, :, : -1]
#마지막 값만 라벨에 저장
labels = data[:,0, -1]
print(x_data.shape) #(20021, 30, 99)
print(labels.shape) #(20021,)
one hot encoding
from tensorflow.keras.utils import to_categorical
#라벨을 카데코리 데이터로 만들어 주어야한다. one hot encoding
y_data = to_categorical(labels, num_classes=len(actions))
y_data.shape
train data와 validation data를 분리해준다.
from sklearn.model_selection import train_test_split
x_data = x_data.astype(np.float32)
y_data = y_data.astype(np.float32)
# 0.8는 training data 0.2은 validation data
x_train, x_val, y_train, y_val = train_test_split(x_data, y_data, test_size=0.2, random_state=2021)
print(x_train.shape, y_train.shape)
print(x_val.shape, y_val.shape)
model을 학습시키기 위해 신경망 층을 만들어 주었다.
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import LSTM, Dense
#모델을 만들어 주어야함 sequential api사용
model = Sequential([
#LSTM과 Dense를 연결해 주어야한다.
#shape는 1:3인데 윈도우 사이즈와 랜드마크, 각도, 비저빌리티가 된다.
LSTM(64, activation='softmax', input_shape=x_train.shape[1:3]),
Dense(32, activation='softmax'),
Dense(len(actions), activation='softmax')
])
#액션중에 무엇인가 추론하도록함
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['acc'])
model.summary()
위처럼 신경망 층을 구성할 때 Dense Layer 하나를 더 추가해 모델을 학습시켜 보았습니다.
3개의 Layer로 학습시킨 모델과 4개의 Layer를 사용해서 학습시킨 모델의 성능을 아래 그림을 통해 확인할 수 있습니다. 3개의 Layer를 쌓아 학습시킨 모델의 성능이 더 좋다는 것을 확인할 수 있습니다. 단순히 층을 많이 쌓아 학습시킨다 해서 성능이 더 좋아지는 것이 아니라는 것을 확인할 수 있습니다. 복잡도가 올라갈수록 실제 결과와 상관 없는 특징까지 학습하여 오버피팅이 일어나게 된 것입니다.
위 모델은 성능이 좋지 않기 때문에 LSTM Layer가 아닌 Bidirectional LSTM Layer를 사용해서 모델을 학습시켜 주었습니다. Bidirectional LSTM은 앞 방향성과 뒷 방향성 모두 기억하기 때문에 모델의 성능이 높아질 것이라고 예상했습니다.
새로 학습시킨 모델의 성능을 보면 손실값이 현저히 줄어든 것을 볼 수 있고, 정확도 또한 95% 정도로 37% 정도 향상 된 것을 확인할 수 있습니다.
학습시킨 모델로 수어 알파벳을 번역해 보았습니다. 26개의 알파벳을 어느 정도로 잘 인식하나 확인해 보았는데 모든 알파벳의 번역이 잘 된 것을 확인할 수 있습니다.
'프로젝트 > 졸업작품 프로젝트' 카테고리의 다른 글
RNN기반 수어 번역 시스템 - 마무리 (1) | 2023.12.08 |
---|---|
RNN기반 수어 번역 시스템 - Flask Server 구축 (1) | 2023.12.08 |
RNN기반 수어 번역 시스템 - Application 개발 (1) | 2023.12.08 |
RNN기반 수어 번역 시스템 - 설계 (1) | 2023.12.08 |