sEMG与IMU融合的手势识别:从信号预处理到双流神经网络实战
2026/9/15 2:10:12 网站建设 项目流程

简介:面向人工智能初学者与手势识别开发者,整合sEMG与IMU数据的手语识别项目,覆盖数据收集、去噪、特征提取、数据分割和神经网络训练完整流程,适用于课程设计、毕业设计以及无障碍交互系统开发。资源包共60个文件、39.91MB,包含Python/C++源码、Visual Studio工程文件、数据采集程序、预处理脚本、TensorFlow权重与checkpoint文件等。具体内含单/双手数据采集工程、WMA与小波变换去噪预处理示例、基于RNN/CNN的模型图与权重参数;txt说明与csv数据文件便于梳理步骤,sln/vcxproj、tlog/pdb与obj则保留VS编译调试过程,方便定位工程结构。由于sEMG与IMU信号处理门槛较高,资源还展示了滤波器选择、时频特征构造、滑动窗口分割以及LSTM/RNN/CNN结构设计等关键环节,并给出模型训练与评估注意事项。已有314人学习,适合希望从原始传感器信号一路做到手势分类的实操学习者。

1. 为什么手语识别要先处理信号,而不是先调模型

把 sEMG 和 IMU 组合起来做手语手势识别,很多第一次接触的人会把精力全放在神经网络结构上,结果训练出来准确率只有六成,然后怀疑是模型不够深。实际情况往往相反:模型不是瓶颈,信号质量才是。sEMG 采集的是肌肉放电产生的电生理信号,幅度只有毫伏级,信噪比天然低于图像和语音;IMU 虽然稳定,但存在积分漂移,对手势中的静态保持段很不友好。两个传感器各自有短板,合在一起却恰好互补——sEMG 反映发力意图,IMU 反映肢体空间姿态,两者融合后既能识别动态轨迹,也能区分静态手型。这个项目实践的关键路径是:先理解两种信号的物理含义,再设计一个可重复的预处理流程,最后才轮到网络结构。这篇文章把数据收集、去噪、分割、特征提取、神经网络搭建这条线完整走一遍,所有脚本基于 PyTorch 和 NumPy,照着跑就能复现一条可用于手语识别项目的基线。

2. 数据收集与同步:sEMG 和 IMU 结合的第一个门槛

2.1 传感器选型与采样率的匹配逻辑

sEMG 和 IMU 不是买来插上就能用的。两者采样率差异很大,常见的 sEMG 采集设备(如 Delsys、MYO 或国产的 OpenBCI 扩展板)采样率通常在 500 Hz 到 2000 Hz 之间;IMU(如 MPU6050、ICM20948)经过滤波后输出频率一般在 50 Hz 到 200 Hz。如果直接把两路数据拼在一起训练,时间轴对不上,模型会学到错误的跨模态关系。

常见做法是先确定主采样率。我一般把 sEMG 的采样率定为 1000 Hz,IMU 重采样到同样的时间戳上。重采样是个容易出错的环节——线性插值会抹掉 IMU 在快速翻转瞬间的峰值,而 sEMG 是生理信号,带宽主要分布在 20 Hz 到 450 Hz 之间,降采样前必须经过抗混叠滤波。推荐的数据流是这样的:

import numpy as np from scipy import signal # sEMG 原始数据: shape (n_samples, n_channels) # IMU 原始数据: shape (n_samples_imu, 6) 列: accel_x/y/z, gyro_x/y/z # 1. sEMG 先做 20 Hz 高通滤波,去除基线漂移 b_high, a_high = signal.butter(2, 20 / (1000 / 2), 'high') emg_filtered = signal.filtfilt(b_high, a_high, emg_raw, axis=0) # 2. IMU 插值到 sEMG 的时间轴 time_emg = np.arange(emg_raw.shape[0]) / 1000.0 time_imu = np.arange(imu_raw.shape[0]) / imu_fs imu_resampled = np.column_stack([ np.interp(time_emg, time_imu, imu_raw[:, i]) for i in range(imu_raw.shape[1]) ])

高通滤波器的截止频率选 20 Hz 是有依据的:sEMG 中的运动伪迹和电极移动噪声大多集中在 0 到 20 Hz 频段,而有效的肌肉激活信息从 20 Hz 左右才开始。filtfilt是零相位滤波,不引入时延,这一点对手势识别尤其重要——每个通道的相位一旦错位,后续特征提取时的多通道相关性就会失真,模型学的特征就不是肌肉协同模式而是滤波相位差。

