☰
行人重识别ReID实战:从工程结构到模型训练全解析
2026/10/6 16:14:10 网站建设 项目流程

简介:行人重识别(ReID)是计算机视觉中的关键任务,目标是在不同摄像头下准确匹配同一行人。这套Python工程代码包面向深度学习初学者和视觉研究者,提供了从数据准备到模型评估的完整实现,覆盖Market-1501等常用数据集的处理流程。压缩包共11个文件,全部为Python脚本,大小约18KB,包含数据加载与增强(data_manager.py、dataset_loader.py)、骨干网络(ResNet.py)、损失函数(losses.py)、优化器配置(optimizers.py)以及评估指标(eval_metrics.py)等模块,结构清晰,适合按模块逐步研读,便于定位数据、模型、优化等核心逻辑。目前已有150人学习下载。通过分析源码,读者可以掌握CNN提取行人外观特征的方法,理解triplet loss、center loss在度量学习中的作用,并学会使用mAP、Rank-1等指标评估模型。代码简洁可运行,兼顾理论讲解与工程实践,是课程设计、论文复现或入门ReID研究的高性价比参考。

1. 行人重识别是什么:一个 zip 里装的是一整套跨镜检索系统

在商超、园区、地铁站跨摄像头找人,靠人眼回看录像动辄一两个小时,行人重识别(Person Re-Identification,ReID)就是让深度学习模型学会“只看一次,之后在几百个陌生人里把 ta 找回来”。一个名为“基于深度学习的行人重识别.zip”的工程包,解压开通常不是一个训练好的模型文件,而是一整套从数据集组织、训练到检索评估的代码管线,这也是它和普通 demo 包最本质的区别。给定一张 query 图,模型输出一个能区分“同一个人在不同摄像头下长什么样”的特征向量,再和全库 gallery 特征算相似度并排序。适合正在做检测/分类、想转入检索方向的工程师或研究生,目标是能复现、能改参、能部署。

2. 先看懂工程结构再动手:ReID 源码包的标准套路与数据流

拿到一个 ReID 的 zip,多数版本的目录结构都长得很像,这要感谢 Market1501 和后来 BoT、TransReID 这些开源工程养成的社区习惯。先别急着跑 train.py,花二十分钟把目录和数据流捋清楚,后面能少踩一半坑。这个环节不依赖具体代码风格,任何基于 PyTorch 的 ReID 实战项目案例,基本都跑不出下面这套约定。

2.1 一个典型 ReID 工程有什么:从 train.py 到 evaluate.py

常见工程解压后,顶层会有 train.py、evaluate.py 两个入口,以及 scripts、configs、datasets、models、losses、samplers、utils 这几个目录。scripts 是启动用的 shell 脚本,configs 是参数配置,datasets 负责数据集加载和目录校验,models 是骨干网络和脖子模块,losses 是交叉熵、三元组等损失定义,samplers 是批次采样器,utils 里是日志和评估工具。train.py 负责训练,evaluate.py 负责加载权重、抽取特征并计算 CMC/mAP。

# 解压并查看顶层结构,先确认没有嵌套目录 unzip 基于深度学习的行人重识别.zip -d ReID cd ReID ls -R . | head -60 # 正常会看到 configs/ datasets/ models/ losses/ samplers/ utils/ train.py evaluate.py

这段命令的作用是把工程解压到 ReID 目录,并用 ls -R 递归列出结构,head -60 控制输出长度。如果解压出来带中文目录名,或者多嵌套了一层同名文件夹,先用 mv 把它提到顶层,否则后面相对路径会全部失效。多数工程用相对路径读数据集,目录层级错一位,报错信息还不会直接指到路径上,而是先以“找不到图片”的形式出现。

