从零开始的手语实时翻译程序学习制作
知识补充
CNN模型简介
深度学习中一种非常强大的神经网络架构,广泛应用于计算机视觉和图像处理任务。关键特点是利用卷积操作自动提取图像特征。
卷积层
核心操作是卷积,卷积操作通过滑动的卷积核(Filter)逐步扫描输入图像,并计算局部区域的加权和,生成特征图(Feature Map)每个卷积核都在图像上滑动并执行相同的计算,因此它的参数是共享的,大大减少了模型的参数量。
操作步骤
输入:假设输入图像是一个二维矩阵,大小为H*W,其中H是图像高度,W是图像宽度,图像可以是灰度图(单通道)或者彩色图(多通道)。
卷积核(Filter):卷积核是一个比输入图像小的矩阵,通常用来在图像上滑动并进行加权求和,卷积核的大小通常为3*3或5*5或更大,取决于任务的复杂性。
运算
卷积核在输入图像上滑动,进行局部区域的加权求和。每次卷积核在图像的某个区域上滑动,都会计算卷积核与该区域的点积,然后将结果存储到输出特征图的相应位置。
输出特征图
卷积操作的结果是一个特征图,其大小由输入图像大小、卷积核大小和步幅(stride)决定,特征图每个值代表卷积核与图像某个区域的局部相应。
关键参数
步幅
步幅是卷积核在输入图像上滑动的步长。步幅越大,特征图的尺寸越小,因为卷积核滑动的越快,减少了计算量。
零填充
避免卷积操作导致特征图尺寸过小,通常会在图像边缘添加一些零填充,保证了卷积核可以完全覆盖图像边缘区域
卷积核数量
在CNN中,通常使用多个卷积核来提取不同的特征。例如第一个卷积层可能有32个卷积核,第二个卷积层可能有64个卷积核,每个卷积核负责提取不同类型的特征。
作用
提取图像特征
卷积层核心作用是从原始图像中提取特征,通过多个卷积层的堆叠,网络可以提取从低级(边缘,纹理)到高级(物体形状,局部特征)等不同的层次特征。
低级特征:卷积核能够提取边缘、角点等局部特征,这些特征对手势的识别很重要
高级特征:通过多层卷积操作,CNN可以组合低级特征,识别更复杂的模式,例如手势的具体形态。
保持空间结构
卷积层具有局部感受野,能够保持图像的空间结构。在卷积操作中,卷积核值处理局部区域,保留了图像中的空间信息,这对于手势识别至关重要。
在手语识别中的作用
静态手势识别:对于每个手势,卷积层提取手势图像的低级特征(如手部形状,位置,边缘)并未后续分类任务提供输入
特征层次构建:通过多层卷积,网络可以从简单的边缘特征逐步构建复杂的手势模式
激活函数
激活函数为网络引入非线性特征,使得神经网络能够学习和表示复杂关系。没有激活函数,神经网络就相当于多个线性变换的组合,不具备任何复杂的表达能力,因此激活函数的作用至关重要。
池化层
用于对特征图进行下采样,减少计算量,降低参数量,同时保留重要特征。
减少特征图尺寸:通过池化操作,特征图的尺寸变小,减少后续层的计算量
减少过拟合:通过降低特征图的分辨率,池化层有效的压缩了数据,帮只模型提高泛化能力,避免过拟合
提取不变特征:池化操作能提高特征在不同输入图像中变化的稳定性,使得网络更加鲁棒。
常见类型
最大池化
从每个池化窗口中选择最大值作为该区域的代表
优点:保留图像中最重要的特征;提高网络的鲁棒性和稳定性
缺点:对于小物体或细节特征的损失较大
平均池化
对池化窗口所有值取平均值,作为该区域代表
优点:可以平滑特征,减少噪声影响;提取的是平均特征信息,适合一些需要平滑特征的场景
缺点:可能导致细节的丢失,因为它没有保留最显著特征
池化层在手语识别中的作用
特征缩放:通过池化操作,手势图像的特征被缩小,减少了计算量,有助于加速训练和推理
保持重要特征:最大池化能够保留手势图像中的显著特征,如手的形状,位置,边缘等,同时消除无关信息
减少过拟合:池化操作减少了网络参数,降低了模型复杂度,从而减少过拟合风险
多层卷积和池化
多层卷积和池化通过层层堆叠卷积层和池化层,能够逐步提取图像中的局部和全局特征,有效提高模型的识别能力
全连接层(Fully Connected Layer,简称FC层)
用于将神经网络中所有神经元之间建立连接,最终输出网络的结果。全连接层通常用于神经网络的最后几层,用于根据前面层提取的特征进行分类,回归等任务。特点是每一个神经元都与前一层所有神经元相连接。
结构
输入:每个神经元都接收前一层所有神经元的输出,形成一个向量
权重:每个连接都有一个权重,决定了该连接的强度。权重矩阵是通过训练学习得到的
偏置:每个神经元都有一个偏置项,帮助模型对数据进行平移,增加模型灵活性。偏置允许神经网络调整调整激活函数的输出,即使输入为零,神经元也可以有一个输出值,其作用是使得神经网络的激活更加灵活,无论输入值如何网络都能学习到合适的函数映射。
输出:每个神经元输出一个值,作为下一层的输入,通常会通过激活函数进行处理
作用
在手语转文字中,卷积层和池化层通常负责提取图像的低级特征,而全连接层负责将这些特征整合,并输出最终的分类结果。
比如以下举例:
手语字母识别:对于每个手语字母,卷积层和池化层提取图像中的手势特征,最后通过全连接层将这些特征映射到对应的字母标签。
手语句子识别:对于一系列连接的手势,卷积层和池化层提取每一帧的图像特征,如果是LSTM(Long Short-Term Memory,长短期记忆网络)处理时序信息,最后通过全连接层将这些特征综合,输出句子预测。
LSTM(Long Short-Term Memory,长短期记忆网络)
用于处理序列数据,用一条传送带+三个“门”控制信息的保留/丢弃,显著缓解梯度消失/爆炸,能学更长依赖。
结构与公式(时间步t)