2.2 手势标注与时间戳对齐的问题

数据收集不只是按个录制键那么简单的。手语手势大多是连续动作,比如"你好"和"谢谢"之间没有明显的停顿,如果按固定时长切窗,会把两个手势的边界切进同一个样本里。这一节的解法分为两层:硬件层和时间戳层。

硬件层:在采集程序里为每个手势动作打 event 标记。常见做法是通过串口或 LSL(Lab Streaming Layer)给数据流打标签。LSL 在实时信号处理项目中用得很广,它能把 sEMG、IMU、视频标记同步到同一个时钟域里。用手按下键盘或者专用 trigger 盒子的瞬间,LSL 会写入一个带时序的标记。

时间戳层:如果设备不支持 LSL,就在软件里维护一个标记数组。录制的时候记录每个手势的起止索引,同时确保这个索引对应到统一时间轴。这里容易犯的错是在循环里用time.time()直接做标记——Python 的调度延迟会带来几十毫秒抖动,而这个抖动在 IMU 的角速度积分中会被放大。

反直觉的一点是:采集时宁可多留余量,也不要卡着手势边界去裁切。我通常会要求受试者每个手势保持至少 1.5 秒,过短的手势段(小于 500 ms)直接丢弃。因为短段经过带通滤波后两端会有边缘效应,即使filtfilt缓解了相位问题,幅度上仍然可能失真。数据收集完成后进入预处理时,首先要画一段波形看看,sEMG 有明显的激活-静息交替,IMU 的姿态变化能对应上动作意图,再往下走。

3. 数据预处理的三个环节:去噪、分割、特征提取

3.1 去噪:sEMG 的频段取舍与 IMU 的重力对齐

sEMG 的噪声源比大多数传感器都多。工频干扰(50 Hz 及谐波)、运动伪迹、电极与皮肤接触阻抗变化,每种噪声都有各自的频谱特征。传统的做法是级联滤波:先做 20 Hz 高通去掉运动伪迹,再做 50 Hz 陷波去掉工频干扰,最后加一个 450 Hz 低通去掉高频噪声。这一套组合在多数肌电研究里是默认起点。

# 级联滤波的完整链路 from scipy.signal import butter, filtfilt def preprocess_emg(emg_raw, fs=1000): # 第1级: 高通 20 Hz,去基线漂移与运动伪迹 b_h, a_h = butter(4, 20 / (fs / 2), 'high') emg = filtfilt(b_h, a_h, emg_raw, axis=0) # 第2级: 陷波 50 Hz 及其二次谐波 for freq in [50, 100, 150]: wn = [freq - 1, freq + 1] # 带宽设为 2 Hz b_n, a_n = butter(2, [wn[0] / (fs/2), wn[1] / (fs/2)], 'bandstop') emg = filtfilt(b_n, a_n, emg, axis=0) # 第3级: 低通 450 Hz,滤除高频噪声 b_l, a_l = butter(4, 450 / (fs / 2), 'low') emg = filtfilt(b_l, a_l, emg, axis=0) return emg

注意陷波滤波器的带宽。带宽设太窄(比如 1 Hz)在实时推理时可能因频谱泄漏而滤不干净;设太宽会把 50 Hz 附近的有效肌电成分一并削掉。2 Hz 是折中值,离线处理时问题不大,实时处理时可以改用自适应陷波,但那是后话——先跑通基线更重要。

IMU 的预处理与 sEMG 侧重不同。sEMG 是时间序列滤波,而 IMU 首先要解决的是坐标系的统一。热词里常出现"imu重力对齐"和"imu内参标定",这里用到的核心操作是把加速度计数据从传感器坐标系转换到世界坐标系。手持手势识别中,手背的朝向随时在变,如果直接用原始加速度值做特征,同一个手势在不同姿态下的特征差异会超过手势本身的差异。常见的做法是用陀螺仪积分得到姿态四元数,再把加速度旋转到世界坐标系:

