简介一份面向本科毕业设计场景的行人重识别Person Re-IDPython源码项目基于深度学习实现完整可运行的跨摄像头目标检索系统流程覆盖数据加载与管理、ResNet特征提取、损失函数计算、距离度量、训练评估等核心环节适用于智慧安防、校园监控、新零售等人物检索场景也适合计算机、人工智能、数据科学等专业学生用于毕设类比、课程设计、大作业或初期项目演示。压缩包共26个文件以22个py源码文件为绝对主体另有2个文本说明与1个README文档整体仅38KB体量轻、模块边界清晰data_manager与dataset_loader负责数据组织ResNet与losses构成网络及优化目标optimizers、train_class和eval_metrics将训练评估链路完整衔接便于逐段阅读和局部修改。已有355人学习下载解压后建议将项目重命名为英文路径再运行即可直接验证整体流程基础较好者可进一步替换数据集、调整骨干网络或改进损失策略把项目作为二次开发起点。1. 行人重识别毕设 zip 下载之后先别急着训练刚拿到一个名为“本科毕业设计-基于深度学习的行人重识别系统python源码.zip”的压缩包大家的第一反应往往是解压、装依赖、跑训练。但这类源码包真正难的不是跑通而是跑完一轮之后你能把它讲清楚。行人重识别Person ReID的目标是跨摄像头、无重叠视野地找回同一行人系统用深度学习网络把图像映射成特征向量再用度量学习让同一行人靠近、不同行人远离。一个完整的 ReID python 源码通常应该覆盖数据处理、模型搭建、训练损失、评测指标和演示五个环节只有模型和 demo 两个文件的严格说只是展示版。适合正在做毕业设计、复现论文或者想把开源 ReID 改到自己数据集上的人。在双击解压前先想明白你要的是一个能交差的 demo还是一套能继续改的框架后面所有选择都会不一样。2. 拆 zip 建环境把 ReID 源码包变成可运行 Demo 的最小路径如果你是从吴恩达深度学习课后题或《动手学深度学习》入门的第一次拆开这种 ReID 源码包很可能蒙圈看不到熟悉的 MNIST只有一堆model.py、train.py和配置文件。别急先按下面三步做最小 Demo 能很快跑起来。2.1 解压前先做目录体检别把源码包当黑匣子我拿到任何一个深度学习毕业设计压缩包都不会直接双击解压更不会急着执行python train.py。先做目录体检再搭隔离环境最后才执行代码。源码包是 zip里面可能带训练脚本、权重文件、数据集子集甚至隐藏目录。盲目解压并把路径加到 PATH小则覆盖同名文件大则误跑不干净的代码。所以第一步只看不解压# 用系统自带 tar 命令也能看 zip 内容 tar -tf 本科毕业设计-基于深度学习的行人重识别系统python源码.zip | head -n 60 # Linux/macOS 也可以 # unzip -l 本科毕业设计-基于深度学习的行人重识别系统python源码.zip | head -n 60tar -tf在 Windows 10/11 上也可以用和 unzip 的结果一样只读文件清单不会执行任何代码。我会重点关注几个特征有没有train.py、eval.py、demo.py、requirements.txt、configs目录、checkpoints目录。如果都齐全这是一套标准的 PyTorch ReID 工程如果只有model.py和infer.py说明训练部分被裁剪了复现论文指标会比较吃力。还要看压缩包根目录是否有隐藏文件、超大文件、以及各文件大小。.pth文件几十到几百 MB 是正常的如果权重只有几 KB那多半是个占位文件后面加载会翻车。有些源码包会带 README.md里面写了数据集下载地址和 Python 版本但 README 不一定和代码同步里面的 Python 版本范围值得先看一眼别把 Python 3.6 时代的老项目硬放到 3.10 上跑。2.2 数据、模型、度量跑通 ReID 前必须理解的三个模块ReID 的训练代码和图像分类只有外观相似。分类任务输出类别概率ReID 输出一个特征向量再在向量空间做检索。源码包里这三个模块分别对应数据加载器决定给模型看什么模型决定如何把图片编码成向量损失函数决定向量怎么排布。数据这一环ReID 不只需要行人 ID还需要摄像头 ID。同一个行人被摄像头 1 和摄像头 2 拍到在测试时如果候选图库混入同一摄像头画面的图模型能靠背景把分数拉高指标虚高但不代表真的跨镜头识别。好的数据加载器会把(image_path, person_id, cam_id)三个值作为一个样本返回。模型这一环常见骨干是 ResNet50。ResNet50 在 ImageNet 上预训练后末尾的分类层不会被直接使用工程师通常只保留到global_feature再接一个 embedding 层把 2048 维压到 512 或 256 维。训练时在 embedding 后面临时挂一个 ID 分类头参与训练推理时把这个分类头丢掉只取 embedding。你在源码里找embedding_dim、num_features、global_feature这些关键词就能定位它在哪一行。损失这一环最小配置是 ID 分类损失加 Triplet Loss。只用 Softmax 训练出的向量也能区分 ID但类内距离没有被显式压缩跨镜头泛化弱Triplet Loss 直接拉近同一个人的不同图片、推远不同人是 ReID 的标配之一。评判源码好不好先看它有没有在数据层保留 cam_id再看训练损失里有没有 triplet。如果两个都没有那它不是真正的 ReID 系统只是个行人分类器。2.3 最小环境命令先装 CPU 版 PyTorch 再做权重体检如果压缩包里没有 requirements.txt或者 requirements 因为是旧项目直接装不上我一般会这样搭最小环境python -m venv .venv-reid source .venv-reid/bin/activate python -m pip install --upgrade pip setuptools wheel pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu pip install opencv-python numpy scipy tqdm pyyaml pandas先把 torch 装成 CPU 版不是为了省流量是为了避开 CUDA 版本错配。cu118、cu121这些版本的包需要匹配驱动一旦装错训练时会出现undefined symbol或马上 OOM。先 CPU 跑通 demo再回头装 GPU 版是最省时间的方式。训练前把pip list输出和 requirements 对比一遍尤其关注torch、torchvision、opencv-python三个版本。接下来对权重文件做一次只读体检确认 checkpoint 里到底有什么import torch ckpt torch.load(checkpoint.pth, map_locationcpu, weights_onlyFalse) print(type(ckpt), list(ckpt.keys())) if state_dict in ckpt: print(state_dict layers:, len(ckpt[state_dict]))weights_onlyFalse是为了兼容旧式 pickle 保存的权重。torch.load底层会反序列化对象对来路不明的文件有风险只在你自己下载、已经看过目录的源码包上使用。如果当前 torch 版本较老不支持weights_only参数把这个参数去掉即可。跑完这段就能确认权重是完整 state_dict还是只有一个空壳。如果是epoch/loss/acc组合那是训练状态如果只有model说明没有保存优化器状态中断后要从上一个完整 epoch 重新练。检查完 checkpoint最后一步是执行一个能看到参数列表的命令。常见的有 argparse 的--helpYACS 的--cfg如果脚本连--help都没有直接看configs/下的 yaml 也行。我一般不会直接跑完整 train而是先跑python train.py --help确认参数名是否和 README 一致不一致时以--help为准因为它读的是当前代码。3. 用 Market1501 训练出一套 ReID数据布局与三个关键参数把环境跑通只是开始。真正动手训练时数据集和超参数决定结果能不能看。我一般先不用复杂私有数据而是拿 Market1501 这种公开基准试水它规模小、对比论文多、指标好复现。3.1 Market1501 目录布局与行人 ID 命名规则Market1501 解压后的目录无论放在哪里都要保持以下结构Market-1501-v15.09.15/ ├── bounding_box_train/ ├── bounding_box_test/ ├── query/ └── gt_query/bounding_box_train是训练集bounding_box_test是候选图库query是待查询照片gt_query里是正确答案。query和bounding_box_test属于同一批测试行人但拍摄场景有重叠也有独立评估时query只在bounding_box_test中找同 person_id。文件命名是 ReID 数据集的隐藏知识。例如0001_c1s1_001051_00.jpg其中0001是行人 IDc1是摄像头 1s1是场景001051是原始帧号。用一段非常短的 Python 可以解析from pathlib import Path root Path(Market-1501-v15.09.15) def parse_name(name: str): pid int(name.split(_)[0]) # 行人 ID cam int(name.split(c)[1][0]) # 摄像头编号 return pid, cam for split in (bounding_box_train, query, bounding_box_test): imgs sorted((root / split).glob(*.jpg)) if imgs: pid, cam parse_name(imgs[0].name) print(split, 图片数:, len(imgs), 示例 PID:, pid, CAM:, cam)name.split(c)[1][0]能取到c1s1中的1是因为字符串中第一个c是渠道代号。如果换用别的数据集这个解析逻辑要单独写。把 PID 和 CAM 拆出来才能做两件事训练时按 PID 采样评估时把同 CAM 的干扰项过滤掉。3.2 三个关键参数骨干、批次采样、分辨率与损失权重以下是 ReID 源码里最常调的参数第一个是骨干网络第二个是 batch 内采样方式第三个是输入分辨率它们直接决定显存和指标。参数常见取值理由骨干网络resnet50ImageNet 预训练好找输出特征够强比 resnet101 小比 mobilenet 高输入分辨率256x128行人图像长宽比约 2:1和原图形状接近不容易被拉伸batch size 与采样64每个 ID 取 4 张图保证一个 batch 内既有正样本又有负样本triplet 才能计算triplet margin0.3Margin 太小难区分太大难收敛常用 0.3 起步学习率Adam 0.00035 或 SGD 0.01ReID 微调预训练模型常用小学习率那个“每个 ID 取 4 张图”是关键。普通DataLoader随机抽 64 张图很可能一个 batch 里只有两三个人triplet 根本没有正样本对。需要自定义RandomIdentitySampler先随机抽 16 个 ID再对每个 ID 抽 4 张图凑成 64。源码里一般叫num_instances或samples_per_id没有这个参数的训练脚本ReID 指标会掉一大截。对应启动命令长这样python train.py \ --config configs/market1501.yaml \ --arch resnet50 \ --height 256 --width 128 \ --batch-size 64 --num-instances 4 \ --lr 0.00035 --max-epoch 60 \ --step-size 20--step-size 20表示每 20 个 epoch 把学习率乘一次系数--max-epoch 60是常见配置在单卡 1080Ti 上约几小时。如果只有 CPU先把--batch-size降到 16、--max-epoch降到 20哪怕 mAP 上不去至少能把流程跑通。先跑通再调参避免一上来就被 OOM 卡住。3.3 断点续训、随机种子与权重保存训练到一半断电、显存不够被杀是毕设最常遇到的返工事故。许多开源 ReID 代码只save(model.state_dict())恢复后优化器和学习率清零后续训练几乎等于重来。所以训练阶段保存不要只存模型torch.save({ epoch: epoch, model: model.state_dict(), optimizer: optimizer.state_dict(), scheduler: scheduler.state_dict(), }, fcheckpoints/epoch_{epoch:03d}.pth)恢复时如果训练脚本支持--resume就指向这个文件并把--last-epoch一并传过去不支持的话把epoch写死读取再手动恢复 optimizer 状态。固定随机种子也是我强烈建议加进源码的防踩坑项random.seed(42) numpy.random.seed(42) torch.manual_seed(42) torch.cuda.manual_seed_all(42) torch.backends.cudnn.deterministic True这样答辩时同一张图多次前向的特征不会跳变别人用你的源码包训练也不会因为随机性得到完全不同的曲线。注意开了deterministic后训练会慢一些要复现指标时再用。4. 评测系统有没有用Rank-1、mAP 与一段最小评估代码训练完不算交作业还要证明 ReID 真的能查到人。见过很多毕设把“精度”当唯一指标但 ReID 更关心排序质量给 query 一张图从 gallery 中返回的前几名里有没有同类。4.1 Rank-1 和 mAP 分别回答什么问题指标含义容易踩的误区Rank-1第一个返回结果命中的比例只看第一位忽略后续正确图片Rank-5前五个返回结果中命中的比例不能反映同一人的多张图是否都排到前面mAP每个 query 的平均精度均值对排序位置敏感是检索系统的主指标用例子说明 AP假设某个 query 在 gallery 里有 3 张同人图片排序是命中、未中、命中、未中、命中。精确率在命中点分别是 1、2/3、3/5AP 就是(1 2/3 3/5)/3约 0.756。mAP 把所有 query 的 AP 做平均。Rank-1 高只代表第一个候选正确mAP 高才代表整段排名都把同一个人排在前面这是检索系统和分类系统的本质区别。很多开源 ReID 仓库的 README 会写这两个数字调用eval.py时也会有输出。如果源码里的评测脚本坏了别急着放弃下一节的代码可以直接抄。4.2 一段可以放进源码包的最小评估脚本下面这段以 PyTorch 为例假设模型在 eval 模式下返回的feat已经是归一化后的 embedding如果没有归一化你自己在最后加一行F.normalize也行。import numpy as np import torch import torchvision.transforms as T from torch.utils.data import DataLoader, Dataset from PIL import Image class ImageList(Dataset): def __init__(self, paths, size(256, 128)): self.paths paths self.tf T.Compose([ T.Resize(size), T.ToTensor(), T.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), ]) def __len__(self): return len(self.paths) def __getitem__(self, i): return self.tf(Image.open(self.paths[i]).convert(RGB)) torch.no_grad() def extract(model, paths, batch_size64): model.eval() feats [] for imgs in DataLoader(ImageList(paths), batch_sizebatch_size, num_workers2): feat model(imgs) # 必须取 embedding不是分类 logits feat feat / feat.norm(dim1, keepdimTrue) feats.append(feat.cpu().numpy()) return np.concatenate(feats) def ap(ranked_ids, query_id, topk100): ranked_ids ranked_ids[:topk] hits (ranked_ids query_id) if not hits.any(): return 0.0 precisions [hits[:i1].mean() for i in range(len(hits))] return np.mean([p for p, h in zip(precisions, hits) if h]) def evaluate(model, q_paths, g_paths, q_ids, g_ids): q_feat extract(model, q_paths) g_feat extract(model, g_paths) sim q_feat g_feat.T rank np.argsort(-sim, axis1) # 每行是 gallery 的降序排列 rank_ids np.asarray(g_ids)[rank] top1 np.mean([qid in ids[:1] for ids, qid in zip(rank_ids, q_ids)]) top5 np.mean([qid in ids[:5] for ids, qid in zip(rank_ids, q_ids)]) mAP np.mean([ap(ids, qid) for ids, qid in zip(rank_ids, q_ids)]) return top1, top5, mAP代码逻辑分三块extract把图片转成归一化特征sim q_feat g_feat.T得到余弦相似度矩阵np.argsort(-sim, axis1)把最相似的 gallery 排到最前。rank_ids再从 ID 数组里取索引得到排序后的 ID 列表最后算 Rank-1、Rank-5、AP 均值。整个脚本不依赖项目本身的 evaluator只要模型前向接口对就能替换。注意最关键的一行注释model(imgs)必须返回 embedding 而不是分类 logits。很多源码为了让训练和测试共用一张图会在model内部判断self.training训练时返回(logits, embedding)测试时返回 embedding。如果你的评估脚本拿到的是二维 logits算出来的相似度毫无意义。先打印feat.shape比如(64, 512)再继续算。Market1501 这类数据集query 和 gallery 有同摄像头重叠评估时不排除会把指标抬高。判断一句老手习惯在sim矩阵上把同 query 的同一摄像头相似度置为-inf再排序# q_cam/g_cam 是字符串数组来自图片路径解析 # for i in range(len(q_cam)): # sim[i, g_cam q_cam[i]] -np.inf同摄像头下背景高度相似模型可能靠背景把分数拉高。这种 gap 在真实跨摄像头场景中不存在所以真正的 ReID 指标必须做同源过滤。5. 源码包跑通的五个坑权重、环境、路径与显存玄学这一节是把上面所有流程里最容易翻车的点拎出来每条按“现象、原因、解决”写都是我实际处理源码包时见过的问题。5.1 torch.load 报 BadZipFile权重文件根本不是 torch 序列化产物现象每次加载 checkpoint 都报zipfile.BadZipFile: File is not a zip file或者RuntimeError: PytorchStreamReader failed。原因checkpoint.pth有可能是占位文件、只下了一半、或者是由旧版 PyTorch 用 tar 序列化保存的。很多时候不是模型问题而是权重文件本身不对。解决先用文件大小和file命令判断再用 2.3 的代码打印 checkpoint 结构。如果确定是旧 torch 存的可以在生成方改成torch.save(model.state_dict(), path, _use_new_zipfile_serializationFalse)让兼容性回到旧格式读方的 torch 版本最好也保持和生成方一致至少大版本不能差太多。5.2 环境装好却找不到 torchvisionrequirements 和当前 Python 版本打架现象明明pip install -r requirements.txt成功python train.py却报ModuleNotFoundError: No module named torchvision。原因requirements 里写死了一个老版本而当前 Python 3.11/3.12 只能装新版本pip 报冲突被忽略或者环境变量PYTHONPATH指向了另一个 Python 的 site-packages。解决先python -c import sys; print(sys.executable)确认解释器是虚拟环境里的再用pip list | grep torch查看版本。如果 Python 版本过高直接建 Python 3.8 环境。用 conda 也可以但别把 conda 默认环境和 venv 混在一个项目里否则后续每次都要和路径玄学纠缠。5.3 loss 一直在降Rank-1 却不涨现象训练日志里分类损失和 triplet 损失都下降但eval.py输出 Rank-1 始终在百分之十几附近。原因九成是数据加载器没做到 ID 平衡batch 里全是不同人triplet 变成负样本轰炸或者是模型在训练时输出 logits评估时也拿 logits 充当特征。解决先打印一个 batch 样本确认每个 ID 是否有 4 张图再看model(trainingTrue)和model(trainingFalse)的返回结构。最快定位方法拿 50 张图做一次过拟合实验单 batch 反复迭代几十次。如果 loss 能降到接近 0说明数据链路通问题在评估口径如果 loss 纹丝不动先看学习率和 BN 层再看标签是否错位。5.4 CUDA out of memory不是机器不行是向 GPU 塞了太多东西现象torch.cuda.OutOfMemoryError在训练启动几分钟后出现或者一跑 eval 就炸。原因第一是 batch size 太大第二是输入分辨率太高第三是评估时把 query 和整个 gallery 同时搬上 GPU 做相似度矩阵。ReID 和分类不同batch-size 64 256x128 resnet50已经接近 8GB 显存上限如果你还把 gallery 全量特征留在显存里很容易 OOM。解决把 batch size 降到 32分辨率暂时用 224x112eval 时用 CPU 算相似度矩阵用 AMP 是最低成本的提速。给一段通用片段scaler torch.cuda.amp.GradScaler() with torch.autocast(device_typecuda, dtypetorch.float16): loss model(x, y) scaler.scale(loss).backward() scaler.step(optimizer) scaler.update()如果开了 AMP 后 loss 出现 NaN可能是某些损失函数对半精度不友好可回退到 FP32 再排查。torch.cuda.empty_cache()只是清缓存不是解决方案真正要控制的是同时驻留显存的数据量。评估阶段把 gallery 分块计算向量输出到磁盘再回内存算距离再大的图库也不至于爆显存。5.5 Windows 解压后中文路径乱码Python 和压缩工具对编码各执一词现象在 Windows 上解压后目录名出现???或乱码train.py --config读不到文件在 Linux 上反而正常。原因zip 内文件名编码可能是 GBK 或 UTF-8而zipfile默认按 cp437 解码导致路径错乱。解决先试系统自带解压工具的兼容模式再用 Python 做一次可控制编码的解压import zipfile from pathlib import Path src 本科毕业设计-基于深度学习的行人重识别系统python源码.zip out Path(reid_out) with zipfile.ZipFile(src) as zf: for info in zf.infolist(): name info.filename try: name name.encode(cp437).decode(utf-8) except UnicodeDecodeError: name name.encode(cp437).decode(gbk) dest out / name if info.is_dir(): dest.mkdir(parentsTrue, exist_okTrue) else: dest.parent.mkdir(parentsTrue, exist_okTrue) dest.write_bytes(zf.read(info))这段代码先强制用 UTF-8 解一遍失败再转 GBK能处理大多数中文 zip。另一个更省事的建议是把整个项目复制到纯英文路径例如C:/reid_bs/不要在带中文和空格的目录下训练。Python 在这种路径下不是跑不了但和torch.hub、cv2.imread撞在一起时问题会非常难定位。我自己的习惯是解压后先tree看一眼再用英文路径重新存一份后面的步骤全部基于那份副本操作。6. 把 ReID 源码包变成答辩现场不会翻车的演示系统训练和评测只证明模型有效答辩现场能不能顺利演示是另一回事。我的建议是花最后一点时间把 gallery 特征固化和 TorchScript 固化做完。6.1 提前缓存 gallery 特征把 gallery 所有图片过一遍模型保存成.npy在线演示时只提取 query 特征再用 numpy 做矩阵乘法速度能快一个量级。# 离线准备将 gallery 特征固化 gallery_feat extract(model, gallery_paths) np.save(gallery_feat.npy, gallery_feat) # 在线检索query 特征和库里向量点积 sim query_feat np.load(gallery_feat.npy).T top_idx np.argsort(-sim)[:10]这样不需要每轮都跑一遍 gallery现场换一张 query 图片只花几十毫秒。如果图片数量几万张可以把np.load的数组再转成 float16 放到内存里精度损失很小速度更快。6.2 用 TorchScript 固化模型避开答辩现场的版本灾难毕设现场最怕被人用一个新环境跑你的代码torch 版本一变checkpoint 就可能读不进去。常见做法是在训练结束后用 TorchScript 把模型固化成一个独立文件import torch model.eval() example torch.randn(1, 3, 256, 128) traced torch.jit.trace(model, example) traced.save(reid_script.pt)torch.jit.trace会把 forward 实际走一遍之后的推理不需要原来model.py里的环境依赖也不需要加载原始 checkpoint。注意 trace 对动态分支不是很友好如果model.forward里根据self.training走不同分支先用eval模式再 trace。我交付毕设源码有个习惯checkpoints目录原样保留另外再导出一份reid_script.pt和存好的gallery_feat.npy演示机里只跑一个几十行的脚本。这样即使现场环境配不对也能保证演示不卡在环境安装上。希望帮到你。本文还有配套的精品资源点击获取