ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

实时动态ASL手语识别系统:CNN+LSTM与OpenCV实战

实时动态ASL手语识别系统:CNN+LSTM与OpenCV实战 简介一套基于CNN与LSTM的实时动态美式手语ASL识别系统面向计算机视觉和深度学习开发者用于解决听障人群沟通场景中的手势自动翻译问题。资源包为zip格式共95个文件压缩包约8.86MB包含可直接运行的exe程序、21个dll依赖库、14个C#源码文件如MainForm.cs、Motiontracking、HaarClassifier.cs、SkinDetect.cs等以及pdb调试符号、xml配置和zbak备份文件工程结构完整便于在Visual Studio中打开调试。系统设计上融合卷积神经网络与长短期记忆网络可同时捕捉静态手势空间特征和连续动作的时间依赖覆盖数据预处理、特征提取、模型训练与部署等完整流程适合作为课程设计或科研入门的参考项目。已有70人浏览学习资源中附带的源码模块和可执行程序能帮助读者快速理解基于OpenCV的手语识别实现思路并进行二次开发。1. 手语识别不是图像分类实时动态ASL为什么需要CNNLSTM把ASL美国手语识别当成图像分类来做是新手最容易踩的坑。单帧图片只是手的静态姿势而大量手语词汇的区别恰恰在运动轨迹上比如谢谢和你好的最终手型相近但移动路径完全不同。CNN能提取单帧的空间特征但看不懂从A位置移动到B位置这段过程LSTM恰好擅长从特征序列里学习时间依赖。所以一套在真实场景里能用的实时动态ASL识别系统几乎都是CNNLSTM的串联结构CNN负责看懂每一帧里手长什么样LSTM负责看懂这几帧之间手是怎么动的。这套方案在OpenCV做视频采集和预处理的配合下能跑通实时摄像头翻译也是深度学习入门到落地之间一条非常典型的技术路线。这篇文章我会把数据怎么喂、模型怎么搭、摄像头管线怎么做、哪些坑必须避开按我实际做过的方案完整写出来。2. 视频帧到张量ASL数据集选型与OpenCV预处理管线很多人在模型上花时间在数据管线上翻车。动态手语识别的输入不是一张图而是一段视频采样成的一组帧。这个视频到张量的转换过程决定了模型能不能学到有效的时序信息。2.1 ASL单词级数据集怎么挑WLASL与MS-ASL的取舍做动态ASL识别首先要明确任务边界你要做的是孤立词识别Isolated Word Recognition还是连续手语句子识别Continuous Sign Language Recognition标题里的实时动态ASL手语识别系统更适合从孤立词起步因为连续手语需要额外的CTC对齐落地复杂度会翻倍。孤立词识别常用的公开数据集是WLASL和MS-ASL。WLASL覆盖词汇量大包含2000多个词的视频样本MS-ASL规模小一些但每个词条的样本质量更干净。实际做的时候我一般建议先用MS-ASL跑通全流程再上WLASL做全量训练。原因很简单WLASL类别多、长尾严重有些冷门词只有几个样本直接拿来做训练模型很容易过拟合到记住训练集而不是学会手语动作。另外这些数据集里的视频是已经分割好的每个视频对应一个词不需要你再做时间点定位这对入门阶段非常友好。如果你的需求是中文手语而不是ASL数据集的逻辑类似只是需要找对应词汇表的手语视频集。但模型结构、预处理方式完全一致不需要改架构。选数据集的时候重点关注三个指标视频采样率是否统一、背景是否复杂、手部在画面中的比例。背景太干净的数据集训练出来的模型到真实摄像头场景会明显退化这一点我会在第5章展开讲。2.2 用OpenCV均匀切帧固定30帧的采样与补帧策略拿到视频文件后第一件事是把它变成帧序列。动态手语动作通常在0.5到2秒之间完成因此我习惯把每个视频统一采样成30帧。这个长度对多数ASL词汇都够用而且能直接作为一个固定的时间维度T送进LSTM。均匀采样和直接连读帧的区别在于直接读连续30帧如果视频本身是30fps那只覆盖了1秒而均匀采样会把整个视频的信息均匀铺开比如一个2秒、60帧的视频均匀取30帧等价于每2帧取1帧动作的全局运动轨迹保留得更完整。import cv2 import numpy as np SEQUENCE_LENGTH 30 FRAME_SIZE (224, 224) def sample_frames(video_path, seq_lenSEQUENCE_LENGTH): cap cv2.VideoCapture(video_path) total int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) if total 0: cap.release() return None # 均匀采样从总帧数里均匀取30个索引 indices np.linspace(0, total - 1, seq_len, dtypeint) frames [] for idx in indices: cap.set(cv2.CAP_PROP_POS_FRAMES, idx) ret, frame cap.read() if not ret: continue frame cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) frame cv2.resize(frame, FRAME_SIZE) frames.append(frame) cap.release() # 如果中途读到坏帧导致不足30帧用最后一帧补齐 while len(frames) seq_len: frames.append(frames[-1]) return np.array(frames, dtypenp.float32) / 255.0这里的关键参数是indices np.linspace(0, total - 1, seq_len, dtypeint)它负责把整个视频的时间轴均匀压缩到30个采样点。注释里也写了如果读取瞬间发生I/O抖动导致丢帧就复制最后一帧补齐。归一化我直接除以255把像素压到[0,1]这个做法加上常见的ImageNet均值标准差归一化效果差异不大但省了一次标准化计算。另外要注意cv2.cvtColor转成RGB再做resize否则后面训练出来的模型和实际推理时颜色通道对不上。2.3 手部ROI裁剪整帧输入还是先用手部检测器很多人问过我这个选择把整帧224×224的图丢进CNN还是先用检测器把手的区域裁出来再送进去我的答案是实现上先跑通整帧方案再考虑ROI裁剪。整帧输入的优势是没有额外的检测模块管线简单、推理稳定而且CNN会自动把注意力放到手部区域。缺点是当背景复杂或者手在画面里占比小的时候模型会学到背景纹理泛化能力变差。ROI裁剪的优势是屏蔽背景干扰但引入了一个新的不稳定因素检测器本身会误检、漏检一旦手部检测框抖起来后续LSTM会接收到位置漂移的特征序列反而比整帧更容易坏。如果你确定要做ROI裁剪我建议用MediaPipe的Hands方案提取21个手部关键点然后根据关键点的包围盒裁剪出手部区域。需要注意不要把裁剪的尺寸做得太小至少保留手部周围30%的边距否则模型会丢掉手腕和前臂的运动线索而ASL里前臂旋转是有意义的。常见做法是把裁剪区域放大1.3倍再resize到224×224。import mediapipe as mp mp_hands mp.solutions.hands def crop_hand_roi(frame, min_detection_confidence0.5): with mp_hands.Hands( static_image_modeTrue, max_num_hands1, min_detection_confidencemin_detection_confidence ) as hands: results hands.process(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)) if not results.multi_hand_landmarks: return None h, w frame.shape[:2] lm results.multi_hand_landmarks[0].landmark xs [p.x for p in lm] ys [p.y for p in lm] x_min, x_max min(xs), max(xs) y_min, y_max min(ys), max(ys) # 放大1.3倍保留腕部运动信息 cx, cy (x_min x_max) / 2, (y_min y_max) / 2 box_w (x_max - x_min) * 1.3 box_h (y_max - y_min) * 1.3 x_min max(0, int((cx - box_w / 2) * w)) x_max min(w, int((cx box_w / 2) * w)) y_min max(0, int((cy - box_h / 2) * h)) y_max min(h, int((cy box_h / 2) * h)) return frame[y_min:y_max, x_min:x_max]max_num_hands1是一个值得注意的参数做ASL的时候两只手可能同时出现但多数词汇以惯用手为主固定取单只手可以减少输入不确定性。min_detection_confidence一般设在0.5到0.7之间太低会引入大量错误检测框太高会导致漏检。在数据集构建阶段建议用static_image_modeTrue这样每帧都做完整检测而不是依赖视频帧之间的追踪状态准确率更高。3. CNNLSTM模型搭建用PyTorch把空间特征和时间序列接起来模型部分的核心思路是把整个系统拆成两段CNN负责把每一帧压缩成一个特征向量LSTM负责把一串特征向量压缩成一个分类结果。我见过很多人一开始就把模型做得很复杂加各种注意力机制和双向结构结果训练难度陡增。我的建议是先把基线模型跑通再逐步加复杂度。3.1 CNN骨干选型ResNet18 vs MobileNetV2CNN骨干的作用是空间特征提取器。每帧224×224×3的图送入CNN输出一个特征向量。这个向量的维度决定了LSTM每次看到的输入有多大。ResNet18是一个很稳的起点在ImageNet上预训练过的权重可以直接用特征维度是512。相比之下MobileNetV2更轻推理速度更快适合实时场景特征维度是1280。我的经验是在实验室环境用ResNet18先验证模型逻辑和训练流程等要部署到实时摄像头推理时再切到MobileNetV2。为什么ResNet18结构规整、梯度稳定训练时不容易出幺蛾子适合做基线MobileNetV2的深度可分离卷积计算量小CPU上也能跑到可用的帧率但同样的学习率设置下收敛略慢需要一点调参耐心。如果你用的是GPU训练、CPU推理MobileNetV2是更实际的选择。如果推理环境也是GPU那ResNet18可以直接保留。实现时要注意一个细节CNN的预训练权重默认是在ImageNet分类任务上训练出来的所以输入的归一化格式mean和std要和ImageNet保持一致。我上面预处理用了简单除以255如果你发现训练收敛很慢第一步就是换成ImageNet的标准归一化。3.2 LSTM时序层输入维度、序列长度与dropout设置CNN把30帧变成30个特征向量形状是30×512或30×1280。这30个向量按时间顺序排列就是LSTM的输入序列。import torch import torch.nn as nn from torchvision.models import resnet18, mobilenet_v2 class CNNLSTM(nn.Module): def __init__(self, num_classes, cnn_typeresnet18, hidden_size256, num_layers2, dropout0.3): super().__init__() # 1) CNN空间特征提取 if cnn_type resnet18: self.cnn resnet18(pretrainedTrue) in_features self.cnn.fc.in_features # 512 self.cnn.fc nn.Identity() # 去掉分类头 elif cnn_type mobilenetv2: self.cnn mobilenet_v2(pretrainedTrue) in_features self.cnn.classifier[1].in_features # 1280 self.cnn.classifier nn.Identity() # 2) LSTM时序建模 self.lstm nn.LSTM( input_sizein_features, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue, dropoutdropout ) # 3) 分类头 self.classifier nn.Sequential( nn.Linear(hidden_size, 128), nn.ReLU(), nn.Dropout(dropout), nn.Linear(128, num_classes) ) def forward(self, x): # x: (B, T, C, H, W) B, T, C, H, W x.shape x x.reshape(B * T, C, H, W) # 把序列变平一次性过CNN features self.cnn(x) # (B*T, in_features) features features.reshape(B, T, -1) # 还原成序列 (B, T, in_features) out, _ self.lstm(features) # (B, T, hidden_size) out out[:, -1, :] # 取最后一个时间步的输出 return self.classifier(out)这段代码里有几个容易搞错的地方。第一x.reshape(B * T, C, H, W)是把所有帧拼成一个大batch一次性过CNN这样比循环30次调用CNN要快得多。第二out[:, -1, :]只取最后一个时间步的输出这是分类任务最常见的做法当然你也可以用torch.mean(out, dim1)做全局平均池化后者往往更稳定因为手语动作的关键信息不一定出现在最后一帧。第三batch_firstTrue让输入输出维度更直观PyTorch官方文档里也推荐这个写法避免维度转置出错。hidden_size256和num_layers2是比较通用的起点。hidden_size太小比如64会限制LSTM的记忆容量太大比如1024在数据量不大的情况下非常容易过拟合。num_layers2足够捕捉起手-移动-定型这样的三段式动作变化叠到3层以上收益很低参数量和训练时间却涨得很快。3.3 训练配置损失函数、优化器与混合精度孤立词识别本质是序列分类所以损失函数直接用交叉熵nn.CrossEntropyLoss就够了。不需要CTC因为我们已经把整个视频压缩成了一个分类标签。优化器我用AdamW初始学习率设为1e-4。这里有个经验值CNN部分用的是预训练权重LSTM和分类头是随机初始化的两者对学习率的敏感度不同。一个工程技巧是给CNN用较小的学习率比如1e-4给LSTM和分类头用较大的学习率比如3e-4。如果用同一个学习率随机初始化部分容易学得太慢或者预训练部分被扰动得太厉害。import torch.optim as optim model CNNLSTM(num_classeslen(class_names)) criterion nn.CrossEntropyLoss() optimizer optim.AdamW([ {params: model.cnn.parameters(), lr: 1e-4}, {params: model.lstm.parameters(), lr: 3e-4}, {params: model.classifier.parameters(), lr: 3e-4} ], weight_decay1e-5) # 训练时使用混合精度加速NVIDIA GPU scaler torch.cuda.amp.GradScaler() for epoch in range(epochs): for batch in train_loader: x, y batch x x.cuda() y y.cuda() optimizer.zero_grad() with torch.cuda.amp.autocast(): outputs model(x) loss criterion(outputs, y) scaler.scale(loss).backward() scaler.step(optimizer) scaler.update() scheduler.step()这里的核心是分组学习率设置。{params: model.cnn.parameters(), lr: 1e-4}让预训练骨干小幅微调model.lstm.parameters()和model.classifier.parameters()用大一些的学习率让新层快速收敛。混合精度是让训练效率翻倍的最简单手段在NVIDIA GPU上用torch.cuda.amp不需要改模型结构只是多加几行代码。如果你用的是CPU训练auto混精不会生效但代码不会报错这一点可以放心。另外我建议配合一个余弦退火学习率调度器CosineAnnealingLR因为AdamW这类优化器到训练后期学习率如果一直不降loss会在一个平台上反复震荡。4. 实时推理管线OpenCV摄像头采集、双线程与翻译输出训练完模型之后真正的工程挑战才刚开始。实时系统里摄像头采集、预处理、模型推理、结果输出必须在每帧的预算时间内完成。如果你把采集和推理串在一起做帧率会被模型延迟直接拖垮。4.1 VideoCapture参数调优分辨率、帧率与缓冲大小OpenCV读摄像头第一件事不是直接cap.read()而是先把采集参数定下来。很多人忽略这些参数导致画面延迟、分辨率不匹配甚至拉流中断。import cv2 cap cv2.VideoCapture(0) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) cap.set(cv2.CAP_PROP_FPS, 30) cap.set(cv2.CAP_PROP_BUFFERSIZE, 2) # 验证参数是否真正生效 actual_w int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) actual_h int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) print(f实际分辨率: {actual_w}x{actual_h})为什么是640×480而不是1920×1080因为手语识别的输入最终会resize到224×224720p以上的原始帧在resize时并没有额外收益反而会占用USB带宽、增加每帧拷贝时间。CAP_PROP_BUFFERSIZE2这个参数很关键如果缓冲区太大比如默认8当你推理速度跟不上采集速度时屏幕上看到的是几百毫秒前的旧画面实时感会彻底消失。压到2可以减少延迟但也不能设成1否则USB传输抖动时采集线程容易卡死。4.2 采集与推理线程分离队列backpressure处理正确的实时管线是双线程采集线程只负责不停从摄像头取帧放进队列推理线程从队列取帧做预处理、推理、输出。这样即使模型推理慢采集也不会停。import queue import threading import time class ASLRealtimePipeline: def __init__(self, model, max_queue_size8): self.model model self.frame_queue queue.Queue(maxsizemax_queue_size) self.running False def capture_worker(self): cap cv2.VideoCapture(0) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) cap.set(cv2.CAP_PROP_BUFFERSIZE, 2) while self.running: ret, frame cap.read() if not ret: print(读帧失败等待重连...) time.sleep(0.5) continue try: self.frame_queue.put(frame, timeout0.1) except queue.Full: # 队列满了丢旧帧继续采集不做阻塞 pass cap.release() def infer_worker(self): while self.running: frame self.frame_queue.get() # 预处理、送入模型、输出结果 # ... def start(self): self.running True threading.Thread(targetself.capture_worker, daemonTrue).start() threading.Thread(targetself.infer_worker, daemonTrue).start()queue.Full触发时选择丢帧而不是阻塞这是实时系统的核心思路宁可少处理几帧也不能让采集线程卡住。因为手语是连续动作丢弃中间某一帧对整体识别的影响远小于整个管线停滞造成的累积延迟。队列上限8帧意味着最大缓冲约0.27秒假设30fps这个延迟在交互场景里是可以接受的。推理线程里不要每帧都跑模型。常见的做法是维护一个长度为30的滑动窗口每收到一帧就推入窗口每10帧或者每0.3秒才把当前窗口的30帧做一次推理。这样既能覆盖一个完整手势动作又不需要每个采集周期都付出推理开销。4.3 词表映射与输出如何把模型logits变成可读文字模型输出的logits只是一个向量要变成hello或thank you这样的文本需要维护一个类别索引到文字标签的映射。这个映射必须和训练数据构建时的类别顺序完全一致否则会翻译成完全错误的词。class_names [book, drink, hello, help, thank_you, yes] def logits_to_label(logits, class_names, top_k3): probs torch.softmax(logits, dim-1) top_probs, top_indices torch.topk(probs, top_k) results [] for prob, idx in zip(top_probs, top_indices): results.append((class_names[idx.item()], prob.item())) return results一个很现实的工程建议不要只输出第一名。在实时场景里手语动作可能只做了一半此时top-1的置信度往往很低top-3里往往包含正确候选。把top-3显示在界面上让使用者自己判断比自信地输出错误答案体验好得多。至于要不要用置信度阈值做最终判定我放到第6章详细说因为这里有一个延迟与准确率的权衡阈值调高了误报减少但真手势出现时要等更久才能触发阈值调低了系统会频繁乱翻译。5. 避坑手册训练不收敛、实时卡顿与手势误判的5个典型问题这一章是从实际项目里一条条踩出来的记录。每一条我都按现象→原因→解决的链路讲清楚方便你出问题时对号入座。5.1 训练损失不降反升学习率失调与标签错位现象训练刚开始几个epochloss降到1.2左右然后开始回升最后稳定在高位震荡再也下不来。原因最常见的是学习率设置过大特别是CNNLSTM这种结构随机初始化的LSTM层梯度幅度和预训练CNN差异很大统一用一个学习率容易让LSTM参数更新过激。另一个隐蔽原因是标签错位——数据加载时图像和label不同步这种情况loss也会异常但通常是从第一个epoch就乱而不是先降后升。解决把初始学习率砍到5e-5先用50个step做一个前向小验证确认loss在下降再放长训练。同时打印一个batch的图像和对应标签人工检查是不是手形标签配对正确。我遇到过数据集某个视频在切帧时损坏导致全黑帧但标注没有过滤掉这种情况模型会被黑帧带偏导致loss突然跳升。5.2 实时推理掉帧严重模型延迟与队列堆积现象摄像头画面明显卡顿界面显示帧率只有个位数手势动作在画面里是跳变的。原因推理线程的处理速度低于采集速度队列迅速填满新帧不断被丢弃。通常不是OpenCV采集慢而是CNNLSTM整体推理延迟太高比如ResNet18在CPU上单帧推理需要50毫秒以上30帧序列一次推理就要1.5秒。解决做一件事把模型切到MobileNetV2并开启int8量化在推理框架里做不是PyTorch训练时。量化后MobileNetV2在CPU上的单帧推理可以压到15毫秒左右。如果还不行把滑动窗口的推理频率从每帧一次改成每5帧一次牺牲一点响应速度换取流畅度。实测中5帧一推的节奏对多数ASL词汇的识别率几乎没有影响因为手势动作的持续时间远大于5帧间隔。5.3 手势频繁误判背景干扰与ROI漂移现象系统在背景杂乱的环境下疯狂输出错误翻译换到纯色背景后准确率明显回升。原因整帧输入模式下CNN把背景纹理也当作特征学习了。另一个来源是手部ROI裁剪后检测框在连续帧之间抖动LSTM把框的抖动误解为手的运动。解决如果用的是整帧输入在训练阶段增加随机背景替换的数据增强——把图像背景换成随机的室内场景图让模型学会忽略背景。如果用的是ROI裁剪则要加一个检测框的时序平滑对连续帧的检测框中心做指数移动平均抑制抖动。def smooth_bbox(prev_box, curr_box, alpha0.7): # alpha越大跟随越慢之前框的权重占alpha缓解单帧跳变 if prev_box is None: return curr_box smoothed [alpha * p (1 - alpha) * c for p, c in zip(prev_box, curr_box)] return smoothedalpha0.7意味着当前框的位置70%由历史位置决定30%由新检测决定。这个系数不是固定不变的如果检测器连续5帧都没检测到手说明手已经离开画面或大幅度移位此时要重置prev_box为None让系统快速重新定位否则手从画面左侧移到右侧的过程中平滑框会一直被拖在旧位置。5.4 ASL类别不平衡重采样与类别权重现象训练后模型对高频词如hello、yes识别准确对低频词几乎永远猜错整体准确率看起来还行但实际不可用。原因WLASL这类数据集里词频差异极大高频词有上千样本低频词只有十来个样本。交叉熵在类别不平衡时天然偏向多数类。解决训练时在DataLoader里做加权采样。class_weights torch.tensor(weights, dtypetorch.float32).cuda() criterion nn.CrossEntropyLoss(weightclass_weights)权重计算方式常用平方根逆频率weight_class sqrt(total_samples / samples_per_class)这样不会让低频词的权重爆炸。只做重采样每个batch里低频词多抽几次效果通常就够如果还不行再加上类别权重。要注意两个手段不要同时开得太狠否则低频词被过度放大高频词反而学不好。5.5 OpenCV拉流中断摄像头被占用与设备热插拔现象程序跑一段时间后捕获不到画面弹出类似No device found的错误或者画面冻结在最后一帧。原因USB带宽不足、摄像头驱动崩溃或者另一个进程占用了摄像头。Windows下和浏览器共用摄像头尤其容易出问题。解决采集线程里加自动重连逻辑检测到cap.read()连续失败超过20次就释放并重新cv2.VideoCapture(0)。同时给采集线程一个保活机制每100帧检查一次cap.isOpened()如果False就尝试重建。另外在程序启动时打印一下cv2.getBuildInformation()看看当前OpenCV是否启用了FFmpeg和V4L2后端这能帮你判断拉流中断是OpenCV版本问题还是硬件问题。6. 最后一公里置信度阈值、滑动窗口去抖与线上验证技巧模型训练好了、实时管线通了系统能不能真正交付还差最后一公里的输出处理。不处理就直接输出top-1标签你会发现系统在用户动作开始之前和结束之后都会乱报体验非常差。置信度阈值是第一个必须调的参数。我一般把阈值设在0.7左右只有当softmax输出的最高概率超过0.7时才把结果显示到界面上。低于阈值就维持上次有效输出或什么都不显示。阈值太高比如0.9会导致真正的正确手势被忽略因为实时场景中手部姿态和采集环境的干扰会让置信度普遍偏低阈值太低比如0.5又会频繁触发误报。开始用0.7然后根据你实际场景中的误报率和漏报率权衡调整。第二个是滑动窗口去抖。实时推理中连续两次推理可能得出不同结果用户手还在动作中模型已经输出了中间态的错误词。正确的做法是做连续N次确认同一个标签连续出现3次也就是在连续3个推理周期中都是top-1且超过阈值才最终输出。这相当于给翻译结果加了一个时间上的低通滤波。第三个是部署前的验证技巧。不要只看测试集准确率要把模型接到真实摄像头前做一次人肉冒烟测试找几个没参与过训练的真人给他们看词表要求每个词做5次统计系统正确响应的次数。特别要注意用户站在不同距离、不同角度时的表现。我自己的习惯是录一段5分钟的真实交互视频然后逐帧回放对照模型输出的时间点看是否出现了输出超前—也就是模型在手势还没做完时就下了结论。超前通常说明模型学到了中间态特征而不是完整动作这种模型在测试集上分数可能不低但实际使用时非常不可靠。如果条件允许最后把部署环境固定下来摄像头位置固定、光照条件固定、背景尽量简洁并在这些固定条件下重新微调模型。CNNLSTM在做手语识别时对环境的敏感度超出很多人预期固定环境比换更强的模型更立竿见影。希望这套从数据、模型到实时管线和避坑的经验能帮到你少走我走过的弯路。本文还有配套的精品资源点击获取
返回列表