# 用 scipy 的 Rotation 做加速度重力对齐 from scipy.spatial.transform import Rotation def align_accel_with_gravity(accel, gyro, dt): """ accel: (N, 3) 原始加速度 gyro: (N, 3) 原始角速度 dt: 采样间隔(秒) """ quat = np.zeros((len(gyro), 4)) quat[0] = [1, 0, 0, 0] # 初始四元数为单位四元数 for i in range(1, len(gyro)): omega = gyro[i] * dt delta_quat = Rotation.from_rotvec(omega).as_quat() # 四元数乘法更新姿态(Hamilton 约定) q_prev = quat[i-1] q_delta = delta_quat quat[i] = [ q_prev[0]*q_delta[0] - q_prev[1]*q_delta[1] - q_prev[2]*q_delta[2] - q_prev[3]*q_delta[3], q_prev[0]*q_delta[1] + q_prev[1]*q_delta[0] + q_prev[2]*q_delta[3] - q_prev[3]*q_delta[2], q_prev[0]*q_delta[2] - q_prev[1]*q_delta[3] + q_prev[2]*q_delta[0] + q_prev[3]*q_delta[1], q_prev[0]*q_delta[3] + q_prev[1]*q_delta[2] - q_prev[2]*q_delta[1] + q_prev[3]*q_delta[0] ] # 加速度对齐到世界坐标系 r = Rotation.from_quat(quat) accel_world = r.apply(accel) # 减去重力分量,得到线性加速度 return accel_world - np.array([0, 0, 9.81])

这段代码是基于纯积分的姿态解算,实际项目里会用 Madgwick 或 Mahony 滤波融合加速度计数据来抑制陀螺零漂。标题里既然强调了预处理,用纯积分做基线就够了,但要清楚它的局限:纯积分在 10 秒以上的长序列里姿态会明显漂移,所以预处理时要尽量切短段。

3.2 活动段分割:不是所有窗口都值得喂给网络

分割是手语识别里最容易被低估的环节。连续手势流的长度在几秒到几十秒不等,直接滑动窗口会导致一个问题——窗口内可能只有一个手势的 40%,模型被迫从半截动作里做预测。常见做法是先用能量阈值做活动检测,再在检测到的活动段上做精确分割。

sEMG 的活动检测比 IMU 更可靠。肌肉发力的瞬间,sEMG 的均方根值(RMS)会在几十毫秒内显著抬升。实现方式是:先计算短时能量,再用双阈值法(hysteresis)判断。双阈值的好处是避免手势起止处能量波动导致的抖动切割:

def segment_by_energy(emg, fs=1000, high_thresh=0.02, low_thresh=0.01, min_duration=0.3, pad=0.15): """基于 sEMG 能量的手势活动段检测""" # 多通道 RMS 取平均作为能量信号 rms = np.sqrt(np.mean(emg**2, axis=1)) # 平滑能量信号 win = int(0.05 * fs) # 50 ms 窗口 kernel = np.ones(win) / win energy = np.convolve(rms, kernel, mode='same') active = False segments = [] start = 0 for i, e in enumerate(energy): if not active and e > high_thresh: active = True start = max(0, i - int(pad * fs)) elif active and e < low_thresh: active = False end = min(len(emg), i + int(pad * fs)) if (end - start) / fs >= min_duration: segments.append((start, end)) # 处理结束处仍处于 active 的情况 if active: segments.append((start, len(emg))) return segments

参数选择上有两个经验值:高阈值为静息态能量的 5 到 8 倍,低阈值为高阈值的 50%。pad=0.15是在活动段前后各留 150 ms 的缓冲,因为从能量曲线看,手势真正发力前的 100 多毫秒已经有微弱肌电活动,切掉会影响激活段完整性。分割完成后每个段都要单独检查长度——IMU 的零速检测可以用陀螺仪的标准差判断手是否停稳,但要是在某个手势中间停住,sEMG 能量可能仍然较高。因此实际项目里我会用 sEMG 能量做主判据、IMU 角速度标准差做辅助校验,两者不一致的段标记为可疑样本,人工查看后再决定是否保留。

3.3 特征提取:时域到频域的常用特征表

分割完成后,每个手势段需要变成一个固定维度的特征向量。窗口大小通常取 200 ms 到 300 ms,重叠率 50%。为什么是这个范围?窗口太短难以捕捉手势动作的连贯性,太长又会模糊掉手势内部的时序变化(比如"一"和"二"的区别就在手指动作的先后序)。特征分三块:时域特征、频域特征和 IMU 特征。常用组合如下:

