简介:这是一份基于YOLOv8与ByteTrack的车辆多目标检测追踪与交通流量统计系统实现,面向智能交通、计算机视觉方向的开发者与学生,可用于实时车辆识别、跨帧追踪、计数统计及道路监控优化等场景。资源包共12个文件,整体大小约70.72MB,包含Python源码脚本、演示视频、效果图片、说明文档及备份文件,目录结构简明,便于直接运行和二次开发。已有69人浏览/学习。通过源码可掌握YOLOv8目标检测、ByteTrack轨迹关联、车辆计数与类型区分等核心逻辑;演示视频和截图能直观展示检测追踪效果,帮助快速验证算法流程、理解交通流量统计的实现思路。系统还涉及违章停车、违规变道等行为的自动识别扩展方向,适合希望将目标检测与多目标跟踪落地到智能交通项目的入门及进阶学习者参考。资源来源于网络分享,仅限学习交流使用。
1. 实时车辆检测追踪与流量统计,一套系统解决的路口排队问题
当路口摄像头对着车流,你想回答的其实不是一个“有车没车”的问题,而是一连串问题:此刻画面里有多少辆车?每辆车从哪来、往哪去?这个方向一分钟过了多少辆?如果把问题拆给技术系统,它就是标题里那套组合——YOLOv8把每一帧里的车辆框出来,ByteTrack给每个框一个稳定ID,再把同一辆车的框串成轨迹,最后用一条虚拟检测线或“车进车出”区域完成交通流量统计。这套思路适用于城市路口、高速匝道、园区出入卡口,能直接给信号灯配时、拥堵预警和调度系统喂数据。适合准备做毕业设计、课程项目,或刚接手一个实时监控类业务的工程师。全文不绕弯子,按「为什么这样选、怎么搭起来跑通、参数怎么调、坑在哪」的顺序写,你能照着复现的最小命令和判断逻辑都在下面。
2. 为什么这一套组合能覆盖“检测+追踪+统计”全链路
2.1 YOLOv8在“只检测车辆”这件事上的三个理由
车辆目标本身的尺寸分布很宽,近处大巴、远处轿车,尺度变化大。YOLOv8相比前代最大的变化是换成了Anchor-Free检测头,直接回归中心点和宽高,对尺度差异的容忍度更好。同时它默认C2f结构比C3更擅长融合多尺度特征,配合SPPF,小目标的召回率比YOLOv5同参数量下有可见提升。对车辆数据来说,你不需要像分割任务那样精细,一个框加置信度就够,YOLOv8的模型体量从n到x可选,给“实时”留了很大余量。我一般会在GPU上用s或m,在CPU盒子上用n或s,在RK3588这类边缘设备上会再压缩成INT8。
第二个理由是部署生态。YOLOv8官方提供ultralytics库,训练、验证、导出ONNX/TensorRT都一条命令,这对不打算长期维护检测代码的流量统计项目太关键了。第三个理由是踩坑成本低,网上关于YOLOv8环境搭建、YOLOv8训练参数含义、数据集处理的资料密度很高,你遇到一个怪问题,搜一下就有解决方案,这在工程排期里是实打实的时间红利。
2.2 ByteTrack为什么被选中:低阈值关联加上“不分家”的轨迹
检测模型只负责当前帧的框,而“这辆车到底是刚进画面还是上一帧那辆”,必须靠追踪器回答。ByteTrack的核心思路是“不做太复杂的外观匹配,靠IoU和低置信度框把轨迹续上”。它放弃了DeepSORT里那套ReID特征,先用高置信度框做关联,再用低置信度框补关联,从而把遮挡和漏检导致的ID Switch压下去。对车辆这种外型相似、速度方向规律的目标,这个策略很有效,而且它没有任何额外训练负担,直接吃检测器输出。
ByteTrack的默认状态也是为行人/车辆场景调的,它的卡尔曼滤波器用的是匀速模型,适合机动性不强的交通流。对比SORT,优势在于低置信度框的使用;对比DeepSORT,少了ReID模型的推理开销。所以它的推荐适用场景正是这里的“路况车流”:密集、外形相似、变化平缓。
2.3 从一帧原始画面到一组流量数字,数据流是怎么走的
整套系统的数据流可以拆成四段。第一段是视频输入,通过OpenCV的VideoCapture从摄像头或MP4文件读帧;第二段是检测,YOLOv8把每帧变成一组坐标框和置信度;第三段是追踪,ByteTrack把框序列关联成带ID的轨迹,输出当前帧每个人目标的ID和框;第四段是统计,在画面里预先画好一条虚拟检测线或一个虚拟出口区域,每次某个ID的框中心穿越这条线或进入离开这个区域,计数器加一。这里有个工程上的关键选择:统计放在追踪之后而不是检测之后,因为只有同一辆车的轨迹形如“在t帧经过线”,你才能准确去重,不会因为一帧漏检导致同一辆车被统计两次。
我见过不少把检测框直接拿去做交点判断的系统,一旦目标短暂消失又出现,计数就翻倍。所以建议你无论如何先从检测和追踪两条链路跑通,再把统计逻辑接在追踪结果上。
3. 环境搭建与车辆数据集准备:先让YOLOv8能为你所用
3.1 Ubuntu 20.04上的YOLOv8环境搭建,CPU版三条命令跑通
常见做法是先搭好Python环境再装ultralytics,CPU版本安装不依赖CUDA,反而能提前把数据链路跑通,方便后面换GPU。以下命令在Ubuntu 20.04上可以直接执行:
# 创建独立环境,避免污染系统Python python3 -m venv yolo_venv source yolo_venv/bin/activate # 安装CPU版PyTorch,坑少且不强行拉CUDA pip install torch==2.2.2+cpu torchvision==0.17.2+cpu -f https://download.pytorch.org/whl/torch_stable.html # 安装ultralytics,自带YOLOv8命令工具 pip install ultralytics第一步创建venv环境是必须做的,因为ultralytics会拉大量依赖。第二步CPU版PyTorch如果不指定index,pip会默认装CUDA版,后面在无GPU机器上运行反而会报找不到驱动。第三步ultralytics的whl包较大,如果网络慢可以加-i指定国内镜像。装完后验证一下安装是否可用,代码是yolo predict source='https://ultralytics.com/images/bus.jpg',看到输出结果中有人和车的框,说明环境OK。
3.2 用Labelme标注车辆数据集后,处理成YOLOv8能用的格式
YOLOv8训练的标签是txt文件,每行内容是“类别id 中心x 中心y 宽度 高度”,坐标全部归一化到0到1,文件名必须和图片同名且后缀是.txt。Labelme标出来的格式是JSON,需要转换。网上很多人拿到一批Labelme标注后不知道怎么转,这里给出去掉透明背景的转换脚本最核心的部分:
import json import os from PIL import Image def labelme2yolo(json_path, img_path, save_dir, categories): with open(json_path, 'r', encoding='utf-8') as f: data = json.load(f) img = Image.open(img_path) w, h = img.size yolo_lines = [] for shape in data['shapes']: label = shape['label'] points = shape['points'] # 取矩形框左上角与右下角 x1, y1 = points[0] x2, y2 = points[1] cx = (x1 + x2) / 2 / w cy = (y1 + y2) / 2 / h bw = abs(x2 - x1) / w bh = abs(y2 - y1) / h yolo_lines.append(f"{categories[label]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") save_name = os.path.basename(json_path).replace('.json', '.txt') with open(os.path.join(save_dir, save_name), 'w') as f: f.write('\n'.join(yolo_lines))这个脚本的核心逻辑是把Labelme的矩形框坐标从绝对像素值转成相对宽高比例。categories是一个字典,比如{"car":0,"bus":1,"truck":2}。你需要注意的点是YOLOv8要求中心点坐标,所以这里要除以图片宽高,很多人错在这里直接存了左上角坐标。还有就是Labelme允许多边形标注,如果你的标注是描边车辆形状而不是矩形,要先把多边形换算成外接矩形,否则训练出来的框会偏。处理完标签后还要按比例切分训练集和验证集,通常8:2或9:1,路径组织成images/train、labels/train这样两个根目录,YOLOv8直接认这个结构。
3.3 训练车辆模型时必调的参数与损失函数曲线怎么看
训练命令本身不复杂,复杂的是参数。一个最基本的训练命令是:
yolo detect train data=vehicle.yaml model=yolov8n.pt epochs=100 imgsz=640 batch=16 lr0=0.01vehicle.yaml的写法大概是train: /home/user/vehicle/images/train、val: /home/user/vehicle/images/val、nc: 3、names: ['car','bus','truck']。model=yolov8n.pt表示用COCO预训练权重做迁移学习,这比从头训练收敛快得多。epochs对于车辆这类类别差异不算大的任务,100轮一般足够;如果你数据量很大到上万张,可以增加到200轮。imgsz除非你想看在更低分辨率下的表现,否则固定640。batch大小由显存决定,GTX1660Ti这种6GB显存跑640分辨率,batch设8到16比较稳。
训练完成后在你设定的runs/detect/train目录里能看到results.png,它包含损失函数曲线图。如果你要单独画损失曲线,可以解析训练日志里的loss值:train/box_loss是边界框损失,train/cls_loss是分类损失,train/dfl_loss是分布损失。一个判断标准是:如果val/box_loss在前30轮还在下降,说明没收敛完,要加epoch;如果训练损失下降但验证损失在第50轮回升,说明过拟合,该加数据增强或者减小模型规模。很多人只看总损失,不拆分量,但box_loss对车辆框的位置精度更敏感。
3.4 用训练好的模型跑单帧推理,确认检测输出格式
训练完之后,你可以先拿一张测试图跑推理,验证结果:
yolo predict model=runs/detect/train/weights/best.pt source=test.jpg save=True如果要拿到可编程调用的结果,不只是存图,需要在Python里手动推理:
from ultralytics import YOLO model = YOLO('runs/detect/train/weights/best.pt') results = model.predict('test.jpg', verbose=False) boxes = results[0].boxes for box in boxes: xyxy = box.xyxy.cpu().numpy()[0] # 左上角右下角坐标 conf = box.conf.cpu().numpy()[0] cls = box.cls.cpu().numpy()[0] print('车辆框坐标', xyxy, '置信度', conf, '类别', cls)这里的xyxy是像素坐标,后面做ByteTrack关联时要直接喂这种格式。很多新手在这里顺手把框缩放了,导致ByteTrack匹配错乱。所以建议你把xyxy、conf、cls三个变量直接组装成一批检测结果,再调用ByteTrack。
4. 用ByteTrack把检测结果串成轨迹,再让流量数字从屏幕里走出来
4.1 YOLOv8检测结果如何喂给ByteTrack,最少依赖的对接方式
ByteTrack在GitHub上有官方版本提供ByteTracker类,但官方代码为了集成不同检测器,写得比较重。在流量统计项目里,我建议直接拿官方核心文件里最必要的部分,只保留STrack、BaseTrack、KalmanFilter和主类,避免多余的缓存逻辑。喂给追踪器的数据是一系列检测结果,每个检测结果包含框坐标、置信度、类别。这些信息要组装成ByteTrack需要的输入格式:
import numpy as np from pathlib import Path import sys sys.path.append('bytetrack_path') from mstracker import ByteTrack tracker = ByteTrack(min_box_area=300, track_thresh=0.45, match_thresh=0.8) def feed_to_tracker(results): dets = [] for box in results[0].boxes: xyxy = box.xyxy.cpu().numpy().squeeze().tolist() conf = float(box.conf.cpu().numpy().squeeze()) cls = int(box.cls.cpu().numpy().squeeze()) dets.append([*xyxy, conf, cls]) dets = np.array(dets, dtype=float).reshape(-1, 6) online_targets = tracker.update(dets, None) return online_targetsstrack对象的tlwh属性是目标的左上角坐标加宽高,track_id是它在追踪器里的唯一编号。这里第二个参数传None表示不使用额外特征,ByteTrack默认用IoU关联已足够。det里的类别字段对ByteTrack没用,但如果你的统计逻辑需要区分大巴和小车,可以在后面保存ID时把类别字段一起记录。
4.2 交通流量计数的两种常见方案:虚拟检测线与区域进出判定
流量统计的核心是确定一个目标“经过指定位置”。最常见的做法是画一条虚拟检测线,当车辆框中心点跨越这条线时计数一次。在OpenCV里,假设检测线是y = fix_y,判断逻辑只需要检测目标ID是第一次从y > fix_y变到y < fix_y时触发计数。这有一个坑:如果车辆停在检测线上,中心点在两帧之间来回抖动,会重复计数。解决方法是给每个ID维护一个Boolean状态,只有状态从0变1或从1变0才计数,且设置冷却帧。
第二种方法是画一个矩形区域作为“入口”,当某个ID的中心点第一次进入区域时计数,离开再进入要重新计数。这种方案对拥堵、走停状态更稳。两种方案的比例,在真实路口场景中,虚拟线适合通畅流,区域判定适合信号灯口的排队流。我的习惯是优先区域判定,因为车道宽度往往超过一个像素,线的鲁棒性差。
4.3 基于ID和轨迹的统计去重,避免一辆车被数两次
这里的关键是理解ByteTrack在断帧后的行为。一个目标被遮挡两秒再出现,ByteTrack可能给新ID也可能恢复旧ID,取决于关联匹配阈值。如果你只统计“中心点穿过线”这一个条件,断帧后恢复的旧ID可能再次穿过线,导致重复计数。所以正确办法是只统计第一次穿越,后续同一ID不再数。
代码实现里给统计模块一个字典passed_id = set(),只有track_id不在这个集合中且穿越方向符合条件时计数,然后加入集合。注意ByteTrack的ID号在追踪器内部是循环利用的,长时间运行后旧ID可能被回收重新分配给新目标,这时如果只靠ID去重,某个新目标会因为“ID存在于集合中”被漏统计。解决方法是给ID加上时间戳,只在30秒内生效;或者统计时不依赖ID,而依赖卡尔曼滤波预测的轨迹连续性。但后者实现复杂度高,一般我会先加时间戳去重用,遇到极端情况再考虑轨迹连续性。
4.4 实时视频流接入与显示输出,延迟从哪里产生
cv2.VideoCapture可以从本地视频、RTSP或USB摄像头取流。实时性瓶颈主要在三处:视频解码、YOLOv8推理、ByteTrack计算。ByteTrack本身非常轻,相比YOLOv8推理消耗几乎可以忽略。所以重点是把YOLOv8推理降下来。对于1080p视频,如果GPU是GTX1660Ti,YOLOv8s推理约20ms,但加上视频解码和绘制,单帧总耗时约40ms,刚好接近25FPS。如果要上30FPS,要么缩小推理分辨率到416或512,要么换成YOLOv8n。以下是用OpenCV读取视频并对每一帧做追踪+统计的循环:
import cv2 from ultralytics import YOLO model = YOLO('best.pt') cap = cv2.VideoCapture('road.mp4') count = 0 while cap.isOpened(): ret, frame = cap.read() if not ret: break results = model.predict(frame, imgsz=640, verbose=False) online_targets = feed_to_tracker(results) for t in online_targets: tlwh = t.tlwh tid = t.track_id # 判断车辆中心点是否穿过统计线 cv2.imshow('stream', frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()这段循环最需要注意的是model.predict(frame, ...)每次调用都有序列化开销,如果追求性能,要改用model(frame, ...)或者使用stream=True的生成器模式。在连续视频流推理时,建议不要重复创建YOLO对象,而是复用模型实例。对于RTSP流断线重连的问题,我通常会在ret为False时做三次重试,再等待5秒重新VideoCapture,否则长期运行会因偶然网络抖动退出整个程序。
5. 实时车辆多目标追踪的常见踩坑与排查
5.1 车辆ID频繁跳变,明明是同一辆车却换来换去
现象:画面中一辆车匀速直行,右上角的ID序号一下是7一下是23,过两秒变成15。原因:ByteTrack的match_thresh设置过低,导致匹配条件过于严格,一个遮挡或几帧漏检后轨迹匹配不上,被当作新目标。解决:把match_thresh往大写,比如从0.8改成0.9或0.98,让关联更宽松。同时检查YOLOv8的conf_thres设置,如果检测置信度阈值设太高,导致低置信度车辆框被滤掉,ByteTrack就没有低置信度框做补充关联了。我自己的经验是YOLOv8推理时把conf设为0.3,iou设为0.5,再配合track_thresh=0.45,对路口车辆场景最稳定。
5.2 帧率上不去,GPU却才跑到一半
现象:GPU利用率只有40%左右,但视频帧率只有12FPS。原因:瓶颈不在推理,而在图像预处理和结果后处理。model.predict内部包含letterbox、颜色格式转换、NMS等模块,这些在CPU上执行,如果输入分辨率大、batch为1,预处理会占掉10-15ms。解决:将model.predict改成model(frame, device='cuda')并设置pre_transform缩短;或者把imgsz从640缩到512,并关闭verbose。另外检查是否开启了amp,除非在特殊设备上,建议保持默认True,它在推理时能减少等待。还有一个常见坑是视频解码线程和推理线程串行,IMX335摄像头输出H.265时CPU解码会吃满一核,帧率被解码拖累,解决方案是改用硬解码或降低码流。
5.3 车辆重合时计数重复,把大卡车数成两辆
现象:一辆卡车被YOLOv8检测成一个框,但ByteTrack却把它当成两个ID,计数翻倍。原因:目标被上部车体的镂空部分或阴影切开,YOLOv8在同一辆车附近输出两个重叠框,ByteTrack对重叠框判定成两个不同目标。这种情况更多发生在模型训练数据里没有这类卡车样本;另一个原因是置信度阈值过低,双框被保留。解决:在追踪器输入端做非极大值抑制(NMS),把针对同一物理目标的多个框合并,可设置iou_threshold在0.3左右,肉眼观察重叠框是否被清除。还有一个偏门但有用的办法是降低min_box_area到500,滤掉那些来自远方的过小框,因为远处车辆重叠导致的误框比例最高。
5.4 夜间或逆光场景漏检率飙升
现象:路灯暗、车灯亮,画面里车灯亮成一团,YOLOv8对远光灯车头的检测框完全丢失。原因:训练数据主要都是白天样本,模型没学过强光切割下的目标形状。解决:收集夜间逆光的真实数据,加入训练集;如果数据量少,可以用图像增强模拟,把亮度随机下降、加高斯噪声,但效果有限。另一个工程方案是数据不变但调整预处理参数:model.predict(frame, augment=True)开启测试时增强,能小幅度提升漏检,但会让推理时间翻倍。实测中最好用的还是给模型喂带自动白平衡的摄像头流,很多摄像头在夜间会切换红外模式,画面从彩色变黑白,你需要在训练集中也加入灰度车辆框样本,否则一样翻车。
5.5 在RK3588上部署YOLOv8时帧率只有个位数
现象:GPU服务器上能跑50FPS的YOLOv8s,用rknn-toolkit2转成RKNN模型后,在RK3588上只有5FPS。原因:没有做量化或选错量化类型,以及模型算子与NPU不兼容。解决:先用rknn-toolkit2导出INT8量化模型,校准数据集选500张车辆图片,避免用COCO全类别校准。转模型时把target_platform设为rk3588,开启optimize_level=1。如果某些算子不兼容,比如YOLOv8的DFL层,考虑使用RKNN官方提供的yolov8.rknn示例,它内部做了算子替换。如果跑不到目标帧率,还要考虑把输入分辨率从640降到512,或者换成YOLOv8n。部署时加载RKNN模型用rknnlite.api,和用ultralytics时完全不同,要重写推理代码。
6. 进阶优化:让交通流量系统更准更快,一个可加的改进都在这里
6.1 改进YOLOv8的head或加入注意力机制,对小目标的作用点
你的交通流量系统如果对远处小目标经常漏检,可以尝试把YOLOv8的检测头换成BiFPN或加入协调注意力机制。常见做法是修改ultralytics下的模块定义,但这要动源码,维护成本高。更轻量的做法是提升imgsz到960,让小目标拥有更多特征像素,但推理时间增加,适合RTSP录播或夜间慢速车道,不适合实时30FPS路口。如果坚持要模型层面改进,建议从增加小目标检测层开始:给YOLOv8模型加一层P2输出头,比单纯加注意力更直接对流量统计有效。做这个改进前先统计你的数据集里车辆框的宽高分布,如果小目标占比超过30%,才有必要动模型。
6.2 从GPU服务器到RK3588的完整部署路径,以及流量统计代码怎么写
在RK3588上部署的关键不是把前端代码跑起来,而是把每帧的检测结果封装成统一接口,让追踪计数逻辑完全共享。你可以把RKNN推理写成一个返回[x1,y1,x2,y2,conf,cls]的函数,然后喂给同一个ByteTrack代码。注意RKNN模型的输入格式是NHWC的uint8或float16,YOLOv8源码前处理里的letterbox要自己实现。这里有一个我自己常用的经验:不要直接用rknn.inference的原始输出再解析,先跑通一个最小例子,把输出形状打出来,对照YOLOv8的decoded格式再做后处理,否则很容易在输出张量的排列顺序上浪费一天。部署完成后量化评估流量统计误差:统计真实手动数30分钟视频,对比系统输出,误差应小于5%。
6.3 用一段连续视频做回归验证,而不是用静态图片集
系统整体调试到接近可用时,最后做一次回归验证。我从第一台路灯下车辆计数项目学到的习惯是:录一段10分钟的白天、夜晚混合视频,带有不同类型车辆和遮挡场景。然后跑完整的检测—追踪—计数流。用这段视频验证三个指标:一、计数误差是否在5%以内;二、目标追踪ID保持率,也就是同一辆车从进入画面到离开画面,ID保持一致的帧数比例是否大于90%;三、系统是否在长时间运行后内存溢出或线程卡死。三个指标都通过,才把系统接到真实摄像头。我见过有的系统在实验室数据上很棒,一接现场断电后RTSP重连就崩溃,最后只好加看门狗定时器重启进程,这种血泪经验希望你能提前避开。
整套方案做下来,我最深的感觉是“检测模型重要,追踪参数更吃经验”。同样一套YOLOv8和ByteTrack,在不同路口亮度和密度的画面下,match_thresh和conf_thres至少要调两个轮次才能稳定。所以建议你先把开源模型和追踪器跑出一个可用的baseline,再逐步替换成自己训练的车种模型,并对准统计线做现场校正。本文里每一段参数落差和每一步代码逻辑,都是值得花时间去验证的,希望帮到你。
本文还有配套的精品资源,点击获取