在 config 或启动脚本里,通常能看到这几个核心参数:数据根目录 data_root、输出目录 save_dir、批次大小 batch_size、学习率 lr、训练轮数 epochs、骨干网络名称 arch、损失类型 loss_type、设备 device。这些参数大多能从命令行覆盖,意味着不改代码也能换配置。判断一个工程是否成熟,一是看 evaluate.py 里有没有独立的评估协议,二是看 datasets 里有没有针对 Market1501 的标准目录处理,如果都不具备,说明它只是一堆临时脚本拼起来的,复现价值有限,别在它身上花太多时间。

2.2 数据怎么流动:一张行人图到特征向量的完整链路

在 ReID 里,一张行人图从读入磁盘到输出特征向量,经过五个环节:读取图片、数据增强、骨干网络提取特征、降维层压缩特征、L2 归一化。训练阶段在降维后还要挂分类头和三元组损失,推理阶段则直接用归一化后的特征做余弦相似度。下面这段代码把五个环节拆开,方便对照工程里每一步在做什么。

# 特征提取流水线示意:读图 -> 增强 -> backbone -> neck -> L2归一化 import torch import torchvision.transforms as T from PIL import Image # 1. 读图,统一转 RGB img = Image.open("query/0002_c1s1_000451_00.jpg").convert("RGB") # 2. 推理增强:缩放、中心裁剪、归一化;训练还会加随机擦除和翻转 transform = T.Compose([ T.Resize([256, 128]), # ReID 通用输入尺寸:高 256,宽 128 T.CenterCrop([256, 128]), T.ToTensor(), T.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ]) x = transform(img).unsqueeze(0) # [1, 3, 256, 128] # 3. backbone 输出 2048 维全局池化特征 # 4. neck 降到 512,并做 BN;推理时用 BN 后的结果即可 # 5. 最后做 L2 归一化,后续算余弦相似度 feat = torch.nn.functional.normalize(neck_feat, dim=1)

这段代码是工程里推理部分的最小形态。Resize 到 [256, 128] 是 Market1501 上约定俗成的输入比例,因为它匹配行人瘦长的外形先验,改成正方形会掉点。CenterCrop 在推理时是为了对齐训练时的随机裁剪统计。归一化沿用 ImageNet 的 mean 和 std,几乎所有 ReID 工程都直接使用,不必自行调整。代码里的 neck_feat 是占位写法,实际是 backbone 输出经过全局平均池化,再过 BNNeck 的结果。

实际工程里这五个环节会被封装进 dataset 的getitem和 model 的 forward,DataLoader 的 num_workers 影响读图速度,pin_memory 能减少数据从 CPU 拷贝到 GPU 的时间。调参时这些是常规项,先确认它们没有问题,再怀疑算法本身。

2.3 为什么行人重识别是「分类 + 度量」的混合问题

刚上手的人最容易问:训练时明明用交叉熵把每个人 ID 当成一个类别来分,为什么推理时输出的不是类别,而是一个向量?这正是 ReID 和图像分类的关键区别。分类任务里一张图对应一个固定标签,ReID 里训练集和测评集的人 ID 几乎完全不相交,模型必须学习“一个人的外观特征如何跨摄像头保持稳定”,这个能力只能靠度量学习来约束。

常见做法是同时挂两个头:一个 ID 分类头用交叉熵,逼着特征具有类别判别力;一个三元组损失用难样本挖掘,把同一 ID 的特征拉近、不同 ID 的特征推远。两个头共享 backbone 和 neck,训练结束后丢掉分类头,只保留特征提取部分。一句话概括:交叉熵教模型“见过谁”,三元组教模型“谁和谁像”,两者缺一,结果都有明显短板——只有交叉熵,难样本区分不开;只有三元组,训练不稳且收敛慢。

在 zip 工程里,判断它做没做度量学习,最明显的标志是有没有 sampler 目录和 triplet loss 定义。只有分类头、没有采样器的版本,通常是从分类项目改过来的半成品。像动手深度学习这类入门书里的分类代码,拿到 ReID 这里必须要补两块:一个是难样本采样器,一个是评估协议,缺了它们训练出来的模型很难在跨摄像头检索上拿到可用指标。