特征名称公式 / 计算方式适用信号说明
均方根(RMS)sqrt(mean(x²))sEMG反映肌肉发力强度
平均绝对值(MAV)mean(abs(x))sEMG鲁棒性比 RMS 好,计算量小
过零率(ZC)信号穿越零点的次数sEMG反映频率变化,与肌肉紧张度相关
波长(WL)sum(abs(diff(x)))sEMG波形复杂度指标
功率谱中值频率(MDF)功率谱累积到 50% 的频率sEMG反映肌肉疲劳程度
加速度均值 / 方差mean / varIMU手部整体运动强度
角速度峰值max(abs(gyro))IMU手势翻转速度
姿态角(roll/pitch/yaw)从四元数换算IMU手型朝向

特征提取的代码可以分两条线。sEMG 特征用 200 ms 窗口滑动计算,IMU 特征可以放宽到 500 ms——IMU 是慢变量,窗口太短,姿态角的方差反而会因为噪声被高估:

def extract_features_emg(emg_seg, fs=1000, win_ms=200, hop_ms=100): """从单个手势段提取 sEMG 特征序列""" win = int(win_ms * fs / 1000) hop = int(hop_ms * fs / 1000) n_windows = (len(emg_seg) - win) // hop + 1 features = [] for i in range(n_windows): x = emg_seg[i*hop : i*hop + win] rms = np.sqrt(np.mean(x**2, axis=0)) mav = np.mean(np.abs(x), axis=0) wl = np.sum(np.abs(np.diff(x, axis=0)), axis=0) # 过零率:相邻采样点符号变化 zc = np.sum(np.diff(np.sign(x), axis=0) != 0, axis=0) / (2 * win) features.append(np.concatenate([rms, mav, wl, zc])) return np.array(features) # shape: (n_windows, n_channels * 4)

所有特征向量统一做 z-score 标准化,标准化的均值和方差只从训练集计算,验证集和测试集用同一套参数变换。这是常见误用里最值得强调的一点:如果对整个数据集做标准化,相当于把测试集分布泄漏进了训练过程,离线表现虚高,上线就崩。

4. 神经网络搭建:双流结构与 sEMG + IMU 的融合时机

4.1 输入形态:二维矩阵的构建

特征提取完成后,每个手势样本是一个二维矩阵,形状为(时间窗数, 特征维度)。时间窗数取决于手势段长度——1 秒的手势、200 ms 窗口、100 ms 步长,大约 9 个窗口;2 秒的手势约 19 个窗口。神经网络要求固定输入长度,所以需要做长度对齐。

常见的对齐策略有三种:截断到固定长度(比如 16 个时间窗)、填充零到固定长度(比如 32 个时间窗)、按时间维做全局池化。我推荐的做法是用 1 维全局平均池化层来吸收长度差异,这样网络可以接受变长输入,训练时不必把所有样本裁到同样长度,保留完整时序信息。

双流结构的核心是让 sEMG 和 IMU 分别经过各自的编码器,在某个中间层做融合。两种信号的物理属性不同,过早融合(输入层直接拼接)会让网络很难学到底层特征间的相互作用;过晚融合(只在 softmax 前拼接)又会让两个编码器各自为政,完全失去互动。常见做法是在编码器输出的特征向量处融合,再送入分类头:

import torch import torch.nn as nn class GestureNet(nn.Module): def __init__(self, emg_dim=64, imu_dim=18, num_classes=20, hidden_dim=128): super().__init__() # sEMG 编码器:1D 卷积提取局部时序模式 self.emg_encoder = nn.Sequential( nn.Conv1d(emg_dim, 64, kernel_size=3, padding=1), nn.BatchNorm1d(64), nn.ReLU(), nn.Conv1d(64, 64, kernel_size=3, padding=1), nn.BatchNorm1d(64), nn.ReLU(), nn.AdaptiveAvgPool1d(1) # 把时间维压成 1,输出 (B, 64, 1) ) # IMU 编码器:GRU 处理姿态序列 self.imu_encoder = nn.GRU( input_size=imu_dim, hidden_size=hidden_dim, num_layers=2, batch_first=True, bidirectional=True ) # 融合层 self.fusion_fc = nn.Sequential( nn.Linear(64 + hidden_dim * 2, 128), nn.ReLU(), nn.Dropout(0.3) ) self.classifier = nn.Linear(128, num_classes) def forward(self, emg_feat, imu_feat): # emg_feat: (B, T_emg, emg_dim) -> (B, emg_dim, T_emg) emg_out = self.emg_encoder(emg_feat.transpose(1, 2)).squeeze(-1) # imu_feat: (B, T_imu, imu_dim) imu_out, _ = self.imu_encoder(imu_feat) imu_out = imu_out[:, -1, :] # 取最后时间步(双向时取拼接向量) # 融合 fused = torch.cat([emg_out, imu_out], dim=-1) return self.classifier(self.fusion_fc(fused))

