ARTICLE DETAIL

资讯详情

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

视频理解之动作识别:从原理到落地的完整实战指南

视频理解之动作识别:从原理到落地的完整实战指南 做动作识别也有几年了从最早的课程大作业一路做到工业项目落地这个方向算是视频理解里最基础也最常被问起的一块。很多人一听“视频理解”就觉得高大上其实说白了动作识别就是让计算机看一段视频判断画面里的人“在做什么”——是跑步、挥手、打篮球还是摔倒。听起来简单真的上手做一遍坑比想象中多得多。这篇文章我会从任务定义讲起把主流的方法路线、选型思路、完整可复现的实操流程还有我踩过的那些坑一次性说清楚。无论你是正在做计算机视觉大作业的学生还是刚入门视频理解领域的研究生又或者是想评估动作识别方案能不能落到业务里的工程师这篇文章应该都能给你省下不少试错的时间。1. 动作识别到底在解决什么问题1.1 任务定义与输入输出动作识别Action Recognition的标准定义是给定一段未分割的视频片段判断这段视频中人物执行的动作类别。这里的“动作”概念很宽泛可以是原子动作如“挥手”“站立”也可以是复合活动如“打篮球”“做菜”。从数学角度看模型要做的事是学习一个映射函数输入是视频帧序列 (X {x_1, x_2, ..., x_T})其中 (x_t \in \mathbb{R}^{H \times W \times 3})输出是动作类别的概率分布 (P(y|X))。我们通常取概率最大的类别作为预测结果。这个任务和图像分类最大的区别就在“时间”上。图像分类只需要处理空间信息而动作识别必须同时处理空间维度和时间维度。一个人“挥手”这个动作单看某一帧模型很难区分是在挥手还是在招手只有把多帧连贯起来看才能捕捉到动作的动态模式。1.2 和图像分类的本质区别我在带新人做项目时经常发现大家误以为动作识别就是把图像分类模型逐帧跑一遍然后取平均。这个思路听起来合理实际效果却很一般。原因很简单逐帧分类完全没有利用帧与帧之间的时序依赖关系。举个例子“倒水”和“喝水”这两个动作如果只看静态帧画面里可能都是一个人手拿杯子视觉特征非常相似。但放到时间轴上倒水时手是从水瓶移向杯子喝水时手是从杯子移向嘴边这两种运动轨迹完全不同。这就是时序信息temporal dynamics的价值。另一个区别是数据量。图像分类数据集动辄上百万张图片而视频数据集UCF101只有1.3万个视频Kinetics-400也只有约30万个视频片段。视频数据标注成本极高一个视频往往需要多个标注者确认还要处理标签歧义比如“跑步”和“慢跑”的边界到底在哪所以数据规模和标注质量都远不如图像领域。1.3 核心难点与挑战动作识别难在哪我总结成几个方面新手踩坑基本都逃不出这几条类内差异大类间差异小。同样是“打篮球”职业比赛和小区篮球场的动作幅度、背景环境差异巨大而“跑步”和“快走”在视觉上又极其相似。模型既要学出足够的类内泛化能力又要捕捉类间的细微差别。背景和相机运动的干扰。一段“跑步”的视频如果镜头跟着人平移光流特征里全是背景的运动信息很容易干扰模型对“人体动作”的建模。这也是为什么很多工业项目要加人体检测器先框出人再识别动作。多尺度问题。人的尺度在视频中变化很大远景里人只占几十个像素近景里人充满整个画面。这对模型的尺度鲁棒性要求很高。时长不定。一个“打哈欠”动作持续1秒“包饺子”可能持续几分钟。模型输入通常固定帧数比如8帧或16帧如何从长视频中采样出有代表性的片段是个容易被忽视但影响很大的细节。1.4 值得落地的应用场景动作识别不是只能在论文里跑实验的技术它在实际业务里有很多刚需场景智慧安防识别打架、摔倒、异常聚集等行为在监控场景中实时预警。体育分析统计运动员的投篮、挥拍、跑动等动作辅助训练和比赛复盘。人机交互通过手势或身体动作控制设备比如体感游戏、智能家居。医疗康复识别患者的康复训练动作是否标准辅助医生评估恢复进度。视频内容理解视频网站自动打标签、审核违规内容、提取精彩片段。我见过不少团队直接用动作识别模型去做“摔倒检测”效果也还行。但要提醒一句动作识别解决的是“这个动作是什么”的问题如果想做“这个动作是否发生”的异常检测那属于另一个任务范畴时序动作检测实现思路差别很大。2. 方法选型从手工特征到 Transformer主流路线怎么选2.1 早期思路与双流网络2014年之前动作识别领域还是手工特征如iDT、HOG/HOF的天下。这类方法先提取视频中的密集轨迹再在轨迹周围计算梯度直方图等特征最后用SVM分类。效果尚可但计算量大且严重依赖光流场Optical Flow的质量。双流网络Two-Stream Network是深度学习在动作识别上的里程碑之作。它的核心想法很直接人类识别动作时既看外观人长什么样、拿着什么也看运动模式肢体怎么动。因此双流网络用两个独立的卷积网络一个吃RGB帧一个吃连续几帧的光流图最后把两个分支的预测分数融合。这个方案在当时的精度提升非常大关键原因是光流输入显式地编码了运动信息大大减轻了模型学习时序动态的负担。但它也有硬伤光流计算非常耗时OpenCV的Farneback算法跑一帧要几十毫秒训练和推理都需要两步工程部署很不方便。2.2 3D卷积从C3D到I3D既然2D卷积是“空间特征提取器”那能不能直接用3D卷积同时处理空间和时间C3DConvolutional 3D就是这么做的。它在每个卷积层同时滑动空间维度和时间维度理论上可以端到端地学习时空特征。C3D在UCF101上取得了不错的成绩但有个致命问题模型参数量巨大训练速度慢而且由于当时数据集规模太小3D卷积的优势并没有完全发挥出来。真正让3D CNN成为主流的是I3DInflated 3D ConvNet。它的做法很聪明把在ImageNet上预训练好的2D卷积网络“膨胀”成3D也就是把卷积核 (K \times K) 变成 (K \times K \times K)并沿着时间轴重复初始化权重。这样既借力了图像预训练模型的特征表达又能处理视频的时序信息。I3D在Kinetics-400上训练后迁移到UCF101和HMDB51精度提升非常明显一举成为领域标杆。直到今天很多顶会论文依然用I3D作为特征提取器。2.3 工程常用方案TSN与TSM的取舍3D CNN效果好但计算量大训练慢。在计算资源有限的场景下比如课程作业、创业团队工程上更常用的是TSNTemporal Segment Networks和TSMTemporal Shift Module。TSN的思路是把视频均匀分成K段每段随机采样一帧或一小段然后让2D CNN独立处理每一帧最后对K个帧的特征做融合。它本质上是一个“分段采样特征聚合”的框架计算量和纯2D CNN一样小却能覆盖整个视频的内容。这个方案很适合做baseline速度极快调参方便。TSM则在TSN的基础上更进一步它通过一种叫“时间移位”Temporal Shift的零参数操作把一部分通道沿着时间轴平移一个位置让2D卷积在推理时也能“看到”前后帧的信息。换句话说它用“作弊”的方式模拟了3D卷积的时序建模能力但计算量几乎没有增加。实际项目中TSM是性价比极高的选择。我在一个安防项目里用TSM替换I3D做摔倒识别推理速度提升了将近4倍精度只掉了不到两个点非常划算。2.4 Transformer路线TimeSformer与VideoMAE近两年Transformer架构也进入了视频理解领域。TimeSformer将视频帧划分成时空patch类似ViT的做法然后分开做时间注意力和空间注意力在多个数据集上超过了I3D。VideoMAE则把MAEMasked Autoencoder的思路扩展到视频上随机遮挡大部分帧和空间patch只让模型从剩余部分重建被遮挡的内容用自监督方式在大量无标注视频上预训练。它学到的特征非常强下游任务只要做轻量微调就能达到不错的精度是目前学术研究的一个主流方向。不过说实话Transformer路线对算力和显存的要求更高而且训练技巧比CNN复杂。如果只是想快速完成一个课程项目或者跑通业务demo我的建议是先把CNN路线搞扎实再考虑升级到Transformer。2.5 选型建议汇总我做了个对比表方便你根据自己的场景快速选型方案计算量推理速度精度工程难度适合场景2D CNN TSN低快中低课程作业、快速baseline双流网络中慢光流耗时中高中学术对比实验C3D / 3D ResNet高慢中高中有GPU资源的研究项目I3D很高慢高中学术基准、数据充足的场景TSM低快高低工业落地、实时推理TimeSformer / VideoMAE很高慢很高高前沿研究、有预训练模型可用这个表不是绝对的具体选型还要考虑你的数据量、标签噪声、场景复杂度和硬件限制。后面我会详细讲实操就拿TSN作为主线因为它最容易跑通也最容易理解。3. 实操从零训练一个人体动作识别模型3.1 数据集选择与预处理动作识别最常用的公开数据集是UCF101101类约1.3万视频和HMDB5151类约7000视频。UCF101类别覆盖比较广包括人机交互、人体动作、运动等而且视频时长较短训练门槛低非常适合作为学习用的“练手集”。如果你有条件访问Kinetics-400或Something-Something效果会更好但体积很大Kinetics-400约300GB对学生党不友好。我建议初期先用UCF101的子集或者完整UCF101跑通整个流程再说。数据预处理的流程一般是这样把每个视频解码成帧序列。可以全量保存为jpg图片也可以在线解码用OpenCV或PyAV实时读取。统一所有视频的短边尺寸比如256像素保持宽高比不变。训练时做随机裁剪224x224随机水平翻转做颜色扰动测试时用中心裁剪。全量保存为图片的好处是训练时不用反复解码视频速度更快但会占用较多磁盘空间。UCF101全量抽帧大约要几十GB如果你的磁盘紧张直接用OpenCV在线解码也行就是训练时会慢一些。3.2 视频帧采样策略踩坑记录采样策略是整个流程里最容易被忽视、却对最终精度影响最大的部分。这里我想重点说清楚。假设我们的模型输入是8帧每帧224×224。如果从一个10秒的视频里随机取8帧可能取不到动作发生的关键时刻如果全取连续帧又会让模型关注的时序视野太窄。TSN的做法是把视频均匀分成8段每段里随机采样一帧这样8帧能覆盖整个视频的内容采样到的帧也具备多样性。如果采样时发现视频帧数少于所需帧数我习惯先做时间维度的线性插值补齐而不是简单重复最后一帧。重复帧会让模型误以为画面长时间静止学到错误的时序特征。测试和推理阶段我会把视频均匀分成8段每段取中间帧或做三次随机采样取平均得到一个更稳定的预测结果。这个技巧能让Top-1精度提升1-2个百分点几乎零成本。3.3 模型搭建用PyTorch实现一个TSN基线下面我给出一个实用且完整的TSN基线代码。我用torchvision里的ResNet18作为2D主干网络在特征层之后做时序平均池化再接线性分类器。这个模型在网络结构上足够简单但在UCF101上能做到约80%以上的Top-1准确率需要预训练权重。import torch import torch.nn as nn import torchvision.models as models class TSN(nn.Module): def __init__(self, num_classes, num_segments8, base_modelresnet18): super().__init__() # 加载ImageNet预训练模型 if base_model resnet18: backbone models.resnet18(weightsmodels.ResNet18_Weights.IMAGENET1K_V1) elif base_model resnet50: backbone models.resnet50(weightsmodels.ResNet50_Weights.IMAGENET1K_V1) else: raise ValueError(Unsupported backbone) # 去掉最后的全局池化和分类层 self.features nn.Sequential(*list(backbone.children())[:-2]) self.global_pool nn.AdaptiveAvgPool2d((1, 1)) # 输出分类 self.fc nn.Linear(backbone.fc.in_features, num_classes) self.num_segments num_segments def forward(self, x): # 输入x的形状: (B, num_segments, C, H, W) batch_size, num_segments, C, H, W x.shape # 把片段维度合并到batch维度一起过主干网络 x x.view(batch_size * num_segments, C, H, W) feat self.features(x) feat self.global_pool(feat) feat feat.view(batch_size, num_segments, -1) # 时序平均池化 feat feat.mean(dim1) out self.fc(feat) return out这段代码里有个细节值得说明为什么把8帧合到batch维度一起过主干网络因为2D CNN本身不知道时间维度的概念把8帧当成8张独立图片处理计算效率最高而且ResNet的预训练权重可以直接复用不需要重新学习特征提取器。时序信息则完全靠后面的平均池化层来聚合。如果你想进一步引入TSM的时间移位思想可以在每个残差块前对输入特征做通道维度的shift。不过这里我先不展开TSM的完整实现避免篇幅过长后面有问题可以在评论区交流。3.4 完整训练流程与关键参数训练流程里数据加载部分我单独提一下因为动作识别的数据loader比图像分类复杂。关键逻辑是读视频 - 解码 - 均匀分段 - 每段取帧 - 空间变换 - 组装成张量。我用一个简化的伪代码来展示核心逻辑class VideoDataset(Dataset): def __init__(self, video_paths, labels, num_segments8, is_trainTrue): self.video_paths video_paths self.labels labels self.num_segments num_segments self.is_train is_train def __len__(self): return len(self.video_paths) def __getitem__(self, idx): frames self.load_frames(self.video_paths[idx]) # 返回一列帧 total_frames len(frames) # 把帧序号均匀分成num_segments组 segment_size total_frames / self.num_segments selected_idx [] for i in range(self.num_segments): start int(i * segment_size) end int((i 1) * segment_size) if self.is_train: # 训练时随机取一帧 pick random.randint(start, max(start, end - 1)) else: # 测试时取中间帧 pick (start end) // 2 selected_idx.append(pick) # 读取对应帧并做空间变换 ... return clip_tensor, self.labels[idx]训练参数方面我的常用配置如下优化器SGDmomentum0.9weight_decay1e-4初始学习率0.01batch size为32时用余弦退火调度cosine annealing逐步降至0Batch size32ResNet18 8帧 224尺寸大约需要8-10GB显存训练轮数UCF101上30-50轮足够收敛官方TSN通常训练到80轮但那是更大模型损失函数交叉熵损失训练过程每隔一个epoch评估一次测试集精度保存精度最高的权重。实际训练中我见过很多人不保存最优权重最后模型过拟合了还在用最后一个epoch的权重去测试效果差很多。这个坑一定要避免。3.5 评估指标与结果分析动作识别领域最常用的评估指标是Top-1准确率预测概率最高的类别是否匹配和Top-5准确率预测概率前五的类别是否包含真实类别。学术论文里一般报告这两个指标。除了准确率我还建议你画一下混淆矩阵。通过混淆矩阵可以直观看到哪些类别容易互相混淆比如UCF101里的“PlayingGuitar”和“PlayingSitar”、“HorseRiding”和“BikeDriving”经常被模型搞混。分析混淆矩阵能帮你定位模型到底在视觉上忽略了什么信息。我曾用上述TSN基线在UCF101上训练最终Top-1准确率在80%左右ResNet18主干。如果换成ResNet50主干能到85%左右。这个成绩在学术实验里不算高但作为baseline和课程作业已经完全够用。推理速度方面ResNet18 8帧 224尺寸在单张V100 GPU上可以达到每秒处理约300个采样片段。如果用TensorRT加速还能再快一截。4. 常见问题与排查技巧实录4.1 训练不收敛怎么办这是新手问得最多的问题。现象是loss不降或者准确率一直卡在随机水平。我的排查顺序如下先检查数据。把batch里的帧可视化出来确认采样后的图片确实包含人体动作而不是一堆黑屏或静止画面。UCF101有很多视频是镜头快速切换的采样策略不合理时8帧可能来自完全不同的镜头模型根本学不到连贯的动作信息。然后检查预处理。归一化是否正确我见过有人把像素值除以255之后再减均值结果均值设置用的是ImageNet统计量值域对不上导致模型一直震荡。确认你的归一化方案和预训练权重一致。最后检查学习率和batch size的匹配关系。学习率0.01配上batch size 16经常会出现loss爆炸。一般batch size减小学习率也要跟着降。我惯用经验是batch size翻倍学习率翻倍batch size减半学习率减半。4.2 过拟合严重怎么办视频模型参数量大数据量又少过拟合几乎是必然的。训练集准确率90%多、测试集只有60%多这种情况很常见。缓解手段按优先级排序使用更强的数据增强。随机裁剪、水平翻转之外还可以加入颜色抖动、随机擦除、时间维度上的随机快慢扰动SlowFast里用到的技巧。增加Dropout。在主干网络输出之后、分类层之前加一个dropout层p0.5。这个改动虽然微小但往往能提升1-2个点。减小模型容量。如果ResNet50过拟合严重可以换ResNet18试试。使用预训练模型。在ImageNet上预训练的权重能显著改善视频模型的过拟合问题因为主干网络已经学到了足够的视觉特征。4.3 类别不均衡怎么处理动作识别数据集往往天然不均衡。UCF101里有些类别的视频数量是其他类别的两倍这种偏差虽然不算剧烈但也会让模型偏向高频类别。最简单的做法是在采样时做类别均衡也就是每个epoch里从样本量最少的那个类别的数量为基准对高频类别做下采样。这个方法实现简单效果立竿见影。如果不想损失训练数据量也可以用class-weighted loss根据每个类别样本数量的倒数给损失加权。这个方法不用改动数据loader但调参时要注意学习率可能要相应调小。4.4 推理速度慢怎么优化如果你部署到边缘设备或者RTSP实时视频流推理速度就是硬指标。优化的方向有几个第一个方向是减少输入帧数。从8帧降到4帧推理时间几乎减半精度通常只降1-2个点。这个trade-off在很多场景下非常划算。第二个方向是换更轻量级的主干网络。比如用MobileNetV3替代ResNet或者换TSM这类零参数时序建模模块保持精度同时大幅压缩计算量。第三个方向是模型量化。用TensorRT做FP16或INT8量化在V100或T4上可以再快2-3倍。不过量化对精度的影响需要实测验证有些动作类别边界模糊量化后性能下降会比较明显。4.5 动作边界模糊如何解决标准的动作识别假设输入视频是“已经切割好的动作片段”但真实场景里动作发生的起止时间点什么都不好判断。比如监控视频里一个人走路走了一半开始打电话到底在哪里算“开 始打电话”这就是时序动作检测Temporal Action Detection的范畴比纯动作识别复杂得多。如果你被迫用一个定长窗口做滑动窗口推理我建议把窗口设置的比目标动作平均时长略短一些stride设成窗口长度的一半这样能把动作的关键部分切进来。我在摔倒检测项目里就是用这个策略配合后处理做时序平滑抑制单帧抖动可以把误检率降下来不少。这里有个通用技巧不要直接用单窗口的预测结果去报警而是取相邻3-5个窗口预测分数的平均再做阈值判断。这个“时序平滑”的trick在几乎所有视频任务里都通用能显著提升鲁棒性。5. 一些个人经验与后续方向我的个人体会是动作识别入门容易、精通难。入门只需要掌握2D CNN 时序聚合这一条技术路线跑通一个baseline理解采样、训练、评估的基本流程就能应付大部分课程作业和应用级需求。但要做到业务可用就必须花大量时间在数据清洗、采样策略优化、精度和速度之间找平衡这些“脏活累活”上。如果这篇文章的读者正在犹豫要不要做这个方向我的建议是从TSN开始。首先在自己熟悉的数据集上跑通baseline然后尝试把模型换成TSM观察精度和训练速度的变化再去理解3D CNN和Transformer方案的改进动机。这个路径能让你对视频理解领域的演进脉络建立起直观的认知而不是停留在Paper的抽象描述里。最后分享一个我在多个项目中验证过的小技巧如果你只有少量标注数据比如几百个视频不要直接随机初始化训练深度模型更不要直接加载ImageNet预训练后冻结主干——试着在大型视频数据集如Kinetics-400上的预训练模型权重上微调哪怕只是冻结前几层效果远超从头训练。现在有社区发布的预训练模型可以直接下载这比自己训练省出好几天时间。动作识别只是视频理解的第一课。理解了时序信息如何与空间信息融合之后接下来无论你做时序动作检测、视频描述、还是视频检索都会觉得顺理成章很多。希望这篇文章能给你的项目开个好头。
返回列表