3. 复现训练全流程:从 Market1501 到跑通 train.py 的最小命令

这章直奔“照着做能跑通”。深度学习 PyTorch 生态里,ReID 的复现难度主要不在网络结构,而在数据划分和训练参数;对很多人来说,这也是第一次理解“数据和指标一起决定模型好坏”。这里说的“跑通”,不只是 loss 在下降,而是训练结束后 evaluate.py 能算出和论文同量级的 Rank-1 和 mAP。

3.1 数据不“改坏”:Market1501 的目录约定

Market1501 是 ReID 最常用的公开数据集,包含 1501 个行人、32668 个检测框,采集自 6 个摄像头。它发布时已经按训练集、查询集和候选集划分好,目录名是 bounding_box_train、query 等。复现时最容易犯的错是把这些目录重命名,或者在训练集里混入 query。目录结构一旦改动,评估脚本按约定路径找不到数据,指标直接崩掉。

Market1501 的关键目录及用途如下:

目录/文件内容本阶段是否需要
bounding_box_train训练集,751 个 ID必需
query查询集,3368 张图必需
bounding_box_test候选集 gallery必需
gt_queryquery 对应的标准答案标注re-ranking 时需要
gt_bbox测试集行人框标注复现论文对比时需要
# 假设工程根目录是 ReID,把数据集整理成工程能认的格式 cd ReID mkdir -p data/market1501 # 用软链接而不是复制,避免占双倍硬盘,也防止误改原始文件 ln -s /path/to/Market1501/bounding_box_train data/market1501/bounding_box_train ln -s /path/to/Market1501/query data/market1501/query ln -s /path/to/Market1501/bounding_box_test data/market1501/bounding_box_test ln -s /path/to/Market1501/gt_query data/market1501/gt_query ln -s /path/to/Market1501/gt_bbox data/market1501/gt_bbox

软链接的 path 要根据实际解压位置调整,不要照抄。这里的关键是保持目录名固定,很多工程在 dataset 代码里硬编码了这些名字,你改了它就得跟着改源码。Market1501 的文件名本身带标签,比如 0002_c1s1_000451_00.jpg,前四位是行人 ID,c1s1 是摄像头号和场景号。有经验的人会用文件名前四位统计训练集 ID 数量,如果比 751 少,多半是解压或移动过程中丢了文件。

3.2 环境配置与最小训练脚本

在跑训练前,先把环境确认到位。ReID 对库版本不挑剔,但 torch 和 torchvision 的版本要配套,CUDA 版本对应错,import torch 直接报错。如果你手头只有普通台式机或笔记本,没法用 GPU,也可以把 device 改成 cpu,batch_size 调到 8 试跑一个 epoch,能完整走通流程就算环境通过。

# 创建虚拟环境并安装依赖,这是最省心的组合之一 conda create -n reid python=3.8 -y conda activate reid pip install torch==1.13.1 torchvision==0.14.1 pip install numpy opencv-python pandas tqdm tensorboard

torch 1.13.1 配 torchvision 0.14.1 在多数显卡驱动下都能直接装到对应 CUDA 版本,不必追新。opencv 用来读图和做数据增强,tqdm 显示进度,tensorboard 看训练曲线,后面排错会用到。深度学习环境配置到这里就够了,ReID 本身没有额外依赖。

训练脚本的主体逻辑在多数工程里一致:构建数据加载器、构建模型、定义损失、循环 epoch。下面这段是清洗后的核心流程,和真实工程对照着看,能快速定位自己改坏的地方。

