(自用)有关于使用深度学习进行手语识别

基于Mediapipe和LSTM的手语识别

本文仅仅是我自己对于实现别人所教学的深度学习预测人工智能所总结的文章。

(任务一)基础知识

LSTM:

LSTM(Long Short-Term Memory)长短期记忆递归神经网络,是一种循环神经网络(RNN)的变体,用于处理和建模具有长期依赖关系的序列数据。与传统的 RNN 相比,LSTM 具有更强大的记忆能力,能够更好地捕捉和处理序列中的长期依赖性。

在传统的 RNN 中,随着时间的推移,信息会逐渐衰减或消失,这被称为梯度消失问题。LSTM 的设计旨在解决这个问题,它引入了一种称为“门控机制”的结构,通过控制信息的流动,使网络能够更好地保留和更新信息。

LSTM 中的关键组件是称为“记忆单元(memory cell)”的结构。记忆单元包含一个内部状态(cell state),它允许网络存储和传递信息。LSTM 通过一系列的门控来控制内部状态的更新和遗忘。

主要的门控机制包括:

输入门(Input Gate):决定是否将新的输入信息纳入内部状态。
遗忘门(Forget Gate):决定是否从内部状态中删除特定信息。
输出门(Output Gate):决定内部状态中的哪些信息将被输出。

这些门控机制的目的是根据当前输入和之前的状态来决定哪些信息应该被保留、遗忘和输出。这使得 LSTM 能够在处理序列数据时更好地捕捉长期依赖关系。

总结一下,LSTM 是一种循环神经网络的变种,通过引入门控机制来解决传统 RNN 中的梯度消失问题,并能够更好地处理和建模具有长期依赖关系的序列数据。它在许多序列相关的任务中表现出色,因此我们使用LSTM来解决手语检测的问题。

张量:

数学上将一维数组称为向量,
将二维数组称为矩阵。另外,可以将一般化之后的向量或矩阵等统
称为张量(tensor)。本书基本上将二维数组称为“矩阵”,将三维数
组及三维以上的数组称为“张量”或“多维数组”。

数据集

我们使用的数据是需要我们通过以下步骤进行收集

输入内容为一个形状为(1,30,1662)的张量

loss函数

损失函数是表示神经网络性能的“恶劣程度”的指标,即当前的
神经网络对监督数据在多大程度上不拟合,在多大程度上不一致。
以“性能的恶劣程度”为指标可能会使人感到不太自然,但是如
果给损失函数乘上一个负值,就可以解释为“在多大程度上不坏”,
即“性能有多好”。并且,“使性能的恶劣程度达到最小”和“使性
能的优良程度达到最大”是等价的,不管是用“恶劣程度”还是“优
良程度”,做的事情本质上都是一样的。例如均方误差:

相关知识

更多的知识可以去阅读**《深度学习入门:基于Python的理论与实现》**

在这里插入图片描述

epoch

epoch是一个单位。一个 epoch表示学习中所有训练数据均被使用过
一次时的更新次数。比如,对于 10000笔训练数据,用大小为 100
笔数据的mini-batch进行学习时,重复随机梯度下降法 100次,所
有的训练数据就都被“看过”了A。此时,100次就是一个 epoch

使用的工具

1.Mediapipe

在这里插入图片描述

如图,Meidapipe 有很多的功能,而我们在此使用Mediapipe有关于手部的抓取关键点。

2.Tensorflow

TensorFlow 是一个开源的机器学习框架,由 Google Brain 团队开发并于2015年发布。它提供了丰富的工具和库,用于构建和训练各种机器学习模型,包括神经网络模型。

此项目运行简要过程

通过Mediapipe分析图像得到关键点数据,然后将收集到的数据通过Tensorflow进行训练。
我们可以通过下列步骤来分别完成这些内容。

验证步骤

一些选择题和判断题,在最下面附中

(任务二) 通过Mediapipe分析图像

1. 下载所需依赖项目

这一步中我们将通过pip下载项目以来的外部库

