AI识万物:从0搭建和部署手语识别系统 ⛵
作者:韩信子@ShowMeAI
深度学习实战系列:https://www.showmeai.tech/tutorials/42
计算机视觉实战系列: https://www.showmeai.tech/tutorials/46
本文地址:https://www.showmeai.tech/article-detail/292
声明:版权所有,转载请联系平台与作者并注明出处
收藏ShowMeAI查看更多精彩内容
据北京听力协会预估数据,我国听障人群数量已过千万。而在全球范围内有4.66亿人患有残疾性听力损失,约占全世界人口的5%。聋哑人士很特殊,他们需要使用手语进行交流,其他与常人无异,我国存在特殊教育水平在各城市中发展力度具有较大差异,国家通用手语推广程度浅,但不懂手语,与听力障碍者交流会非常困难。
在本篇内容中,ShowMeAI 借助深度学习与神经网络技术,针对这个问题从 0 构建 1 个应用程序,检测手语并将其翻译给其他人进而打破手语隔阂。
搭建和部署完成后,你可以通过摄像头,轻松测试模型,如下图所示,快来一起试试吧。这个动图中的手势代表的单词,见文末哦!
手语介绍
我们先来简单了解一下手语,它由 3 个主要部分组成:
- 手指拼写:这是一种手动的交流方式,用双手和手指拼写单词。每个字母都用指定的手位置表示。
- 单词级符号词汇:这是一个大型视频数据集,用于识别单词或字母的整个手势。
- 非手部特征:包括任何面部表情、嘴巴、舌头或身体姿势。
在本文中,我们先解决第①个部分的问题。我们准备使用的解决方案是基于视觉数据的神经网络
深度学习与计算机视觉
人工智能和计算机视觉的最典型的模型是卷积神经网络(CNN),它在典型的计算机视觉应用中(如图像识别、目标检测等)应用广泛。我们在本次应用的核心技术也将采用 CNN。
CNN 网络有着如上图所示的网络结构,典型的结构包括卷积层、池化层、激活层、全连接层等,对于输入图像,可以有效抽取图像内容表征,并进行分类或其他处理。卷积层等特殊结构,可以在控制参数量的前提下,保证良好的图像特征提取能力。
关于卷积神经网络的详细知识可以参考ShowMeAI下述教程:
小试牛刀,打通流程
我们来构建一个 CNN 识别的流程,会分成以下基础步骤:
- 数据读取与切分
- 数据可视化及预处理
- CNN网络构建与训练
① 导入相关库
我们在这里主要使用 TensorFlow 构建网络与训练,会使用 Numpy 做数据计算与处理,以及使用 Matplotlib 进行简单可视化。
对于这些工具库,ShowMeAI都制作了快捷即查即用的速查表手册,大家可以在下述位置获得:
我们先把这些工具库导入。
# 导入工具库
import string
import pandas as pd
import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt
from tensorflow import keras
from functools import partial
from tensorflow.keras.preprocessing.image import ImageDataGenerator, array_to_img
② 读取数据集
本数据集为手语字母对应的数据集,图片 size 不大,所以也叫做 sign_mnist 数据集(类比手写数字数据集 mnist),部分示例图片如下
数据集大家可以在 Kaggle平台对应数据集页面 下载,也可以通过ShowMeAI的百度网盘地址下载。
实战数据集下载(百度网盘):公众号『ShowMeAI研究中心』回复『实战』,或者点击 这里 获取本文 [5] 从0搭建基于神经网络的手语识别系统 『sign_mnist 数据集』
ShowMeAI官方GitHub:https://github.com/ShowMeAI-Hub
下面我们加载训练集与测试集并切分特征与标签:
# 读取数据
test = pd.read_csv("sign_mnist_test.csv")
train = pd.read_csv("sign_mnist_train.csv")
# 输出基本信息
print("训练集维度", train.shape)
print("测试集维度", train.shape)
# 输出标签信息
labels = train["label"].value_counts().sort_index(ascending=True)
labels
# 切分特征与标签
train_x = train.drop(labels = "label", axis = 1)
train_y = train["label"]
test_x = test.drop(labels = "label", axis = 1)
test_y = test["label"]
train_x.head()
# 数据预处理与可视化
# 存储标签数据
test_classes= test_y
train_clasees = train_y
# 特征转为numpy格式
train_x = train_x.to_numpy()
test_x = test_x.to_numpy()
# 把数据转为3维图像数据(图片数量*宽*高,这里如果是灰度图,颜色通道为1,省略)
train_x = train_x.reshape(-1,28,28)
test_x = test_x.reshape(-1,28,28)
# 在训练集中取样30张图片,做可视化查看
def plot_categories(training_images, training_labels):
fig, axes = plt.subplots(3, 10, figsize=(16, 15))
axes = axes.flatten()
letters = list(string.ascii_lowercase)
for k in range(30):
img = training_images[k]
img = np.expand_dims(img, axis=-1)
img = array_to_img(img)
ax = axes[k]
ax.imshow(img, cmap="Greys_r")
ax.set_title(f"{letters[int(training_labels[k])]}")
ax.set_axis_off()
plt.tight_layout()
plt.show()
plot_categories(train_x, train_y)
③ 卷积神经网络CNN搭建
我们使用 TensorFlow 的 high level API(即keras)搭建一个简易CNN神经网络,并拟合一下数据
def create_model():
model = tf.keras.models.Sequential([
# 卷积层
tf.keras.layers.Conv2D(32, (3,3), activation='relu', input_shape=(28, 28, 1)),
# 池化层
tf.keras.layers.MaxPooling2D(2,2),
# 卷积层
tf.keras.layers.Conv2D(32, (3,3), activation='relu'),
# 池化层
tf.keras.layers.MaxPooling2D(2,2),
# 展平
tf.keras.layers.Flatten(),
# 全连接层
tf.keras.layers.Dense(512, activation='relu'),
# softmax分类
tf.keras.layers.Dense(26, activation='softmax')])
model.compile(
optimizer='adam', #优化器
loss='sparse_categorical_crossentropy', #损失函数
metrics=['accuracy']) #评估准则
return model
# 初始化模型
model = create_model()
# 拟合数据
history = model.fit(train_x, train_y, epochs=20, validation_data=(test_x, test_y))
我们这里在全量数据集上迭代20个轮次,结果如下:
我们可以看到,这里的数据并不特别复杂,在自己从头搭建的 CNN 模型上,经过训练可以达到训练集 100% 验证集 92% 的准确率。
我们再对训练过程中的「准确率」及「损失函数」变化值进行绘制,以了解模型状态。
# 获取准确率与损失函数情况
acc = history.history['accuracy']
val_acc = history.history['val_accuracy']
loss = history.history['loss']
val_loss = history.history['val_loss']
# matplotlib绘制训练过程中指标的变化状况
epochs = range(len(acc))
plt.plot(epochs, acc, 'r', label='Training accuracy')
plt.plot(epochs, val_acc, 'b', label='Validation accuracy')
plt.title('Training and validation accuracy')
plt.legend()
plt.figure()
plt.plot(epochs, loss, 'r', label='Training Loss')
plt.plot(epochs, val_loss, 'b', label='Validation Loss')
plt.title('Training and validation loss')
plt.legend()
plt.show()
问题与优化
① 深度网络与梯度消失
一般来说,随着 CNN 网络层数变深,模型的学习能力会变强,也能学到更多的信息。但训练深度CNN存在梯度消失的问题。
梯度消失和梯度爆炸部分内容也可以参考ShowMeAI的对吴恩达老师课程的总结文章 深度学习教程 | 深度学习的实用层面
非常深的神经网络的梯度会很快变为零(反向传播的梯度连乘带来的问题),这最终会使整个梯度下降变慢。有一些特殊结构的神经网络,可以大程度缓解这个问题,比如最著名的 ResNet,当然,大家可以借助 ResNet 预训练模型快速迁移学习应用在我们当前的手语识别问题上,为了让大家对ResNet 细节更清晰,我们在这里手动搭建 ResNet-50(即50层的ResNet网络)来训练和做效果对比。
ResNet的详细讲解也可以参考ShowMeAI的 深度学习教程 | 吴恩达专项课程 · 全套笔记解读中的文章 深度学习教程 | 经典CNN网络实例详解。
② ResNet 模型简介
ResNet 是 Residual Networks 的简称,是迄今为止我们看到的最流行和最成功的深度学习模型之一。ResNets 由残差块组成,残差块的核心组件是『跳跃连接/skip-connection』。跳跃连接,也称为快捷连接,让神经网络跳过某些层并将一层的输出馈送到神经网络中另一层的输入。它能帮助模型避免乘以中间跳过的那些层的权重,从而有助于解决梯度消失的问题。
然而,使用 ResNet 和跳跃连接,由于中间有卷积层和池化层,一层输出的维度可能与另一层的输出维度不同。为了解决这个问题,可以使用两种方法:
- 快捷连接填充多个零实体以增加其维度
- 添加 1X1 卷积层来匹配维度。
但是,对于第二种方法,我们需要在输出中添加一个额外的参数,而第一种方法不需要。
③ ResNet为何有效
ResNet的效果核心有2点:
- ① 它使用我们上面提到的跳跃连接,它跳过层来解决梯度消失的问题。
- ② 它通过让模型学习恒等函数来确保最高层的性能至少与最低层一样好。
④ 构建ResNet-50
下面我们参考 keras 官方 ResNet 构建方式,构建一个 ResNet-50,如下所示,我们先构建基本模块,再组装成最终的网络。
# Defining the identity block of the Resnet-50 Model.
def identity_block(X, f, filters, training=True):
# filter of the three convs
f1,f2,f3 = filters
X_shortcut = X
# First Component
X = tf.keras.layers.Conv2D(filters = f1, kernel_size = 1, strides = (1,1), padding = 'valid')(X)
X = tf.keras.layers.BatchNormalization(axis = 3)(X, training = training) # Default axis
X = tf.keras.layers.Activation('relu')(X)
# Second Component
X = tf.keras.layers.Conv2D(filters = f2, kernel_size = f, strides = (1,1), padding = 'same')(X)
X = tf.keras.layers.BatchNormalization(axis = 3)(X, training = training) # Default axis
X = tf.keras.layers.Activation('relu')(X)
# Third Component
X = tf.keras.layers.Conv2D(filters = f3, kernel_size = 1, strides = (1,1), padding = 'valid')(X)
X = tf.keras.layers.BatchNormalization(axis = 3)(X, training = training) # Default axis
# Adding the two tensors
X = tf.keras.layers.Add()([X_shortcut,X])
X = tf.keras.layers.Activation('relu')(X)
# Returning the last output
return X
# Defining the Convolution Block of the Resnet-50 Model.
def convolutional_block(X, f, filters, s=2,training=True):
# filter of the three convs
f1,f2,f3 = filters
X_shortcut = X
# First Component
X = tf.keras.layers.Conv2D(filters = f1, kernel_size = 1, strides = (1,1), padding = 'valid')(X)
X = tf.keras.layers.BatchNormalization(axis = 3)(X, training = training) # Default axis
X = tf.keras.layers.Activation('relu')(X)
# Second Component
X = tf.keras.layers.Conv2D(filters = f2, kernel_size = f, strides = (s,s), padding = 'same')(X)
X = tf.keras.layers.BatchNormalization(axis = 3)(X, training = training) # Default axis
X = tf.keras.layers.Activation('relu')(X)
# Third Component
X = tf.keras.layers.Conv2D(filters = f3, kernel_size = 1, strides = (1,1), padding = 'valid')(X)
X = tf.keras.layers.BatchNormalization(axis = 3)(X, training = training) # Default axis
# Converting the Input Volume to the match the last output for addition.
X_shortcut =tf.keras.layers.Conv2D(filters = f3, kernel_size = 1, strides = (s,s), padding = 'valid')(X_shortcut)
X_shortcut = tf.keras.layers.BatchNormalization(axis = 3)(X_shortcut, training = training)
X = tf.keras.layers.Add()([X_shortcut,X])
X = tf.keras.layers.Activation('relu')(X)
# Adding the last two tensors
X = tf.keras.layers.Add()([X, X_shortcut])
X = tf.keras.layers.Activation('relu')(X)
# Returning the output tensor
return X
# Defining a modified Resnet-50 Model using the Identity and Convolution Blocks.
def ResNet50(input_shape = (28, 28, 1), classes = 26):
# Defining the input as a tensor with shape input_shape
X_input = tf.keras.Input(input_shape)
# Zero-Padding
X = tf.keras.layers.ZeroPadding2D((3, 3))(X_input)
# Stage 1
X = tf.keras.layers.Conv2D(64, (5, 5), strides = (1, 1))(X)
X = tf.keras.layers.BatchNormalization(axis = 3)(X)
X = tf.keras.layers.Activation('relu')(X)
X = tf.keras.layers.MaxPooling2D((3, 3), strides=(2, 2))(X)
# Stage 2
X = convolutional_block(X, f = 3, filters = [64, 64, 256], s = 1)
X = identity_block(X, 3, [64, 64, 256])
X = identity_block(X, 3, [64, 64, 256])
# Add an Average Pool Layer
X = tf.keras.layers.AveragePooling2D((2,2))(X)
# Output Layer
X = tf.keras.layers.Flatten()(X)
X = tf.keras.layers.Dense(classes, activation='softmax')(X)
# Create Model
model = tf.keras.Model(inputs = X_input, outputs = X)
return model
⑤ 训练ResNet-50
下面我们在数据集上,使用 ResNet-50 网络进行训练
# 初始化模型
model = ResNet50()
# 编译
model.compile(optimizer="adam",metrics=["accuracy"],loss = "sparse_categorical_crossentropy")
# 训练
history = model.fit(train_x, train_y, validation_data = (test_x, test_y), epochs =10)
得到如下结果
优化效果对比
我们对ResNet-50也绘制训练过程中准确率和损失函数的变化,如下
# 获取准确率与损失函数情况
acc = history.history['accuracy']
val_acc = history.history['val_accuracy']
loss = history.history['loss']
val_loss = history.history['val_loss']
# matplotlib绘制训练过程中指标的变化状况
epochs = range(len(acc))
plt.plot(epochs, acc, 'r', label='Training accuracy')
plt.plot(epochs, val_acc, 'b', label='Validation accuracy')
plt.title('Training and validation accuracy')
plt.legend()
plt.figure()
plt.plot(epochs, loss, 'r', label='Training Loss')
plt.plot(epochs, val_loss, 'b', label='Validation Loss')
plt.title('Training and validation loss')
plt.legend()
plt.show()
对比图如下:
我们观察到,从简单的 CNN 模型换到 ResNet 模型时,测试集的准确率从92% 到 97% 。也说明了,ResNet 的结构确实能够带来效果上的提升。
部署与实时测试
在这里我们做一个简单的测试,使用 OpenCV 的视频录制功能,通过 python 收集我们的摄像头的镜头采集的图像并进行实时预测。
ShowMeAI给OpenCV工具库制作了快捷即查即用的 OpenCV 速查表手册,大家可以点击查看和下载。
具体的过程是,我们解析捕获的每一帧图像,将其处理为灰度图(类似于我们模型的训练集),在图像中心抓取一个 400*400 像素的正方形区域(参见 x0,x1,y0,y1),将正方形调整为我们最初的 28x28 大小并使用我们的模型进行测试(之前保存到 .h5 文件)。
# 导入工具库
import keras
import numpy as np
from PIL import Image
import string
import pandas as pd
import tensorflow as tf
# 导入OpenCV
import cv2
from matplotlib import pyplot
# 设定维度
dim = (28, 28) # 图像维度
letters = list(string.ascii_lowercase) # 识别的字母
x0 = 1920 // 2 - 400 # 400px left of center
x1 = 1920 // 2 + 400 # 400px right of center
y0 = 1080 // 2 - 400 # 400px right of center
y1 = 1080 // 2 + 400 # 400px right of center
# 初始化视频捕获
video=cv2.VideoCapture(0)
cv2.namedWindow('Webcam') # 构建1个窗口
cv2.moveWindow('Webcam',40,30) # 放置窗口
while video.isOpened(): # 只要没有关掉实时摄像头
ret,capture = video.read() # 抓取每个视频帧
cropped = capture[y0:y1, x0:x1] # 截取
img = cv2.cvtColor(cropped, cv2.COLOR_BGR2GRAY) # 转成灰度图
img = cv2.GaussianBlur(img, (5, 5), 0) # 图像平滑
img = cv2.resize(img, dim) # 图像大小缩放
pyplot.imshow(img, cmap='gray') # 可视化展示图片
pyplot.show() # 展示
img = np.reshape(img, (1,img.shape[0],img.shape[1],1))
img = tf.cast(img, tf.float32)
pred=model.predict(img)
# 可视化实时效果
cv2.rectangle(capture, (x0,y0),(x1,y1),(255,0,0),2) # 为图片添加矩形框
cv2.putText(capture,'{} res50'.format(letters[np.argmax(pred[0])]),(x0+25,y0+50),cv2.FONT_HERSHEY_SIMPLEX,0.9,(0,255,0),1) # 预测字母
cv2.imshow('Webcam', capture) # 展示视频
# 结果输出
print(pred)
print(letters[np.argmax(pred[0])])
# 退出视频输入
key = cv2.waitKey(1)
if key == ord('q'):
break
video.release()
cv2.destroyAllWindows()
为了更轻松地对预估结果查看,我们把将预测的字母显示在实时画面上(请参阅下面的 gif 以测试单词 hello
)。
参考资料
- 实战数据集下载(百度网盘):公众号『ShowMeAI研究中心』回复『实战』,或者点击 这里 获取本文 [5] 从0搭建基于神经网络的手语识别系统 『sign_mnist 数据集』
- ShowMeAI官方GitHub:https://github.com/ShowMeAI-Hub
- 深度学习教程 | 吴恩达专项课程 · 全套笔记解读: https://www.showmeai.tech/tutorials/35
- 卷积神经网络解读: https://www.showmeai.tech/article-detail/221
- 深度学习的实用层面: https://www.showmeai.tech/article-detail/216
- 经典CNN网络实例详解: https://www.showmeai.tech/article-detail/222
- 深度学习与计算机视觉教程: https://www.showmeai.tech/tutorials/37
- 卷积神经网络详解: https://www.showmeai.tech/article-detail/264
- Tensorflow 速查手册: https://www.showmeai.tech/article-detail/109
- OpenCV 速查表手册: https://www.showmeai.tech/article-detail/112
- Numpy 速查手册: https://www.showmeai.tech/article-detail/100
- Matplotlib 速查手册: https://www.showmeai.tech/article-detail/103
- https://arxiv.org/ftp/arxiv/papers/1905/1905.05487.pdf
- https://www.analyticsvidhya.com/blog/2021/06/sign-language-recognition-for-computer-vision-enthusiasts
AI识万物:从0搭建和部署手语识别系统 ⛵的更多相关文章
- 使用tensorflow搭建自己的验证码识别系统
目录 准备验证码数据 保存为tfrecords文件 验证码训练 学习tensorflow有一段时间了,想做点东西来练一下手.为了更有意思点,下面将搭建一个简单的验证码识别系统. 准备验证码数据 下面将 ...
- VMware vSphere6.0 服务器虚拟化部署安装图解(最全,最详细)-搭建的所有步骤
VMware vSphere6.0 服务器虚拟化部署安装图解 一 .VMware vSphere部署的前期规划要点 1.vSphere的优点 (略) 2如何利用现在的设备架构虚拟化环境 在虚拟化过程中 ...
- jenkins+git+maven搭建自动化部署项目环境
简介 折腾了两个晚上,趁着今晚比较有空,把jenkins+git+maven搭建自动化部署项目环境搭建的过程记录一下,这里我把github作为git的远程仓库(https://github.co ...
- 使用gogs,drone搭建自动部署
使用gogs,drone搭建自动部署 使用gogs,drone,docker搭建自动部署测试环境 Gogs是一个使用go语言开发的自助git服务,支持所有平台Docker是使用go开发的开源容器引擎D ...
- 记录使用gogs,drone搭建自动部署测试环境
使用gogs,drone,docker搭建自动部署测试环境 Gogs是一个使用go语言开发的自助git服务,支持所有平台 Docker是使用go开发的开源容器引擎 Drone是一个基于容器技术的持续集 ...
- Storm-0.9.0.1安装部署 指导
可以带着下面问题来阅读本文章: 1.Storm只支持什么传输 2.通过什么配置,可以更改Zookeeper默认端口 3.Storm UI必须和Storm Nimbus部署在同一台机器上,UI无法正常工 ...
- hadoop集群搭建--CentOS部署Hadoop服务
在了解了Hadoop的相关知识后,接下来就是Hadoop环境的搭建,搭建Hadoop环境是正式学习大数据的开始,接下来就开始搭建环境!我们用到环境为:VMware 12+CentOS6.4 hadoo ...
- AI应用开发实战 - 从零开始搭建macOS开发环境
AI应用开发实战 - 从零开始搭建macOS开发环境 本视频配套的视频教程请访问:https://www.bilibili.com/video/av24368929/ 建议和反馈,请发送到 https ...
- Vue项目搭建与部署
Vue项目搭建与部署 一,介绍与需求 1.1,介绍 Vue 是一套用于构建用户界面的渐进式框架.与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用.Vue两大核心思想:组件化和数据驱动.组 ...
随机推荐
- 课堂测试——HDFS操作
要求: 编程实现以下功能,并利用Hadoop提供的Shell命令完成相同任务: 向HDFS中上传任意文本文件,如果指定的文件在HDFS中已经存在,则由用户来指定是追加到原有文件末尾还是覆盖原有的文件: ...
- Map和WeakMap的方法和区别
Map Map是一组键值对的结构,具有极快的查找速度. 一.构造函数不同 let map = new Map(); let weakmap = new WeakMap(); 二.内置函数不同 Map的 ...
- 数据库与MySQL的下载使用
目录 数据存储演变史 数据库应用发展史 数据库本质 数据库分类 关系型数据库 非关系型数据库 SQL与NoSQL MySQL简介 版本问题 下载使用 目录结构 基本使用 简单使用 系统服务 修改密码 ...
- 第06组Alpha冲刺(3/6)
目录 1.1 基本情况 1.2 冲刺概况汇报 1.郝雷明 2.鲍凌函 3.曾丽莉 4. 曹兰英 5. 方梓涵 6.董翔云 7.杜筱 8.黄少丹 9. 詹鑫冰 10.吴沅静 1.3 冲刺成果展示 1.1 ...
- React history.push()无法跳转 url改变页面不渲染
一.history.push()无法跳转参考了很多文章 研究一下生命周期 render是要有state变化才会执行 BrowserHistory只有props变化 无法触发render 如下改造环境 ...
- 中国天气api接口xml,json
http://m.weather.com.cn/data/101110101.html 大坑有木有??反应慢不说了,还老不更新!! 想贴段代码的,现在又打不 开了(貌似3月4号以后没更新过) ==== ...
- 全球共有多少MySQL实例在运行?这里有一份数据
摘要 Shadowserver Foundation在5月31日发布了一份全网的MySQL扫描报告,共发现了暴露在公网的360万个MySQL实例.因为这份报告基数够大,而且信息也非常完整,从数据库专业 ...
- node环境下怎样优化引入多文件(实现自动化)
const mocks = [];function getJsonFiles(jsonPath) {function findJsonFile(path) {let files = fs.readdi ...
- 使用Node.js还可以发邮件
前言 今天,我们给大家开发一个小效果.篇幅比较短,主要给大家展示效果.实战 首先我们初始化一个Node项目 npm init -y 创建一个app.js文件 'use strict'; const n ...
- HDLBits->Circuits->Arithmetic Circuitd->3-bit binary adder
Verilog实例数组 对于一个定义好的简单module,例如加法器之类,如果我们要对其进行几十次几百次的例化,并且这些例化基本都是相同的形式,那么我们肯定不能一个个的单独对其进行例化,此时我们就可以 ...