# 训练主流程核心片段:P×K 采样 + ResNet50 + BNNeck import torch from torch import nn from torch.utils.data import DataLoader # 假设 dataset 已按 Market1501 读入,train_set 返回 img, pid, camid # P×K 采样:P 个身份,每人 K 张图,batch = P*K sampler = RandomIdentitySampler(train_set, num_instances=4) loader = DataLoader(train_set, batch_size=64, sampler=sampler, num_workers=4, pin_memory=True) model = build_model(arch="resnet50", num_classes=751, last_stride=1, neck="bnneck") model.cuda() # 双头损失:交叉熵 + 三元组;Adam 时 lr 建议从 3.5e-4 起步 criterion_id = nn.CrossEntropyLoss() criterion_tri = TripletLoss(margin=0.3) optimizer = torch.optim.Adam(model.parameters(), lr=3.5e-4, weight_decay=5e-4) lr_scheduler = WarmupMultiStepLR(optimizer, milestones=[40, 90], gamma=0.1, warmup_epochs=10) for epoch in range(120): model.train() for imgs, pids, _ in loader: imgs = imgs.cuda() feats, logits = model(imgs) # feats 给三元组,logits 给分类头 loss_id = criterion_id(logits, pids) loss_tri = criterion_tri(feats, pids) loss = loss_id + loss_tri optimizer.zero_grad() loss.backward() optimizer.step()

这段代码是 BoT 风格的训练设置,很多 zip 里的 train.py 就是它的完整版加命令行解析。batch_size 等于 64 时,RandomIdentitySampler 默认是 16 个身份乘每人 4 张图,保证每个 batch 里同 ID 有足够样本供三元组挖掘。Adam 学习率 3.5e-4 配合 warmup,前 10 个 epoch 线性升温,第 40 和 90 epoch 衰减 0.1 倍。loss_id 和 loss_tri 直接相加是最常见的组合,部分工程会乘 0.5 权重,差别不大。

这里有个容易忽略的细节:last_stride=1。ResNet50 默认最后一级下采样 stride 是 2,ReID 为了保留更多空间细节,会把它改成 1,特征图分辨率翻倍,rank-1 通常能涨 2 到 3 个点。如果你的工程里没这个参数,训练一次后指标偏低,先检查它。别让 codex 跑深度学习模板代码时替你悄悄把这类关键参数“优化”掉,ResNet50 的 last_stride、BNNeck、P×K 采样这三个东西缺一个,都算不上合格的 ReID 工程。

3.3 训练中途看什么:日志里 loss 和 Acc 的含金量

训练日志通常长这样:“epoch 5, loss 3.21, acc 62.4, lr 0.00021”。新手最容易盯着 loss,其实更该看定期在验证集上算出来的 Rank-1 和 mAP。交叉熵的 acc 反映分类头学得怎么样,但 acc 高不代表检索排序好,因为检索要看候选集里的整体排序质量。

epoch 10 loss 1.98 acc 78.5 cmc:63.2/79.1 mAP:52.4 lr 0.00018 epoch 40 loss 0.42 acc 92.3 cmc:85.6/93.4 mAP:72.8 lr 0.00018 epoch 90 loss 0.18 acc 96.1 cmc:89.2/95.7 mAP:78.1 lr 0.000035

这份日志是典型的收敛过程:第 10 个 epoch 时 mAP 已过半,说明骨架和采样器接对了;第 40 个 epoch 时 acc 超过 92,但 CMC 还在涨,说明度量头仍有潜力;第 90 个 epoch 后学习率衰减,两个指标继续爬升。如果在第 40 个 epoch 时 mAP 还不到 50,多半是数据加载错了或者采样器没生效,而不是训练不够。先看数据,再调模型,这比加训练轮数更管用。

4. 模型选型与关键策略:ResNet50 + BNNeck + 随机擦除为什么是标配

看完训练流程,自然会问:这个工程里的模型结构能不能换?先说结论:能换,但得不偿失的情况很多。在 ReID 这个方向,模型选型和训练策略绑定得很紧,深度学习模型那块动一个参数,后面的指标和显存可能全变。

4.1 三套主流骨架的取舍:ResNet50、Swin/TransReID 与轻量网络

