news 2026/10/19 12:01:47

YOLOv5+DeepSORT车辆检测与跟踪项目全解析:数据集、训练与避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
YOLOv5+DeepSORT车辆检测与跟踪项目全解析:数据集、训练与避坑指南

简介:一套融合YOLOv5与DeepSORT的车辆检测与追踪项目资源,面向计算机视觉开发者、算法研究人员以及智能交通应用工程师,适用于实时交通监控、停车场管理、无人驾驶环境感知等场景,能有效降低目标检测与多目标跟踪的技术门槛。压缩包共含2000个文件,整体约29.76MB,其中1588个xml标注文件保存车辆位置信息,409个txt文件多为数据划分列表与标签说明,另有2个md说明文档和1个Python脚本,脚本可用于自动拆分训练集与验证集,方便快速进入模型训练流程。当前已有150人学习浏览。项目提供处理好的数据集以及训练好的模型,免去数据采集与人工标注的繁琐工作,同时保留了清晰的目录组织和可复用的工程脚本。YOLOv5负责高效准确的目标检测,DeepSORT在遮挡、重叠情况下仍能稳定追踪车辆,两者结合可直接迁移到智能交通、车流统计等真实任务中,无论是算法验证、毕业设计还是二次开发,都能在此基础上快速展开。

1. 点开视频那一刻的惊喜:一个YOLOv5+DeepSORT车辆检测项目到底能省多少事

把一段高速收费站监控扔给一个只有YOLOv5的脚本,它会在每一帧画出所有车辆框,但你追问“画面里这辆车到底是刚才那辆还是新来的”,它答不上来。这个车辆检测项目把YOLOv5和DeepSORT接成了一条完整链路:YOLOv5负责实时给出检测框,DeepSORT负责把检测框变成带ID的稳定轨迹,还附带了已经清洗、标注、划分好的车辆数据集和训练验证集划分脚本,拿到手不用从标注工具开始熬三天。适合两类人:做车辆跟踪毕业设计或工程Demo的开发者,以及想对比不同检测器接DeepSORT效果的研究者。

2. 项目解剖:从README到car_net配置,它到底给了你什么

2.1 资源清单:别被一堆txt吓到

打开压缩包的第一印象是“东西不多”,README.md、split_train_val.py、val.txt,加上一排car_net_5.txt、car_net_7.txt、car_net_40.txt、car_net_141.txt、car_net_151.txt、car_net_161.txt。没有满屏的源码文件,也没有一堆训练日志,这份资源的重点在“配置、脚本、数据”这三个环节上。

文件作用
README.md项目说明、环境准备、模型与数据集的用法入口
split_train_val.py把图片列表按比例划分成训练集和验证集
val.txt验证集图片路径列表,一行一张图片
car_net_*.txtcar_net系列的模型结构配置,数字代表不同规格

car_net这几个txt是最容易被误会的文件。我第一次看也以为里面是车目标标注,打开才发现是网络结构的参数描述。不同数字对应不同的深度、宽度、anchor组合,实际训练时可以按显存和精度要求选一份。数字小的适合边缘设备,数字大的代表更深的网络结构,追求精度但显存消耗也更大。README里应该有对应的说明,我拿到手之后先花五分钟把这几个文件的内容看一遍,比到处问人“为什么报错”有用得多。

2.2 数据集已经被处理成了什么样子

目标检测项目最耗时间的环节不是训练,而是数据准备。原始视频要抽帧、筛掉模糊的、用标注工具画框、导出YOLO格式,再处理类别不平衡。这套项目里附带的数据集已经走到了最后一步:图片和标签文件按YOLO惯例放在images和labels两个目录下,标签文件是一个个txt,每行代表一个目标。

0 0.516406 0.430556 0.123438 0.259722

这一行五列的含义分别是:类别编号、归一化后的目标中心点x坐标、中心点y坐标、目标宽度、目标高度。类别0对应的就是“car”。坐标全部除以图片宽高做了归一化,所以无论图片是1080p还是720p,训练时都无需逐张处理。val.txt里保存的则是验证集图片的相对路径或绝对路径,YOLOv5在训练和验证时直接读这个列表去定位图片。这种结构最大的好处是:你拿到数据后不需要再做任何格式转换,直接写一个data yaml指向train.txt和val.txt就能开训。

