
简介一套面向深度学习毕设/课设的Python源码聚焦遮挡视频中的行人重识别任务并自带GUI界面。系统完整覆盖视频帧提取、行人检测、特征提取以及利用注意力机制处理遮挡区域并完成特征匹配既可用于算法验证也便于二次开发。压缩包共744个文件总大小10.94MB其中715张jpg图片构成行人重识别样本集16个py文件承载系统主程序及各功能模块另有6个xml、3个txt、1个ttf及iml、out等文件负责工程配置与辅助说明。当前已有196人浏览学习适合需要快速搭建行人重识别演示系统的开发者参考。借助这套代码可系统学习从视频输入到识别结果展示的完整管线理解遮挡场景下注意力特征增强的工程实现GUI界面支持直接导入视频测试为毕设答辩或课程汇报提供可运行的代码基础。1. 遮挡视频行人重识别的痛点这套源码把哪几层补上了监控视频里行人互相遮挡、被栏杆或车辆挡住是常态传统行人重识别Re-ID模型在完整人体图上表现不错一旦遮挡比例超过 20%特征匹配的相似度会急剧下降跨摄像头检索基本失效。这个毕设源码给了一套完整的参考实现视频文件导入、按帧率抽帧、图像预处理、YOLO 系列检测、ResNet/DenseNet 特征提取、遮挡区域检测与注意力增强、特征匹配以及 PyQt5 封装的 GUI 操作界面。对准备做深度学习毕设或课设的人来说它不是单纯跑通一个模型而是把检测 特征 遮挡 检索这一条实际工程链路串起来而且每个环节都能看到可改的源码方便在答辩时讲清楚每一个模块为什么这么设计。2. 行人检测与特征提取的选型逻辑YOLO 与 ResNet 的搭配直接跑这套源码之前建议先把两个底层选型搞明白检测器为什么选 YOLO 而不是 Faster R-CNN特征提取为什么用 ResNet50。这两步决定后面的整体精度和速度也是答辩时最容易被抓着问的点。2.1 检测环节YOLO 与 Faster R-CNN 的取舍视频行人重识别面对的是连续帧检测器需要满足两个要求一是能定位出行人的包围框二是不能拖慢整体处理速度。YOLO 系列把候选框生成和分类回归放进同一个网络一次前向就能输出所有检测结果在视频场景下优势很明显。Faster R-CNN 精度上限通常更高尤其对小目标、遮挡严重的目标召回率更好但两阶段结构导致推理速度偏低实时性不够。检测器速度小目标召回遮挡场景适用环节YOLOv5s高中中视频帧逐帧检测Faster R-CNN低高高关键帧精细检测YOLOv8s较高中高中高需要较高精度的视频推理源码里默认可以替换 YOLO 权重文件实际使用中我会优先选 YOLOv5s 或 YOLOv8s因为视频抽帧后往往有几百到几千张图单帧检测时间需要控制在几十毫秒内否则 GUI 操作会明显卡顿。如果检测目标偏小比如画面里行人只有几十个像素的宽高可以切到 YOLOv8s 并加大输入分辨率或者退回 Faster R-CNN 对关键帧做二次确认。这里不要一条路走到黑建议先跑一版观察哪些行人是漏检的再决定是否换模型。import cv2 import torch def detect_pedestrians(frame, model, device): # 预处理BGR转RGBresize到模型输入尺寸 img cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) img cv2.resize(img, (640, 640)) img_tensor torch.from_numpy(img).permute(2, 0, 1).float().div(255.0) img_tensor img_tensor.unsqueeze(0).to(device) results model(img_tensor) # 解析YOLO输出的boxes, scores, class_ids boxes results.xyxy[0][:, :4].cpu().numpy() scores results.xyxy[0][:, 4].cpu().numpy() class_ids results.xyxy[0][:, 5].cpu().numpy() person_boxes [] for box, score, cls in zip(boxes, scores, class_ids): if int(cls) 0 and score 0.5: # COCO类别0为person person_boxes.append(box.astype(int)) return person_boxes这段代码把检测逻辑拆成单独函数results.xyxy是 YOLOv5 输出格式每一行是左、上、右、下边界与置信度。加score 0.5是过滤低置信度框避免把背景误检成行人。COCO 类别 ID 0 表示 person所以类别筛选很关键不然会把车、人等混在一起。实际运行前建议先打印results的结构不同 YOLO 版本输出格式可能不同YOLOv8 就改成了torch.Tensor而不是Detection对象。2.2 特征提取ResNet50 为什么是默认主力检测出行人包围框后下一步是把裁剪出的行人图编码成一个固定长度的特征向量。ResNet50 在这类任务里是最常见的骨架网络原因有三点一是 ImageNet 预训练权重容易获取二次训练收敛快二是残差结构能在网络加深时不至于梯度消失训练比较稳定三是去掉最后的全连接层后输出是 2048 维特征向量很多 Re-ID 头网络都是在这个维度上设计的。DenseNet 与 ResNet 的区别在于它把每一层特征都传给后面所有层特征复用更充分理论上有更强的表达能力但显存消耗会更高。源码里如果切换 backbone不只改模型结构还要注意全连接层维度可能变化。为了后续匹配稳定我一般会在 ResNet50 后面再加一个 BNNeck 层把全局特征和局部特征分开处理这样在遮挡场景下能保留一部分局部判别力。import torch.nn as nn from torchvision import models class FeatureExtractor(nn.Module): def __init__(self, backboneresnet50, feat_dim2048): super().__init__() if backbone resnet50: base models.resnet50(pretrainedTrue) self.features nn.Sequential(*list(base.children())[:-2]) else: raise ValueError(fUnsupported backbone: {backbone}) self.feat_dim feat_dim self.bnneck nn.BatchNorm1d(feat_dim, affineFalse) def forward(self, x): # x: (B, 3, H, W) 行人图像 x self.features(x) x nn.functional.adaptive_avg_pool2d(x, (1, 1)) x x.view(x.size(0), -1) return self.bnneck(x)这里把base.children()的后两层去掉只保留特征提取阶段adaptive_avg_pool2d把任意大小的特征图压缩到 1×1避免输入尺寸不一致导致维度不匹配。bnneck用 BatchNorm 把特征拉回近似标准正态分布能让后续余弦相似度计算更稳定。实际调试时要注意pretrainedTrue会从网络下载权重如果网络不可用需要提前下载后改成本地路径否则会在环境搭建阶段卡住。2.3 从视频到特征向量的完整管道源码里视频处理的整体流程是GUI 选择视频文件OpenCV 按原始帧率读取每 N 帧取一帧做检测检测到的行人图缩放为 256×128 的输入尺寸然后送入特征提取网络。为什么是 256×128这是行人 reid 领域一个约定俗成的尺寸与 Market1501 数据集一致能保留行人整体的长宽比又不会让特征图过小丢失细节。video_path test_video.mp4 cap cv2.VideoCapture(video_path) fps cap.get(cv2.CAP_PROP_FPS) frame_interval int(fps // 5) # 每秒处理5帧 frame_idx 0 while True: ret, frame cap.read() if not ret: break if frame_idx % frame_interval 0: boxes detect_pedestrians(frame, detector, device) for box in boxes: x1, y1, x2, y2 box person_img frame[y1:y2, x1:x2] person_img cv2.resize(person_img, (128, 256)) # person_img转tensor并输入FeatureExtractor feat feature_extractor(preprocess(person_img)) person_features.append((box, feat)) frame_idx 1frame_interval是抽帧间隔fps // 5表示每秒处理 5 帧这是平衡精度和计算量的常用做法。如果视频里有快速行走的行人相邻帧位移很大5 帧处理一次可能漏掉某些中间位置可以改成每秒 10 帧如果视频较长建议降低到每秒 2 帧否则特征文件会非常大。处理时还要保证每个行人只在被检测到的帧里提取一次特征避免对同一个行人连续提取十几条相似特征影响后续匹配排序。3. 遮挡处理与特征增强的落地实现行人被遮挡时整体特征会被污染比如衣服的颜色被货架遮挡模型看到的是货架颜色而不是行人衣服颜色。这一节重点看源码里如何处理遮挡先检测遮挡区域再对特征做增强让没有被遮挡的局部特征发挥更大作用。3.1 遮挡检测用注意力掩码定位受影响区域遮挡检测常见做法有两种一种是单独加载一个分割模型输出行人 mask与检测框做减法得到遮挡区域另一种是根据特征响应的变化来判断用注意力图定位哪些区域是不可信的。源码里比较轻量的是第二种在 ResNet50 输出的特征图上计算空间注意力注意力分数低的区域就是模型判断为不可信或背景的部分这些部分对应的特征响应会被弱化。def attention_mask(feature_map): # feature_map: (B, C, H, W) avg_pool torch.mean(feature_map, dim1, keepdimTrue) # (B,1,H,W) max_pool torch.max(feature_map, dim1, keepdimTrue)[0] # (B,1,H,W) attention torch.sigmoid(avg_pool max_pool) return attention这个注意力计算思路来自 CBAM平均池化和最大池化分别捕捉全局响应和最强响应相加后用 sigmoid 映射到 0 到 1。在遮挡区域特征响应通常较低所以attention值也会相对偏低。得到注意力图后特征图与它逐元素相乘再池化输出特征相当于主动降低遮挡区域的影响。这个步骤不要加太深嵌套否则容易把非遮挡区域的判别特征一起削弱。3.2 局部部件注意力增强把行人分成几块看单纯全局注意力还不够因为遮挡往往只影响某个局部比如上半身或腿部。更有效的方法是把行人高度方向切成若干部件对每个部件单独计算注意力最后把部件特征拼接起来。这个思路来自 PCB 和 MGN 等经典 Re-ID 方法。源码里常见的配置是把特征图水平切 6 块每块单独池化再通过1×1卷积降维到 256 维最后拼接成一个 1536 维的特征向量。参数取值说明部件数量6水平均分较多部件能捕捉更细粒度特征部件特征维度256降维后便于拼接与匹配全局特征维度2048保留原始全局信息拼接后维度2048 6×256 3584匹配时可选全局或拼接特征部件数量不是越多越好。切到 7 块以上时每块包含的行人区域太窄遮挡后几乎整块都不可靠反而引入更多噪声。我一般会从 6 开始跑如果发现明显被遮挡的行人检索不到再尝试把部件数量降到 4让每块区域更大、语义更完整。def part_based_feature(feature_map, parts6): B, C, H, W feature_map.shape part_feats [] for i in range(parts): part_h H // parts part feature_map[:, :, i*part_h:(i1)*part_h, :] pooled nn.functional.adaptive_avg_pool2d(part, (1, 1)) part_feats.append(pooled.view(B, -1)) return torch.cat(part_feats, dim1)这段代码按高度方向把特征图均分每个part单独做全局平均池化得到该区域的局部特征。torch.cat之后得到的是一个拼接向量后续配合注意力权重做加权融合。注意这里不能直接对原始图像切块因为原始图像像素没有经过语义特征提取切出来的是低层信息效果远不如在特征图上切块。3.3 特征增强的调试方法加入遮挡处理模块后不能只看最终 mAP 涨了没有还要看具体故障样例。我建议在 GUI 里同时显示原始检测框、注意力图像、最终特征向量对应的关键响应区域。如果某一行人被遮挡后检索排名反而下降优先检查是不是注意力把不该抑制的特征也抑制了比如行人上衣颜色恰好与背景颜色相近时网络可能误判整个上半身都是遮挡区域。此时可以调高attention的阈值或者把注意力与局部部件特征做加权全局特征权重 0.6部件局部特征权重 0.4效果往往比直接用拼接全连接更可控。4. 特征匹配与 GUI 界面实战到了这一步系统已经能把视频转成行人特征向量集合剩下的工作是对比查询目标与已知库中所有行人特征的相似度并把结果展示到界面上。这章给出可执行的匹配代码以及 GUI 模块的实际操作流程。4.1 余弦相似度匹配与排序行人重识别最常用的度量方式是余弦相似度因为它只关心两个向量的方向差异对特征向量的长度不敏感能适应不同相机下亮度、对比度造成的整体偏移。import numpy as np from numpy.linalg import norm def match_person(query_feat, gallery_feats, topk10): similarities [] for feat in gallery_feats: sim np.dot(query_feat, feat) / (norm(query_feat) * norm(feat) 1e-6) similarities.append(sim) similarities np.array(similarities) top_index np.argsort(-similarities)[:topk] return top_index, similarities[top_index]1e-6是防止两个特征向量同时为零向量导致除零错误。argsort(-similarities)把相似度从大到小排序取前topk个结果。实际使用中query_feat 可能是某一次 GUI 框中选中的行人gallery_feats 是提前预处理好的所有视频行人特征。若库里特征是 PyTorch 张量需要先.cpu().numpy()转成 numpy 数组否则np.dot会报类型错误。匹配效果不理想时有几个快速验证点一是检查 gallery 里是否存在多个人相同 ID 的帧重复重复帧会把真实匹配排到很后面二是看 query 特征与 gallery 特征是否都经过同一套预处理比如训练时做了归一化推理时没做余弦值会整体偏低三是遮挡行人被误检成两个框时需要用非极大值抑制NMS合并同一行人的重复框。4.2 基于 PyQt5 的 GUI 交互逻辑GUI 是毕设答辩的加分项源码里用 PyQt5 实现了一个可选视频文件、显示检测结果、点击行人框触发重识别、展示检索榜单的界面。核心交互是主界面显示视频当前帧帧上画出每个行人检测框用户用鼠标点击某个框程序弹出该行人的特征匹配结果右侧按相似度递减显示 top8 行人图。# gui_demo.py 片段 class ReIDWindow(QMainWindow): def __init__(self): super().__init__() self.video_btn QPushButton(导入视频, self) self.video_btn.clicked.connect(self.load_video) self.video_label QLabel(self) self.result_list QListWidget(self) def load_video(self): path, _ QFileDialog.getOpenFileName(self, 选择视频, , Video Files (*.mp4 *.avi)) if path: self.video_path path self.reid_engine ReIDEngine(path) self.show_next_frame() def mouse_press_event(self, event): # 将鼠标坐标转换为原图像坐标再判断落在哪个检测框内 box self.get_box_at_point(event.pos()) if box: query_feat self.reid_engine.extract_feature(box) top_index, scores match_person(query_feat, self.reid_engine.gallery_features) self.show_results(top_index, scores)mouse_press_event是让我最头疼的一个环节界面缩放后鼠标坐标要与原图坐标对应否则点击命中不了检测框。常见做法是记录 QLabel 显示图像与原始图像的缩放比例然后用event.pos().x() / scale_x换算。ReIDEngine类需要封装整个检测和特征提取流程避免在 GUI 事件循环里直接放模型推理否则界面会卡死好几秒。可以把匹配计算放到线程里主线程只接收结果并刷新 UI。4.3 环境依赖与安装避坑要把这套源码跑起来环境版本是关键。torch与torchvision版本不匹配是最常见的启动失败原因比如 torch 2.0 搭配 torchvision 0.15 才正常如果用 0.16 就可能出现cannot import name nms from torchvision这类错误。建议先固定版本再安装其他依赖。pip install torch2.0.1 torchvision0.15.2 pip install opencv-python4.8.1.78 pip install pyqt55.15.9 pip install numpy1.24.3opencv的版本也需要注意4.8 以上对某些老视频编码格式支持不好如果VideoCapture打开.avi失败且cap.isOpened()返回 False可以考虑转成.mp4再导入。GUI 测试时可以先用一个小视频长度 30 秒到 1 分钟这样也能避免第一次运行抽帧太多导致内存报错。5. 在毕设基础上的四个有效改进方向这套源码已经能跑通整个流程但距离一篇有创新点的毕设还有一段距离。下面给出四个可以直接在源码上动手改的方向按实施难度排序。5.1 把随机擦除增强和数据增强结合遮挡场景训练时最直接的提升方式是随机擦除Random Erasing。在输入行人图送入网络前随机选一个矩形区域用均值像素覆盖模拟真实遮挡。源码若只做了 resize 和归一化可以加入这一步每次训练迭代以一定概率对行人图做擦除。这个概率建议设置在 0.5 到 0.7 之间太高容易把行人面部等关键区域完全擦除模型反而学不到有效特征。5.2 在特征匹配后增加重排序直接按余弦相似度排序的结果只考虑单次特征计算会漏掉样本之间的近邻关系。建议加入简单的 K-reciprocal 重排序先初步得到 top50 近邻再互相验证两个样本是否同时出现在对方的近邻列表中最后重新计算排序分。这种后处理方法不需要重新训练模型只需在 GUI 匹配环节加一个后处理函数就能让 top10 命中率明显提升。计算量也不大因为库中特征量级通常在几千到几万之间Python 矩阵操作可以几秒内完成。5.3 把全局特征改为全局加局部特征联合检索第四章讲到的部件特征拼接是一个轻量改进点。当前源码如果没有局部特征可以只在特征提取模块加一个分支全局特征池化后保留 2048 维另外把特征图切为水平 4 块每块单独池化得到 1024 维最终拼接成 3072 维向量。匹配时设置全局特征权重为 0.7、局部特征权重为 0.3再用加权余弦相似度排序。这样在遮挡情况下至少有一个局部特征是可靠的防止整体特征“失明”。5.4 用 Kalman 跟踪减少重复提取视频中同一行人会连续出现在多个帧里如果不加跟踪同一个行人会被重复注册到 gallery导致匹配时一个 ID 占多个名次严重影响真实匹配的排名。常见做法是给每个检测框维护一个轻量级 IoU 匹配用两个连续帧之间的交并比判断是否是同一目标。IoU 阈值一般取 0.3小于 0.3 就认为是新目标或发生遮挡离开大于 0.5 可以直接合并。对遮挡场景还可以用三分法的局部特征做跟踪关联减少因遮挡造成的检测框跳变。改进完成后用 Market1501 或自建视频片段计算 mAP 和 CMC Top1记录每个模块开关前后的指标变化这部分实验曲线放到论文中会比泛泛的流程图更有说服力。本文还有配套的精品资源点击获取