骨架参数量复现难度适用阶段典型收益
ResNet50约 25M低绝大多数工程默认稳定,和论文易对齐
ResNet50 + IBN约 25M中跨数据集泛化域泛化更好
Swin-T / ViT-Base约 28M/86M中高追求 SOTA指标上线更高,训练敏感
MobileNetV3/轻量骨干约 7M中边缘部署速度优先,精度下降可接受

ResNet50 是 ReID 圈事实上的“工业标准”,论文对比都在它上面跑,复现时最容易对齐指标。Swin 和 ViT 骨架在行人重识别上能到更高精度,但对学习率、warmup、数据增强更敏感,训练不稳是常态,新手拿它当第一版容易翻车。IBN-Net 结构在跨数据集测试时优势明显,如果你后续要在别的监控场景上直接用,它比原版 ResNet50 更值得。

轻量网络适合深度学习模型部署的场景。实时检索系统里,摄像头数量多,单卡推理压力大,MobileNetV3 能把单张图的推理时间压到几毫秒,代价是 Rank-1 降 5 到 8 个点。对工程落地来说,这个差距通常能用 re-ranking 或更好的数据增强补回来一部分。选型没有绝对答案,但第一版复现我一般只会用 ResNet50,因为它把变量控制得最少。

4.2 BNNeck、随机擦除和 P×K 采样各自解决什么问题

这三个名字在 ReID 论文里频繁出现,作用各不相同。BNNeck 解决的是交叉熵和三元组对特征分布要求不一致的问题:交叉熵希望特征的范数大、类别边界明显,三元组希望特征落在超球面上、分布紧凑。BNNeck 在 neck 输出的两个分支上分别做处理,分类头分支加 BN,度量分支不加,损失可以在各自舒适的分布上优化。没有它,两个损失会互相拉扯,训练曲线震荡明显。

随机擦除(Random Erasing)是 ReID 里性价比最高的数据增强。行人经常被遮挡,或者被其他行人挡住,随机擦除一块矩形区域,让模型不要把注意力全压在某个局部部件上。它和普通分类任务里的 CutOut 类似,但擦除比例和时间点对 ReID 更敏感。P×K 采样则是难样本挖掘的基础:随机采样 P 个身份,每个身份取 K 张图,这样每个 batch 内部天然形成正负样本对。下面是一段典型配置:

# 随机擦除与 P×K 采样的典型参数 from random_erasing import RandomErasing train_transform = T.Compose([ T.Resize([256, 128]), T.RandomCrop([256, 128], padding=10), # 先随机裁剪,模拟位移 T.RandomHorizontalFlip(p=0.5), T.ToTensor(), T.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), RandomErasing(probability=0.5, sh=0.2, mean=[0.4914, 0.4822, 0.4465]), ]) # 采样器参数:每 batch 16 个身份,每个身份 4 张图 sampler = RandomIdentitySampler(dataset, num_instances=4, batch_size=64)

擦除概率 0.5、遮挡面积比例上限 0.2,这两个数字是多数论文调试出来的稳定区间。如果你碰到擦除后模型收敛变慢,先降低 probability 而不是取消擦除,取消后 Rank-1 通常掉 2 个点左右。RandomCrop 的 padding=10 也很关键,它给模型提供轻微位移不变性,配合擦除能明显提升跨摄像头鲁棒性。

4.3 数据并行与 batch size 的关系

batch size 在 ReID 里不是简单的显存问题,它直接影响采样质量。P×K 采样下,batch size 是 P 和 K 的乘积,64 的 batch 对应 16 个身份、每个身份 4 张图。如果把 batch size 降到 32,常见操作是改成 8 个身份乘每人 4 张,但 8 个身份会减少难样本挖掘的覆盖面,指标在训练中期就能看出差距。显存不足时,优先降 K 而不是降 P,比如 16 个身份乘每人 2 张,batch 还是 32,身份覆盖面保住,三元组只损失一点。

# 单卡显存不够时的启动参数示例 python train.py --batch-size 32 --num-instances 2 --lr 3.5e-4 # 多卡时先用 torchrun 指定可用 GPU,再按卡数等比放大 batch torchrun --nproc_per_node=2 train.py --batch-size 128 --lr 6e-4

