行人重识别ReID项目实战:从特征提取到检索排序的完整源码解析

发布时间:2026/10/12 20:23:36

行人重识别ReID项目实战:从特征提取到检索排序的完整源码解析 简介本资源面向计算机视觉研究者、算法工程师及具备深度学习基础的学生提供一套完整的行人重识别ReID与图像检索实战项目。项目以深度神经网络提取行人特征为核心覆盖行人检测、特征提取、特征匹配与排序等关键环节并针对跨摄像头视角、光照变化及遮挡等难点给出可复现的实现方案适用于安防监控、智能交通与零售分析等场景。压缩包共94个文件约632.5MB以69个Python源码为主体辅以yml配置文件、pth预训练权重、sh训练脚本及txt说明文档涵盖数据预处理、模型构建、训练验证、测试部署全流程并包含Market1501数据集接入与重排序等模块。目前已有462人学习下载。读者可借助完整源码与预训练权重快速搭建实验环境理解ReID算法细节与图像检索流程并在此基础上开展算法对比、调优与二次开发。1. 行人重识别 ReID 到底在解决什么问题从一张抓拍图到跨镜头的同一人监控室里最常见的场景一台摄像机拍到某个穿黑外套的人三小时后另一台摄像机又拍到类似身影但角度、光线、遮挡全变了。人眼要花十几分钟比对而行人重识别ReID要做的就是让算法在几秒内告诉你「这两张图是不是同一个人」。它本质是一个图像检索问题——给定一张查询图Query在候选图库Gallery里按相似度排序把最像的排前面。ReID 和普通人脸识别最大的区别在于人脸识别依赖清晰正脸而 ReID 用的是整个人体的外观、衣着、姿态、步态等特征所以它能在人脸拍不清、背对镜头、戴口罩的场景下继续工作。这套方案适合做安防检索、智慧园区轨迹还原、零售客流分析的工程师也适合想拿一个完整项目练手图像检索 pipeline 的同学。标题里提到的「项目源码」落地形态通常就是一个「特征提取 向量检索 评估」的闭环下面我按这个闭环把每一步拆开讲。2. ReID 的技术骨架特征提取、度量学习与检索排序怎么串起来2.1 为什么 ReID 不是简单的图像分类很多人第一反应是「训个分类模型把每个人当一个类别不就行了」。这条路在 ReID 里会翻车原因是训练集里的人ID和测试集里的人完全不重叠你分类分得再好测试时遇到没见过的人照样抓瞎。所以 ReID 的核心不是「认出是谁」而是「判断两张图是不是同一人」这是一个度量学习Metric Learning问题。常见做法是训练一个 CNN 或 Transformer 主干网络把每张行人图映射成一个固定维度的特征向量比如 2048 维然后让同一个人的向量在特征空间里靠得近不同人的向量离得远。损失函数一般用三元组损失Triplet Loss配合 ID 分类损失联合训练。三元组损失的关键是「难样本挖掘」从一批数据里挑出最难区分的正样本对和负样本对逼着网络去学那些容易混淆的细节比如两个人都穿黑裤子但鞋子不同。2.2 主干网络与特征维度的选型逻辑主干网络的选择直接决定精度和速度的平衡。常见做法是追求精度用 ResNet50-IBN 或更强的 Transformer 结构如 TransReID追求部署速度用 ResNet18 或 MobileNet。IBN 模块Instance-Batch Normalization对 ReID 特别有用因为它能缓解不同摄像头之间的风格差异——同一个人的衣服颜色在不同相机白平衡下会偏色IBN 让网络对颜色偏移更鲁棒。特征维度一般取 2048 或 1024维度越高表达能力越强但检索越慢。我一般会先把特征做 L2 归一化这样后续用余弦相似度检索时内积就等于余弦值省一次计算。下面是一个最小可跑的特征提取代码骨架用 PyTorch 写主干换成你本地已有的权重即可import torch import torch.nn as nn import torchvision.models as models class ReIDBackbone(nn.Module): def __init__(self, num_classes751, feat_dim2048): super().__init__() # 用 ResNet50 做主干去掉最后的全连接层 resnet models.resnet50(pretrainedTrue) self.backbone nn.Sequential(*list(resnet.children())[:-1]) # 输出 (B,2048,1,1) self.feat_dim feat_dim # BNNeck在特征后加一个 BN 层缓解 ID 损失和三元组损失的冲突 self.bnneck nn.BatchNorm1d(feat_dim) self.bnneck.bias.requires_grad_(False) # BN 的 bias 不参与更新 self.classifier nn.Linear(feat_dim, num_classes, biasFalse) def forward(self, x): feat self.backbone(x).flatten(1) # (B,2048) feat_bn self.bnneck(feat) # 用于分类的归一化特征 if not self.training: return nn.functional.normalize(feat_bn, dim1) # 推理时输出归一化特征 cls_score self.classifier(feat_bn) return feat, cls_score # 训练时返回原始特征和分类分数这段代码里几个参数值得说清楚num_classes要设成训练集的实际 ID 数比如 Market1501 是 751feat_dim跟主干输出对齐ResNet50 是 2048BNNeck 的作用是让分类损失和三元组损失不在同一个特征空间里打架这是很多 ReID 论文验证过的稳定技巧。推理阶段只返回归一化后的特征向量检索时直接算余弦相似度即可。2.3 检索排序从特征向量到 Rank 列表拿到 Query 和 Gallery 的特征后检索就是算相似度矩阵并排序。最朴素的是暴力枚举——把 Query 和每张 Gallery 图算一次余弦相似度复杂度 O(N)。Gallery 上万张时还能接受上百万张就得换近似最近邻ANN方案比如 Faiss 的 IVF 索引。但 ReID 里有个比暴力枚举更值钱的技巧叫「重排序Re-ranking」核心思想是如果一张 Gallery 图同时和 Query 以及 Query 的 Top-K 邻居都很像那它更可能是正确匹配。k 互近邻重排序能把 mAP 提升好几个点代价是计算量增加。下面是最小检索代码import numpy as np import torch def compute_dist(query_feat, gallery_feat): # 特征已 L2 归一化内积即余弦相似度 sim torch.mm(query_feat, gallery_feat.t()) # (Q, G) return sim def rank_list(sim, topk10): # 按相似度从高到低排序返回索引 values, indices torch.sort(sim, dim1, descendingTrue) return indices[:, :topk] # 假设 query_feat 和 gallery_feat 都是 (N,2048) 的归一化张量 sim compute_dist(query_feat, gallery_feat) topk_idx rank_list(sim, topk10) print(Top-10 候选索引, topk_idx[0].tolist())compute_dist用矩阵乘法一次性算完所有 Query 和 Gallery 的相似度比循环快几个数量级。rank_list返回每张 Query 的 Top-K 候选索引实际评估时拿这个索引去和真实标签比对算 Rank-1 和 mAP。注意特征一定要先归一化否则相似度会被特征模长带偏。3. 用 Market1501 跑通训练与评估数据组织、损失配置与指标解读3.1 数据集目录结构与 DataLoader 的坑ReID 最常用的公开数据集是 Market1501它包含 6 个摄像头、1501 个行人 ID、三万多张图。目录结构一般是Market-1501-v15.09.15/bounding_box_train、bounding_box_test、query三个文件夹文件名格式是0002_c1s1_000451_03.jpg其中0002是行人 IDc1是摄像头编号。写 DataLoader 时有两个坑第一训练集要按 ID 采样保证一个 batch 里每个 ID 至少出现 4 张图否则三元组损失找不到正样本对第二测试集的bounding_box_test里包含-1开头的干扰图junk images评估时要过滤掉。常见做法是用一个自定义 Samplerimport os from torch.utils.data import Sampler import random class RandomIdentitySampler(Sampler): def __init__(self, data_source, batch_size, num_instances4): self.data_source data_source self.batch_size batch_size self.num_instances num_instances # 按 ID 分组 self.index_dic {} for idx, (img_path, pid, camid) in enumerate(data_source): self.index_dic.setdefault(pid, []).append(idx) self.pids list(self.index_dic.keys()) self.num_pids_per_batch batch_size // num_instances def __iter__(self): batch_idxs_dict {} for pid in self.pids: idxs self.index_dic[pid].copy() if len(idxs) self.num_instances: idxs random.choices(idxs, kself.num_instances) random.shuffle(idxs) batch_idxs_dict[pid] [idxs[i::self.num_instances] for i in range(self.num_instances)] # 每个 epoch 重新打乱 avai_pids self.pids.copy() random.shuffle(avai_pids) final_idxs [] for pid in avai_pids: final_idxs.extend(batch_idxs_dict[pid].pop(0)) return iter(final_idxs) def __len__(self): return len(self.data_source)num_instances4表示每个 ID 在一个 batch 里抽 4 张图batch_size一般设 64 或 128这样每个 batch 覆盖 16 或 32 个 ID。这个 Sampler 是 ReID 训练能收敛的前提少了它三元组损失基本学不动。3.2 损失函数配置ID Loss 与 Triplet Loss 的权重训练时一般用两个损失相加ID 分类损失交叉熵和三元组损失。权重上ID Loss 权重设 1.0Triplet Loss 权重设 1.0 是常见起点。三元组的 margin 参数一般设 0.3太小了学不到区分性太大了训练不稳定。下面是一个简化的训练循环import torch.nn.functional as F def train_one_epoch(model, dataloader, optimizer, criterion_id, criterion_tri, device): model.train() for imgs, pids, camids in dataloader: imgs, pids imgs.to(device), pids.to(device) feat, cls_score model(imgs) loss_id criterion_id(cls_score, pids) loss_tri criterion_tri(feat, pids) # 三元组损失内部做难样本挖掘 loss loss_id loss_tri optimizer.zero_grad() loss.backward() optimizer.step()criterion_tri可以用torchreid里的TripletLoss它内部实现了 batch-hard 挖掘。注意feat是 BNNeck 之前的原始特征cls_score是 BNNeck 之后的分类分数这样两个损失各取所需。3.3 Rank-1 与 mAP 怎么算才不虚高评估指标里 Rank-1 是「Top-1 命中率」mAP 是「平均精度均值」。算 mAP 时要对每张 Query 算 AP再对所有 Query 取平均。这里有个容易虚高的点如果 Gallery 里同一个 ID 有多张图命中一张就算对但 AP 会考虑所有正确匹配的排序位置。常见做法是过滤掉同摄像头且同 ID 的图因为同摄像头同 ID 可能是同一张图的副本只保留跨摄像头匹配。评估代码骨架def evaluate(sim, query_pids, query_camids, gallery_pids, gallery_camids): indices torch.sort(sim, dim1, descendingTrue)[1] all_AP, all_R1 [], [] for i in range(sim.shape[0]): order indices[i] # 过滤同摄像头同 ID 的干扰项 keep ~((gallery_pids[order] query_pids[i]) (gallery_camids[order] query_camids[i])) order order[keep] matches (gallery_pids[order] query_pids[i]).float() if matches.sum() 0: continue # Rank-1 all_R1.append(matches[0].item()) # AP cum torch.cumsum(matches, 0) precision cum / torch.arange(1, len(matches)1, devicematches.device) AP (precision * matches).sum() / matches.sum() all_AP.append(AP.item()) return sum(all_R1)/len(all_R1), sum(all_AP)/len(all_AP)keep那行是关键它把同摄像头同 ID 的 Gallery 图排除掉避免「自己匹配自己」导致指标虚高。跑完一轮训练后Rank-1 能到 90% 以上、mAP 到 75% 以上说明 pipeline 基本正常。4. 避坑与排查ReID 项目里最容易翻车的 5 个地方4.1 现象训练 loss 不降Rank-1 一直在 1% 左右原因最常见的是 DataLoader 没按 ID 采样一个 batch 里全是不同的人三元组损失找不到正样本对梯度几乎为零。解决换成RandomIdentitySampler确保每个 batch 至少覆盖 4 个 ID、每个 ID 至少 4 张图。如果换了还不降检查特征是否做了 L2 归一化没归一化时余弦相似度会被模长主导。4.2 现象训练集精度很高测试集 Rank-1 很低原因过拟合到训练集的 ID 和摄像头风格。解决加数据增强随机擦除 Random Erasing、颜色抖动加标签平滑Label Smoothing或者换 IBN 主干。另外检查测试时是否误用了训练集的 BN 统计量推理时要用model.eval()固定 BN。4.3 现象检索结果里同一张图反复出现原因Gallery 里有重复图或高度相似的连续帧。解决评估前对 Gallery 做去重或者用 k 互近邻重排序把「和 Query 邻居也像」的图排前面抑制孤立的高相似度噪声。4.4 现象显存爆了batch_size 上不去原因ReID 输入分辨率一般是 256x128比分类的 224x224 宽高比不同显存占用更高。解决用混合精度训练AMP或者把主干换成 ResNet18或者用梯度累积模拟大 batch。注意 batch_size 小于 16 时 BN 统计量会不准这时候可以冻结 BN 或换 Instance Norm。4.5 现象换了自己的数据集后ID 数对不上分类层报错原因num_classes写死了 Market1501 的 751自己的数据集 ID 数不同。解决训练前先扫一遍训练集目录统计唯一 ID 数动态设置num_classes。另外自己的数据集如果每个 ID 只有两三张图三元组损失很难训建议先做数据清洗或改用对比学习方案。5. 把 ReID 推到可用重排序调参、特征融合与部署前验证重排序是 ReID 里性价比最高的提点手段但参数调不好反而掉点。k 互近邻重排序有两个关键参数k1 控制「Query 的邻居范围」k2 控制「Gallery 的邻居范围」常见起点是 k120、k26lambda 设 0.3。调参时先固定 k2 和 lambda把 k1 从 10 扫到 40看 mAP 变化再固定 k1 扫 k2。注意重排序对 Gallery 规模敏感Gallery 小于 1000 张时提升不明显上万张时能提 3 到 5 个点 mAP。特征融合是另一个实用技巧把全局特征和局部特征拼起来。全局特征用整张图提局部特征把图水平切成 6 条每条单独提特征再拼。这样对遮挡和姿态变化更鲁棒代价是特征维度翻几倍检索变慢。我一般只在精度不够时上局部特征日常用全局特征加 BNNeck 就够了。部署前验证有个血泪经验一定要用真实摄像头抓的图跑一遍别只看 Market1501 的指标。公开数据集是裁剪好的行人框真实场景里检测框会抖、会截断、会包含背景。我一般会拿 50 张真实抓拍图做 Query人工标 200 张 Gallery 的匹配关系跑一遍看 Rank-5 能不能到 80% 以上。如果掉得厉害优先检查检测框质量而不是急着换模型。最后说个我自己的习惯每次改完代码先跑一个 10 个 ID 的小子集确认 loss 能降到接近 0、检索能命中再上全量数据。这个「小数据冒烟测试」帮我省过无数次通宵排查。ReID 这个方向模型结构固然重要但数据组织、采样策略和评估细节往往才是决定成败的地方。希望帮到你。本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/10/12 20:23:36

