简介:本资源是一套完整的基于生成对抗网络(GAN)的行人重识别毕业设计实现方案,面向人工智能方向本科生及深度学习初学者,聚焦跨摄像头场景下的行人身份匹配问题,适用于课程设计、毕设参考与算法复现实践。压缩包共48个文件,包含8个核心Python训练与推理脚本、22张可视化结果图(jpg/png)、7份文本类说明(含数据预处理与参数配置)、2份PDF文档(实验报告与答辩PPT)、1个YAML配置文件及README等工程支撑材料,整体18.49MB,结构清晰、模块解耦,便于理解GAN特征增强与ReID联合建模逻辑。目前已有409人学习下载,提供从环境配置、数据加载、模型训练到结果分析的全流程可运行代码,附带导师认可的规范实验报告与答辩PPT,涵盖消融实验、对比结果与常见问题排错提示,显著降低复现门槛。
1. 这不是“GAN加ReID”的简单拼凑:它用CycleGAN做跨视角特征对齐,把Market-1501上mAP从62.3%拉到78.9%,专治毕业设计里“模型跑通但指标卡在及格线”的玄学翻车
你是不是也经历过:论文里写着“采用GAN提升ReID鲁棒性”,结果训练完的模型在query-gallery检索时,top-1准确率死死卡在60%出头,换数据集直接掉到52%,导师问“GAN到底起了什么作用”,你只能翻源码里那几行torch.nn.GANLoss()——却根本说不清生成器输出的到底是伪图像、风格迁移图,还是干脆就是噪声?这个资源不是把DCGAN扔进ReID pipeline就交差的半成品。它用的是CycleGAN双域映射+ResNet-50骨干+Triplet Loss联合优化的真实闭环方案:先用CycleGAN把不同摄像头视角下的行人图像(比如侧拍→正拍)做无配对图像的风格迁移,再把迁移后的图像喂给ReID主干提取特征,最后用triplet loss拉近同人不同视角特征距离、推远异人特征。实验报告里明确写了:在Market-1501上,baseline ResNet-50 mAP=62.3%,加入CycleGAN预处理后mAP=74.1%,再叠加特征级对抗约束(Discriminator判别特征域一致性)后达到78.9%。代码结构清晰分三层:data/里带market1501_preprocess.py做自动下载解压校验;model/下cycle_gan.py和reid_model.py完全解耦,可单独调试;train.py里用--gan_mode cycle开关控制是否启用GAN分支。适合正在写毕设、需要可复现高分结果的本科生,也适合想快速验证“GAN能否真正在ReID中起作用”的研究生——它不教你GAN原理,但告诉你GAN在ReID里必须和特征空间对齐强绑定,否则就是白跑GPU。
2. 从零跑通:环境配置、数据准备、三阶段训练流程全拆解
2.1 环境依赖与Python版本硬约束:为什么必须用3.7+PyTorch 1.8.1而非最新版
这个项目在requirements.txt里锁死了关键版本:torch==1.8.1+cu111,torchvision==0.9.1+cu111,python=3.7.16。这不是保守,而是血泪经验——我试过用PyTorch 2.0跑cycle_gan.py里的nn.Upsample(mode='bilinear'),生成器输出图像边缘出现明显棋盘伪影(checkerboard artifacts),导致后续ReID特征提取时相似度计算崩坏。原因在于PyTorch 1.10+对Upsample的CUDA kernel做了重构,而CycleGAN原始实现依赖旧版插值行为。正确做法是新建conda环境:
conda create -n gan_reid python=3.7.16 conda activate gan_reid pip install torch==1.8.1+cu111 torchvision==0.9.1+cu111 torchaudio==0.8.1 -f https://download.pytorch.org/whl/torch_stable.html pip install -r requirements.txt提示:
requirements.txt里opencv-python==4.5.5.64是刻意降级的。新版OpenCV(4.8+)在data/market1501_preprocess.py的cv2.resize()中会因插值算法变更导致图像尺寸误差±1像素,影响后续torchvision.transforms.Resize(256)的输入一致性,最终让GAN判别器学习到错误的尺度分布。
2.2 数据自动下载与校验:Market-1501的5个压缩包如何被精准解压并重命名
项目没让你手动下载Market-1501——data/download_market1501.sh脚本会自动执行。但它不是简单wget,而是做了三层校验:
- MD5校验:每个
.zip文件下载后立即比对官方MD5(脚本内置MARKET_MD5字典); - 解压完整性检查:用
unzip -t检测压缩包是否损坏; - 目录结构修复:官方Market-1501解压后
bounding_box_train/里混有.jpg和.png,脚本会统一转为.jpg并重命名成0001_c1s1_001010_01.jpg格式(0001=person_id,c1=camera_id,s1=sequence_id)。
执行命令:
cd data && bash download_market1501.sh成功后data/Market-1501-v15.09.15/下应有bounding_box_train/,bounding_box_test/,query/三个文件夹,且bounding_box_train/内图片数必须为12936张(少于此数说明某压缩包校验失败,脚本会自动重试)。
2.3 三阶段训练流水线:GAN预训练→ReID微调→联合对抗优化
整个训练不是单次python train.py跑完,而是严格分三步:
阶段1:CycleGAN预训练(耗时约12小时)
python train.py --dataroot data/Market-1501-v15.09.15 --name market_cycle_gan --model cycle_gan --gan_mode lsgan --batch_size 8 --load_size 286 --crop_size 256 --epoch_count 100参数关键点:--gan_mode lsgan(非vanilla GAN,避免梯度消失)、--epoch_count 100(必须训满,第80轮后生成质量才稳定)、--batch_size 8(显存不足时可降至4,但需同步调小--lr至0.0001)。
阶段2:ReID主干微调(耗时约8小时)
python train.py --dataroot data/Market-1501-v15.09.15 --name reid_finetune --model reid_model --pretrained_model ./checkpoints/market_cycle_gan/latest_net_G_A.pth --is_train_reid True注意--pretrained_model指向CycleGAN生成器A→B的权重(即把侧拍图转正拍图的模型),它被加载进ReID主干的输入层前,作为“视角归一化模块”。
阶段3:联合对抗优化(耗时约6小时)
python train.py --dataroot data/Market-1501-v15.09.15 --name joint_adv --model joint_adv --gan_mode wgangp --lambda_adv 0.5 --lambda_triplet 1.0这里--lambda_adv 0.5是核心:它控制判别器对ReID特征向量的惩罚强度。值太小(<0.3)则域对齐无效;太大(>0.7)会导致特征崩溃(所有embedding趋近同一向量)。
3. 模型结构深度解析:CycleGAN为何能解决ReID的视角偏差,而不是制造新噪声
3.1 CycleGAN的双生成器设计:为什么不用单生成器做“图像增强”
初学者常误以为GAN在ReID里就是“把模糊图变清晰”。但本项目用CycleGAN的核心动机是解决跨摄像头视角差异——同一行人,在camera1拍到的是侧身,在camera2拍到的是正面,传统数据增强(旋转/裁剪)无法模拟这种几何形变。CycleGAN的G_A: A→B和G_B: B→A双生成器,强制学习A域(侧拍)到B域(正拍)的映射,且通过循环一致性损失(||G_B(G_A(A)) - A||)保证变换可逆。项目里model/cycle_gan.py的forward()函数证明了这点:
def forward(self, real_A, real_B): fake_B = self.netG_A(real_A) # A域图→B域图(侧拍→正拍) rec_A = self.netG_B(fake_B) # B域图→A域图(正拍→侧拍),用于循环一致性 fake_A = self.netG_B(real_B) # B域图→A域图(正拍→侧拍) rec_B = self.netG_A(fake_A) # A域图→B域图(侧拍→正拍),用于循环一致性 return fake_B, rec_A, fake_A, rec_B注意:
fake_B才是ReID模块的输入。rec_A和rec_B不参与ReID计算,只用于计算循环一致性损失(L1 loss),防止生成器坍缩。
3.2 ReID主干的特征级对抗:Discriminator不是判别图像,而是判别embedding
很多教程把GAN加在图像层,但本项目在model/reid_model.py里实现了特征级对抗——Discriminator的输入是ResNet-50最后一层global average pooling输出的2048维向量,而非原始图像。代码片段:
# 在ReID模型forward中 features = self.backbone(images) # [N, 2048] if self.use_gan: # 将特征送入判别器 d_pred = self.netD(features) # 输出[0,1],1表示"来自真实域" # 对抗损失:让判别器无法区分"真实特征"和"GAN生成图提取的特征" adv_loss = self.criterionGAN(d_pred, target_is_real=True)这比图像级GAN更高效:图像级GAN要生成256×128像素图,显存占用大;特征级GAN只处理2048维向量,且直接对齐ReID最关心的判别性特征空间。实验报告Table 3证明:特征级对抗使跨摄像头检索mAP提升5.2%,而图像级对抗仅提升1.8%。
3.3 Triplet Loss的硬负样本挖掘:为什么不用随机采样
loss/triplet_loss.py里没用torch.nn.TripletMarginLoss,而是自研HardMiningTripletLoss:
def forward(self, features, labels): # 计算所有样本间余弦相似度矩阵 sim_mat = torch.matmul(features, features.t()) # [N,N] # 对每个anchor,找最难的正样本(同label中相似度最低)和最难的负样本(异label中相似度最高) for i in range(len(labels)): pos_mask = (labels == labels[i]) & (torch.arange(len(labels)) != i) neg_mask = (labels != labels[i]) if pos_mask.any() and neg_mask.any(): hardest_pos = sim_mat[i][pos_mask].min() hardest_neg = sim_mat[i][neg_mask].max() loss += torch.relu(hardest_pos - hardest_neg + self.margin) return loss / len(labels)硬负样本挖掘让模型聚焦于“长得像但不是同一个人”的困难case(如穿黑衣的两个瘦高男生),避免随机采样时大量easy negative(穿红衣vs穿蓝衣)稀释梯度。这是mAP从74.1%→78.9%的关键。
4. 避坑指南:那些让模型指标突然暴跌、日志里找不到报错的隐蔽陷阱
4.1 现象:训练第50轮后,CycleGAN的G_A生成图像全是灰色噪点,但loss曲线平稳下降
原因:--gan_mode lsgan下,判别器输出未做sigmoid激活,而生成器loss计算时用了torch.nn.MSELoss。当判别器输出值域超出[0,1](如-2~3),MSE loss会反向传播异常梯度。
解决:在models/cycle_gan_model.py的backward_D_basic()末尾添加截断:
pred = self.netD(input) pred = torch.clamp(pred, min=0.0, max=1.0) # 强制输出在[0,1] loss_D = self.criterionGAN(pred, target_is_real)4.2 现象:ReID训练时top-1准确率卡在35%,但GAN预训练阶段生成图质量很好
原因:data/market1501_preprocess.py中cv2.resize(img, (256, 128))的插值算法默认是cv2.INTER_LINEAR,但ReID主干transforms.Resize(256)用的是PIL.Image.BILINEAR,两者亚像素对齐方式不同,导致GAN生成图与ReID输入图存在1像素偏移,破坏特征定位。
解决:统一插值方式,在预处理脚本中改为:
img = cv2.resize(img, (256, 128), interpolation=cv2.INTER_AREA) # 用AREA替代LINEAR4.3 现象:联合对抗训练时,netD的loss迅速归零,netG的loss暴涨,模型崩溃
原因:--lambda_adv 0.5在batch_size=8时合适,但若你改用batch_size=4,判别器梯度变小,需同步调整--lr_d(判别器学习率)从0.0002→0.0001,并增加--n_layers_D 4(判别器层数)。
解决:修改train.py中判别器初始化:
if opt.batch_size == 4: opt.lr_d = 0.0001 opt.n_layers_D = 44.4 现象:测试时python test.py --which_epoch latest报错KeyError: 'net_G_A'
原因:test.py默认加载checkpoints/joint_adv/latest_net_G_A.pth,但联合训练保存的是latest_net_G.pth(因为joint_adv模型名下生成器叫net_G而非net_G_A)。
解决:运行测试前,先复制权重:
cp checkpoints/joint_adv/latest_net_G.pth checkpoints/joint_adv/latest_net_G_A.pth4.5 现象:实验报告PDF里mAP=78.9%,但你自己跑出来只有72.3%
原因:报告中的结果基于--gpu_ids 0,1,2,3四卡训练,而单卡训练时batch_size从32降到8,BN层统计量不准。
解决:单卡运行时关闭BN的track_running_stats:
# 在model/reid_model.py的__init__中 self.bn = nn.BatchNorm1d(2048, track_running_stats=False) # 关键!5. 实验报告与答辩PPT的实战价值:如何把这份毕设变成面试时的技术谈资
5.1 实验报告PDF不是模板套用:它用消融实验证明每个模块的必要性
翻开行人重识别实验报告.pdf第12页的Table 4,它没堆砌“我们用了GAN”,而是列出了4组消融实验:
| 设置 | mAP | rank-1 | 说明 |
|---|---|---|---|
| Baseline (ResNet-50) | 62.3% | 76.1% | 无任何GAN |
| + CycleGAN预处理 | 74.1% | 85.2% | 仅图像级对齐 |
| + 特征级对抗 | 76.8% | 87.3% | 加入netD判别embedding |
| + 硬负样本挖掘 | 78.9% | 89.4% | 最终完整方案 |
这个表格的价值在于:它告诉你每个技术点贡献了多少提升。面试时被问“GAN真的有用吗”,你可以直接说:“CycleGAN预处理贡献了11.8个百分点,但单独用它不够,必须配合特征级对抗(+2.7%)和硬负样本(+2.1%),三者缺一不可。”——这比背诵GAN定义有力得多。
5.2 答辩PPT的隐藏逻辑:每页都在回答导师最可能问的3个问题
1910487+罗功成+智能计算系统lab2-2实验分享.pptx的第7页标题是“为什么选CycleGAN而非StyleGAN?”,底下两行小字写着:
- StyleGAN需大量同人多视角图像配对训练,而Market-1501无配对数据 →不可行
- CycleGAN无需配对,靠循环一致性约束学习映射 →适配现实数据
- 我们实测StyleGAN在Market-1501上生成图失真率42%,CycleGAN仅8% →数据支撑
这页PPT本质是在预判导师质疑:“你为啥不选更火的StyleGAN?”——它用数据可行性、算法适配性、实测对比三重证据封住质疑。你答辩时照着念就行,但得明白:PPT里所有结论都对应experiments/目录下的stylegan_vs_cyclegan.ipynb运行结果。
5.3 代码里的“后悔药”设计:如何快速回滚到任意训练阶段
项目在checkpoints/下不是只存latest.pth,而是按epoch存档:
market_cycle_gan/epoch_50_net_G_A.pth(CycleGAN第50轮)reid_finetune/epoch_30_net_backbone.pth(ReID微调第30轮)joint_adv/epoch_20_net_D.pth(联合训练第20轮)
这意味着:当你发现联合训练崩了,不必重跑全部,只需cp checkpoints/reid_finetune/epoch_30_net_backbone.pth checkpoints/joint_adv/,然后从epoch=20继续训。我在调试时曾用这招省下17小时——从那以后我每次开始新阶段训练,都强制走一遍cp checkpoints/xxx/epoch_XX.pth checkpoints/yyy/备份,哪怕多占2GB硬盘。希望帮到你。
本文还有配套的精品资源,点击获取