应用
LSTM的时序模式适用于手势的动态特征。我们采用CNN提取帧特征得到向量喂给LSTM进行处理。
为了提取孤立词,我们时序池化后分类进行提取。
在使用过程中我们采用LSTM的变体,BILSTM,它具有前向后向时序的特征,前后文照应更强,我们利用此特征生成一句一句的翻译。
CTC模型介绍
适用场景:语音识别、手写/OCR、手语视频转文本等输入/输出长度不对齐且只需句级标注的任务。CTC通过对其路径求和让模型在无需帧级标签的前提下训练。
相关概念介绍
输入特征序列:x1:T,长度T(例如T帧视频或声谱帧)。
词表:V(字符/拼音/词片),再额外加blank。
模型在每个时间步t产生一个分布:

p(k|xt)表示在实践步t模型对当前步输出为k的后验概率(即已知这一帧的内容估计对应标签为k的概率)
这里的xt常指编码器在第t步的特征/状态(ht),实际来自整段输入做过卷积的处理
若是双向/Transformer,ht含有全序列x1:t的信息,
若是流式/单向RNN,则主要依赖到当前的上下文
目标标签序列:
![]()
长度为U(不包含blank)
折叠函数B(collapse)
1.合并相邻的重复(但跨过blank不合并)
2.删除所有blank
例如:a_aab_cc_ --->(合并相邻重复)a_ab_c_--->(去blank)aabc
CTC目标:让所有能折叠刀标签y的路径概率之和最大

即让图中的LCTC最小。这里的πt为一个路径中的第t个字符选用乘积形式就是说这个路径是由这T个字符组成的概率大小。再取所有可以通过折叠得到y句子的路径概率求和,让这个值尽可能大。
HMM(隐马尔可夫模型)
用“看不见的状态序列”(马尔可夫链)+可见的观测序列(如帧序列),来刻画时序数据,并用一套动态规划算法评估、解码。学习。
概念介绍
隐状态:系统真正运行在的模式/标签,记作zt
观测:我们能测到的信号,记作xt
马尔可夫性:下一步只以来当前隐状态P(zt|z(t-1))
发射:给定隐状态生成观测P(xt|zt)
举手语翻译的例子,隐状态是我们手语的原子词汇,而观测是展现出的视觉特征(RGB/光流/骨骼关键点/手部形状)
与CTC的对比
HMM显示有转移A与发射B,A能处理一个隐状态后面更可能跟什么隐状态,B能处理一个隐状态可能会有什么样的观测,能表达谁后面更可能跟谁。而CTC一句观测对每帧给出后验分布,并用固定规则做单调对齐,得到符号序列,若要形成自然语言句子,需要额外的语言/翻译模型。
batch size
batch size指的是模型在一次前向传播+反向传播中,同时处理的样本数量
batch size=1:一次只看一个样本,算一次梯度,更新一次参数
batch size=16:一次同时看16个样本,综合它们的误差,算一次梯度,再更新参数
从数学角度看:batch size 决定“梯度是怎么来的
训练时参数更新公式本质是:

batch size=1时

梯度噪声很大,更新很抖,但泛化能力有时更好
batch size=B时