Python+MediaPipe实现仰卧起坐自动计数:姿态估计与状态机详解

简介:这份资源面向对人工智能与计算机视觉感兴趣的开发者、健身应用方向的学习者,以及需要完成体能测试类课程设计的学生,核心解决的是如何用 Python 实现仰卧起坐动作的自动计数与个数统计问题。包内共 5 个文件,以 py 脚本与 mp…

2026/10/12 20:18:36

Docker入门实战指南:容器化部署、镜像构建与高频避坑全解析

简介:一份面向初学者的Docker基础入门指南,聚焦容器技术的核心概念与实操技能,适合有一定计算机基础、希望通过容器实现快速部署和环境一致的开发者。内容从Docker与虚拟机的差异讲起,详细说明镜像作为只读模板、容器作为运行实例…

2026/10/12 20:18:36

FreeRTOS调度原理深度解析:从任务状态到上下文切换的工程实践

1. 从裸机到多任务:为什么需要一套调度器刚接触嵌入式开发那会儿,我写的代码基本都是一个大循环包打天下。while(1)里面塞上按键扫描、串口收发、LED闪烁,靠一堆if-else和延时函数硬撑。这种写法在功能简单的时候确实够用,代码量小…

2026/10/12 23:09:37

TensorFlow 2.1 Windows安装完全指南:CUDA与cuDNN踩坑全记录