使用pip命令

pip install tensorflow==2.10.0 tensorflow-gpu==2.10.0 opencv-python mediapipe sklearn matplotlib

此命令的目的是下载程序运行所需要的依赖。其中,tensorflow和Mediapipe已在上文中介绍,而此处使用opencv则是为了进行图像处理

2.验证手段

通过在一个python文件中输入以下代码,如果没有报错,则依赖下载完成

import cv2
import numpy as np
import os
from matplotlib import pyplot as plt
import time
import mediapipe as mp

3. 通过使用MediaPipe来获取关键点

这一步主要是通过使用MediaPipe的模型,分析你所录制的图像,然后提取到手部的关键点以下为关键点的一些信息。
这些信息将包括x,y,z坐标等信息,结构如下图

在这里插入图片描述在这里插入图片描述

mp_holistic = mp.solutions.holistic # Holistic model
mp_drawing = mp.solutions.drawing_utils # Drawing utilities

MP 此处代码中,Holistic 是一个计算机视觉库,用于进行人体姿势预测和关键点检测。

4. 获取关键点坐标的准备工作:定义需要用到的相关函数

这是第一个函数,是将image的 BGR 转化为 RGB

def mediapipe_detection(image, model):
    image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # COLOR CONVERSION BGR 2 RGB
    image.flags.writeable = False                  # Image is no longer writeable
    results = model.process(image)                 # Make prediction
    image.flags.writeable = True                   # Image is now writeable 
    image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) # COLOR COVERSION RGB 2 BGR
    return image, results

这是第二个函数,是用来将Mediapipe的模型预测的内容即关键点绘制到图像即此处的image上

def draw_landmarks(image, results):
    mp_drawing.draw_landmarks(image, results.face_landmarks, mp_holistic.FACE_CONNECTIONS) # Draw face connections
    mp_drawing.draw_landmarks(image, results.pose_landmarks, mp_holistic.POSE_CONNECTIONS) # Draw pose connections
    mp_drawing.draw_landmarks(image, results.left_hand_landmarks, mp_holistic.HAND_CONNECTIONS) # Draw left hand connections
    mp_drawing.draw_landmarks(image, results.right_hand_landmarks, mp_holistic.HAND_CONNECTIONS) # Draw right hand connecti

这是第三个函数,与上面的函数差不多,就是对绘制的线条和点进行了个性化的设置,能让画面更加明晰

def draw_styled_landmarks(image, results):
    # 绘制面部连接
    mp_drawing.draw_landmarks(image, results.face_landmarks, mp_holistic.FACE_CONNECTIONS, 
                             mp_drawing.DrawingSpec(color=(80,110,10), thickness=1, circle_radius=1), 
                             mp_drawing.DrawingSpec(color=(80,256,121), thickness=1, circle_radius=1)
                             ) 
    # 绘制姿势连接
    mp_drawing.draw_landmarks(image, results.pose_landmarks, mp_holistic.POSE_CONNECTIONS,
                             mp_drawing.DrawingSpec(color=(80,22,10), thickness=2, circle_radius=4), 
                             mp_drawing.DrawingSpec(color=(80,44,121), thickness=2, circle_radius=2)
                             ) 
    # 绘制左手连接
    mp_drawing.draw_landmarks(image, results.left_hand_landmarks, mp_holistic.HAND_CONNECTIONS, 
                             mp_drawing.DrawingSpec(color=(121,22,76), thickness=2, circle_radius=4), 
                             mp_drawing.DrawingSpec(color=(121,44,250), thickness=2, circle_radius=2)
                             ) 
    # 绘制右手连接
    mp_drawing.draw_landmarks(image, results.right_hand_landmarks, mp_holistic.HAND_CONNECTIONS, 
                             mp_drawing.DrawingSpec(color=(245,117,66), thickness=2, circle_radius=4), 
                             mp_drawing.DrawingSpec(color=(245,66,230), thickness=2, circle_radius=2)
                             ) 