Conv1d编码器对 sEMG 特征序列很合适,原因在于肌电特征在相邻时间窗之间有较强的局部相关性——单个手势动作的发力有一个从小到大再到小的过程,卷积核能捕捉这个模式。GRU 处理 IMU 序列是因为姿态变化存在时序依赖,前一个时刻的角速度会影响后续姿态角,循环结构天然适合。

AdaptiveAvgPool1d(1)是处理变长输入的关键——不管输入序列长度是多少,池化后都变成一个特征向量。这也是"图神经网络"这类复杂结构在这个任务里没必要的原因:sEMG 特征和 IMU 特征的关系本质上是时序对齐关系,不是图结构关系,用图网络反而会在小数据集上过拟合。

4.2 训练策略与数据增强

手势识别项目的数据量通常不大,几十个类别每个类别 50 到 100 个样本是常态。这么大的数据集上直接训练深层网络会严重过拟合,所以训练策略比网络结构更重要。

数据增强方面,sEMG 和 IMU 各有常用手段。sEMG 可以做幅值缩放——肌肉发力大小因人而异,同一个手势在 0.8 到 1.2 倍幅值范围内变化不影响手势类别;IMU 可以做姿态扰动——在手势轨迹上叠加小幅旋转噪声,模拟手部初始角度的微小差异。这些增强手段能显著提升模型对受试者变化和电极位置偏移的鲁棒性:

def augment_pair(emg_feat, imu_feat): """离线增强:每次训练迭代随机应用""" # sEMG 幅值缩放 scale = torch.empty(1).uniform_(0.8, 1.2) emg_aug = emg_feat * scale # IMU 添加高斯噪声(模拟传感器抖动) noise_std = 0.02 imu_aug = imu_feat + torch.randn_like(imu_feat) * noise_std return emg_aug, imu_aug

训练时采用分阶段学习率。开始时用较大的学习率(如 0.001)快速收敛,在验证集准确率平台期后降为 0.0001 做精细调优。优化器用 AdamW 而不是 Adam——带权重衰减时 AdamW 的实现更规范,过拟合时效果差异明显。损失函数直接交叉熵即可,不需要加权,除非某个手势类别出现频率明显偏低。

跨被试验证是另一个绕不开的环节。手语的个体差异很大,同一手势不同人的动作幅度、速度、肌肉放电模式都不一样。如果随机划分数据集,同一个人的不同样本会被分到训练集和测试集,测的是模型记住了这个人,而不是学会了手势。正确的做法是按受试者分组:训练集包含若干个人的所有数据,测试集包含完全没参加过训练的人的数据。这种 Leave-One-Subject-Out 验证才能评估真实使用场景的泛化能力。

4.3 类别不均衡的处理

手语识别中的类别不均衡常被忽视。常用手势和非常用手势的出现频率差异可以到 10 倍以上。如果不做处理,模型会把高频手势全部预测对,低频手势几乎全部预测错,整体准确率看似有 85%,实际上低频类别一个都没学会。

处理方式按优先级排序:先做类别加权损失函数,把频率倒数的平方根作为类别权重;如果效果不够,再做过采样,对低频类别的样本做增强复制。第二优先级要注意的是不要用简单的重复采样——同一个样本的完全拷贝放进训练集,模型会把复制样本和原始样本都记住,效果相当于把学习率放大而信息量没有增加,最好用增强后的变体补足数量。

# 类别加权交叉熵 def compute_class_weights(labels, num_classes): counts = np.bincount(labels, minlength=num_classes).astype(np.float32) weights = 1.0 / np.sqrt(counts + 1e-6) return torch.tensor(weights / weights.mean()) # 使用时传入损失函数 criterion = nn.CrossEntropyLoss(weight=compute_class_weights(train_labels, num_classes))

类别权重的开方处理是有讲究的。直接用倒数权重,会把低频类别的重要程度放大到几乎所有样本都被预测为该类;开方后压低校正幅度,在多数项目中能取得更稳定的结果。

5. 融合网络的进阶优化:早停策略与多模态对齐的验证方法

