简介面向计算机专业课程设计与期末大作业的Python步态识别行人项目提供完整可运行的工程源码和详细PDF文档。项目覆盖数据准备、模型训练、推理演示等关键环节适合需要从零搭建算法项目、撰写课程报告的学生参考。压缩包共509个文件大小约37.93MB以291个Python源码文件为主体包含模型权重pth、checkpoint、pbtxt、数据集分片data、index、meta以及运行所需的依赖库与配置文件pyd、dll、exe、cfg结构清晰便于按模块调用。另有PDF介绍文档可辅助理解项目设计与实现思路。目前已有150人学习下载项目曾获导师指导并通过98分评价工程完整度和规范度较高。读者可直接运行源码观察步态识别效果也可结合文档拆解特征提取、模型训练等环节作为课程设计答辩或毕设扩展的重要参考。1. 步态识别用什么 Python 方案才不至于让项目翻车步态识别在 CASIA-B 这类公开数据集上的准确率往往比多数人预想的乐观。很多人拿到基于 python 实现的步态识别行人项目这类源码包后第一反应是换更大的网络、上更花哨的注意力机制结果训练时间翻倍、准确率反而掉下去。作为一个做过几轮行人识别项目的人我的结论是这类项目的核心价值不在模型多新而在把视频输入 → 前景分割 → 特征提取 → 身份匹配这整条 Python 链路老老实实串起来。它特别适合两类人一是计算机视觉方向的课设/毕设需要一整套能跑通、能写进文档的完整方案二是想入门行人 ReID 和生物特征识别想搞清楚视频数据怎么变成一张特征图的开发者。2. 核心算法选型GEI 特征和小型 CNN 为什么是课程设计的最优解2.1 从视频到步态能量图轮廓序列是怎么压成一张图的步态识别的输入不是单张照片而是一段行走视频。同一个人在不同帧里姿态不同靠单帧判断身份极不稳定常见做法是把一整段行走周期的轮廓合成为一张图牺牲时序细节换稳定性。这就是 GEIGait Energy Image步态能量图的核心思路对归一化后的二值轮廓序列逐像素取平均公式为G(x, y) (1 / N) * Σ I_t(x, y)其中 I_t 是第 t 帧归一化后的轮廓图N 是有效帧数。合成后的 GEI 保留了行人的体态比例、四肢摆动范围等静态和动态信息对单帧检测噪声不敏感。GEI 在 CPU 上就能快速计算特征维度低后续接一个轻量分类器就能出结果。它的代价是丢失了行走相位信息——快走和慢走的同一人GEI 会有差异不过在受限数据集上这个损失完全可接受。选择 GEI 而不是 3D 卷积或 LSTM还有一层现实考量课程设计和源码复现场景下训练时间、显存占用、代码可读性都要兼顾。3D CNN 需要把视频序列整体送入网络显存开销大LSTM 需要处理变长序列调参难度高。GEI 方案把时序信息预先压缩到单帧模型结构回到普通 2D CNN绝大多数机器都能跑。如果你后续想追新方法GEI 也可以作为 Baseline和 GaitSet 这类方法做对照实验。2.2 分类器选择LDA、SVM、小型 CNN 的边界在哪里拿到 GEI 特征后分类方案有三种常见路线。第一种是把 GEI 拉平成向量做 PCA LDA属于传统机器学习方案在样本量小、类别多的情况下速度极快缺点是特征表达能力有限跨视角时准确率衰减明显。第二种是提取 GEI 的 HOG 或 LBP 特征喂给 SVM对光照变化稳健一些但同样难以捕捉高层语义。第三种是小型 CNN 端到端分类这也是现在多数 Python 步态识别项目采用的方案。一个小型 CNN 在公开数据集上的效果通常能比 LDA/SVM 高出 10 到 20 个百分点代价是需要 GPU 训练、需要自己做数据增强。三者的对比可以简单列为下面这张表方案 | 训练速度 | 跨视角鲁棒性 | 代码复杂度 | 典型准确率表现 PCALDA | 秒级 | 较差 | 低 | 60%~75% HOGSVM | 分钟级 | 中等 | 中 | 65%~78% 小型 CNN | 小时级 | 较好 | 中高 | 80%~95%需要强调的是这张表里的准确率区间是在 CASIA-B 的常规划分协议下得到的经验值不是某个模型的官方成绩。对课设项目来说选小型 CNN 基本不会错既能体现深度学习方法又不会因为模型太大导致训练时间失控。如果只是想要一个能跑通的 DemoPCALDA 可以作为快速验证手段确认数据预处理没问题之后再换 CNN。2.3 环境与依赖跑通这个项目需要哪些 Python 库这类项目的运行环境通常围绕 OpenCV、NumPy、PyTorch 搭建。OpenCV 负责读取视频、做帧差和形态学处理NumPy 负责数组运算和 GEI 合成PyTorch 负责训练分类器。建议用虚拟环境隔离依赖避免和系统 Python 打架。一个可以照抄的依赖安装步骤如下python -m venv venv source venv/bin/activate # Windows 下用 venv\Scripts\activate pip install opencv-python numpy scikit-learn matplotlib pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118提示如果机器没有 NVIDIA GPU把最后一行换成pip install torch torchvisionPyTorch 会安装 CPU 版本训练速度慢一些但完全能跑。OpenCV 和 NumPy 的版本兼容性比较敏感建议先用虚拟环境装最新稳定版。scikit-learn 在后续评估准确率、做分类报告时会用到Matplotlib 用来输出 GEI 可视化图和训练曲线写文档时这两样东西很加分。VSCode 里配置 Python 环境时直接把解释器指向 venv 目录即可调试断点能省下大量排查时间。3. 数据准备与预处理把 CASIA-B 视频变成干净的训练集3.1 数据集拆解训练集、Gallery、Probe 按什么标准划分步态识别项目最常用的公开数据集是 CASIA-B它包含 124 个行人、每人 11 个视角0° 到 180°间隔 18°、三种着装条件正常行走NM、背包BG、穿大衣CL。评估协议通常这样划分前 62 人作为训练集后 62 人作为测试集从测试集每人的 NM 条件中抽取一个序列组成 Gallery身份库其余 NM 序列以及全部 BG、CL 序列作为 Probe待查询序列。具体划分可以参考下表数据角色 | 行人范围 | 使用的序列 | 用途 训练集 | 前 62 人 | NM#1-4、BG#1、CL#1 | 训练 CNN 分类器 Gallery | 后 62 人 | NM#5 或 #6 | 构建身份特征库 Probe | 后 62 人 | 其余 NM、BG、CL | 测试识别准确率这套划分协议本身也是论文里通行的做法写成文档时可以直接引用。要注意的是训练集和测试集的行人必须完全分开否则属于数据泄漏准确率虚高但评审一眼就能看穿。每个行人的视频编号规范要核对清楚建议用一个字典结构统一管理路径后面构建 Gallery 和 Probe 时能少踩命名坑。3.2 前景分割用背景减除把行人轮廓从视频里抠出来CASIA-B 的拍摄环境是室内固定机位背景基本不变所以用背景减除就能得到较干净的前景。核心做法是取视频第一帧或单独的背景图作为参考当前帧和背景帧做差分超过阈值的像素判定为前景。这里有一个实际经验阈值不要拍脑袋设成固定值不同视频的亮度差异挺大。先用灰度图的像素分布看一眼再定阈值会更稳。import cv2 import numpy as np def extract_mask(video_path, bg_path, threshold25): cap cv2.VideoCapture(video_path) bg cv2.imread(bg_path, cv2.IMREAD_GRAYSCALE) masks [] while True: ret, frame cap.read() if not ret: break gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) diff cv2.absdiff(gray, bg) # 用自适应阈值方案避免不同视频亮度差异导致分割失败 _, mask cv2.threshold(diff, threshold, 255, cv2.THRESH_BINARY) # 形态学处理开运算去噪点闭运算填补轮廓内部空洞 kernel cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)) mask cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel) mask cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel) # 归一化到统一尺寸减少身体远近造成的比例差异 mask cv2.resize(mask, (64, 128), interpolationcv2.INTER_NEAREST) masks.append(mask) cap.release() return np.stack(masks, axis0)这段代码做了三件事读取视频和背景图、逐帧差分得到前景掩码、形态学清理后归一化尺寸。阈值 25 是常用起点实际使用中如果发现轮廓破碎就适当调低到 15~20如果发现背景噪声混入就上调到 30~35。归一化到 64×128 是考虑到 GEI 后续要喂给 CNN尺寸太大会让训练变慢太小会丢失肢体细节。还有一点CASIA-B 部分视频自带标注好的前景掩码文件如果追求准确率可以直接用标注掩码省去背景减除这一步带来的噪声。3.3 GEI 合成序列平均策略与帧选择边界有了二值轮廓序列后合成 GEI 的逻辑很简单但有两个细节直接决定质量。第一个细节是帧选择视频开头和结尾往往有站立、转身等非行走帧直接平均会得到一张半人半影的模糊图。解决办法是去掉每段视频的前 10 帧和最后 10 帧或者根据轮廓质心的位移判断行人是否在行走位移小于阈值的帧直接丢弃。第二个细节是归一化时机建议先对每帧做尺寸归一化再平均而不是先平均再缩放后者会把个体身高差异混入特征。def build_gei(masks, frame_rangeNone): if frame_range is not None: start, end frame_range masks masks[start:end] # 丢弃质心位移过小的帧静止/转身帧 selected [] prev_center None for mask in masks: ys, xs np.where(mask 0) if len(xs) 50: continue center (np.mean(xs), np.mean(ys)) if prev_center is not None: movement np.sqrt((center[0] - prev_center[0]) ** 2 (center[1] - prev_center[1]) ** 2) if movement 1.0: continue prev_center center selected.append(mask.astype(np.float32)) if len(selected) 0: return None gei np.stack(selected, axis0).mean(axis0) # 裁掉周围无效黑边只保留包含前景的区域 rows np.any(gei 0.05, axis1) cols np.any(gei 0.05, axis0) if rows.any() and cols.any(): rmin, rmax np.where(rows)[0][[0, -1]] cmin, cmax np.where(cols)[0][[0, -1]] gei gei[rmin:rmax 1, cmin:cmax 1] return cv2.resize(gei, (64, 128), interpolationcv2.INTER_LINEAR)GEI 合成的逻辑说明先按帧范围截取中间行走片段再计算每帧轮廓质心的移动量把静止帧过滤掉最后对剩余帧取平均。质心阈值的设定要参考视频分辨率和帧率25 帧每秒、分辨率为 320×240 的视频阈值设为 1.0 像素比较合适如果是高清视频可以适当放大到 2~3 像素。裁掉黑边后再统一缩放到 64×128这样能把不同行人的身体比例对齐。合成一张 GEI 后建议立刻用 Matplotlib 可视化检查如果看到明显重影或断肢先查帧过滤参数不要急着进模型训练——这块出问题后面所有结果都是白搭。4. 模型训练与效果验证准确率过 80% 要盯哪三个参数4.1 小型 CNN 结构不只是堆卷积还要考虑样本量步态识别任务的训练集规模通常不大。以 CASIA-B 常规划分来看训练集只有 62 个类别每个类别约 6 到 9 张 GEI。在这种数据量下ResNet-50 级别的网络极易过拟合反而是一个 4 到 6 层的轻量 CNN 表现更稳定。我常用的网络结构是三个卷积块加全局平均池化参数量小、训练快、泛化好。import torch import torch.nn as nn class GaitNet(nn.Module): def __init__(self, num_classes62): super().__init__() self.features nn.Sequential( nn.Conv2d(1, 32, kernel_size5, stride2, padding2), nn.BatchNorm2d(32), nn.ReLU(inplaceTrue), nn.Conv2d(32, 64, kernel_size3, stride2, padding1), nn.BatchNorm2d(64), nn.ReLU(inplaceTrue), nn.Conv2d(64, 128, kernel_size3, stride2, padding1), nn.BatchNorm2d(128), nn.ReLU(inplaceTrue), nn.AdaptiveAvgPool2d((1, 1)) ) self.classifier nn.Linear(128, num_classes) def forward(self, x): x self.features(x) x x.view(x.size(0), -1) return self.classifier(x)结构说明三个卷积层逐步把通道数从 1 提升到 128空间分辨率从 64×128 依次减半最后用自适应平均池化把特征压成 128 维向量。BatchNorm 放在卷积和 ReLU 之间能加速收敛并减少对学习率的敏感度。这个网络在 CPU 上训练一个 epoch 只要几分钟在 GPU 上更是几十秒。相比直接套用 ImageNet 预训练模型这种从零训练的轻量网络在步态这种灰度图上反而更可控因为预训练模型的低层特征分布和 GEI 差异太大。4.2 三个必须调的参数输入尺寸、Batch Size 和帧过滤阈值输入尺寸直接影响网络容量。64×128 是默认选择显存足够时可以试 96×192 或 128×256准确率会小幅提升但训练时间和过拟合风险同步上升。CASIA-B 下我一般不建议超过 128×256因为每个类别的样本太少大输入会把噪声细节也学进去。Batch Size 的取值和类别数强相关。62 个类别、每类样本极少的情况下Batch Size 设为 8 到 16 比较合理。太小如 2 或 4会导致 BatchNorm 统计量不稳定训练损失震荡太大如 64 或 128会让每个 batch 覆盖不到足够多的类别模型偏向高频类别。训练轮数建议 40 到 60 个 epoch配合学习率余弦退火比固定学习率更容易收敛到稳定点。第三个参数是帧过滤阈值它不属于网络训练但对 GEI 质量影响极大。帧过滤阈值设得太严会把正常行走帧也丢掉GEI 出现残缺设得太松静止帧混入GEI 出现重影。经验做法是对同一个人的同一段视频分别用阈值 0.5、1.0、2.0 生成三张 GEI肉眼对比后确定。这一步是纯玄学还是有点规律可循的分辨率和帧率越高阈值可以适当调大。提示训练时把 GEI 像素值归一化到 [0, 1]不要用 [0, 255]。否则第一个卷积层的输入分布太大BN 层需要额外 epoch 才能把均值拉回来。4.3 验证协议Gallery 和 Probe 怎么构建准确率怎么算训练完分类器后识别阶段的做法不是直接取 softmax 输出而是把分类器倒数第二层的 128 维向量当作行人特征。Gallery 中每个行人提取一个或多个特征向量存入库Probe 中每个待识别序列提取特征然后计算余弦相似度取相似度最高的 Gallery 身份作为识别结果。这种基于特征比对的方式比直接分类更接近实际的识别场景。def build_gallery(model, gallery_loader, device): model.eval() gallery_feats {} with torch.no_grad(): for paths, images, labels in gallery_loader: images images.to(device) feats model.features(images).view(images.size(0), -1) feats feats / feats.norm(dim1, keepdimTrue) for path, feat, label in zip(paths, feats, labels): gallery_feats[path] (feat.cpu().detach(), label.item()) return gallery_feats def identify(probe_feat, gallery_feats): best_score -1.0 best_id None for path, (gallery_feat, label) in gallery_feats.items(): score (probe_feat * gallery_feat).sum().item() # 余弦相似度 if score best_score: best_score score best_id path return best_id, best_score特征比对的逻辑是先建库再用 probe 特征逐一和库中特征算余弦相似度最高分对应的身份就是识别结果。在得到准确率时要分条件统计NM 条件下准确率通常会明显高于 BG 和 CL 条件。这一步千万不能只报一个平均值。把三种条件正常、背包、大衣的准确率分别列出来你会在文档评审上直接拉开和其他同学的差距——多数同学的文档只写了准确率 91%但没有区分到底是在哪种条件下测出来的这本身就很有说服力。5. 步态识别常见问题排查视角、遮挡与过拟合的五个坑5.1 视角差异让准确率骤降数据增强的正确用法现象模型在 NM 条件下准确率 93%同一个模型测试时换一个视角准确率直接掉到 70% 以下。原因CASIA-B 的 11 个视角覆盖 0° 到 180°训练时如果把所有视角混合模型确实能学到视角不变特征但如果训练和测试视角分布不一致比如训练时以 90° 为主、测试时以 0° 为主模型就把视角特征当成身份特征学了。解决思路分两层。第一层是数据划分时把视角均匀摊开每个行人的 11 个视角全部进入训练集测试时按视角分组报告准确率。第二层是数据增强对 GEI 做水平翻转相当于凭空增加镜像视角在 [0.9, 1.1] 范围内做随机缩放模拟距离变化随机擦除部分矩形区域模拟遮挡。水平翻转对齐后能直接提升 2~3 个百分点代价几乎为零。5.2 前景分割残渣干扰 GEI形态学处理和连通域过滤别省现象合成出的 GEI 上出现明显的条状或块状噪点分类准确率怎么调参都上不去。原因背景减除时视频里的阴影、反光、背景物体移动都会产生误检像素这些噪声被平均到 GEI 里后产生脏特征。解决形态学开闭运算做完后再加一步连通域过滤只保留面积最大的连通区域。def keep_largest_connected_area(mask): num_labels, labels, stats, _ cv2.connectedComponentsWithStats(mask, connectivity8) if num_labels 1: return mask # 0 是背景标签从 1 开始找最大前景区域 largest_idx 1 np.argmax(stats[1:, cv2.CC_STAT_AREA]) cleaned (labels largest_idx).astype(np.uint8) * 255 return cleaned这个函数的逻辑很直接用 OpenCV 的连通域分析找出所有前景区域按面积排序保留下最大的一个。注意 CASIA-B 中行人位于画面中央且面积通常最大所以这个策略有效。如果是多人同框的复杂场景就不该依赖这个方案需要换成行人检测模型这类场景已经超出 GEI 方案的适用边界了。5.3 Gallery 和 Probe 命名不一致字典构建的细节坑现象训练时好好的测试时准确率接近随机。排查后发现是 Gallery 的路径字典把行人编号解析错了导致身份错配。原因CASIA-B 的文件名格式是视角-序列-行人编号的结构化命名例如090-nm-01-090表示 90° 视角、NM 条件、序列 01、行人编号 090。处理时如果用错分隔符或者把序列号和视角号弄混字典就配错了。解决构建路径到标签的映射时用一个统一函数解析文件名先跑一遍打印确认再进测试。建议直接把解析函数放进数据加载器里而不是手写多个字典拼接。这个问题看起来低级但几乎人人都踩过属于是这类项目最典型的黑匣子问题——代码能跑但数据逻辑错了。5.4 过拟合与类别不均衡训练标签再平衡现象训练集准确率 98%验证集 60%。原因62 个类别中每类样本数差异明显NM 条件有 4 到 6 张 GEIBG 和 CL 可能只有 1 到 2 张CNN 天然偏向多数类。解决训练时用 WeightedRandomSampler 按样本数反比采样让每个 batch 里各身份出现频率尽量均衡同时在损失函数上对少数类加权重。from torch.utils.data import WeightedRandomSampler def make_balanced_sampler(labels, num_samples_per_epoch2000): unique, counts np.unique(labels, return_countsTrue) weights 1.0 / counts sample_weights np.array([weights[np.where(unique lbl)[0][0]] for lbl in labels]) return WeightedRandomSampler(weightssample_weights, num_samplesnum_samples_per_epoch, replacementTrue)这个采样器的逻辑是每个样本的采样权重和它的类别样本数成反比样本少的类别在每次采样中有更高概率被选中。num_samples_per_epoch 设为 2000 时每个 epoch 的迭代次数会明显增多训练时间变长但类别平衡度大幅改善。用这个采样器配合数据增强验证集准确率通常能拉回 5~8 个百分点。5.5 视频帧率不一致和站立帧GEI 平均策略的静默错误现象合成 GEI 后部分行人出现分身效果——轮廓周围有一圈淡淡的残影。原因视频开头和结尾包含行人从静止到启动的过渡帧这些帧的轮廓质心位置和其他帧差异很大平均后产生残影。解决在 GEI 合成时过滤质心位移小的帧或者直接截取视频中间 60%~80% 的连续片段。这个问题的隐蔽之处在于残影有时候看不清肉眼不容易发现但特征分布已经偏移了。建议合成阶段设计一个开关先稀疏采样 20 帧生成一张预览 GEI可视化确认轮廓干净后再跑全量合成避免整套数据跑完才发现质量不行。这种返工很费时间属于典型的花几分钟预览能省一小时重跑的场景。6. 进阶技巧跨视角识别和门禁式验证的改造方向如果只是把分类器训练到 85% 准确率就交付那项目只完成了一半。课程设计和毕设想拿高分往往差在一个延伸实验上。GEI CNN 这条基线最大的短板是跨视角泛化而 CASIA-B 恰好提供了 11 个视角的数据非常适合用来做这个延伸把 Gallery 从单一视角扩展为同一行人的 3 个视角例如 0°、90°、180°Probe 用其他视角看准确率如何变化。def build_multi_view_gallery(model, loader, views_per_id3): model.eval() gallery {} with torch.no_grad(): for paths, images, labels in loader: feats model.features(images).view(images.size(0), -1) for path, feat, label in zip(paths, feats, labels): # 每个身份最多保留 views_per_id 个视角的特征向量 if label.item() not in gallery: gallery[label.item()] [] if len(gallery[label.item()]) views_per_id: gallery[label.item()].append(feat.cpu().detach()) return gallery识别时probe 特征与某身份下所有视角特征逐一算余弦相似度取该身份的最高分作为匹配分数。每人多视角入库后跨视角准确率通常能从 60% 左右抬到 80% 以上这还是在不换网络结构的前提下。我个人习惯在交付前再检查一遍三个容易被忽略的细节GEI 可视化图是否干净、准确率是否按条件分开统计、训练曲线是否平滑。这三样东西既是技术质量检查也是文档里展示专业度的核心素材。把条件准确率表、跨视角对比图、几个典型样本的 GEI 图放进文档里整套项目的完成度一眼就能看出来。希望这条从数据预处理到特征比对的完整链路能帮你少走几个月的弯路。本文还有配套的精品资源点击获取