这是第四个函数,用于保存检测到的关键点的坐标

def extract_keypoints(results):
    pose = np.array([[res.x, res.y, res.z, res.visibility] for res in results.pose_landmarks.landmark]).flatten() if results.pose_landmarks else np.zeros(132)
    face = np.array([[res.x, res.y, res.z] for res in results.face_landmarks.landmark]).flatten() if results.face_landmarks else np.zeros(1404)
    lh = np.array([[res.x, res.y, res.z] for res in results.left_hand_landmarks.landmark]).flatten() if results.left_hand_landmarks else np.zeros(21*3)
    rh = np.array([[res.x, res.y, res.z] for res in results.right_hand_landmarks.landmark]).flatten() if results.right_hand_landmarks else np.zeros(21*3)
    return np.concatenate([pose, face, lh, rh])

在第四个函数之中,我们以检测到的pose为例

pose = []
for res in results.pose_landmarks.landmark:
    test = np.array([res.x, res.y, res.z, res.visibility])
    pose.append(test)

pose是一个数组,然后我们通过for循环将results中有关pose的结果读取出来,存储在pose中
其中pose中单个元素的结构为x坐标,y坐标,z坐标,以及该点的可见性。

那么我们以同样的方式存储检测到的 face, pose, leftHand, rightHand

3.使用Mediapipe进行预测

接下来我们就需要用到上面的函数来获取图像中的关键点的坐标值了

# 这个是通过opencv的库来调用你设备的摄像头
cap = cv2.VideoCapture(0)
# 设置 mediapipe 模型 
with mp_holistic.Holistic(min_detection_confidence=0.5, min_tracking_confidence=0.5) as holistic:
    while cap.isOpened():

        # 读取图像
        ret, frame = cap.read()

        # 用mediapipe进行预测
        image, results = mediapipe_detection(frame, holistic)
        print(results)
        
        # 绘制关键点
        draw_styled_landmarks(image, results)

        # 在屏幕上展示
        cv2.imshow('OpenCV Feed', image)

        # 退出逻辑
        if cv2.waitKey(10) & 0xFF == ord('q'):
            break
    cap.release()
    cv2.destroyAllWindows()
draw_landmarks(frame, results)
plt.imshow(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))

在这里插入图片描述

在这里插入图片描述

4. 提取关键点的数值

目前我们已经完成了对于检测到的图像的关键点之间的绘制,那么接下来我们就需要将检测到的坐标进行保存

i. 示例

其中zeros的意思是,当视频中没有检测到这部分内容的数组赋值为0

上传extract_keypoints的内容

(任务三)录制数据集

当我们采集了这些数据之后需要将其保存下来,以便后面训练模型使用。

其中save是将np中的数组保存在result_test文件夹下的0.npy中,load则为读取。
接下来我们需要通过使用np.save函数来保存我们的数据。此处的代码为保存一帧的数据。

np.save('0', result_test)
np.load('0.npy')

result_test形式参考

result_test
array([ 0.3835876 ,  0.47759178, -0.77978629, ...,  0.        ,
        0.        ,  0.        ])

接下来,我们就要收集一个序列的数据了。
以下代码的基本的流程是通过设置完基本的参数,然后根据actions的多少来收集其中的action的内容。

1.设置参数
# 输出numpy数组的路径
DATA_PATH = os.path.join('MP_Data') 

# 我们尝试预测的动作,以下为三个动作
actions = np.array(['hello', 'thanks', 'iloveyou'])

# 每一个动作的序列数
no_sequences = 30

# 一个序列的长度,此处为30帧
sequence_length = 30

# Folder start
start_folder = 30
2.开始收集

当我们完成了参数的设置之后,我们就可以开始收集这些数据了。当然,就依靠下面代码是不够的,下面的代码是创建目录的结构而不是收集数据,我们将在下一步完善这些代码