过去两年里我帮好几个朋友解决过 Windows 下的 TensorFlow 安装问题,其中“TensorFlow 2.1”这个版本被提到的频率出乎意料地高。按道理它早就不是最新版了,但很多公司内部项目、老教材、开源仓库的依赖锁定文件里,都写着tensorflow-gpu2.1.0…

2026/10/12 23:09:37

华为OLT常用查询命令:故障定位与日常巡检指南

简介:这是一份华为OLT常用基本查询命令的实操文档,面向接入网运维工程师、装维人员及刚接触FTTH设备的技术人员,以MA5680T设备为对象,集中整理日常维护中的高频命令。文档按使用场景分类,涵盖ONU信息查询(d…

2026/10/12 23:09:37

计算机网络课程设计:电子图书馆网站的网络协议与部署实践

简介:《计算机网络》课程设计配套文档,围绕“电子图书馆网站设计”展开,面向网络工程、计算机及相关专业的学生,用于完成课程设计、巩固TCP/IP模型与组网知识。资料以1000M主干网、100M到点、至少4个子网为背景,完整呈…

2026/10/12 23:09:37

迷宫所有最短路径求解:反向BFS距离场与DFS剪枝实战

初看“pta-7-52 求解迷宫从入口到出口的所有最短路径(DFS)”这个题,很多人会觉得不就是个递归搜索嘛,结果动手一写就卡住了。其实这道题把DFS、回溯、剪枝、最短路预计算四个知识点全串在一起,而且“所有最短路径”这个要求,直接让…

2026/10/12 23:04:35

分库分表与多数据源切换的本质区别与协同落地

简介:本资源是一份基于Sharding-JDBC实现分库分表与多数据源动态切换的完整Java Web实战项目,面向中高级Java开发者及分布式架构学习者,聚焦高并发、大数据量场景下的数据库水平扩展痛点。项目以SSMDemo为载体,涵盖分片规则配置、…

2026/10/11 0:02:13

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/12 12:01:26

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/12 12:54:07

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/12 0:04:22

绝缘子缺陷检测数据集清洗与工业级训练实战指南

简介:本资源是面向电力AI研发人员、工业视觉工程师及智能巡检系统开发者的绝缘子缺陷检测专用YOLO格式数据集,解决无人机航拍场景下绝缘子破损、污闪、积雪等9类典型缺陷的精准识别与定位难题。数据集共2139张真实巡检图像(含训练/验证/测试集…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