2.3 split_train_val.py 脚本逐行拆

项目里的split_train_val.py是标准的数据集划分脚本,代码不长,但思路值得抄。常见实现长这样:

import os import random import argparse def main(): parser = argparse.ArgumentParser(description='Split dataset into train/val') parser.add_argument('--image_dir', type=str, default='images', help='image folder') parser.add_argument('--label_dir', type=str, default='labels', help='label folder') parser.add_argument('--ratio', type=float, default=0.8, help='train ratio') args = parser.parse_args() names = [f for f in os.listdir(args.image_dir) if f.endswith(('.jpg', '.png', '.jpeg'))] random.shuffle(names) split_idx = int(len(names) * args.ratio) with open('train.txt', 'w', encoding='utf-8') as f_tr, \ open('val.txt', 'w', encoding='utf-8') as f_val: for i, name in enumerate(names): image_path = os.path.join(args.image_dir, name) label_path = os.path.join(args.label_dir, os.path.splitext(name)[0] + '.txt') if not os.path.exists(label_path): continue if i < split_idx: f_tr.write(image_path + '\n') else: f_val.write(image_path + '\n') if __name__ == '__main__': main()

这个脚本做三件事:先把图片文件名全部读出来并随机打乱,再按ratio算出训练集和验证集的分界下标,最后写出train.txt和val.txt。需要注意的是它不复制图片,只写路径,这样不会产生冗余数据。参数上,--image_dir是图片目录,--label_dir是标注目录,--ratio默认0.8表示80%数据用于训练,20%用于验证。脚本里加了一个保护逻辑:如果某张图片找不到同名标签文件就跳过,避免把没有标注的坏样本带进训练集。这套处理思路比单纯按文件名前几位做硬划分靠谱得多,因为视频抽帧出来的相邻帧内容高度相似,直接顺序划分会让训练集和验证集内容重叠,随机打乱能显著改善验证指标的可信度。

3. 核心实现:YOLOv5检测框如何变成DeepSORT的稳定ID

3.1 先想清楚DeepSORT到底缺什么

DeepSORT是对SORT的升级,SORT只靠卡尔曼滤波预测目标位置、用匈牙利算法做检测框和轨迹的匹配,缺陷是目标一旦被遮挡再出现,就很容易被当成一个新目标。DeepSORT增加了一个ReID外观特征提取器,每个跟踪目标会保存外观特征,遮挡后靠特征相似度找回同一个目标。但DeepSORT本身不产生检测框,它只是消费检测结果的跟踪器。如果你喂给它的检测框质量差,后面的联动匹配全部白搭。

这就解释了为什么YOLOv5和DeepSORT是常见搭配:YOLOv5速度快、对小目标检测效果好,能够在每一帧给出足够稳定的框;DeepSORT把这些框作为观测值,结合历史轨迹预测当前帧每个目标的期望位置,再做匹配。两者的边界清晰:检测器负责“看见”,跟踪器负责“记住谁是谁”。车辆这种刚性物体运动规律强,外观变化小,非常适合这种组合。

3.2 一个Tracker类把它们拧到一起

实际工程里我不会在每一帧里零散地调用两个模型的API,而是封装成一个VehicleTracker类,让主循环只面对一个update方法。这个类的常见实现如下:

import torch from yolov5.models.experimental import attempt_load from yolov5.utils.general import non_max_suppression from deep_sort.deep_sort import DeepSort class VehicleTracker: def __init__(self, yolo_weights, reid_weights, device='cpu'): self.device = device self.detector = attempt_load(yolo_weights, map_location=device) self.tracker = DeepSort( reid_weights, max_dist=0.2, min_confidence=0.3, nms_max_overlap=1.0, max_iou_distance=0.7, max_age=70, n_init=3, nn_budget=100 ) def detect(self, frame_bgr): # OpenCV读进来是BGR,YOLOv5训练时用的是RGB,需要先转换 img = torch.from_numpy(frame_bgr[:, :, ::-1].copy()).permute(2, 0, 1).float() / 255.0 pred = self.detector(img.unsqueeze(0).to(self.device), augment=False)[0] pred = non_max_suppression(pred, conf_thres=0.4, iou_thres=0.45)[0] dets = [] if len(pred) == 0: return dets for box in pred.cpu().numpy(): x1, y1, x2, y2, conf, cls = box if int(cls) == 0: # 只保留车辆类别 dets.append([x1, y1, x2, y2, conf]) return dets def update(self, frame_bgr): detections = self.detect(frame_bgr) return self.tracker.update(detections, frame_bgr)