for action in actions: 
    dirmax = np.max(np.array(os.listdir(os.path.join(DATA_PATH, action))).astype(int))
    for sequence in range(1,no_sequences+1):
        try: 
            os.makedirs(os.path.join(DATA_PATH, action, str(dirmax+sequence)))
        except:
            pass

3. 收集训练和测试所需要的的关键点值

完成了文件夹的创建,那么接下来我们就要开始数据的收集了,我们已经学会了存储一阵的数据,那么接下来我们将录制和保存一个序列的数据。

4.打开摄像头
cap = cv2.VideoCapture(0)
# Set mediapipe model 
with mp_holistic.Holistic(min_detection_confidence=0.5, min_tracking_confidence=0.5) as holistic:
5.开始收集
# 新的循环
# 循环遍历actions
for action in actions:
    # Loop through sequences aka videos
    for sequence in range(start_folder, start_folder+no_sequences):
        # Loop through video length aka sequence length
        for frame_num in range(sequence_length):

            # 读取图像
            ret, frame = cap.read()

            # 用meidiapipe模型预测,得到图像上的关键点
            image, results = mediapipe_detection(frame, holistic)

            # 绘制关键点
            draw_styled_landmarks(image, results)
            
            # 两次录制间的等待逻辑
            if frame_num == 0: 
                cv2.putText(image, 'STARTING COLLECTION', (120,200), 
                           cv2.FONT_HERSHEY_SIMPLEX, 1, (0,255, 0), 4, cv2.LINE_AA)
                cv2.putText(image, 'Collecting frames for {} Video Number {}'.format(action, sequence), (15,12), 
                           cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1, cv2.LINE_AA)
                # Show to screen
                cv2.imshow('OpenCV Feed', image)
                cv2.waitKey(500)
            else: 
                cv2.putText(image, 'Collecting frames for {} Video Number {}'.format(action, sequence), (15,12), 
                           cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1, cv2.LINE_AA)
                # Show to screen
                cv2.imshow('OpenCV Feed', image)
            
            # NEW Export keypoints
            keypoints = extract_keypoints(results)
            npy_path = os.path.join(DATA_PATH, action, str(sequence), str(frame_num))
            np.save(npy_path, keypoints)

            # Break gracefully
            if cv2.waitKey(10) & 0xFF == ord('q'):
                break
                
cap.release()
cv2.destroyAllWindows()
6. 验证手段

(用户自行检查)检查目录结构是否为MP_Data–动作文件夹(如hello,thankyou,iloveyou)–序列号文件夹(0-29)–0至29.npy文件,如下图所示

在这里插入图片描述

(上传)result_test中的内容,我们可以检验result_test中的数据结构是否正确

(任务四),训练神经网络

我们在第五步中收集完了数据,那么接下来我们要为训练模型做准备。

首先当我们训练分类模型时,需要对每一种动作给定标签

1.引入设置标签的依赖
from sklearn.model_selection import train_test_split
from tensorflow.keras.utils import to_categorical
2.设置标签
label_map = {label:num for num, label in enumerate(actions)}

#这是label_map的结果,你可以输出对照以下
{'hello': 0, 'thanks': 1, 'iloveyou': 2}
3. 验证步骤
np.array(sequences).shape
(180, 30, 1662)

标签工作完成之后,我们就可以训练我们自己的神经网络了

4.引入训练模型的依赖
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import LSTM, Dense
from tensorflow.keras.callbacks import TensorBoard
5.准备工作
# 数据集的根目录,此步是用来连接数据集
DATA_PATH = os.path.join('MP_Data')

# 数据集中的动作
actions = np.array(['hello', 'thankyou', 'iloveyou'])
6.训练模型

以下代码中有关loss,epoch可参考最上处的内容。

以下代码可以简要概括为一个模型增加了3个LSTM层,3个dense层,然后输入张量为(30,1662),根据最开始对LSTM的介绍,我们可以知道return)sequences是需要在此处的前两层返回内容的,所以前两层设置为true。
然后activation是激活函数,此处我们选择的激活函数是relu,可以参考前面的基础知识部分