梯度更稳定,GPU利用率更高,收敛更平滑
工程视角:batch size直接影响三件事
显存占用:在实际训练过程中显存与batch size基本呈线性相关
训练速度:batch太小,GPU吃不满,频繁同步,慢;batch过大,单步很慢,整体也不一定快。应该选取适中的batch size。
收敛域泛化:
经验规律(不是铁律):
| batch size | 特点 |
|---|---|
| 很小 (1~4) | 噪声大,泛化好,但训练不稳定 |
| 中等 (8~32) | 最常用,稳定+泛化平衡 |
| 很大 (64+) | 稳定但可能泛化下降,需要调 LR |
模型介绍
model_ctc.py
import torch
import torch.nn as nn
import torch.nn.functional as F
def build_mediapipe_A():
V = 75
A = torch.eye(V)
pose_edges = [
(0, 1), (1, 2), (2, 3), (3, 7),
(0, 4), (4, 5), (5, 6),
(0, 8), (8, 9), (9, 10),
(11, 12),
(11, 13), (13, 15),
(12, 14), (14, 16),
(15, 17), (16, 18),
(6, 20), (10, 19),
]
hand_edges = [
(0, 1), (1, 2), (2, 3), (3, 4),
(0, 5), (5, 6), (6, 7), (7, 8),
(0, 9), (9,10), (10,11), (11,12),
(0,13), (13,14), (14,15), (15,16),
(0,17), (17,18), (18,19), (19,20)
]
left_hand_offset = 33
right_hand_offset = 54
left_hand_edges = [(i + left_hand_offset, j + left_hand_offset) for i, j in hand_edges]
right_hand_edges = [(i + right_hand_offset, j + right_hand_offset) for i, j in hand_edges]
A[15, 33] = A[33, 15] = 1
A[16, 54] = A[54, 16] = 1
for i, j in pose_edges + left_hand_edges + right_hand_edges:
A[i, j] = 1
A[j, i] = 1
D = torch.diag(1.0 / torch.sqrt(A.sum(1) + 1e-6))
return D @ A @ D
class STGCNBlock(nn.Module):
def __init__(self, in_ch, out_ch, A, stride=1):
super().__init__()
self.A = A
self.theta = nn.Conv2d(in_ch, out_ch, 1)
self.tconv = nn.Conv2d(out_ch, out_ch, (7, 1), padding=(3, 0), stride=(stride, 1))
self.bn = nn.BatchNorm2d(out_ch)
self.relu = nn.ReLU(inplace=True)
self.down = None
if in_ch != out_ch or stride != 1:
self.down = nn.Sequential(
nn.Conv2d(in_ch, out_ch, 1, stride=(stride, 1)),
nn.BatchNorm2d(out_ch),
)
def forward(self, x):
A = self.A.to(x.device)
y = self.theta(x)
y = torch.einsum("nctv,vw->nctw", y, A)
y = self.tconv(y)
res = x if self.down is None else self.down(x)
y = self.bn(y)
return self.relu(y + res)
class BiLSTM_Head(nn.Module):
def __init__(self, in_ch, num_classes, hidden_size=256, num_layers=2, dropout=0.3):
super().__init__()
self.hidden_size = hidden_size
self.num_layers = num_layers
# BiLSTM层
self.lstm = nn.LSTM(
input_size=in_ch,
hidden_size=hidden_size,
num_layers=num_layers,
batch_first=False, # 使用 (T, N, C) 格式以适应CTC
bidirectional=True,
dropout=dropout if num_layers > 1 else 0
)
# 输出层
self.output = nn.Linear(hidden_size * 2, num_classes) # *2 因为双向
# Dropout层
self.dropout = nn.Dropout(dropout)
def forward(self, x):
# 输入 x 形状: [N, C, T] -> 需要转换为 [T, N, C] 给LSTM
x = x.permute(2, 0, 1) # [T, N, C]
# 通过BiLSTM
lstm_out, _ = self.lstm(x) # [T, N, hidden_size * 2]
# Dropout
lstm_out = self.dropout(lstm_out)
# 输出层
output = self.output(lstm_out) # [T, N, num_classes]
return output
class STGCN_BiLSTM_CTC(nn.Module):
def __init__(self, num_classes, gcn_channels=(64, 128, 256), lstm_hidden=256, lstm_layers=2):
super().__init__()
self.V = 75
self.A = build_mediapipe_A()
c1, c2, c3 = gcn_channels
self.g1 = STGCNBlock(3, c1, self.A)
self.g2 = STGCNBlock(c1, c2, self.A)
self.g3 = STGCNBlock(c2, c3, self.A)
self.gap = nn.AdaptiveAvgPool2d((None, 1))
# 用BiLSTM替换MSTCN
self.bilstm = BiLSTM_Head(
in_ch=c3,
num_classes=num_classes,
hidden_size=lstm_hidden,
num_layers=lstm_layers
)
def forward(self, x: torch.Tensor) -> torch.Tensor:
N, T, D = x.shape
assert D == 225, f"Expected feature dim 225, got {D}"
# 空间特征提取
x = x.view(N, T, 75, 3).permute(0, 3, 1, 2).contiguous()
y = self.g1(x)
y = self.g2(y)
y = self.g3(y)
# 全局平均池化
y = self.gap(y).squeeze(-1) # [N, Cg, T]
# 通过BiLSTM
y = self.bilstm(y) # [T, N, num_classes]
return F.log_softmax(y, dim=-1)
问题背景与整体思路
手语翻译本质上是一个结构化时序到文本序列的映射问题。与普通视频分类或动作识别不同,它同时具备以下三个显著特征:
1.强空间结构性 手语由人体姿态与双手关节协同完成,关键点之间并非独立,而是受骨架拓扑强约束。
2.强时间序列依赖 单个手势的语义往往依赖于前后多个帧的连续变化,而非孤立帧。
3.弱对其监督 在大多数数据集中,只能获得“句级或词级标签”,而无法精确标注每一帧对应的语义单位。
针对以上特点,本文采用了一种ST-GCN+BiSTM+CTC的组合模型,整体思路如下:
使用ST-GCN(时空卷积网络)建模人体与双手关键节点的空间拓扑及短期时间动态;使用BiLSTM进一步捕捉长程时序依赖;使用CTC(Connectionist Temporal Classification)解决无显式对其条件下的序列预测问题。
数据表示:MediaPipe75关键点序列
本模型基于MediaPipe提取的关键点序列作为输入特征,具体结构为:
Pose:33个关键点
Left Hand:21个关键点
Right Hand:21个关键点
合计75个关键点,每个点包含(x,y,z)三维坐标,因此:
单帧特征维度:75*3=225
输入张量形状:[N,T,225]
其中N为batch size,T为视频帧数
在进入图卷积网络前,特征会被重塑为
[N, C, T, V] = [N, 3, T, 75]
以符合ST-GCN的输入规范。
人体-手部图结构建模(邻接矩阵设计)
节点与拓扑定义
为了显式建模骨架,本文将75个关键点视为图的节点,并构建一张固定拓扑图:
姿态关键点:采用MediaPipe Pose官方拓扑
单只手:以wrist为根节点,向五指分叉的梳状结构
左右手分辨偏移索引后假如整体图中
身体与手部的关键连接
仅有姿态图和手部图仍然是不够的,为了让模型理解手相对于身体的位置关系,本文显式的添加了如下连接:
左手wrist与左肩/肘部对应关键点,右手wrist与右肩/肘部对应关键点
这一步使得身体与双手构成一张连通图而非三个鼓励子图,对于建模空间语义至关重要。
邻接矩阵归一化
最后邻接矩阵采用对称归一化:

