GAN+行人重识别毕设实战:从原理到代码的完整方案
发布时间:2026/10/1 3:34:06来源:尧图网络
简介本资源为基于GAN生成对抗网络实现行人重识别ReID的Python项目源码面向计算机、人工智能、通信等专业的在校学生与教师可用于毕业设计、课程设计、大作业或初期项目立项演示帮助读者理解生成对抗网络在跨摄像头行人检索任务中的建模思路与工程落地方式。压缩包共96个文件以16个py源码文件为核心辅以jpg、png图像样本、txt日志与说明、md文档、pdf实验报告及pptx汇报材料整体约36.97MB目录涵盖数据准备、模型定义、训练与结果记录等模块。目前已有160人学习浏览。项目代码完整且功能验证通过附带实验报告与分组实验分享材料读者可据此复现训练流程、观察不同迭代参数下的结果对比并在此基础上进行二次开发或功能扩展具备较高的学习借鉴价值。1. 行人重识别遇上GAN一个毕设级方案的完整拆解行人重识别要解决的问题很具体给你一张某摄像头拍到的行人照片让你在另一个摄像头的历史画面里把同一个人找出来。难点在于同一个人的外观会因为光照、角度、遮挡、分辨率发生剧烈变化而不同的人可能穿着极其相似。传统做法靠手工特征换个场景就崩。深度学习进来之后主流思路是训练一个特征提取网络把行人图像映射到高维向量空间让同一个人的向量距离近、不同人的距离远。但这里有个绕不开的坎数据不够。标注跨摄像头行人数据成本极高公开数据集规模有限模型很容易过拟合。生成对抗网络GAN就是在这个缺口上切入的——用生成器造出逼真的行人图像扩充训练集或者直接做跨域风格迁移把源域图像转成目标域的视觉风格缓解域偏移问题。这套方案适合做毕设、课程设计也适合想入门深度学习实战的开发者因为它把CNN、GAN、度量学习、特征检索串成了一条完整链路。2. GAN到底在行人重识别里干什么原理与选型2.1 生成器与判别器的博弈如何服务ReIDGAN的核心是生成器G和判别器D的对抗训练。G试图生成逼真图像骗过DD试图区分真假。在行人重识别场景里这个博弈有三种典型用法。第一种是数据增强。用GAN生成不同姿态、不同光照下的行人图像补充到训练集里。比如有人用DCGAN生成行人局部区域缓解遮挡样本不足的问题。第二种是跨域迁移。源域有标注数据目标域没有用CycleGAN把源域图像转成目标域风格让模型在目标域上也能提取有效特征。第三种是特征层面的对抗用GAN做特征解耦把身份相关特征和域相关特征分开提升泛化能力。选型上毕设级别最稳妥的路线是主干网络用ResNet50或ResNet18做特征提取GAN部分用DCGAN或CycleGAN做数据增强或风格迁移损失函数用三元组损失加交叉熵。这个组合代码资源多、训练相对稳定、显存要求可控。2.2 主干网络与损失函数的搭配逻辑主干网络负责把行人图像变成特征向量。ResNet50是ReID里的经典选择因为它在ImageNet上预训练后迁移能力强深层残差结构能捕捉细粒度纹理。如果显存有限ResNet18也能跑但精度会降几个点。损失函数决定特征空间长什么样。交叉熵损失做身份分类三元组损失拉近同类、推远异类。实际训练时通常联合使用import torch import torch.nn as nn import torch.nn.functional as F class ReIDLoss(nn.Module): def __init__(self, num_classes, margin0.3): super(ReIDLoss, self).__init__() self.ce_loss nn.CrossEntropyLoss() self.margin margin def forward(self, features, logits, labels): # 交叉熵损失身份分类 ce self.ce_loss(logits, labels) # 三元组损失特征空间距离约束 triplet self.batch_hard_triplet_loss(features, labels) return ce triplet def batch_hard_triplet_loss(self, features, labels): # 计算样本对距离矩阵 dist_mat torch.cdist(features, features, p2) loss 0.0 count 0 for i in range(features.size(0)): pos_mask (labels labels[i]) (torch.arange(len(labels)) ! i) neg_mask labels ! labels[i] if pos_mask.sum() 0 or neg_mask.sum() 0: continue # 最难正样本和最难负样本 hardest_pos dist_mat[i][pos_mask].max() hardest_neg dist_mat[i][neg_mask].min() loss F.relu(hardest_pos - hardest_neg self.margin) count 1 return loss / max(count, 1)这段代码里num_classes是训练集行人ID总数margin控制正负样本对的距离间隔一般设0.3到0.5。batch_hard_triplet_loss对每个样本找最难正样本和最难负样本训练信号更强。注意torch.cdist计算的是欧氏距离特征向量最好先做L2归一化。2.3 用CycleGAN做跨域风格迁移的配置要点如果毕设选题偏向域适应CycleGAN是常见选择。它不需要成对数据能把源域图像转成目标域风格。关键配置如下参数建议值说明输入尺寸256×128行人图像宽高比约1:2生成器结构ResNet×66个残差块平衡质量和速度判别器PatchGAN 70×70局部判别纹理更真实学习率0.0002Adam优化器前10轮恒定批次大小4~8视显存调整循环一致性权重10.0保证内容不丢失训练时先固定源域和目标域图像文件夹用--dataroot指定路径。常见做法是训练200轮左右观察生成图像是否保留行人身份信息。如果生成的人脸糊成一团说明循环一致性权重太低或训练不充分。3. 从零跑通环境、数据、训练、检索四步走3.1 环境配置与依赖安装先确认Python版本。建议3.8到3.10太新的版本可能遇到PyTorch兼容问题。用conda建虚拟环境conda create -n reid_gan python3.9 conda activate reid_gan pip install torch1.13.1 torchvision0.14.1 --index-url https://download.pytorch.org/whl/cu117 pip install numpy opencv-python pillow scikit-learn matplotlib tqdm tensorboard如果没GPU把cu117换成cpu。装完后验证import torch print(torch.__version__) print(torch.cuda.is_available())输出True说明GPU可用。显存建议8G以上CycleGAN训练时批次大小4大概占6G。3.2 数据集准备与目录结构常用公开数据集有Market-1501、DukeMTMC-reID、CUHK03。以Market-1501为例下载后解压目录结构应该是Market-1501/ ├── bounding_box_train/ # 训练集751个ID约12936张 ├── bounding_box_test/ # 测试集750个ID约19732张 ├── query/ # 查询集750个ID3368张 └── gt_query/ # 查询对应的真实匹配如果做GAN数据增强把bounding_box_train里的图像按ID分文件夹每个ID一个子目录。生成器训练时按ID采样保证生成图像有身份标签。数据加载用torchvision.datasets.ImageFolder加自定义采样器from torch.utils.data import Dataset, DataLoader from PIL import Image import os class ReIDDataset(Dataset): def __init__(self, root, transformNone): self.root root self.transform transform self.img_paths [] self.labels [] self.label_to_idx {} # 遍历每个ID文件夹 for idx, pid in enumerate(sorted(os.listdir(root))): pid_dir os.path.join(root, pid) if not os.path.isdir(pid_dir): continue self.label_to_idx[pid] idx for img_name in os.listdir(pid_dir): self.img_paths.append(os.path.join(pid_dir, img_name)) self.labels.append(idx) def __len__(self): return len(self.img_paths) def __getitem__(self, index): img Image.open(self.img_paths[index]).convert(RGB) label self.labels[index] if self.transform: img self.transform(img) return img, labelroot指向训练集根目录每个子文件夹名就是行人ID。transform里加随机裁剪、翻转、归一化。注意测试集和查询集的ID可能不在训练集里评估时要用检索指标而不是分类准确率。3.3 训练循环与关键参数设置训练脚本的核心结构import torch from torch import optim from torch.utils.data import DataLoader from torchvision import transforms # 数据增强 train_transform transforms.Compose([ transforms.Resize((256, 128)), transforms.RandomHorizontalFlip(p0.5), transforms.Pad(10), transforms.RandomCrop((256, 128)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) dataset ReIDDataset(rootMarket-1501/bounding_box_train, transformtrain_transform) loader DataLoader(dataset, batch_size32, shuffleTrue, num_workers4) # 模型、损失、优化器 model build_resnet50_reid(num_classes751) criterion ReIDLoss(num_classes751, margin0.3) optimizer optim.Adam(model.parameters(), lr3.5e-4, weight_decay5e-4) scheduler optim.lr_scheduler.StepLR(optimizer, step_size40, gamma0.1) for epoch in range(120): model.train() for imgs, labels in loader: imgs, labels imgs.cuda(), labels.cuda() features, logits model(imgs) loss criterion(features, logits, labels) optimizer.zero_grad() loss.backward() optimizer.step() scheduler.step() print(fEpoch {epoch}, Loss: {loss.item():.4f})batch_size32是ReID常见设置太小三元组损失采样不够太大显存吃紧。学习率3.5e-4配合Adam是经典组合StepLR每40轮降一次。训练120轮左右Market-1501上mAP能到80%以上。如果loss震荡检查特征是否做了L2归一化或者把margin降到0.2。3.4 特征提取与检索评估训练完保存模型测试时提取查询集和图库集的特征算距离排序def extract_features(model, loader): model.eval() feats, labels [], [] with torch.no_grad(): for imgs, lbls in loader: imgs imgs.cuda() f model(imgs) # 只取特征不取分类logits f torch.nn.functional.normalize(f, p2, dim1) feats.append(f.cpu()) labels.append(lbls) return torch.cat(feats), torch.cat(labels) query_feats, query_labels extract_features(model, query_loader) gallery_feats, gallery_labels extract_features(model, gallery_loader) # 计算距离矩阵 dist_mat torch.cdist(query_feats, gallery_feats, p2) # 对每个查询按距离升序排列 indices torch.argsort(dist_mat, dim1) # 计算mAP和Rank-1 correct 0 for i in range(len(query_labels)): if gallery_labels[indices[i][0]] query_labels[i]: correct 1 rank1 correct / len(query_labels) print(fRank-1: {rank1:.4f})extract_features里做了L2归一化保证距离计算等价于余弦距离。torch.cdist输出的是查询数×图库数的矩阵。Rank-1只看第一个结果对不对mAP要考虑所有正确匹配的排序位置。如果Rank-1高但mAP低说明模型只学到了粗粒度特征需要加强三元组损失权重。4. 避坑指南GANReID训练中最容易翻车的五个地方4.1 生成图像身份信息丢失现象CycleGAN训练完后生成的行人图像风格变了但人脸和衣服细节糊成一团用这些图像训练ReID模型精度反而下降。原因循环一致性损失权重太低生成器只顾风格迁移不管内容保留。或者判别器太强生成器被迫生成“平均脸”来骗过判别器。解决把循环一致性权重从10提到15甚至20同时降低判别器学习率到生成器的1/2。训练时每隔10轮保存生成图像人工检查身份是否可辨。如果还是糊减少残差块数量让生成器容量小一点。4.2 三元组损失不收敛现象训练前几轮loss正常下降到第20轮左右突然飙升或者一直卡在某个值不动。原因批次内采样到的正样本对太少或者特征没有归一化导致距离尺度失控。另外如果margin设得太大模型找不到满足条件的解。解决确保每个批次至少包含4个ID、每个ID至少4张图。用BatchSampler做PK采样。特征做L2归一化后再算三元组。margin从0.3开始试不收敛就降到0.2。如果还不行检查数据标签是否对齐有没有把不同ID的图放到同一个文件夹。4.3 显存溢出与批次大小冲突现象训练时报CUDA out of memory减小批次后loss震荡厉害。原因ResNet50加GAN生成器同时占显存256×128输入下批次32可能超8G。另外如果数据加载用了多进程每个进程都会拷贝一份数据到显存。解决先降批次到16用梯度累积模拟大批次accum_steps 2 for i, (imgs, labels) in enumerate(loader): loss model_step(imgs, labels) / accum_steps loss.backward() if (i 1) % accum_steps 0: optimizer.step() optimizer.zero_grad()accum_steps2表示每2个批次更新一次参数等效批次32。同时把num_workers降到2避免内存拷贝开销。4.4 评估指标虚高现象训练集上Rank-1到99%测试集上只有50%。原因训练集和测试集的ID有重叠或者查询集和图库集用了同一批图像。Market-1501的测试集ID和训练集完全不重叠如果自己划分数据一定要按ID划分不能按图像随机划分。解决检查bounding_box_train和bounding_box_test的ID列表有没有交集。用set(train_ids) set(test_ids)验证输出应该是空集。另外查询集图像不能出现在图库集里否则就是自己查自己。4.5 GAN训练不稳定导致模式崩溃现象生成器只生成少数几种姿态或颜色多样性极差。原因判别器太强生成器梯度消失。或者学习率设置不当生成器和判别器失衡。解决用Wasserstein GAN加梯度惩罚替代原始GAN损失或者把判别器更新频率降到生成器的1/2。具体做法是每训练2次生成器才训练1次判别器。另外在生成器损失里加多样性正则项鼓励生成不同姿态。5. 进阶技巧用特征解耦提升跨域泛化如果毕设想做出差异化可以在GAN基础上做特征解耦。核心思路是把ResNet提取的特征分成两部分一部分编码身份信息一部分编码域风格信息。用对抗损失让域分类器分不清域标签从而让身份特征对域变化不敏感。具体实现时在主干网络后接两个全连接分支class DisentangleReID(nn.Module): def __init__(self, backbone, feat_dim2048, id_dim512, domain_dim128): super().__init__() self.backbone backbone self.id_head nn.Linear(feat_dim, id_dim) self.domain_head nn.Linear(feat_dim, domain_dim) self.classifier nn.Linear(id_dim, num_classes) def forward(self, x): feat self.backbone(x) id_feat self.id_head(feat) domain_feat self.domain_head(feat) logits self.classifier(id_feat) return id_feat, domain_feat, logits训练时身份分支用三元组损失和交叉熵域分支用交叉熵但加梯度反转层。梯度反转层在前向传播时恒等反向传播时把梯度取反让主干网络学到的特征无法区分域。这样训练出来的模型在源域和目标域上都能提取稳定的身份特征。验证方法在Market-1501上训练直接在DukeMTMC-reID上测试不做任何微调。如果Rank-1能到40%以上说明解耦有效。普通模型跨域直接测试通常只有20%到30%。我自己的习惯是每次改完损失函数或网络结构先跑10轮看loss曲线确认没有异常再跑完整训练。毕设时间有限不要一上来就设200轮先小规模验证思路再放大。另外生成图像一定要人工看别只看指标GAN的玄学就在这儿指标好看但图糊了后面全白搭。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网