model = Sequential() # 实例化模型,以便于下面更方便构建模型

# 下面这行代码向模型中添加了一个 LSTM 层
# 这个 LSTM 层有 64 个神经元,return_sequences=True 表示输出序列中的每个时间步都产生输出
# activation='relu' 表示激活函数使用 ReLU。input_shape=(30,1662) 指定了输入数据的形状,
# 其中 30 表示时间步的长度,1662 表示每个时间步的特征维度

model.add(LSTM(64, return_sequences=True, activation='relu', input_shape=(30,1662)))
model.add(LSTM(128, return_sequences=True, activation='relu'))
model.add(LSTM(64, return_sequences=False, activation='relu'))

# 这行代码添加了一个全连接层(Dense 层)用于进行非线性变换,并包含 64 个神经元。
model.add(Dense(64, activation='relu'))
model.add(Dense(32, activation='relu'))

# 这行代码添加了最后一个全连接层,神经元的数量为 actions.shape[0],并使用 softmax 激活函数将输出转换为概率分布。
model.add(Dense(actions.shape[0], activation='softmax'))

# 这行代码配置了模型的优化器、损失函数和评估指标。使用 Adam 优化器,交叉熵作为损失函数,同时计算分类准确度作为评估指标。
model.compile(optimizer='Adam', loss='categorical_crossentropy', metrics=['categorical_accuracy'])
7.为模型添加标签

下面这段代码这段代码是一个数据预处理的过程,用于准备训练和测试数据集。

首先,定义了一个名为 label_map 的字典,它将动作标签(actions 列表中的元素)映射为数字标签(从 0 开始的递增数字)。
然后,创建了两个空列表 sequences 和 labels,用于存储序列数据和对应的标签。
接下来,通过嵌套的循环遍历每个动作 action 和每个序列 sequence,从文件中加载一系列帧数据(使用 np.load 函数),并将每个帧的数据添加到名为 window 的列表中。
将完整的 window 列表添加到 sequences 列表中,同时将对应的动作标签的数字表示(通过 label_map 字典)添加到 labels 列表中。
将 sequences 转换为 NumPy 数组 X,将 labels 转换为独热编码形式的 NumPy 数组 y(使用 to_categorical 函数进行独热编码),并将其转换为整数类型。
最后,使用 train_test_split 函数将数据集划分为训练集和测试集,其中 X_train 和 y_train 是训练集的输入序列和标签,X_test 和 y_test 是测试集的输入序列和标签。test_size=0.25 表示测试集占总数据集的 25%。
这段代码的主要目的是加载序列数据,并将其转换为适用于训练和测试的格式。X_train 和 y_train 是用于训练模型的输入和标签数据,而 X_test 和 y_test 是用于评估模型性能的测试数据。

label_map = {label: num for num, label in enumerate(actions)}
sequences, labels = [], []
for action in actions:
    for sequence in range(no_sequences):
        window = []
        for frame_num in range(sequence_length):
            res = np.load(os.path.join(DATA_PATH, action, str(sequence), "{}.npy".format(frame_num)))
            window.append(res)
        sequences.append(window)
        labels.append(label_map[action])

X = np.array(sequences)
y = to_categorical(labels).astype(int)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25)

model.fit(X_train, y_train, epochs=2000, callbacks=[tb_callback])
print(model.summary())

8. 保存权重

显然,我们在上一步的预测工作没有问题,那么接下来我们需要将训练完成的模型保存,以便于我们之后使用。

将模型保存下来很简单,只需要将以下所示代码附加到之前的代码下面就可以保存这个模型,这个模型会保存于项目根目录并且名字为action.h5的文件

model.save('action.h5')