5.1 早停是手势识别第一防线

训练手势识别模型时,收敛速度远比图像模型快——特征维度低,数据量小,通常 30 到 50 个 epoch 就到顶了。如果使用固定的 epoch 数(比如 100),后 50 个 epoch 几乎全在过拟合。早停策略是训练脚本中性价比最高的保护机制:监控验证集损失,连续 10 个 epoch 没有改善就停止训练,同时保存验证集损失最低时的权重。

best_val_loss = float('inf') patience = 10 trigger_times = 0 for epoch in range(max_epochs): train_one_epoch(model, train_loader, optimizer, criterion) val_loss = evaluate(model, val_loader, criterion) if val_loss < best_val_loss: best_val_loss = val_loss trigger_times = 0 torch.save(model.state_dict(), 'best_gesture.pth') else: trigger_times += 1 if trigger_times >= patience: print(f"Early stop at epoch {epoch}") break

除了早停,还要在每次 eval 时把model.eval()torch.no_grad()配对使用。BatchNorm 层在训练和推理时的统计方式不同,漏掉model.eval()会导致同样的权重在验证集上表现异常,不少调试耗时都花在找这类问题上。

5.2 融合权重的消融实验

很多人做完融合网络就直接汇报准确率,但其实一个关键问题需要回答:融合到底带来了多少提升,还是某个模态单独就能完成任务。

消融实验的方法:分别训练三个模型。单一 sEMG 模型,把 IMU 编码器置为恒等映射;单一 IMU 模型,把 sEMG 编码器置为恒等映射;双流融合模型。对比它们在相同测试集上的准确率和宏平均 F1。

模型预期准确率说明
仅 sEMG70% ~ 80%能区分发力模式明显的手势,但区分相近手型会困难
仅 IMU65% ~ 75%能区分手臂轨迹,但对静态手型无能为力
双流融合85% ~ 95%组合信息,两类错误都有机会互相纠正

如果融合后准确率没有明显提高,问题往往不在融合方法,而在前面某一环节。优先排查:IMU 姿态解算是否漂移、sEMG 电极位置是否稳定、分割长度是否对齐。消融实验的意义不只是证明模型好,更重要的是给出部署决策依据——如果嵌入式设备只能支持一个传感器,看消融结果就知道应该保哪一个。

5.3 实时推理时的预处理一致性

对接实时推理时有一个隐藏陷阱:离线预处理和线上推理的流程必须完全一致。离线脚本里先全局标准化再做窗口切分,线上推理也必须先积累一段静息数据计算标准化参数,否则一上线的准确率会掉 10 到 20 个百分点。

另一个容易出问题的是滑动窗口在推理端的边界处理。离线训练时窗口是完整分割段内的,线上推理时每个窗口是滑动的,最近一次预测可能跨越了两个手势的边界。常见的工程化做法是维护一个环形缓冲区,只有新数据进来时窗口满了才做一次推理;一旦活动检测判定手势结束,清空缓冲区并等待下一个手势起点。这个方法很朴素,但比任何复杂的状态机都可靠:

class RealtimeBuffer: def __init__(self, window_size): self.buffer = np.zeros((window_size, n_features), dtype=np.float32) self.window_size = window_size self.ptr = 0 def push(self, sample): # 环形缓冲:push 时覆盖最旧的数据 self.buffer[self.ptr] = sample self.ptr = (self.ptr + 1) % self.window_size def get_window(self): # 按时间顺序返回(从最旧到最新) if self.ptr == 0: return self.buffer return np.concatenate([self.buffer[self.ptr:], self.buffer[:self.ptr]])

环形缓冲区配合活动段检测,能确保送入网络的窗口始终是完整连续段,避免抢跑或滞后。前一个手势结束后的最后几个窗口很可能还是 sEMG 高能量状态,需要等能量降到低阈值以下再重置缓冲区。实时系统里手势结束的判定延迟不超过 200 ms,对交互场景来说是可以接受的。

最后验证融合网络效果时,除了看整体准确率,还要看混淆矩阵中容易错分的类别对。手语中指向性和描述性手势容易混淆,这类错误通常说明预处理阶段的特征提取没有区分出关键差异——比如两个手势的动作轨迹相似但发力模式不同,那就应该加强 MAV 和 WL 特征的表征能力,而不是一味加深网络层数。一个经得起推演的预处理流,比几层多余的卷积对结果的提升更明显。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询