带你开发一个视频动态手势识别模型
本文分享自华为云社区《CNN-VIT 视频动态手势识别【玩转华为云】》,作者: HouYanSong。
CNN-VIT 视频动态手势识别
人工智能的发展日新月异,也深刻的影响到人机交互领域的发展。手势动作作为一种自然、快捷的交互方式,在智能驾驶、虚拟现实等领域有着广泛的应用。手势识别的任务是,当操作者做出某个手势动作后,计算机能够快速准确的判断出该手势的类型。本文将使用ModelArts开发训练一个视频动态手势识别的算法模型,对上滑、下滑、左滑、右滑、打开、关闭等动态手势类别进行检测,实现类似华为手机隔空手势的功能。
算法简介
CNN-VIT 视频动态手势识别算法首先使用预训练网络InceptionResNetV2逐帧提取视频动作片段特征,然后输入Transformer Encoder进行分类。我们使用动态手势识别样例数据集对算法进行测试,总共包含108段视频,数据集包含无效手势、上滑、下滑、左滑、右滑、打开、关闭等7种手势的视频,具体操作流程如下:
首先我们将采集的视频文件解码抽取关键帧,每隔4帧保存一次,然后对图像进行中心裁剪和预处理,代码如下:
def load_video(file_name): cap = cv2.VideoCapture(file_name) # 每隔多少帧抽取一次 frame_interval = 4 frames = [] count = 0 while True: ret, frame = cap.read() if not ret: break # 每隔frame_interval帧保存一次 if count % frame_interval == 0: # 中心裁剪 frame = crop_center_square(frame) # 缩放 frame = cv2.resize(frame, (IMG_SIZE, IMG_SIZE)) # BGR -> RGB [0,1,2] -> [2,1,0] frame = frame[:, :, [2, 1, 0]] frames.append(frame) count += 1 return np.array(frames)
然后我们创建图像特征提取器,使用预训练模型InceptionResNetV2提取图像特征,代码如下:
def get_feature_extractor(): feature_extractor = keras.applications.inception_resnet_v2.InceptionResNetV2( weights = 'imagenet', include_top = False, pooling = 'avg', input_shape = (IMG_SIZE, IMG_SIZE, 3) ) preprocess_input = keras.applications.inception_resnet_v2.preprocess_input inputs = keras.Input((IMG_SIZE, IMG_SIZE, 3)) preprocessed = preprocess_input(inputs) outputs = feature_extractor(preprocessed) model = keras.Model(inputs, outputs, name = 'feature_extractor') return model
接着提取视频特征向量,如果视频不足40帧就创建全0数组进行补白:
def load_data(videos, labels): video_features = [] for video in tqdm(videos): frames = load_video(video) counts = len(frames) # 如果帧数小于MAX_SEQUENCE_LENGTH if counts < MAX_SEQUENCE_LENGTH: # 补白 diff = MAX_SEQUENCE_LENGTH - counts # 创建全0的numpy数组 padding = np.zeros((diff, IMG_SIZE, IMG_SIZE, 3)) # 数组拼接 frames = np.concatenate((frames, padding)) # 获取前MAX_SEQUENCE_LENGTH帧画面 frames = frames[:MAX_SEQUENCE_LENGTH, :] # 批量提取特征 video_feature = feature_extractor.predict(frames) video_features.append(video_feature) return np.array(video_features), np.array(labels)
最后创建VIT Model,代码如下:
# 位置编码 class PositionalEmbedding(layers.Layer): def __init__(self, seq_length, output_dim): super().__init__() # 构造从0~MAX_SEQUENCE_LENGTH的列表 self.positions = tf.range(0, limit=MAX_SEQUENCE_LENGTH) self.positional_embedding = layers.Embedding(input_dim=seq_length, output_dim=output_dim) def call(self,x): # 位置编码 positions_embedding = self.positional_embedding(self.positions) # 输入相加 return x + positions_embedding # 编码器 class TransformerEncoder(layers.Layer): def __init__(self, num_heads, embed_dim): super().__init__() self.p_embedding = PositionalEmbedding(MAX_SEQUENCE_LENGTH, NUM_FEATURES) self.attention = layers.MultiHeadAttention(num_heads=num_heads, key_dim=embed_dim, dropout=0.1) self.layernorm = layers.LayerNormalization() def call(self,x): # positional embedding positional_embedding = self.p_embedding(x) # self attention attention_out = self.attention( query = positional_embedding, value = positional_embedding, key = positional_embedding, attention_mask = None ) # layer norm with residual connection output = self.layernorm(positional_embedding + attention_out) return output def video_cls_model(class_vocab): # 类别数量 classes_num = len(class_vocab) # 定义模型 model = keras.Sequential([ layers.InputLayer(input_shape=(MAX_SEQUENCE_LENGTH, NUM_FEATURES)), TransformerEncoder(2, NUM_FEATURES), layers.GlobalMaxPooling1D(), layers.Dropout(0.1), layers.Dense(classes_num, activation="softmax") ]) # 编译模型 model.compile(optimizer = keras.optimizers.Adam(1e-5), loss = keras.losses.SparseCategoricalCrossentropy(from_logits=False), metrics = ['accuracy'] ) return model
模型训练
完整体验可以点击Run in ModelArts一键运行我发布的Notebook:
最终模型在整个数据集上的准确率达到87%,即在小数据集上训练取得了较为不错的结果。
视频推理
首先加载VIT Model,获取视频类别索引标签:
import random # 加载模型 model = tf.keras.models.load_model('saved_model') # 类别标签 label_to_name = {0:'无效手势', 1:'上滑', 2:'下滑', 3:'左滑', 4:'右滑', 5:'打开', 6:'关闭', 7:'放大', 8:'缩小'}
然后使用图像特征提取器InceptionResNetV2提取视频特征:
# 获取视频特征 def getVideoFeat(frames): frames_count = len(frames) # 如果帧数小于MAX_SEQUENCE_LENGTH if frames_count < MAX_SEQUENCE_LENGTH: # 补白 diff = MAX_SEQUENCE_LENGTH - frames_count # 创建全0的numpy数组 padding = np.zeros((diff, IMG_SIZE, IMG_SIZE, 3)) # 数组拼接 frames = np.concatenate((frames, padding)) # 取前MAX_SEQ_LENGTH帧 frames = frames[:MAX_SEQUENCE_LENGTH,:] # 计算视频特征 N, 1536 video_feat = feature_extractor.predict(frames) return video_feat
最后将视频序列的特征向量输入Transformer Encoder进行预测:
# 视频预测 def testVideo(): test_file = random.sample(videos, 1)[0] label = test_file.split('_')[-2] print('文件名:{}'.format(test_file) ) print('真实类别:{}'.format(label_to_name.get(int(label))) ) # 读取视频每一帧 frames = load_video(test_file) # 挑选前帧MAX_SEQUENCE_LENGTH显示 frames = frames[:MAX_SEQUENCE_LENGTH].astype(np.uint8) # 保存为GIF imageio.mimsave('animation.gif', frames, duration=10) # 获取特征 feat = getVideoFeat(frames) # 模型推理 prob = model.predict(tf.expand_dims(feat, axis=0))[0] print('预测类别:') for i in np.argsort(prob)[::-1][:5]: print('{}: {}%'.format(label_to_name[i], round(prob[i]*100, 2))) return display(Image(open('animation.gif', 'rb').read()))
模型预测结果:
文件名:hand_gesture/woman_014_0_7.mp4 真实类别:无效手势 预测类别: 无效手势: 99.82% 下滑: 0.12% 关闭: 0.04% 左滑: 0.01% 打开: 0.01%

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
MindSpore强化学习:使用PPO配合环境HalfCheetah-v2进行训练
本文分享自华为云社区《MindSpore强化学习:使用PPO配合环境HalfCheetah-v2进行训练》,作者: irrational。 半猎豹(Half Cheetah)是一个基于MuJoCo的强化学习环境,由P. Wawrzyński在“A Cat-Like Robot Real-Time Learning to Run”中提出。这个环境中的半猎豹是一个由9个链接和8个关节组成的2D机器人(包括两个爪子)。在这个环境中,目标是通过施加扭矩在关节上使猎豹尽可能快地向前(向右)奔跑,正向奖励基于前进的距离,而向后移动则会得到负向奖励。猎豹的躯干和头部是固定的,扭矩只能施加在前后大腿、小腿和脚上。 动作空间是一个Box(-1, 1, (6,), float32),其中每个动作代表链接之间的扭矩。观察空间包含猎豹不同身体部位的位置值和速度值,其中所有位置值在前,所有速度值在后。默认情况下,观察不包括猎豹质心x坐标,可以通过在构建时传递exclude_current_positions_from_observation=False来包括它。如果包括,观察空间将有18个维度,其中第一个维度代...
- 下一篇
从原始边列表到邻接矩阵Python实现图数据处理的完整指南
本文分享自华为云社区《从原始边列表到邻接矩阵Python实现图数据处理的完整指南》,作者: 柠檬味拥抱。 在图论和网络分析中,图是一种非常重要的数据结构,它由节点(或顶点)和连接这些节点的边组成。在Python中,我们可以使用邻接矩阵来表示图,其中矩阵的行和列代表节点,矩阵中的值表示节点之间是否存在边。 原始边列表 假设我们有一个原始边列表,其中每个元素都表示一条边,例如: edges = [(0, 1), (0, 2), (1, 2), (2, 3)] 在这个例子中,每个元组(a, b)表示节点a和节点b之间存在一条边。 转换为邻接矩阵 我们首先需要确定图中节点的数量,然后创建一个相应大小的零矩阵。接着,我们遍历原始边列表,根据每条边的两个节点,将对应的矩阵元素设为 1。最终得到的矩阵就是我们所需的邻接矩阵。 让我们来看看如何用Python代码实现这一过程: def edges_to_adjacency_matrix(edges): # 找到图中节点的数量 max_node = max(max(edge) for edge in edges) + 1 # 创建零矩阵 ad...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- SpringBoot2全家桶,快速入门学习开发网站教程
- CentOS7设置SWAP分区,小内存服务器的救世主
- CentOS7安装Docker,走上虚拟化容器引擎之路
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- CentOS8安装Docker,最新的服务器搭配容器使用
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题