detect方法里做了三步关键处理:颜色通道转换、归一化、NMS去重。x1/y1/x2/y2是检测框左上角和右下角在原始图片上的像素坐标,conf是置信度。这里只保留类别0(车辆),如果你的数据集把“卡车”“轿车”分成了不同类别,可以改成多个类别号。DeepSort类的几个参数有实际意义:max_dist是ReID特征余弦距离阈值,越小越难匹配,适合遮挡严重的场景调大一点;min_confidence是检测置信度下限,低于这个值的框不会进入跟踪器;max_age是轨迹丢失后最多保留的帧数,车辆短暂被挡时靠它续命;n_init是Track初始化需要的连续匹配帧数,防止一个误检框就建立一条假轨迹。update返回的是一个列表,每个元素是[x1, y1, x2, y2, track_id],track_id是整个视频流内唯一且尽量稳定的车辆编号。

3.3 主循环:视频流里保持ID稳定

有了Tracker类,主循环就非常简单了:

import cv2 cap = cv2.VideoCapture('demo.mp4') tracker = VehicleTracker('your_yolo_weights.pt', 'your_reid_weights.pt', device='cuda:0') while True: ret, frame = cap.read() if not ret: break tracks = tracker.update(frame) for x1, y1, x2, y2, track_id in tracks: cv2.rectangle(frame, (int(x1), int(y1)), (int(x2), int(y2)), (0, 255, 0), 2) cv2.putText(frame, f'ID:{track_id}', (int(x1), int(y1) - 8), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2) cv2.imshow('vehicle-tracking', frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()

这个循环里没有手动调阈值、没有自己写IOU计算,所有逻辑都在Tracker里完成了。track_id不会像单纯YOLOv5那样每次都重新生成,而是由DeepSORT内部维护。车辆被前车挡住几帧后重现时,卡尔曼滤波会预测被遮挡期间它大概的位移,ReID特征再确认外观,所以只要遮挡不超过max_age,ID就能延续。实际部署时要注意:如果视频分辨率高、GPU性能弱,建议在进入Tracker前先对帧做等比缩放,或者设置跳帧策略,每两帧跑一次检测,中间帧直接用上一帧的跟踪结果做插值。

4. 训练与配置:用项目自带的数据集重训一个car_net

4.1 把car_net_5.txt变成YOLOv5能吃的配置

YOLOv5训练需要两类配置:data配置描述数据集路径和类别名,模型配置描述网络结构。项目里的car_net_*.txt负责后者。最常见做法是,在项目根目录下建一个car_net.yaml作为data配置:

# car_net.yaml train: ./data/car/train.txt val: ./data/car/val.txt nc: 1 names: ['car']

train和val指向split_train_val.py生成的txt文件,nc是类别数,names是类别名列表。如果项目里只有一个车辆类别,nc就是1。至于car_net_5.txt这类文件,训练时放在--cfg参数位置。不同的数字对应不同的网络深度与宽度组合,我一般会先试car_net_5.txt,因为它结构更小,显存占用低,迭代一轮验证集指标的时间短,方便快速确认数据路数通不通。等基本流程跑通了,再换更大的car_net_141或car_net_161去追精度。

4.2 训练命令与关键参数

确认配置无误后,从YOLOv5主目录执行训练:

python train.py \ --data car_net.yaml \ --cfg car_net_5.txt \ --weights yolov5s.pt \ --batch-size 16 \ --epochs 100 \ --img 640 \ --patience 10 \ --device 0

--data指向刚才写的car_net.yaml,--cfg指向选定的模型配置。--weights推荐用官方yolov5s.pt做迁移学习起点,因为车辆检测和COCO的通用目标检测共享大量底层特征,从预训练权重开始收敛快得多。--batch-size受显存约束,8G显存跑yolov5s建议从16起步,显存不足就降到8或4。--img是输入分辨率,640是训练速度和精度比较平衡的点,如果后续要部署到Jetson这类设备,可以提前用448或416训练,推理时会快一截。--patience是早停参数,验证集mAP连续10轮不提升就自动停止,避免一直空跑。首次训练建议不要上来就用超大模型和超长epoch,先让项目跑通一次全流程,确认数据读取、loss计算、评估环节都正常,再开始调参数。

4.3 跑一次验证:val.txt在这里起什么作用

训练结束后,用val.txt做离线评估:

python val.py \ --data car_net.yaml \ --weights runs/train/exp/weights/best.pt \ --img 640 \ --batch-size 8

val.py会自动读取car_net.yaml里指定的val.txt,对每个图片做推理,然后和标签算mAP@0.5、mAP@0.5:0.95、Precision、Recall这几项指标。单类别车辆场景下,mAP@0.5反映的是“车检出率”的主要参考值,mAP@0.5:0.95则更严格,它对框和真实框的贴合程度更敏感。如果你的项目最后要接DeepSORT,我更关注Recall和检测框稳定性,因为跟踪器非常讨厌“时隐时现”的检测结果——这一帧多出一个框,下一帧又丢掉,DeepSORT会误判目标离开,产生大量虚假轨迹。

5. 避坑现场:五个最常见的跑不通和性能翻车点

5.1 环境三连:numpy版本、torch版本、OpenCV后端

第一个坑出现在import阶段。现象:import deep_sort时报错,提示numpy.ndarray size changed,或者某个方法从numpy里找不到。原因:DeepSORT早期实现依赖numpy 1.x的二进制接口,而新装的YOLOv5环境默认拉到了numpy 2.x,二进制的ABI对不上。解决:新建一个conda环境,先把numpy降到1.24.x再装其他依赖,顺序反过来容易白折腾。

第二个坑是RTSP视频流打不开。现象:本地mp4用cv2.VideoCapture读没问题,换成网络摄像头地址就超时或黑屏。原因:OpenCV自带的ffmpeg后端对某些RTSP编码格式兼容性差。解决:确认opencv-python装了带ffmpeg的版本,或者在读取端改用ffmpeg命令行拉流,把流转成帧队列再喂给Tracker。

第三个坑是GPU利用率很高但FPS上不去。现象:显卡风扇狂转,监控里显示GPU占用接近100%,但画面卡顿。原因:推理时没有关闭梯度计算,输入也没有切半精度,检测器默认走了FP32全精度反向链路。解决:在detect方法外层套上torch.no_grad(),模型加载后调用.half(),输入张量也转成img.half(),FP16推理在主流显卡上能换来接近一倍的帧率提升。

5.2 数据与配置:val.txt路径分隔符、car_net被误当标注

第四个坑非常隐蔽。现象:训练一开始就报train: No labels in ...,但val.txt明明存在,标签目录里也确实有txt。原因:split_train_val.py在Windows上生成的路径用反斜杠分隔,比如images\frame_001.jpg,而Linux训练时反斜杠会被当作普通字符,导致路径失效。解决:写脚本时不要直接拼字符串路径,用os.path.join生成后统一replace('\\', '/'),或者训练前在Linux下对train.txt和val.txt做一次路径替换。

第五个坑是把car_net_141.txt误当成标注文件。现象:把car_net_*.txt复制到labels目录后,训练时标签解析全部异常,loss剧烈抖动。原因:文件名里带“car_net”非常像目标标注文件,实际它保存的是模型结构参数,不是YOLO标注。解决:动手前先随便打开一个txt看一眼,如果是“0 0.5 0.5 0.1 0.2”这种五列数字才是标注,如果是一堆网络层描述就属于模型配置,放到--cfg参数去用。这个简单的检查能省掉一晚上的排查时间。

5.3 跟踪效果为什么会崩

第六个坑出现在跟踪环节。现象:车辆明明一直停在画面里,ID却每隔几秒变一次,计数系统不停重复累加。原因:min_confidence设太高,YOLOv5对远处车辆输出的置信度本来就偏低,直接被过滤器丢掉;或者DeepSORT的ReID权重没真正加载成功,外观特征退化成了全零向量,匹配完全退化成SORT行为。解决:先把min_confidence降到0.25到0.3,同时打印一次tracker初始化日志,确认真实读取了reid权重文件路径。如果ID仍然跳,把max_age从70调到120,让轨迹在目标短暂离开后保留更久。记住一个原则:检测不稳时先调检测,不要急着调跟踪参数。

6. 二次开发:把车辆检测改成你想要的目标跟踪

6.1 一个最容易上手的改造:接RTSP实时流

把本地mp4换成摄像头流,是这套项目落地到真实场景的第一步。改动非常小:

import cv2 cap = cv2.VideoCapture('rtsp://your_ip:554/live') cap.set(cv2.CAP_PROP_BUFFERSIZE, 2) while True: ret, frame = cap.read() if not ret: continue tracks = tracker.update(frame) # 其余绘制逻辑不变

关键点是设置CAP_PROP_BUFFERSIZE为较小的值,否则解码器内部积压的帧会越来越多,造成画面延迟越来越大。实测里几行代码就能让项目从“跑通demo”变成“跑真数据”。

6.2 再做一点:给轨迹加ROI计数

如果业务需要统计某个区域进出车辆数,可以拿track_id做去重。项目中每个track_id对应一辆被持续跟踪的车,记录它第一次出现在画面时的ID,放入一个set,最后统计set的大小,就得到总车辆数。如果要统计跨线车流,只需记录每个ID上一次的框中心点和当前框中心点,判断两者是否落在ROI线的两侧,是则计数加一。这套改造不涉及模型训练,只改后处理逻辑,适合验证DeepSORT的稳定度。

6.3 验证方法

改完代码不要急着接真摄像头。我先拿一段录好的混合场景视频,人工数一遍实际车辆数,再跑算法看统计结果。对比时重点看三个数:检测漏掉的车辆数、ID跳变导致的重复计数、以及车辆被完全遮挡后是否还能对得上号。我的习惯是先把检测置信度调到0.3,让“漏检”和“误检”大致平衡,再针对记录里偏差最大的那一段单独抽帧检查是检测器问题还是跟踪器问题。从那以后我每次接手跟踪项目,都强制走一遍“数据路径检查 → 检测单帧效果确认 → 跟踪ID稳定性观察”的顺序,不再上来就调一堆参数。希望帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/19 11:48:27

MySQL增删改查核心操作详解:INSERT SELECT UPDATE DELETE实战

MySQL这玩意儿&#xff0c;干开发的绕不开&#xff0c;做运维的绕不开&#xff0c;搞数据分析的同样绕不开。你去看招聘要求&#xff0c;十个岗位里八个写着"熟悉MySQL"&#xff0c;但真正问起来&#xff0c;很多人对增删改查的理解还停留在"会写几条SQL"的…

作者头像 李华
网站建设 2026/10/19 11:46:28

Python构建可审计算法决策系统:让每次决策有迹可循

算法决策系统这几年越来越常见&#xff0c;从信贷审批、价格调整到内容推荐&#xff0c;后台都是代码在替业务拿主意。可“拿主意”这三个字&#xff0c;通常在系统上线前没人仔细琢磨&#xff0c;等出了问题&#xff0c;比如某批用户被误伤、决策结果解释不清、监管或业务方要…

作者头像 李华
网站建设 2026/10/19 11:46:20

MySQL锁机制详解:并发控制与死锁优化策略

明白&#xff0c;这个标题确实值得好好展开。我准备了大量关于锁机制、并发控制、死锁排查与优化的实战内容&#xff0c;但需要你补全以下输入信息&#xff0c;我才能按你的具体要求输出完整博文&#xff1a; 项目标题: MySQL--》理解锁机制中的并发控制与优化策略 项目正文:…

作者头像 李华
网站建设 2026/10/19 11:42:45

嵌入式AI语音识别:在Cortex-M4上实现本地化语音控制

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/19 11:40:01

VC++6.0 MFC计算器完整实现与答辩级调试指南

简介&#xff1a;本资源是一份基于VC6.0与MFC框架开发Windows桌面计算器的完整实践项目&#xff0c;面向C初学者及Windows GUI编程入门者&#xff0c;解决从零构建带消息响应、控件交互与数值运算功能的MFC对话框应用这一典型学习痛点。压缩包共31个文件&#xff0c;含6个头文件…

作者头像 李华
网站建设 2026/10/19 11:39:35

国产MCU选型实战指南:从参数对标到场景穿透

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华