多卡训练时 batch size 翻倍,学习率也要相应调大,常见做法是线性缩放。两张卡从 64 变 128,学习率从 3.5e-4 调到 6e-4,不是简单翻倍,具体倍数靠验证集微调。数据并行时注意每个 GPU 上的 batch 要独立做 P×K 采样,不少工程在分布式改造时把采样器丢了默认随机,指标掉得莫名其妙。

5. ReID 复现避坑:指标对不上、显存不足和数据集损坏的 4 个真实问题

这一章是给“训练能跑但结果不对劲”的人准备的。下面的问题我在不同工程里见过多次,每条都按现象、原因、解决的顺序写,可以直接照着排查。

5.1 坑:zip 解压后路径乱套,训练一启动就报“找不到文件”

现象:解压、整理目录后运行 train.py,报错 FileNotFoundError,但路径看起来是对的;或者在 windows 上解压后训练正常,换到 Linux 上同一份代码报错。

原因:这个 zip 工程在压缩时可能带了中文目录名,或者内部多嵌套了一层文件夹。Windows 上路径大小写不敏感掩盖了问题,Linux 上目录里文件名大小写不一致直接失效。还有一部分工程的 config 里写的是相对路径,从不同目录启动 train.py 结果完全不同。

解决:解压后第一件事是 cd 进工程目录执行 ls,确认数据目录真实位置,然后用软链接统一指向原始 Market1501,不要复制重命名。启动命令固定从工程根目录执行,不要用 python /path/to/train.py 这种跨目录方式。如果数据文件名有中文或空格,先用 rename 批量清掉,ReID 代码普遍不做中文路径处理。

5.2 坑:训练 loss 正常下降,但 Rank-1 和论文差 10 个点以上

现象:训练日志里 loss 从 4 降到 0.3,acc 到 95 以上,但 evaluate.py 算出来的 Rank-1 只有 70 出头,论文里同配置是 89 以上。

原因:最大的可能性是评估阶段和训练阶段数据预处理不一致。训练用随机裁剪、随机翻转、随机擦除,评估应该只用 Resize 加 CenterCrop。如果评估代码里带了 RandomCrop 或漏了 CenterCrop,特征分布对不上,指标直接崩。另一个常见原因是 query 和 gallery 划分错了,有些工程会把 query 图片也塞进 gallery,检索时自己检索自己,mAP 虚高但 Rank-1 偏低,两者矛盾时基本就是数据划分问题。

解决:先固定随机种子,然后把 evaluate.py 里的预处理和训练预处理逐行对比。再检查评估代码里是否在初始化 dataset 时用了 train 模式,有些 dataset 类会在 mode 为 train 时自动加随机增强。最后统计 query 和 gallery 是否有重叠文件名,有就说明划分脚本有问题,回去看数据准备那步。

5.3 坑:batch size 调小后反而不收敛或者收敛极慢

现象:显存不够,把 batch_size 从 64 调到 24 或 16,结果训练 30 个 epoch 后 loss 还在 2 以上,mAP 不到 30。

原因:ReID 的 batch 不适合直接“整体缩放”。batch_size=64 对应 16 个身份乘 4 张图,缩到 24 如果还是随机采样成 24 个不同身份,每个身份只有 1 张图,三元组完全失效,BN 层的统计量也因为 batch 太小而漂移。这属于典型的“参数改了但没改配套结构”。

解决:如果显存只够 batch 24,改成 8 个身份乘每人 3 张图,至少保证每个身份有 3 张正样本。同时把 BN 层改为冻结状态或使用较小的 batch norm momentum,比如 momentum 从 0.1 改成 0.01。再配合梯度累积,每 2 个 batch 更新一次参数,等效 batch 48,这样既保住采样结构,又不至于让 BN 统计量乱跳。