其中D为度矩阵,该处理可以避免高度节点在信息聚合时产生数值放大,有助于训练稳定性
ST-GCN Block:时空卷积模块解析
单个ST-GCN Block的输入输出形状均为
[N, C, T, V]
其内部结构由四个关键步骤组成:
通道映射(1*1卷积)
通过Conv2d(in_ch,out_ch,kernel=1)对节点特征进行线性投影,相当于对每个(t,v)的特征向量做通道变换。
空间图卷积(邻接传播)
利用矩阵乘法实现图卷积

该操作使每个节点能够聚合其邻居节点(及自身)的信息。
时间卷积
使用(7*1)的卷积核,仅沿时间维进行卷积,用于提取短期动态模式(局部动作变化)。
残差连接
当通道数或时间步长不一致时,通过1*1卷积对残差分支进行对其,保证深层网络训练稳定。
主干网络结构
本文采用三层ST-GCN堆叠结构
(3 → 64) → (64 → 128) → (128 → 256)
其设计在于低层关注局部关节运动,高层逐步组合成更复杂的全身运动模式。
在ST-GCN主干之后,使用:
AdaptiveAvgPool2d((None, 1))
对关节维V=75做全局平均池化,而保留时间维T,得到:
[N, 256, T]
即帧级全面特征序列。
BiLSTM时序建模头
虽然ST-GCN已包含时间卷积,但其感受野仍然有限,为了进一步捕捉长距离手势依赖和前后文语义信息,本文引入双向LSTM作为书序建模模块。
更多推荐
所有评论(0)