ARTICLE DETAIL

资讯详情

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

遮挡视频行人重识别Python源码解析:从YOLO检测到ResNet特征提取

遮挡视频行人重识别Python源码解析:从YOLO检测到ResNet特征提取 简介这份深度学习毕设资源围绕遮挡场景下的视频行人重识别任务提供含GUI界面的完整Python工程适合计算机视觉方向本科生毕业设计与课程设计参考。系统覆盖视频帧提取与预处理、行人检测、基于预训练网络的特征提取、遮挡区域检测与注意力特征增强、特征匹配等核心链路资源共744个文件、约10.94MB主体为715张行人样本图片另含16个Python源码、6个XML配置、3个TXT说明及字体等工程文件目录结构清晰。目前已有196人学习下载。该工程并非单一算法脚本而是从视频导入到遮挡行人身份匹配的完整方案预处理模块负责缩放归一化检测模块定位行人重识别模块通过注意力机制抑制遮挡干扰读者可通过GUI界面选择本地视频直接观察遮挡检测与匹配结果对理解重识别难点、开展毕设实验、扩充系统功能均有参考价值。1. 遮挡视频行人重识别为什么这套 Python 源码值得拿来当毕设骨架遮挡视频行人重识别是一个比普通图像分类更能撑起毕设和课设的分量级选题。它的难点在于行人不是静态的、背景不是干净的、遮挡更是常态。纯理论讲一堆 Transformer 和注意力机制不如直接拿到一套能跑的 Python 源码把视频抽帧、行人检测、特征提取、遮挡处理、匹配识别这一整条链路跑通。这套源码自带 GUI 界面支持通过界面选择本地视频完成从视频输入到识别结果展示的完整闭环。适合正在做深度学习方向毕设的学生也适合想快速搭建一个人 ReID 演示系统的开发者——拿到手不会是一个黑匣子而是能改、能调、能写进论文的东西。2. 系统拆解从视频输入到重识别结果这条路是怎么连起来的拿到这套源码后第一步不是去读算法而是先看清楚整个系统的模块边界。行人重识别本质上是检测 特征提取 匹配三段式这套系统在此基础上加了两个关键模块视频前端的帧提取以及遮挡场景下的特征增强。2.1 模块划分与项目文件结构整个项目从结构上可以分为四层数据输入层、检测层、特征层、匹配展示层。数据输入层负责读取视频文件按帧率抽帧并做预处理检测层定位行人位置把行人从背景中切出来特征层提取裁剪后行人图像的特征向量并做遮挡感知的增强处理匹配展示层把特征向量与预注册的库做比对输出识别结果到 GUI 界面。先看一下项目里的关键文件。.gitignore 说明这是一个完整的 Git 工程reid_system.iml 是 PyCharm 的工程描述文件意味着直接用 PyCharm 打开即可识别项目结构。大量 .jpg 图片是行人样本图集这些图片在做特征注册、模型验证时非常有用——不需要额外去下载行人数据集就能把 demo 跑起来。在实际工程中模块拆分我一般会按如下方式组织reid_system/ ├── data/ # 视频文件与行人样本图 ├── models/ # 检测与特征提取的模型权重 ├── utils/ │ ├── video_utils.py # 视频读取与抽帧 │ ├── detector.py # YOLO 检测封装 │ ├── extractor.py # ResNet 特征提取 │ ├── occlusion.py # 遮挡检测与注意力处理 │ └── matcher.py # 特征匹配与距离计算 ├── gui/ │ ├── main_window.py # 主界面 │ └── result_panel.py # 识别结果展示 └── main.py这不是说压缩包里就是这个目录结构而是拆完这套系统后你完全可以按这个思路重组自己的代码。检测和特征提取属于计算密集模块单独抽成类方便后面在 GPU 和 CPU 之间切换。2.2 视频帧提取与图像预处理的参数选择视频抽帧是整个流程的起点这一步的质量直接影响后续检测和识别的效果。视频导入后系统通过 OpenCV 的 VideoCapture 来读取视频帧核心逻辑如下import cv2 def extract_frames(video_path, frame_interval5): cap cv2.VideoCapture(video_path) if not cap.isOpened(): raise IOError(f无法打开视频: {video_path}) frames [] frame_idx 0 while True: ret, frame cap.read() if not ret: break if frame_idx % frame_interval 0: frames.append(frame) frame_idx 1 cap.release() return framesframe_interval 控制每隔多少帧采样一次默认 5 表示每秒从 25 帧的视频里抽出约 5 帧。这个参数直接决定了检测的压力间隔太小连续帧重复计算推理速度跟不上间隔太大可能漏掉行人短暂出现的片段。对于行人重识别这种场景遮挡发生后行人消失在画面里的时间通常不超过 1 秒所以帧间隔尽量不要超过 10否则漏检率会明显上升。抽帧之后的预处理包括调整图像大小到统一尺寸、归一化像素值、必要时灰度化。检测模型输入尺寸一般设为 416×416 或 640×640特征提取网络则把裁剪后的小图统一缩放为 224×224。2.3 GUI 主循环与模块调用链GUI 的存在让这套系统从命令行工具变成了可演示的毕设作品。主界面通常包含三个区域视频选择区、识别结果展示区、控制按钮区。用户在界面上点击选择视频文件后系统依次调用抽帧 → 检测 → 特征提取 → 匹配最后把结果回填到界面上。调用链的设计有一个容易忽略的点抽帧和检测不能串行到底。视频时长 1 分钟抽 300 帧每帧都做检测和特征提取纯 CPU 推理要跑十几分钟用户在 GUI 上等不了。合理的做法是把检测和特征提取放进独立的处理线程GUI 主线程只负责更新进度和展示结果帧数据通过队列传递。代码层面通常是这样衔接的import threading import queue task_queue queue.Queue() result_queue queue.Queue() def process_video_task(video_path): frames extract_frames(video_path, frame_interval5) for frame in frames: task_queue.put(frame) task_queue.put(None) # 结束信号 def inference_worker(): while True: frame task_queue.get() if frame is None: break boxes detect_pedestrians(frame) features extract_features(frame, boxes) result_queue.put((boxes, features))process_video_task 在生产线程里只做视频读取不碰模型推理inference_worker 在消费线程里做检测和特征提取。两个线程之间通过 task_queue 解耦GUI 界面每隔一段时间轮询 result_queue 刷新显示。这种异步架构是实现视频边播边识别效果的关键很多第一次做这类的同学直接在一个循环里既读视频又跑模型界面必然卡死。3. 行人检测与特征提取YOLO ResNet 的工程化实现检测和特征提取是这套系统里计算量最大的两个环节。行人检测负责把人从背景里分离出来特征提取负责为每个行人生成一个稳定的身份向量。两个模块强耦合检测框的质量直接决定提取出的特征是否干净。3.1 YOLO 检测器配置、置信度阈值与推理检测部分选 YOLO 是常见实践。YOLO 系列在行人检测这种单类别目标上表现稳定推理速度快部署简单。在老一些的版本如 YOLOv3、YOLOv5中需要手动下载权重文件并配置 cfg当前主流版本用 ultralytics 库一行代码就能加载模型。这里以通用做法来拆解from ultralytics import YOLO def detect_pedestrians(frame, conf_threshold0.5): model YOLO(yolov8n.pt) results model(frame, classes[0], confconf_threshold, imgsz640) boxes [] for r in results: for box in r.boxes: x1, y1, x2, y2 map(int, box.xyxy[0].tolist()) score float(box.conf[0]) boxes.append((x1, y1, x2, y2, score)) return boxesclasses[0] 表示只检测 COCO 类别中的 person 类别这能过滤掉误检为其他类别的目标。conf_threshold 默认 0.5实际调试中我经常降到 0.3——视频场景里行人可能出现遮挡、姿态变化置信度阈值太高容易漏检但降得太低会有大量背景框混进来这个平衡点需要看具体视频场景。3.2 ResNet 特征提取器预训练权重与特征向量输出特征提取网路选预训练 ResNet 是成熟做法。ResNet 在 ImageNet 上预训练的权重具有通用的视觉表征能力不需要在行人数据上重新训练也能提取出区分度不错的特征。项目中的做法是把 ResNet 的最后一层全连接去掉输出一个固定长度的特征向量import torch import torchvision.models as models class FeatureExtractor(torch.nn.Module): def __init__(self, model_nameresnet50, feature_dim512): super().__init__() if model_name resnet50: backbone models.resnet50(pretrainedTrue) else: backbone models.resnet18(pretrainedTrue) self.features torch.nn.Sequential( *list(backbone.children())[:-2] ) self.global_pool torch.nn.AdaptiveAvgPool2d((1, 1)) self.fc torch.nn.Linear(2048, feature_dim) def forward(self, x): x self.features(x) x self.global_pool(x) x x.view(x.size(0), -1) return self.fc(x)去掉最后两层是因为 ResNet 最后两层是全局池化和分类全连接层前者压缩了空间信息后者把特征向量映射到 ImageNet 的 1000 个类别——这两层对行人重识别任务都没有用。自己接一个 Linear 层把维度压缩到 512一方面是降低匹配阶段的计算量另一方面是让输出向量维度可控。pretrained 参数建议保持 True即使是毕设 demo 也不要随机初始化权重。行人数据量不大从头训练 ResNet 50 收敛非常慢预训练权重能保证开箱即用。3.3 检测框裁剪与特征对齐检测到行人后需要把对应区域裁剪出来再送入特征提取网络。这里有一个很多新手会踩的细节直接按检测框坐标裁剪会把行人的边缘截断。YOLO 默认输出的检测框是紧贴目标的而行人重识别需要保留一定的边缘上下文否则特征提取时目标不完整。常见的做法是对检测框做 1.2 倍的扩边def crop_pedestrian(frame, box, expand_ratio0.2): x1, y1, x2, y2, _ box w, h x2 - x1, y2 - y1 dx, dy int(w * expand_ratio), int(h * expand_ratio) x1 max(0, x1 - dx) y1 max(0, y1 - dy) x2 min(frame.shape[1], x2 dx) y2 min(frame.shape[0], y2 dy) crop frame[y1:y2, x1:x2] crop cv2.resize(crop, (224, 224)) crop cv2.cvtColor(crop, cv2.COLOR_BGR2RGB) return cropexpand_ratio 取 0.2 表示上下左右各扩 20%这个经验值来自实践中 0.1 太紧、0.3 会引入过多背景的结论。裁剪后统一缩放到 224×224这是 ResNet 标准输入尺寸。最后一步 BGR 转 RGB 经常被忽略——OpenCV 读进来是 BGR 通道序而 ResNet 预训练权重是按 RGB 喂的通道顺序反了特征会差一大截而且这种错误不会报错只表现为识别精度异常低。4. 遮挡处理与特征匹配注意力机制是怎么被用进来的遮挡是行人重识别里最难的一环。行人被栏杆、车辆、其他人挡住一部分时整体特征会被污染直接拿全局特征去做匹配效果会断崖式下跌。这套系统的解决方案是先检测遮挡区域再用注意力机制强化未遮挡部分的特征贡献。4.1 遮挡检测与注意力加权遮挡检测的思路是判断行人图像中哪些区域是可靠的——通常用人体部位的可视比例来估算。最简单可行的方法是把裁剪后的行人图像横向切成三段头、躯干、腿部分别计算每个区域的局部特征响应强度响应低于阈值的段视为被遮挡。在代码实现中更通用的做法是对特征图做空间注意力加权class AttentionEnhance(torch.nn.Module): def __init__(self, in_channels, reduction16): super().__init__() self.avg_pool torch.nn.AdaptiveAvgPool2d((1, 1)) self.fc torch.nn.Sequential( torch.nn.Linear(in_channels, in_channels // reduction), torch.nn.ReLU(), torch.nn.Linear(in_channels // reduction, in_channels), torch.nn.Sigmoid() ) def forward(self, x): b, c, h, w x.size() y self.avg_pool(x).view(b, c) attn self.fc(y).view(b, c, 1, 1) return x * attn这段代码实现的是通道注意力通过全局平均池化把每个通道的特征图压缩成一个值经过两层全连接得到每个通道的重要性权重再用 Sigmoid 归一化到 0 到 1 之间最后对原始特征图逐通道加权。原理直观被遮挡区域的通道响应往往偏低注意力机制会自动压低它们对最终特征的贡献。4.2 特征匹配策略与距离阈值特征匹配阶段做的事情是把待识别的特征向量与预先注册的行人特征库做比对找到最相似的那一个。相似度度量常用余弦距离计算方式如下import numpy as np def cosine_similarity(feat_a, feat_b): feat_a feat_a / (np.linalg.norm(feat_a) 1e-8) feat_b feat_b / (np.linalg.norm(feat_b) 1e-8) return float(np.dot(feat_a, feat_b)) def match_person(query_feat, gallery, threshold0.6): best_score -1 best_id None for person_id, gallery_feat in gallery.items(): score cosine_similarity(query_feat, gallery_feat) if score best_score: best_score score best_id person_id if best_score threshold: return None, best_score return best_id, best_score这里的核心是 threshold。0.6 意味着余弦相似度至少要达到 0.6 才认为匹配成功低于这个值就判定为未注册行人。阈值太小会把陌生人误判成库中的人阈值太大会把同一人因遮挡、姿态变化判成陌生人。实际使用中建议先跑 10 到 20 个视频片段统计同类和异类的相似度分布再在中间取分界值。做毕设时这个分析还能作为论文的支撑数据。4.3 GUI 展示层识别结果的可视化GUI 层是整个系统的门面也直接决定答辩演示的效果。界面上视频播放区域用来实时显示检测结果——每个行人画一个检测框框顶标注识别到的 ID未匹配的行人标为Unknown。主要界面逻辑可参考这样的结构import tkinter as tk from tkinter import filedialog, Label, Button, Frame import cv2 from PIL import Image, ImageTk class ReIDApp: def __init__(self, root): self.root root self.video_path None self.current_frame None self.panel Label(root) self.panel.pack() Button(root, text选择视频, commandself.load_video).pack() def load_video(self): self.video_path filedialog.askopenfilename( filetypes[(Video files, *.mp4 *.avi *.mov)] ) self.run_reid() def run_reid(self): cap cv2.VideoCapture(self.video_path) while cap.isOpened(): ret, frame cap.read() if not ret: break boxes detect_pedestrians(frame) self.draw_boxes(frame, boxes) # 转换并刷新界面 img cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) img_pil Image.fromarray(img) img_tk ImageTk.PhotoImage(img_pil) self.panel.configure(imageimg_tk) self.panel.image img_tk self.root.update()为什么用 tkinter 而不用 PyQt毕设场景下 tkinter 是 Python 标准库答辩演示环境不需要额外装 PyQt 的依赖降低部署风险。原压缩包中的项目我倾向于认为也是类似的内置方案因为源码 zip 内没有外置库描述文件。GUI 刷新用 self.root.update() 强制刷新事件循环这样视频的每一帧都能实时画出来。5. 避坑指南这套系统里最容易翻车的五个问题这个项目跑通不难但想稳定地出结果有几个坑必须先趟一遍。以下是我在拆这套源码时实际遇到的问题和排查思路每一条都按现象 → 原因 → 解决来写。5.1 视频打开后界面黑屏但程序未崩溃现象GUI 界面正常启动点击选择视频后界面黑屏控制台无报错程序也没退出。原因VideoCapture 打开视频失败时不会抛异常而是返回一个空的 cap 对象循环一直读不到有效帧。常见原因有两个视频路径中包含中文字符OpenCV 在某些平台的旧版本不支持中文路径或者是视频编码格式不为 OpenCV 所兼容。解决在 extract_frames 开头加上 isOpened 判断失败时弹窗提示。路径中文问题可以用 cv2.VideoCapture 前把路径做一次编码转换或者直接要求视频文件名改为英文。5.2 检测框大量错位或重复现象视频的画面上检测框忽大忽小同一位置出现多个重叠框框住的区域明显不是完整行人。原因YOLO 的推理尺寸和输入帧尺寸不一致导致检测框坐标换算错误。另一个常见原因是 nms非极大值抑制的阈值设置太宽松YOLOv8 默认的 iou 阈值对行人这种密集场景不够严格。解决推理前把帧统一缩放到 imgsz 参数指定的尺寸推理后再按原图尺寸比例还原坐标。同时检查是否对帧做了 letterbox 填充——如果原图不是正方形YOLO 推理时会在外侧补灰边补边的坐标必须参与还原计算否则框就是歪的。处理方式是直接绕开 letterbox按比例缩放后把输入尺寸填充到 640 的倍数并在还原时记录 scale 比例。5.3 特征提取结果乱成一团同一人相似度反而低于不同人现象识别结果惨不忍睹同一行人在连续帧中的特征相似度只有 0.4而不同两个人的相似度却到了 0.7。原因这种颠倒现象十有八九是预处理环节出问题最常见的是 BGR 和 RGB 通道序搞混。其次是 ResNet 的输入归一化没做对——预训练模型要求的 mean 和 std 是 [0.485, 0.456, 0.406] 这一组 ImageNet 统计值如果直接传 0 到 255 的原始像素或者用了其他归一化参数特征分布就乱了。解决在特征提取入口统一做一次预处理def preprocess_crop(crop): crop cv2.cvtColor(crop, cv2.COLOR_BGR2RGB) crop crop.astype(np.float32) / 255.0 mean np.array([0.485, 0.456, 0.406]) std np.array([0.229, 0.224, 0.225]) crop (crop - mean) / std crop np.transpose(crop, (2, 0, 1)) return torch.tensor(crop, dtypetorch.float32).unsqueeze(0)做完之后再跑一遍相似度对比如果数值恢复到同人 0.8 以上说明之前就是归一化的问题。5.4 GPU 显存不足直接 OutOfMemory 崩溃现象程序检跑不到 20 帧就报 CUDA out of memory视频窗口卡死。原因推理时每次送入的 batch 太大或者视频帧经过检测后产生大量行人裁剪图前一帧的特征提取还没结束后一帧的数据已经进了显存。此外PyTorch 默认会为每个显存操作分配缓存长时间运行不断累积。解决先降 batch。不要每帧都做批量推理可以把检测到的 5 到 10 个行人裁剪图合并成一个 batch 一次前向计算而不是每个行人单独调用一次模型。显存紧张时限制 torch.cuda 的内存分配torch.cuda.set_per_process_memory_fraction(0.8)这个设置把单进程可用显存限制在 80%剩余的留给 GUI 渲染和其他开销程序运行时算得慢一点但不至于崩。如果只剩 CPU也可以把模型切到 CPU 跑帧间隔放到 10用 CPU 推理展示 demo 足够用了。5.5 相似度阈值调来调去结果很不稳定现象阈值设 0.5 时误检多改成 0.7 时漏检多似乎有什么隐形的因素在影响相似度数值。原因特征向量没有归一化。ResNet 输出的特征向量直接做余弦相似度时不同行人向量本身的模长差异会影响相似度分布。另外gallery 里注册的特征如果来自不同时间、不同光照条件分布本身就比理想情况散得多单一全局阈值确实难以稳定。解决匹配前对特征向量做 L2 归一化再用向量点积代替原始余弦计算。同时不要只取最高相似度一个候选保留 Top-3 候选当最高分和次高分非常接近差值小于 0.05时判为不确定在界面中标记为需人工确认。这个 Top-K 策略在论文里也很好展开能直接把算法层的工作量化出对比结果。6. 验证方法与调参习惯跑通之后把精度往上提的实操动作系统跑通只是第一步。要拿去答辩或者作为课设提交至少要做一次系统的定量验证这个验证过程本身也是论文的实验章节素材。我做这套验证时习惯分三步走。第一步是构建一个 mini gallery从视频中手动选出每个行人的 3 到 5 张清晰正样本提取特征存入库中。第二步是取视频中不同时间段、包含不同程度遮挡的片段作为查询集跑一遍匹配记录每个查询的匹配结果和相似度分数。第三步是画出相似度得分的分布图——同一人的得分分布和不同人的得分分布如果有明显分界说明特征区分度足够如果两个分布大面积重叠说明特征提取环节还有问题。调参方向上优先动两个旋钮而不是盲目改模型。第一个是 frame_interval把 5 改成 3 或 8观察漏检率和识别率的权衡第二个是检测置信度阈值在 0.3 到 0.6 之间扫描每次记录匹配准确率。这两个参数是性价比最高的调优点改动一行代码就能见效。完成以上验证后再考虑是否引入更复杂的遮挡感知策略——例如把通道注意力替换成空间注意力或者叠加一个姿态估计分支来判断哪些部位可见。从那以后我每次拿到重识别项目都强制先做一次 10 个视频片段的 baseline 测试记录原始准确率再动手调参绝不凭感觉直接改模型结构。这个习惯帮我避开了很多自我感觉良好但实际没有提升的无效优化也建议你保留一份这样的 base 记录毕设答辩时这就是实验对比的原始依据。希望这套代码能帮你在行人重识别这条路上少走一段弯路。本文还有配套的精品资源点击获取
返回列表