训练完成后的输出则代表成功

Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
lstm (LSTM)                  (None, 30, 64)            442112    
_________________________________________________________________
lstm_1 (LSTM)                (None, 30, 128)           98816     
_________________________________________________________________
lstm_2 (LSTM)                (None, 64)                49408     
_________________________________________________________________
dense (Dense)                (None, 64)                4160      
_________________________________________________________________
dense_1 (Dense)              (None, 32)                2080      
_________________________________________________________________
dense_2 (Dense)              (None, 3)                 99        
=================================================================
Total params: 596,675
Trainable params: 596,675
Non-trainable params: 0
_________________________________________________________________

(上传)模型文件

9. 进行预测

使用固定的数值,合适的张量形状输入以测试结果

res = model.predict(X_test)
actions[np.argmax(res[4])]
'hello'
actions[np.argmax(y_test[4])]
'hello'

10. 读取模型

当我们保存了模型之后我们肯定要读取模型,我们只需要使用以下两句就能读取我们之前训练的模型。

model = tens.keras.models.load_model('number.h5')
model.load_weights('number.h5')
11.验证步骤

当你执行以上步骤时,在你的工程的根目录会有以下图片所示文件,之后就可以通过直接读取这个模型

在这里插入图片描述

(上传)用户将此模型上传

(任务五)最后的测试

那么最后,我们将综合运用以上步骤,即先通过opencv获取图像,然后通过mediapipe模型分析图像,得到关键点数据,然后
绘制关键点,再将合适的张量输入到模型中,模型会给出一个输出,然后通过合适的逻辑展示结果。

1.可视化检测结果

将结果可视化为多个矩形,以表示模型预测的概率

from scipy import stats
colors = [(245,117,16), (117,245,16), (16,117,245)]
def prob_viz(res, actions, input_frame, colors):
    output_frame = input_frame.copy()
    for num, prob in enumerate(res):
        cv2.rectangle(output_frame, (0,60+num*40), (int(prob*100), 90+num*40), colors[num], -1)
        cv2.putText(output_frame, actions[num], (0, 85+num*40), cv2.FONT_HERSHEY_SIMPLEX, 1, (255,255,255), 2, cv2.LINE_AA)
        
    return output_frame
2. 一些需要用到的变量
# 1. New detection variables
sequence = []
sentence = []
predictions = []
threshold = 0.5

打开摄像头

cap = cv2.VideoCapture(0)
# Set mediapipe model 
with mp_holistic.Holistic(min_detection_confidence=0.5, min_tracking_confidence=0.5) as holistic:
    while cap.isOpened():

    # 获取图像
    ret, frame = cap.read()

    # 通过Mediapipe获取关键点
    image, results = mediapipe_detection(frame, holistic)
    print(results)
    
    # 绘制关键点
    draw_styled_landmarks(image, results)
    
    # 2. 模型预测的逻辑
    keypoints = extract_keypoints(results)
    sequence.append(keypoints)
    sequence = sequence[-30:]
    
    if len(sequence) == 30:
        res = model.predict(np.expand_dims(sequence, axis=0))[0]
        print(actions[np.argmax(res)])
        predictions.append(np.argmax(res))
        
        
    #3. 可视化逻辑
        if np.unique(predictions[-10:])[0]==np.argmax(res): 
            if res[np.argmax(res)] > threshold: 
                
                if len(sentence) > 0: 
                    if actions[np.argmax(res)] != sentence[-1]:
                        sentence.append(actions[np.argmax(res)])
                else:
                    sentence.append(actions[np.argmax(res)])

        if len(sentence) > 5: 
            sentence = sentence[-5:]

        # Viz probabilities
        image = prob_viz(res, actions, image, colors)
        
    cv2.rectangle(image, (0,0), (640, 40), (245, 117, 16), -1)
    cv2.putText(image, ' '.join(sentence), (3,30), 
                   cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2, cv2.LINE_AA)
    
    # 展示
    cv2.imshow('OpenCV Feed', image)

    # 退出循环
    if cv2.waitKey(10) & 0xFF == ord('q'):
        break
cap.release()
cv2.destroyAllWindows()
1.验证步骤

当你的结果符合以下图片时则证明模型能够正常运行。

在这里插入图片描述

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值