ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

车辆重识别实战:yolov5+reid跨摄像头车辆检索系统详解

车辆重识别实战:yolov5+reid跨摄像头车辆检索系统详解 简介基于YOLOv5与ReID的车辆重识别系统Python源码及配套项目说明、模型文件主要面向计算机、人工智能、数据科学与大数据技术、通信、物联网等专业的在校学生和毕业设计开发者解决跨摄像头场景下的车辆检测、特征提取与身份匹配问题。压缩包共596个文件整体仅26.87MB以Python源码为核心含214个可读脚本与248个pyc编译文件覆盖模型训练、推理及接口调用全流程同时配有68个YAML配置、12个Markdown项目文档、10个XML与若干Dockerfile、Shell脚本方便在不同环境下部署复现另有演示视频和PDF便于直观理解。项目代码经过完整验证、运行稳定并附有详细说明文档可帮助用户理清目录结构与调用关系降低复现门槛。目前已有323人学习下载。资源既适合初学者按步骤入门YOLOv5与ReID技术栈也可在此基础上升级车牌识别、轨迹跟踪等扩展功能适合作为毕业设计、课程设计、大作业或实训项目的起点。1. 车辆重识别不是给车再拍一张照yolov5 负责看哪里reid 负责认出来一套基于 yolov5reid 实现的车辆重识别系统拆开看是两段模型的接力yolov5 先从视频帧里把车框出来reid 模型再把框出来的车压成一个特征向量拿去历史库里比对排序回答画面里这辆车是不是三天前另一台摄像头出现的那辆。它解决的是跨摄像头、跨时间的身份保持问题不是简单的图上有没有车。很多第一次做毕设的同学把 yolov5 当成了重识别本身。yolov5 只能告诉你这是 car 还是 busreid 才负责区分这是哪一辆车。检测、重识别两级串联才构成完整的车辆重识别系统。这套方案适合需要交付 python 源码、项目说明文件和模型文件的毕业生检测侧有成熟预训练权重reid 侧有公开数据集可借力两个模块能分开调试论文也好分章写。下面按原理、跑通、训练、避坑、验证的顺序把这套系统从零讲透。2. 从检测到特征检索车辆重识别的完整链路与 reid 模型的工作原理2.1 检测、跟踪、重识别三件事先别把 yolov5 当 reid 用车辆重识别这个题目里最容易混在一起的是三件不同的事。检测detection回答画面里有没有车、车在哪输出的是边界框和类别。yolov5 在这里干的活就是每帧找车输出car / bus / truck和坐标。跟踪tracking回答相邻两帧里的是不是同一辆常用 ByteTrack、DeepSORT 这类算法在检测框之上做关联。重识别re-id回答的是隔了很久、甚至换了一个摄像头之后还是不是同一辆它不依赖运动轨迹只依赖外观特征。拿 yolov5 的输出直接当身份号是新手最常见的误解。yolov5 对一辆黑色轿车和另一辆黑色轿车都输出car类别相同不代表身份相同。重识别的本质是把检测框里的车提成一个特征向量再和所有历史车辆比对按相似度排序。整个毕设的重识别部分严格说指的是 reid 模型和后面的检索逻辑yolov5 只是给 reid 喂数据的前置检测器两者职责必须分开写进论文里。2.2 reid 模型在做什么把一张车图压成一个可比较的特征向量reid 模型的结构并不神秘大部分开源的车辆重识别模型都是分类骨干网络 特征嵌入的写法。常见做法是拿 ImageNet 预训练的 ResNet50 当 backbone去掉最后的全连接层接一个全局平均池化得到 2048 维特征再经过一个 BatchNorm 和一个线性层压到 512 维。训练时在这个特征后面接一个身份分类头有多少个车辆 ID 就分多少类同时用三元组损失triplet loss拉近同类、推远异类。推理时分类头去掉只保留特征提取的部分。所有车图都会经过同一套预处理得到同一维度的向量然后做 L2 归一化。归一化之后两个向量的余弦相似度就等于它们的点积数值落在 -1 到 1 之间排序和设阈值都稳定。检索时就拿查询图的特征去和库里的所有特征做矩阵乘法一次得到全部相似度取 top-k 输出。这就是 reid 的完整工作方式。需要特别强调reid 模型输出的只是外观相似度不是身份判定。它认为两辆车相似不代表它们一定是同一辆最终结果要靠排序和业务规则去收口。这也是后面调阈值时最容易被忽略的边界。2.3 为什么选 yolov5reid 而不是端到端方案车辆重识别这个题目完全可以做成端到端方案比如 FairMOT、JDE 这类联合检测与重识别的多目标跟踪网络但毕设主线不建议选它。第一个原因是调参成本联合训练要同时优化检测损失、reid 损失和关联损失任何一项失衡都会让整体效果崩掉新手很难定位到底哪部分出了问题。第二个原因是演示风险答辩现场要跑视频端到端模型一旦出现 ID Switch同一辆车中途换编号很难用几句话解释清楚。yolov5reid 的两段式结构最大优势是模块解耦。检测效果不好就单独换检测器不动 reidreid 效果不好就单独重新训练特征提取部分不动检测。论文结构也因此好写先写检测章节再写重识别章节最后写检索与评测。yolov5 侧有 COCO 预训练权重可以直接识别车辆类reid 侧有 VeRi、VehicleID 这些公开数据集和预训练模型可以微调复现门槛比端到端方案低一个量级。2.4 数据流全景query、gallery 与跨摄像头约束整个系统的数据流是这样的给定一张待查车图query先用 yolov5 检测并裁剪出车身区域送入 reid 模型得到查询特征q再从多路摄像头视频里用同样的检测特征提取流程把每一辆出现过的车都提成特征组成候选库gallery特征矩阵G最后计算q与G的余弦相似度降序排列输出前若干结果。这里有一个评测上的硬规矩同一个 ID 的 query 样本和 gallery 样本必须来自不同摄像头。因为重识别要验证的是换了个摄像头还认不认得出来如果 query 和 gallery 里混着同一个摄像头同时段的样本模型只要记住背景就能刷出虚高的分数rank-1 能到 0.9 以上一换真正跨摄像头的数据就掉到 0.5 出头。毕设包里的项目说明通常会把数据集划分方式写清楚动手之前先确认这份划分是否遵循了跨摄像头原则这比先调模型参数重要得多。3. 从零跑通 yolov5reidconda 环境配置、检测命令与特征提取3.1 conda yolov5 环境配置实操先锁 python 与 torch 版本环境配置是这套系统里最玄学的一步绝大多数代码跑不起来都不是代码问题而是 torch、CUDA、python 三方版本打架。我的习惯是先建一个独立 conda 环境把版本一次性锁死再动代码。conda create -n vehicle-reid python3.8 -y conda activate vehicle-reid # 查显卡驱动支持的最高 CUDA 版本再决定装哪个 torch nvidia-smi # 以 CUDA 11.7 为例装对应版本的 torch 与 torchvision pip install torch1.13.1cu117 torchvision0.14.1cu117 \ --index-url https://download.pytorch.org/whl/cu117 # 如果机器没有 N 卡或下载很慢先用 CPU 版把流程跑通 # pip install torch1.13.1 torchvision0.14.1 -i https://pypi.tuna.tsinghua.edu.cn/simplepython 3.8 是 yolov5 和绝大多数 reid 开源代码兼容性最好的版本不建议一上来就用 3.11。torch 的安装要看nvidia-smi输出的驱动版本驱动太老就装不了新 cu 后缀的轮子。装完之后用python -c import torch; print(torch.cuda.is_available())验证输出 True 再继续否则后面所有检测速度都会慢得让人怀疑人生。conda 环境只是隔离了 python 依赖它不决定 CUDA 能不能用。torch 的安装包内部自带 CUDA runtime所以重点是把 torch 版本和显卡驱动匹配上而不是在 conda 里再装一个 cudatoolkit。这个误区能让新手白折腾一整天。3.2 yolov5 跑车辆检测的最小命令与裁剪代码环境就绪后常见做法是从 ultralytics 官方仓库拉一份 yolov5 源码然后先跑通检测。第一次运行会自动下载yolov5s.pt权重之后就能离线用了。git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt python detect.py \ --weights yolov5s.pt \ --source traffic.mp4 \ --conf-thres 0.35 \ --iou-thres 0.45 \ --classes 2 5 7 \ --save-txt --save-conf--classes 2 5 7是 COCO 数据集的类别编号2 对应 car5 对应 bus7 对应 truck。这套参数会把检测结果和标签文件同时保存下来。--conf-thres 0.35表示置信度阈值调低漏检变少但误检变多调高则相反具体值要看你的摄像头距离和画面清晰度。但毕设系统通常不需要把每帧都存成文件更常见的是在代码里直接调用模型、拿到框就裁剪。用 torch.hub 方式加载更灵活import torch import cv2 # 加载本地权重不联网下载 detector torch.hub.load(ultralytics/yolov5, custom, pathyolov5s.pt, force_reloadFalse) detector.conf 0.35 # 置信度阈值 detector.classes [2, 5, 7] # 只保留车辆类 def get_vehicle_crops(frame): results detector(frame) boxes results.xyxy[0].cpu().numpy() # x1,y1,x2,y2,conf,cls crops [] for x1, y1, x2, y2, conf, cls in boxes: x1, y1 max(0, int(x1)), max(0, int(y1)) x2, y2 min(frame.shape[1], int(x2)), min(frame.shape[0], int(y2)) if x2 - x1 10 and y2 - y1 10: # 过滤过小噪点框 crops.append(frame[y1:y2, x1:x2]) return crops cap cv2.VideoCapture(traffic.mp4) ok, frame cap.read() vehicle_list get_vehicle_crops(frame)detector.conf和detector.classes直接改模型对象的属性比每次传参清晰。裁剪后的crops就是喂给 reid 模型的输入。坐标必须做边界截断因为检测框偶尔会超出画面范围直接切片会报错。3.3 加载 reid 模型做一次特征提取先对齐 checkpoint 结构reid 模型的加载最怕的是训练时保存的 checkpoint 结构和推理脚本对不上。先定义一个最常见的推理网络再写一个通用的加载函数能省掉大半的报错。import torch import torch.nn as nn import torchvision.models as models class ReidNet(nn.Module): def __init__(self, feat_dim512, num_classes776): super().__init__() base models.resnet50(pretrainedTrue) self.backbone nn.Sequential(*list(base.children())[:-2]) self.gap nn.AdaptiveAvgPool2d(1) self.bn nn.BatchNorm1d(2048) # 归一化特征分布 self.classifier nn.Linear(2048, num_classes) # 仅训练时使用 def forward(self, x, with_classifierFalse): feat self.backbone(x) feat self.gap(feat).flatten(1) feat self.bn(feat) if with_classifier: return feat, self.classifier(feat) return feat # 通用加载函数兼容 DataParallel 前缀和嵌套 state_dict def load_reid_model(model, ckpt_path): state torch.load(ckpt_path, map_locationcpu) if state_dict in state: state state[state_dict] state {k.replace(module., ): v for k, v in state.items()} model.load_state_dict(state, strictFalse) model.eval() return model net load_reid_model(ReidNet(feat_dim512), vehicle_reid.pth)num_classes要和你训练时的身份数一致不一致会导致分类头加载报错strictFalse是为了先让模型加载成功再看打印的 missing keys 定位问题。加载完成后特征提取还要接一套固定的预处理尺寸、归一化均值方差都必须和训练时一致from torchvision import transforms transform transforms.Compose([ transforms.ToPILImage(), transforms.Resize((256, 256)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), ]) def extract_feature(crop): x transform(crop).unsqueeze(0) # (1,3,256,256) with torch.no_grad(): feat net(x) feat torch.nn.functional.normalize(feat, dim1) return feat # (1,512) 已 L2 归一化Resize((256, 256))是正方形缩放很多车辆 reid 模型用的是(384, 128)这种贴近车身的宽高比。如果换用后者检测裁剪出来的车身比例要基本匹配否则特征会被背景干扰。L2 归一化之后两个特征的相似度直接用矩阵乘法就能算这是后面检索的基础。3.4 低显存运行模型的四个参数显存不够时的取舍顺序毕设常见的显卡是 6G 显存的入门卡检测和 reid 两个模型同时跑很容易爆显存。我的经验是先按下面的顺序调整每一步都重新验证效果再往下走调整项默认值低显存建议效果代价reid 输入尺寸384x128256x128rank-1 会小幅下降通常可接受检测输入尺寸640416 或 480小目标车辆检出率变差检测推理精度fp32开启model.half()需要 GPU 支持 fp16精度略降视频抽帧每帧检测每 3~5 帧检测一次快速移动车辆可能漏检检测侧的model.half()只在 Turing 及以上架构的显卡上有效老卡直接报错。reid 侧不要轻易用 half特征提取对数值精度更敏感归一化之后的小数误差会影响排序稳定性。4G 显存以下建议直接走 CPU 推理把检测输入降到 416reid 用 256x128单路视频也能到实时。如果训练和推理同时进行显存会互相挤占。我一般会把训练和推理拆成两个独立脚本训练完再跑推理验证避免一个进程里同时挂着两个模型的显存开销。低显存运行模型的核心原则是先降输入分辨率再降 batch最后才考虑换更小的骨干网络。4. 自己训练车辆 reid 模型reid 数据集组织、P×K 采样与关键超参数4.1 选 reid 数据集VeRi 与 VehicleID 各自的适用场景车辆重识别最常用的公开数据集是 VeRi 和 VehicleID选哪个取决于你的毕设目标。VeRi 是真实街道场景下 20 台摄像头采集的约 776 辆车、5 万张图像带车牌、颜色、车型属性标注并且官方提供了 train/query/gallery 的固定划分。它最大的价值在于天然满足跨摄像头评测适合做完整的检索链路演示是毕设论文最稳妥的选择。VehicleID 规模更大约 2.6 万辆车但多数样本集中在车头和车尾视角摄像头数量较少评测时需要按官方 protocol 随机切 subset自己写评测逻辑时容易出错。CompCars 偏车辆分类和属性识别图像大多来自网络爬取和周边摄像头不适合直接当 reid 评测集。如果毕设包的项目说明里已经指定了数据集先认真读它的目录约定和划分规则别急着换数据。自己换数据集意味着评测协议、baseline 分数都要重写工作量比想象中大得多。4.2 把标注整理成 train/query/gallery 目录跨摄像头划分是关键reid 数据集的标准组织方式是三个目录train 按身份 ID 分目录如train/0001/xxx.jpgquery 放待查询图像gallery 放候选库图像。原始标注通常是一张 csv 或 txt逐行记录图像路径、车辆 ID、摄像头 ID 和时间戳。import os, shutil, itertools def organize_reid_dirs(src_csv, out_root, train_ids576): src_csv 每行img_path,vehicle_id,camera_id,timestamp os.makedirs(f{out_root}/train, exist_okTrue) os.makedirs(f{out_root}/query, exist_okTrue) os.makedirs(f{out_root}/gallery, exist_okTrue) with open(src_csv) as f: for line in f: img, vid, cam, ts line.strip().split(,) vid, cam int(vid), int(cam) if vid train_ids: dst f{out_root}/train/{vid:04d} else: # 同一 ID 按摄像头奇偶拆到 query/gallery保证跨摄像头 dst (f{out_root}/query/{vid:04d} if cam % 2 1 else f{out_root}/gallery/{vid:04d}) os.makedirs(dst, exist_okTrue) shutil.copy(img, f{dst}/{cam:03d}_{os.path.basename(img)})这里最关键的逻辑是cam % 2的划分同一个 ID 的图像按摄像头编号奇偶拆开query 和 gallery 必然来自不同摄像头。如果偷懒随机切分同一个摄像头的同框画面很可能同时出现在 query 和 gallery 里重识别就退化成背景匹配成绩虚高得毫无意义。这个点也是答辩老师最爱追问的地方务必在项目说明里写清楚划分规则。4.3 P×K 采样与 tripletID 双损失核心训练参数表与训练步写法reid 训练和普通分类训练最大的区别在于 batch 的组织方式。普通分类训练随机采样一个 batch 里可能几十张图全是不同车辆正样本对极少triplet 损失根本学不动。常见做法是 P×K 采样每个 batch 取 P 个身份每个身份取 K 张图保证 batch 内既有正样本对又有难负样本。import numpy as np # 先按 ID 聚合所有图像路径 pid2imgs {pid: [img for img, p, *_ in samples if p pid] for pid in all_pids} # 每个 step 采样 16 个 ID每个 ID 抽 4 张图共 64 张 batch_pids np.random.choice(all_pids, size16, replaceFalse) batch_paths list(itertools.chain.from_iterable( np.random.choice(pid2imgs[pid], size4, replaceFalse) for pid in batch_pids))有了这样的 batch训练 step 的核心就是双损失相加。身份分类损失让每个 ID 的特征可分三元组损失让同类更近、异类更远feat net(imgs) # (B, 512)已 L2 归一化 logits net.classifier(feat) # 身份分类头 loss_ce F.cross_entropy(logits, pids) # 分类损失 sim feat feat.t() # B×B 余弦相似度矩阵 dist 1 - sim loss_tri batch_hard_triplet(dist, pids, margin0.3) loss loss_ce loss_tri loss.backward()batch_hard_triplet对每个 anchor 找 batch 内最远的同类作为正样本、最近的异类作为负样本让最难的样本参与梯度更新。训练时需要注意的核心参数大致如下参数推荐值说明骨干网络ResNet50ImageNet 预训练收敛快特征维度512L2 归一化后的嵌入维度batch 采样16 ID x 4 图显存不够先降到 8 x 4优化器Adamlr3.5e-4比 SGD 对 triplet 更稳学习率策略每 20 轮乘 0.1或 cosine 衰减训练轮数60VeRi 上 40~80 轮可收敛数据增强随机擦除、颜色抖动提升光照泛化随机擦除random erasing对车辆 reid 特别重要。车辆外观大部分是光滑漆面遮挡和反光频繁随机抹掉一块区域能让模型学得更鲁棒的特征而不是死记整体轮廓。4.4 检测侧同步准备把车辆数据转成 yolov5 训练自己的数据集reid 效果好不好一半取决于检测框的质量。如果检测框太松把大量背景裁进车身区域reid 特征会被路面和绿化带污染。所以检测侧也建议用自己场景的数据微调 yolov5这就是常见的yolov5 训练自己的数据集流程。yolov5 的标注格式是每张图对应一个同名 txt每行类别 cx cy w h全部归一化到 0~1。如果手头是 VOC 格式的 xml可以用下面的脚本转换import xml.etree.ElementTree as ET def voc_to_yolo(xml_path, out_txt, class_map{car: 0, bus: 1, truck: 2}): root ET.parse(xml_path).getroot() w int(root.find(size/width).text) h int(root.find(size/height).text) with open(out_txt, w) as f: for obj in root.findall(object): name obj.find(name).text if name not in class_map: continue b obj.find(bndbox) x1 float(b.find(xmin).text); y1 float(b.find(ymin).text) x2 float(b.find(xmax).text); y2 float(b.find(ymax).text) cx (x1 x2) / 2 / w cy (y1 y2) / 2 / h bw max((x2 - x1) / w, 1e-6) bh max((y2 - y1) / h, 1e-6) f.write(f{class_map[name]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}\n)归一化坐标是 yolov5 的硬性要求漏除宽高会让 anchor 全乱训练直接不收敛。类别名必须和 data.yaml 完全一致# vehicle.yaml train: ./dataset/images/train val: ./dataset/images/val nc: 3 names: [car, bus, truck]然后执行训练命令--weights yolov5s.pt表示在 COCO 预训练权重上继续微调比从头训练快得多python train.py --data vehicle.yaml --weights yolov5s.pt \ --epochs 50 --batch-size 8 --img 640检测模型微调一轮之后裁出的车身框会更贴合目标reid 侧的特征质量随之提升。这两个模型看似独立实际上是一根链条上的两环检测框质量差reid 再怎么调参都补不回来。5. 车辆重识别系统避坑指南五个必踩的坑与排查路径5.1 同一辆车被拆成两个 ID阈值设置与特征对齐问题现象查询一辆车返回的 top-10 里找不到它自己或者同一辆车的多张图被 reid 分到了距离很远的位置。原因视角、光照、遮挡都会让同一辆车的外观特征漂移。检测框抖动也会带进不同比例的背景特征向量被污染。如果还设了绝对相似度阈值比如相似度大于 0.75 才算同一辆跨摄像头之后这个阈值很容易把真正的同一辆车挡在门外。解决不要用绝对阈值做身份判定改为在 gallery 内做相对排序只取 top-k。测试时把原图和水平翻转图各提一次特征取平均作为最终特征能明显提升鲁棒性。有多帧抓拍时把同一辆车在多个摄像头下的特征加权平均比用单帧特征稳得多。5.2 不同车被认成同一辆颜色主导特征的翻车现场现象白色同款 SUV 连续出现误匹配rank-1 返回的全是同型号不同车看着全是对的其实全是错的。原因ResNet50 的全局特征里车身颜色和整体轮廓占比太高。当数据集里同色同款车很多时这些特征不足以区分细粒度差异车尾灯、天窗、贴纸等细节全部丢失。解决把 reid 输入从 256x128 提到 384x192分辨率越高细节越容易保留前提是显存够用。训练时加随机擦除和颜色抖动强制模型寻找颜色之外的结构线索。如果还想再进一步可以换成分块特征结构比如把特征图切几块分别池化再拼接但对毕设来说先保证数据增强和分辨率到位效果提升往往比换网络更明显。5.3 6G 显存训练 reid 直接 OOM先动 batch 还是先动分辨率现象batch 设 6416 个身份 x 4 张图ResNet50 前向一次显存直接 out of memory。原因训练时显存占用大头是中间激活值它随 batch 大小和输入分辨率线性增长。6G 显存跑这个配置本身就勉强再加上 BN 的统计量爆掉是正常的。解决按下面的顺序逐步降先把 P×K 从 16x4 降到 8x4这是最有效的再把输入从 384x192 降到 256x128最后开混合精度用torch.cuda.amp.GradScaler包裹训练 step。混精度之后梯度变小要留意 loss 是否还在下降。如果这三步做完还是炸那就只能换 ResNet18 之类的轻骨干或者用梯度累积来模拟大 batch而不是强行塞进显存。5.4 实时视频演示卡在 8 帧检测与 reid 串行执行的延迟瓶颈现象答辩演示视频每帧都跑一遍 yolov5 检测加 reid 特征提取画面卡顿实时性只有 8 FPS现场效果很差。原因检测和 reid 串行执行每帧几十辆车的特征都要重新算计算量全堆在关键路径上。而实际上连续帧之间同一辆车的特征变化很小重复计算纯属浪费。解决三步走。第一视频抽帧检测每 3~5 帧跑一次中间帧沿用上一帧的框。第二同一辆车在连续帧里用 IoU 关联特征做过一次就不再重算只对新增车辆提取特征。第三query 检索放到后台线程界面只负责展示结果不要让检索阻塞视频渲染。演示机性能不足时先把视频降到 960x540 再送检测主观画质损失不大帧率能翻倍。5.5 加载模型报 missing keycheckpoint 前缀与结构不一致现象load_state_dict报 Missing key(s) 或 Unexpected key(s)看一眼 key 发现有的带module.前缀有的模型结构相差一层。原因训练时用了 DataParallel 或多卡保存的权重带module.前缀推理脚本重建的网络结构没有分类头或者分类头名字对不上。更隐蔽的是有些源码把整个 checkpoint 包成一个字典真正权重嵌在state_dict字段里。解决用前面第 3.3 节那个通用加载函数先剥掉嵌套字典再统一去掉module.前缀最后用strictFalse加载。加载后打印模型的缺失 key逐条对照网络定义。如果缺失的是分类头那是正常的推理本来就不需要它如果缺失的是 backbone 卷积层那就是网络结构定义错了得回头检查 backbone 有没有被意外改动。6. 把 yolov5reid 串成可演示的检索链路查询、排序与效果验证6.1 一次完整查询检测裁剪、特征提取与 top-k 排序跑通单点功能之后最后一步是把所有模块串成一条完整的检索链路。核心思路是gallery 特征库提前算好存成文件查询时只对 query 图做一次检测和一次特征提取和整个库做一次矩阵乘法毫秒级返回结果。# 假设 gallery_feats 已提前提取并保存为 .pt形状 (N, 512) gallery_feats torch.load(gallery_feats.pt) gallery_paths torch.load(gallery_paths.pt) def run_query(query_img, topk10): crops get_vehicle_crops(query_img) # yolov5 检测裁剪 if not crops: return None # 画面里没有车 query_crop max(crops, keylambda c: c.shape[0] * c.shape[1]) q_feat extract_feature(query_crop) # (1, 512) 已归一化 sims (q_feat gallery_feats.t()).squeeze(0) # 与全库比对 idx sims.argsort(descendingTrue)[:topk] return [gallery_paths[i] for i in idx], sims[idx]取最大的检测框作为查询目标是为了避免误检的小框干扰。gallery_feats必须在演示前离线构建完成实时去提取库里几千张图的特征任何机器都扛不住。整条链路里yolov5 和 reid 都串在同一份代码里任何一个环节报错都同步暴露问题所以逐个模块验证通过后再合并是最稳的。6.2 用 rank-1、mAP 与 CMC 验证效果把检索结果可视化成一张图效果验证别只看一两张图感觉挺像。车辆重识别有三个固定指标项目说明里一定要写明你用的是哪套评测协议指标含义在毕设里的定位rank-1第一张返回结果就命中最直观演示时看这个mAP所有正确结果在排序中的平均精度比 rank-1 严格论文表格用CMC前 k 位累积命中率曲线画曲线图答辩展示效果好把检索结果拼成一张 top-10 的横向对比图第一列放 query后面十列放检索结果命中项用绿框标出。这张图比任何文字描述都更能说明系统效果答辩时也省去反复切视频的尴尬。我自己的习惯是每次改动之后先跑一遍固定评测集记录 rank-1 和 mAP再决定要不要保留这次改动。曾经有一次调高输入分辨率后 rank-1 涨了 2 个点却被差一个撞色的误匹配打乱了演示节奏最后靠回滚到基线版本救场。从那以后我养成了给每个实验版本存一份权重和对应指标记录的习惯重识别模型的改动经常是此消彼长没有指标兜底很难说清楚到底是变好了还是变坏了。希望帮到你。本文还有配套的精品资源点击获取
返回列表