5.4 坑:GPU 显存直接 OOM,连一个 epoch 都跑不完

现象:启动训练后几秒钟报 CUDA out of memory,然后进程退出。有些人把 batch_size 调到 4 能跑,但训练完全失去意义。

原因:显存爆掉不一定全是 batch size 的锅。输入图片尺寸如果从 [256,128] 被改成 [384,192],显存占用翻 2.25 倍;backbone 如果用 ResNet50 的 last_stride=2,特征图尺寸比 last_stride=1 小一半,占用更少但丢细节。还有一个隐藏项是 DataLoader 的 num_workers 开太多,每多一个 worker 都会复制数据到共享内存,峰值显存看似不变但内存带宽吃紧。

解决:先看这个 zip 工程里默认的 Resize 是不是被改成了大尺寸,统一回到 [256,128]。再确认 last_stride 是否等于 1,它影响显存和精度的平衡。显存仍不够时,用梯度累积替代调小 batch,或者开 torch.cuda.amp 混合精度,显存能降一半,ReID 训练对 fp16 的敏感度比检测低得多,基本可以无痛用上。

6. 验证与进阶:用一张 query 手工走一遍检索,再谈值不值得做

训练完不是终点,你得亲眼看到检索结果才敢把它交给下游。这一章只提供一个最小验证链路,以及这条路继续往深走的几个方向。

6.1 手工验证:加载权重、抽特征、算余弦相似度

用 evaluate.py 能看到整体指标,但看单张图的检索排序,能更直观地判断模型学到的是“外观”还是“某个摄像头下的背景”。手工验证只需要加载模型、抽 query 特征、遍历 gallery 算相似度。

# 手工检索验证:输出前 5 个结果的文件名 import torch import torch.nn.functional as F model.eval() with torch.no_grad(): q_feat = F.normalize(model(transform_query(img_query).cuda())[1], dim=1) g_feats = [] # gallery 特征列表,由 evaluate 阶段缓存得到 names = [] # 对应的文件名 for g_img, name in gallery_loader: f = F.normalize(model(g_img.cuda())[1], dim=1) g_feats.append(f); names.extend(name) g_feats = torch.cat(g_feats, dim=0) sims = torch.mm(q_feat, g_feats.t())[0] # 余弦相似度矩阵 top5 = torch.topk(sims, 5).indices.tolist() print([names[i] for i in top5])

这段代码里 model 的输出取索引 [1],是因为前向返回了 (feats, logits) 或 (feats, pooled_feats) 两个值,具体取哪个看工程定义。验证时用 L2 归一化后的特征做矩阵乘法,等价于余弦相似度。如果 top5 里出现了和 query 同一个 ID 但不同摄像头的结果,说明模型学到了跨摄像头不变性;如果 top5 全是同一个摄像头下的图,大概率模型偷懒了,它学到的是环境背景,不是行人本身。

6.2 进阶方向:re-ranking、多尺度测试与部署形态

这个小流程跑通后,你可以按预算选进阶方向。re-ranking 是性价比最高的一项,它把 gallery 内部的结构关系考虑进去,通常能再涨 2 到 5 个点 mAP,但推理时要把整个 gallery 特征送入内存计算,适合离线检索场景。多尺度测试是另一个稳定技巧,把同一张图缩成 192x96、256x128、320x160 三份分别抽特征再拼接,涨点不多但稳定,几乎不花训练成本。

更接近交付形态的做法是:把特征抽取封装成 ONNX 或 TensorRT 的部署接口,前接检测模型,后接向量数据库做近似最近邻检索。ReID 模型本身不复杂,但工程链路比训练长得多。我第一次完整跑通这套链路时,最深的教训是不要在训练脚本里省评估代码,训练和评估的预处理、数据划分必须同一个来源,否则指标永远是自欺欺人。先把这套手工验证跑通,再谈优化,这条路值得投入,它能帮你把零散的检测和目标跟踪串成真正能用的跨镜检索系统。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询