简介:基于YOLOv5与ReID的车辆重识别系统毕业设计项目,提供完整Python源码、项目说明文档与预训练模型,适合计算机相关专业学生用于毕业设计、课程设计或大作业,也可作为车辆检测与跨镜头追踪方向的学习入门项目。资源共596个文件,以Python源码(py/pyc)、模型配置(yaml/yml)、说明文档(md/pdf)为主,另含Dockerfile、shell脚本、演示视频等,整体压缩包约26.87MB,目录结构清晰,便于按模块查看与二次开发。目前已有323人学习下载。项目中包含车辆检测、特征提取与重识别全流程实现,并附有环境部署所需配置文件和运行脚本,可帮助读者快速跑通系统,理解YOLOv5目标检测与ReID行人/车辆重识别结合的技术方案,为后续功能扩展或论文写作提供扎实参考。
1. 车辆重识别不是“识别车辆”:YOLOv5+ReID 到底在解决什么问题
停车场三个摄像头各拍到一辆白色SUV,普通目标检测只能告诉你“这里有一辆车”,但回答不了“这三段画面里的是不是同一辆”。车辆重识别(Vehicle ReID)补的就是这一步:先靠YOLOv5把车辆检测出来,再用ReID网络提取车辆的外观特征,最后通过特征距离判断同一性。这套资源把检测和重识别串成了一条完整管线,附Python源码、项目说明和训练好的模型,解压就能跑通“视频进、匹配结果出”的全流程。适合做毕设和课设,也适合想搞懂“检测+检索”落地方式的人照着改。
2. 先把环境跑通:版本选型与最小命令集
2.1 环境准备:选对 Python 和 PyTorch 版本
这套系统的技术栈是“YOLOv5检测器 + ReID特征提取网络”,两个部分对版本都很敏感,尤其是PyTorch。YOLOv5官方推荐PyTorch 1.8以上,但ReID部分的常见实现(尤其是从 torchvision 加载ResNet等骨干时)在PyTorch 2.0之后会出现接口变化。稳妥的组合是Python 3.8 + PyTorch 1.10~1.12 + CUDA 11.x,这个区间里YOLOv5和ReID的兼容性都比较好。
conda create -n vehicle_reid python=3.8 -y conda activate vehicle_reid pip install torch==1.12.1 torchvision==0.13.1 --index-url https://download.pytorch.org/whl/cu113 pip install -r requirements.txt我的习惯是新项目一律用conda建独立环境,不往base环境里塞依赖。Python锁3.8是因为老代码里不少依赖还没有兼容3.10以上的版本;torch和torchvision要成套安装,单独装其中一个很容易在导入时出现 undefined symbol 之类的报错。requirements.txt一般包含numpy、opencv-python、pandas、tqdm、pyyaml、matplotlib这些,装完后在命令行里跑python -c "import torch; print(torch.__version__)"验证一下PyTorch是否正常导入。
如果电脑没有NVIDIA显卡,也可以装CPU版torch,但推理速度会慢很多。车辆重识别视频推理时,检测和特征提取都要逐帧计算,CPU模式下720p视频大概只能跑2-3帧每秒,仅适合验证流程,不建议作为演示环境。
2.2 目录结构、路径规范与启动推理
解压后先做两件事:第一,项目路径改成纯英文;第二,确认权重文件在项目内部而不是在压缩包外部。初次跑项目的人最常在这两步上翻车,但这两步也是最容易忽视的。
unzip vehicle_reid_system.zip -d vehicle_reid_yolov5 cd vehicle_reid_yolov5 ls -la解压后你应该看到类似这样的结构:主入口脚本、模型权重目录weights、项目说明文档、检测模块和ReID模块的代码包。项目说明文档里一般会写明入口是哪个文件,如果入口不明确,找带if __name__ == '__main__'的python文件,或者找文件名里含run/inference/main的脚本。
提示:项目路径绝对不能带中文和空格,这是YOLOv5读取配置文件时的硬伤。比如
/用户/下载/毕业设计/这种路径,在加载 yaml 配置时会因为编码问题直接抛 FileNotFoundError,并且报错信息指向的文件未必存在,排查起来很费时间。
启动推理的命令一般长这样,具体参数会根据项目说明里的主入口脚本名调整:
python run.py \ --source test_video.mp4 \ --det-weights weights/yolov5s.pt \ --reid-weights weights/reid_resnet50.pth \ --conf-thres 0.4 \ --sim-thres 0.65--source支持视频文件、图片文件夹、摄像头设备号,甚至RTSP流;--det-weights是YOLOv5检测权重,--reid-weights是车辆重识别特征提取权重;--conf-thres控制检测置信度,我一般会从默认的0.25调到0.4,减少背景误检;--sim-thres是特征相似度阈值,高于这个值才判定为同一辆车。跑完后的带框标注视频和匹配结果会输出到runs/目录下,这里面既有标注了ID的视频文件,也有每帧的检测结果表格。
2.3 推理输出怎么读
输出目录里核心的产物有两类:一类是标注视频,另一个是车辆匹配记录。标注视频里每辆车会有一个矩形框和ID编号,同一ID表示系统判定为同一辆车;匹配结果是结构化表格,包含帧号、检测框坐标、车辆ID和相似度分数。
看输出时要抓住三个关键指标:检测框数量、特征提取状态、匹配结果。检测框数量不是越多越好,视频里一辆车被框出十几个框通常意味着置信度阈值太低;特征提取状态看日志里是否有feat dim: 2048类似的输出,有的话说明ReID模型成功加载;匹配结果则看相似度分数分布,跑完后扫描一遍相似度低于0.5却仍然匹配的case,这些大概率是误匹配。
3. 核心链路拆解:检测、裁剪、特征、匹配四步怎么串
3.1 YOLOv5车辆检测与类别过滤
YOLOv5自带的yolov5s.pt是COCO数据集训练权重,检测范围覆盖80类,但车辆重识别只关心车。COCO中与车相关的有 car对应id 2、bus对应id 5、truck对应id 7,其他类别如“摩托车”和“自行车”在车辆重识别场景下应该直接过滤掉。加载模型后设置model.classes可以只保留目标类别,这比在后处理里人工过滤更省算力。
import torch detector = torch.hub.load('ultralytics/yolov5', 'yolov5s', pretrained=True) detector.conf = 0.4 detector.iou = 0.45 detector.classes = [2, 5, 7] # COCO类别id:car/bus/truck detector.max_det = 50 results = detector(frame) # 输入为BGR的numpy数组 boxes = results.xyxy[0].cpu().numpy() # 每行: x1,y1,x2,y2,conf,clsdetector.conf是置信度阈值,建议卡在0.4到0.5;detector.iou是NMS的IoU阈值,默认0.45基本不用动;classes限定只输出指定类别,这一步能省掉大量误检——在停车场场景里,行人、交通标志都会干扰后续的车辆特征提取。max_det限制单帧最大检测框数量,防止密集场景下ReID一次性处理过多裁剪图导致显存溢出。
3.2 车辆图像预处理与ReID特征提取
检测到车辆框后,需要把每个框对应的局部图像裁剪出来,送入ReID网络提取特征。ReID网络的输入尺寸通常和分类网络不同,常见的是256×128,也有用224×224的;这个长宽比是为了适配车辆和行人这种“竖高”目标。裁剪框直接用slicing截取,再加一个letterbox适配输入尺寸,不要直接cv2.resize拉变形。
import cv2 import torch from torchvision import transforms class VehicleReID: def __init__(self, ckpt_path, device='cpu', input_size=(256, 128)): self.device = device self.input_size = input_size checkpoint = torch.load(ckpt_path, map_location=device) # 不同实现的权重保存格式不同,可能是model本身或state_dict,按项目说明取用 self.model = checkpoint['model'] if isinstance(checkpoint, dict) and 'model' in checkpoint else checkpoint self.model.eval().to(device) self.transform = transforms.Compose([ transforms.ToPILImage(), transforms.Resize(input_size), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ]) def extract(self, crop): if crop is None or crop.size == 0: return None x = self.transform(crop).unsqueeze(0).to(self.device) with torch.no_grad(): feat = self.model(x) # L2归一化:让特征向量的模长为1,方便后续用点积算余弦相似度 feat = torch.nn.functional.normalize(feat, p=2, dim=1) return feat.cpu().numpy()[0]归一化用的mean和std是ImageNet统计值,大多数从ImageNet预训练转过来的ReID骨干都沿用这一套,不需要自己重算。最关键的是normalize(feat, p=2, dim=1)这行——归一化后特征向量的模长为1,后续点积运算的结果就等于余弦相似度,省掉了余弦公式里的除法,也让不同批次的特征分数处于同一个量级。如果没有这一步,直接拿未归一化特征算距离,光照、角度变化都会把相似度分数整体抬高或压低,阈值就没法用了。
ReID模型输出维度因骨干网络而异,常见情况如下表:
| 骨干网络 | 输入尺寸 | 输出维度 | 说明 |
|---|---|---|---|
| ResNet50 | 256×128 | 2048 | 最常用,速度和精度均衡 |
| ResNet101 | 256×128 | 2048 | 精度略高,推理更慢 |
| OSNet | 256×128 | 512 | 轻量级,适合嵌入式设备 |
模型输出维度和相似度计算没有关系,但和显存占用、比对速度相关。特征维度越高,单条特征占用的内存越大,在车辆库很大的场景下做全量比对时耗时差别会体现出来。
3.3 特征匹配:用向量点积替代循环
车辆重识别最朴素的做法是对目标和候选库里的每辆车算余弦相似度,按分数排序。手工两层循环当然能跑,但库大了以后性能很差,正确做法是把库里的特征堆成一个 (N, dim) 的大矩阵,用一次矩阵乘完成全量比对。
import numpy as np def match(query_feat, gallery_feats, threshold=0.6): # gallery_feats: (N, dim),每行都已做过L2归一化 # query_feat: (dim,),同样已归一化 scores = gallery_feats @ query_feat order = np.argsort(-scores) hits = [(int(i), float(scores[i])) for i in order if scores[i] >= threshold] return hitsgallery_feats @ query_feat是矩阵乘,等价于对每一行算点积。因为特征都已归一化,点积结果就是余弦相似度,范围在-1到1之间。argsort(-scores)按相似度降序排列,返回候选库里的索引和分数。threshold是判断“同车”的硬阈值,车辆外观相近时不同车的相似度也可以到0.5以上,所以这个值要从0.5开始往高扫描,看测试集上的误匹配率来定。
匹配结果出来后,系统一般会把同一个ID的检测框拼接成一条轨迹,或者输出一张“查询车辆 → 候选车辆按相似度排序”的表格。这一步是整个系统的最终出口,检测或特征提取的误差都会在这里被放大。
4. 排查与避坑:从环境报错到匹配结果不对的五个坑
4.1 配置和权重文件因中文路径加载失败
现象:项目解压后按默认路径直接运行,启动阶段报 FileNotFoundError,报错指向的yaml或pt文件明明存在,但程序就是读不到。
原因:YOLOv5的配置文件依赖路径字符串,Windows下中文路径经过编码转换后和实际文件名不一致,OpenCV和PyTorch在读文件时直接失败。
解决:项目目录必须纯英文。操作上是先解压到英文目录,再重命名为vehicle_reid这类名字,最后检查project和name配置里不能包含中文。不要试图去设置系统编码UTF-8来绕过,这个坑在Windows 10的老版本编码设置下很难根治,改路径是最省事的方式。
4.2 检测置信度太低导致ReID特征里混入大量背景
现象:车辆匹配结果张冠李戴,两辆完全不同的车被分到同一个ID,检查裁剪图发现很多框其实框住了车身周围一大片地面。
原因:YOLOv5默认置信度阈值0.25,在复杂场景下会输出大量低质量检测框,车身只占框的一部分,ReID网络提取的特征里车身信息被背景稀释。
解决:训练时把detector.conf提到0.4以上,低于这个分数的直接丢弃;同时给检测框加一个5%-10%的内缩量,把边缘的冗余背景去掉。裁剪时用margin参数做内缩:
x1, y1, x2, y2 = box[:4].astype(int) w, h = x2 - x1, y2 - y1 mx, my = int(w * 0.05), int(h * 0.05) x1, y1, x2, y2 = x1 + mx, y1 + my, x2 - mx, y2 - my crop = frame[y1:y2, x1:x2]这个内缩量不是固定值,要根据应用场景调整。园区监控里车辆占画面比例大,内缩5%足够;高速公路卡口场景,车辆占满整个画面,内缩可以到10%。
4.3 GPU显存不足,推理时直接OOM
现象:程序能正常启动,但跑到视频中段时显存不足报错,或者batch_size设置稍大就直接崩溃。
原因:YOLOv5推理和ReID特征提取都占显存。ReID模型输入batch size为16时,单张256×128图片的显存开销大约0.3G,但检测部分的多尺度推理会临时占用更多显存。4G显存的卡很容易吃满。
解决:显存低于6G时,把ReID的batch size降到4;检测端开fp16半精度推理;如果视频较长,按帧间隔抽取关键帧处理,而不是每帧都做检测和特征提取。实际工程里我一般跳帧间隔设为3-5帧,车辆重识别不比实时跟踪,不需要逐帧处理。
4.4 PyTorch版本过高导致YOLOv5的Upsample报错
现象:运行检测代码时报AttributeError: 'Upsample' object has no attribute 'recompute_scale_factor'。
原因:PyTorch 1.11之后nn.Upsample的内部实现变了,旧版YOLOv5代码里自动计算scale factor的写法不再兼容。这是YOLOv5多年以来最常见的版本兼容报错,属于“祖传坑”。
解决:优先把PyTorch锁到1.10-1.12区间,和项目的依赖说明保持一致。如果必须用新版PyTorch,找到报错堆栈里upsample相关的代码,给nn.Upsample显式指定scale_factor,不要依赖recompute_scale_factor自动计算。我的建议是直接换环境,不要改源码,改源码会牵连后续的模型导出和部署流程。
4.5 同一辆车在视频里被分配多个ID
现象:一辆车从画面左边开到右边,ID跳了三四个;或者车辆被遮挡几帧后重新出现,系统认为是新车。
原因:纯ReID单帧匹配没有时序约束,某一帧检测漏了、特征抖动大,前后帧的特征相似度低于阈值就会断链。
解决:加一个简单的滑动窗口投票机制——连续5帧里同一位置区域出现3次以上相似度超过阈值的匹配,才确认ID;单帧的高相似度匹配先保留为候选。这种方案比直接引入DeepSORT式跟踪器轻量,不需要额外训练,原理也不复杂。每次帧匹配时维护一个候选字典,键是候选ID,值是最近5帧的相似度列表,列表满5帧后取平均,平均值过阈值才确认。
5. 进阶验证:阈值扫描、骨干替换与一套检查习惯
5.1 用测试集做阈值扫描,不看单一案例
特征相似度阈值和检测置信度阈值是这套系统里最有“手感”的两个参数,直接凭几个视频片段调不准。正确做法是准备一小组标注好的测试片段,里面明确标注了哪些帧是同一辆车,然后写一个扫描脚本,遍历候选阈值,把每个阈值下的匹配准确率打出来。
thresholds = [0.3, 0.4, 0.5, 0.6, 0.65, 0.7, 0.75] for th in thresholds: acc = evaluate(th) # 在标注数据上跑匹配,统计同车召回率和误匹配率 print(f"th={th:.2f} acc={acc:.3f}")输出结果呈现的是一个单调变化曲线:阈值越低,召回率越高但误匹配暴涨;阈值越高越干净,但很多本应匹配上的车被漏掉。常规选择是取误匹配率低于5%时的最大阈值。从那以后,我每次部署这套系统,都强制走一遍“阈值扫描、类别过滤验证、路径检查”三件套,确认无误后再交付演示,这习惯帮我避开了不少交付时的翻车现场。
5.2 换ReID骨干,代码改动只有一个地方
嫌ResNet50效果不够好,想换OSNet或BoT这类更强骨干时,改法集中在模型实例化处。ReID在车辆场景下的骨干选型参考:
| 骨干 | 输出维度 | 车辆ReID特点 | 适用场景 |
|---|---|---|---|
| ResNet50 | 2048 | 通用性强,训练资料多 | 快速出成果 |
| OSNet | 512 | 轻量,小目标友好 | 边缘设备、实时推理 |
| BoT(Bag of Tricks) | 2048 | 精度高,对视角变化鲁棒 | 数据集大、追求精度 |
换骨干时要注意ReID网络输出的特征维度会变。把extract方法里模型的实例化和输出维度处理同步改掉就行,特征匹配部分的矩阵乘代码不受维度影响;换完后必须重新走阈值扫描,因为不同骨干网络产出的相似度分数分布并不一致。如果还想继续加深效果,可以用VeRi这类车辆重识别公开数据集微调ReID模型,ResNet50在VeRi上微调后rank-1通常比直接套用预训练权重提升15个百分点以上。这套系统本身已经把数据加载、训练脚本都搭好了,照着项目说明里的训练命令跑一遍就能看到微调前后的差异,这也是项目里最值得钻研的部分。希望帮到你。
本文还有配套的精品资源,点击获取