ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于YOLOv8与ByteTrack的车辆流量统计系统实战

基于YOLOv8与ByteTrack的车辆流量统计系统实战 简介在智慧交通与安防监控领域从视频流中自动识别并统计车辆数量是高频需求。目标检测技术能定位画面中的车辆而多目标跟踪MOT则负责为每个目标分配稳定ID两者结合才能实现精准计数。YOLOv8作为新一代单阶段检测模型凭借高速度和易部署特性成为工业落地的优选ByteTrack则通过低分框二次关联策略有效应对遮挡与密集场景降低ID切换率。两者构成的“检测跟踪”范式配合虚拟检测线逻辑可轻松完成双向车流统计、时段流量聚合等任务。本文从环境搭建、核心代码实现到性能优化与嵌入式部署完整展示了一套可落地的车辆计数解决方案为相关工程实践提供参考。1. 项目概述1.1 为什么要做车辆流量统计我之前在做一个智慧交通相关的项目客户提的需求很简单想知道某个路口每天经过多少辆车高峰期是什么时候能不能自动生成报表。一开始我试过最土的办法——让实习生蹲在路边用计数器按按了一天回来手都酸了数据还不准。后来决定上一套纯视觉的检测方案用监控摄像头实时跑把经过的车辆一个不落地识别出来、跟住、数清楚。这个需求背后的技术栈其实已经非常成熟了。检测端用 YOLOv8这是目前单阶段目标检测里综合性价比很高的一个模型小模型跑在普通GPU上都能到上百帧跟踪端用 ByteTrack字节跳动开源的MOT算法在拥挤、遮挡场景下特别能打而且不需要ReID特征省掉了大量训练数据标注的成本。两者一搭就是一套标准的检测关联多目标跟踪范式放在车辆流量统计这个场景里刚刚好。这篇内容适合谁看如果你是做智慧交通、安防监控、工业视觉相关工作的工程师或者正在研究多目标跟踪MOT的学生再或者只是想把YOLOv8从单纯画框升级成能计数、能跟踪的实战项目这篇都能给你省下不少试错的时间。我会把从环境配置到代码实现、再到踩坑记录的全部过程都摊开讲。1.2 系统整体工作流程整个系统的核心逻辑用一句话概括YOLOv8负责看检测ByteTrack负责记跟踪你负责划一条线让它数统计。具体来说每一帧图像进来先走YOLOv8检测出所有的vehicle框再把框交给ByteTrack去做跨帧关联同一个目标维持同一个ID。有了稳定ID之后我们去监测每个ID的位置变化轨迹一旦越过预设的虚拟检测线就计数一次。这样做的好处是什么如果你的方案只是检测到车就加一那同一辆车在画面里停两秒就会被重复计数或者一辆车被漏检一帧又被重新算了一次。加了跟踪就能彻底规避这类问题。而且ByteTrack的特殊之处在于它对低分检测框的利用——那些模糊、被遮挡、只有半个身子的目标它也会尝试分配ID——这对密集车流场景特别关键。市区早晚高峰遮挡是家常便饭如果只靠高置信度框去跟踪车一挡住就丢ID计数结果根本没法看。2. 技术选型YOLOv8与ByteTrack为什么是黄金组合2.1 YOLOv8在各检测模型里的定位YOLO系列从v5开始就成了工业落地的主力v8在Ultralytics团队手里进一步把训练、导出、部署的体验做到了一体化。相比Faster R-CNN这类两阶段模型YOLOv8的速度优势是代际级别的——同样是做车辆检测Faster R-CNN跑一帧可能要80毫秒以上YOLOv8n在同等硬件上能跑5毫秒左右。精度方面在COCO数据集上YOLOv8m能达到50的mAP配合车辆这种目标特征明显、类别不算复杂的场景完全够用。更关键的一点是YOLOv8的输出格式对跟踪算法非常友好。它的推理结果包含边界框坐标、置信度和类别ID而ByteTrack本质上只需要框这个几何信息就可以工作。它甚至不需要你重新训练一个专用检测器直接用预训练权重就行——COCO里本来就有car、truck、bus、motorcycle这些类别你要做的只是在后处理时把它们过滤出来。我还要提一下YOLOv8的anchor-free设计。老一代YOLOv5及以前是anchor-based需要在训练前对数据集做聚类生成先验框尺寸v8改成anchor-free之后只需要预测目标中心点到四条边的距离对车辆这类宽高比变化大的目标反而更稳。窄体货车、长挂车、小轿车哪怕形状差异再大检测头都能统一处理。2.2 ByteTrack的核心思想不只是跟踪而是不放弃每一个目标ByteTrack的论文标题叫《ByteTrack: Multi-Object Tracking by Associating Every Detection Box》核心就一句话低分检测框也要参与关联。传统的跟踪算法比如DeepSORT在拿到检测结果后会先把低置信度的框丢掉只对高置信度框做卡尔曼滤波预测和匈牙利匹配。这在目标清晰的场景没问题但遇到遮挡时就很麻烦——一个行人被电线杆挡住一半置信度掉到0.3算法直接把它删了等目标重新出现时又当成一个新目标ID就跳了。ByteTrack的做法是反过来先把所有的检测框按置信度从高到低排好高分框之间做第一次IoU匹配留下来的未匹配高分框和未匹配低分框再做第二次匹配。它的动机其实特别朴素检测框分数低不代表框是错的它可能是目标被遮挡导致特征不全但框的位置仍然大概率是对的。关联本来就该看位置不该单看分数。我第一次跑通ByteTrack之后最大的感受是密集场景下ID Switch的次数明显比DeepSORT要少。举个具体数字我在一段车流量比较大的路口视频上测过DeepSORT大概能维持几百帧内ID稳定但一遇到大车遮挡小车的情况就掉链子ByteTrack在同一段视频里的ID切换率能降低一半左右。而ID稳定直接决定了最后流量统计的准头。2.3 为什么不选DeepSORT很多做车辆检测的人第一反应是DeepSORT毕竟它名气大、教程多。但我做这个项目的结论是在纯车辆流量统计场景DeepSORT的ReID优势用不上反而拖后腿。DeepSORT需要一个外观特征提取网络在车辆跟踪里通常用的是行人ReID模型或者专门训练的车辆ReID模型。这意味着两件事第一你要额外准备一个模型推理耗时上去了第二这个模型需要针对你的场景微调不然它提取的特征在老小区门口和高速收费站的表现可能差很多。ByteTrack是纯几何的方法只需要卡尔曼滤波预测motion IoU匹配零额外训练成本换场景直接用。如果你不追求这辆车从哪个匝道进、哪个匝道出这种精细关联只是要数清楚多少车过去ByteTrack的性价比几乎是碾压级的。当然DeepSORT也不是没优势。如果目标之间有大量完全相同的视觉特征比如都是白色轿车而且长时间遮挡导致卡尔曼预测发散DeepSORT的外观分支能帮你重新找回目标。ByteTrack在极端遮挡下确实会有跟丢的情况。但从流量统计的需求来说车被完全挡住好几秒的情况极少而且哪怕ID丢了一次只要检测线附近的逻辑处理得当计数依然不会出大错。所以我的建议很明确先用ByteTrack跑不通再考虑带特征提取的方案。3. 环境搭建与依赖准备3.1 硬件配置与系统要求做这个项目最好有一块NVIDIA显卡显存4GB以上就够跑YOLOv8m了。我实际开发用的是一块GTX 1660Ti6GB跑YOLOv8s加上ByteTrack视频流1080p大概在30~40 FPS完全能撑起实时两个字。如果你只有CPU也不是不能玩YOLOv8n在CPU上对720p视频大约能跑15~20 FPS但一旦分辨率上去帧率会掉得很难看。显卡驱动方面我踩过一个坑新版的Ultralytics库对CUDA版本比对PyTorch版本还敏感。先确认你的显卡驱动支持到哪个CUDA版本再决定装哪个PyTorch。比如在Linux下用nvidia-smi看到Driver Version是525或更高基本就支持CUDA 12.x如果驱动版本是470左右老老实实装CUDA 11.x配套的PyTorch。操作系统上Windows和Linux都行。Windows的好处是调试方便坏处是后续如果要上TensorRT或深度优化的部署很多坑是Windows独有的Linux的好处是环境干净Docker化部署也方便适合最终上线。我的建议是开发机用Windows、生产环境用Linux代码层面尽量不做平台依赖的假设。3.2 依赖安装的一站式指引我用的是Python 3.9 PyTorch 2.0.1 Ultralytics 8.0.x的组合。PyTorch 2.x比1.x在编译图优化上快不少对YOLOv8这种结构比较规整的模型收益明显实测推理速度能提升10%~20%这还没算AMP混合精度。如果你用的是PyTorch 1.13及以下我的建议是升级到2.0以上再做性能测试差距肉眼可见。# 创建虚拟环境避免污染系统Python conda create -n yolotrack python3.9 -y conda activate yolotrack # 安装PyTorch以CUDA 11.8为例版本按自己驱动调整 pip install torch2.0.1 torchvision0.15.2 --index-url https://download.pytorch.org/whl/cu118 # 安装YOLOv8核心库 pip install ultralytics # 安装ByteTrack依赖 pip install loguru scikit-learn filterpy这里有个细节ByteTrack官方仓库的依赖里有lap这个库但在Windows上编译经常出问题。我的替代方案是装scipy用它的linear_sum_assignment替代匈牙利匹配。虽然速度上lap略快一些但对几辆车、几十个目标的场景差距微乎其微换来的是跨平台零编译烦恼。4. 检测与跟踪核心实现4.1 YOLOv8检测模块的封装检测这块的核心代码并不复杂但要把它设计成可以随时替换模型、方便后续优化的模块就不要把所有逻辑都写在主循环里。我习惯封装一个Detector类对外只暴露一个detect(frame)方法内部再处理预处理、推理、后处理。import cv2 import torch import numpy as np from ultralytics import YOLO class Detector: def __init__(self, model_pathyolov8m.pt, conf_thres0.25, deviceNone): self.device device if device else (cuda if torch.cuda.is_available() else cpu) self.model YOLO(model_path) # 预热模型避免第一次推理时初始化耗时 self.model.predict(np.zeros((640, 640, 3), dtypenp.uint8), deviceself.device) self.conf_thres conf_thres # 这里只保留交通工具相关类别COCO中 # 2: car, 3: motorcycle, 5: bus, 7: truck self.vehicle_classes {2, 3, 5, 7} def detect(self, frame): results self.model.predict(frame, imgsz1280, confself.conf_thres, deviceself.device, verboseFalse) boxes [] scores [] class_ids [] if len(results) 0 or results[0].boxes is None: return boxes, scores, class_ids for box in results[0].boxes: cls_id int(box.cls.item()) if cls_id not in self.vehicle_classes: continue x1, y1, x2, y2 map(float, box.xyxy[0].cpu().numpy()) boxes.append([x1, y1, x2, y2]) scores.append(float(box.conf.item())) class_ids.append(cls_id) return np.array(boxes, dtypenp.float64), np.array(scores), class_ids这里面有几个点必须说明滤镜车辆类别就这三行代码但决定了整个计数系统的准确性。巡警车、救护车、消防车如果你也想数那就得把类别扩展到4个或者更多如果你只数小汽车那就要把类别严格限制在car里面。我遇到过客户说你们统计怎么把公交车也数进去了原因就是过滤条件只写了car和truck没有排除bus。imgsz的取舍我设了1280而不是默认的640是因为路口的车往往很远目标很小640分辨率下远端车辆可能只有十几个像素检测器很难稳定出框。代价是推理时间增加1660Ti上大概从5ms涨到12ms还在可接受范围内。如果你的摄像头离路面很近、画面里车都很大那640甚至480都够用能省一半推理时间。4.2 ByteTrack STrack类的核心逻辑ByteTrack的实现细节比较多我在这里挑最核心的STrack类讲。它本质上是一个带状态的目标内部维护了卡尔曼滤波状态和当前帧的观测值。# byte_track/byte_tracker.py (简化版) class STrack: def __init__(self, tlwh, score, cls_id): self._tlwh np.asarray(tlwh, dtypenp.float64) # [x, y, w, h] self.score score self.cls_id cls_id self.kalman_filter None self.mean, self.covariance None, None self.is_activated False def predict(self): # 卡尔曼预测根据历史运动趋势推测下一帧位置 if self.mean is None or self.kalman_filter is None: return self.mean, self.covariance self.kalman_filter.predict(self.mean, self.covariance) def update(self, new_tlwh, score, cls_id, kf): # 卡尔曼更新用当前检测校正预测结果 self.kalman_filter kf self._tlwh new_tlwh self.score score self.cls_id cls_id if self.mean is None: self.mean, self.covariance self.kalman_filter.initiate(self._tlwh_to_xyah()) else: self.mean, self.covariance self.kalman_filter.update(self.mean, self.covariance, self._tlwh_to_xyah())卡尔曼滤波在这个项目里扮演的角色是预测下一帧目标在哪里。车匀速运动时预测误差极小匹配很容易成功车突然刹车或变道时预测会偏但靠IoU匹配仍然大概率能拉回来。你可能不需要理解卡尔曼滤波的数学推导但要知道它的两个关键参数std_weight_position位置不确定性值越大预测越飘、对新检测越不敏感和std_weight_velocity速度不确定性值越大越容易被新检测拉走。ByteTrack给了一套默认参数实际跑下来在车辆场景表现很好我几乎没动过。4.3 用K近邻和IoU做两级关联ByteTrack官方代码在匹配时用了一个KalmanFilterDistance代码里先对轨迹做运动预测然后计算预测位置和检测框的IoU最后用匈牙利算法求解最优匹配。我在项目里做了一点简化改成了两步匹配的思路效果没有明显变差但代码量少了很多def associate_boxes(tracks, detections, iou_threshold0.2): # 第一轮高分框匹配 high_score_dets detections[detections[:, 4] 0.5] low_score_dets detections[detections[:, 4] 0.5] # 计算IoU矩阵 iou_matrix compute_iou(np.array([t.predict() for t in tracks]), high_score_dets[:, :4]) # 匈牙利匹配 matched_indices linear_sum_assignment(-iou_matrix) ... # 第二轮用低分框匹配未命中的track ...这里有个细节要注意低分匹配时IoU阈值要放松。高分匹配我设了0.3低分匹配我放宽到0.15因为在低分场景里检测框本身就有点歪阈值太严就什么都匹配不上了。这个经验值来自我对拥堵路口的调试不同项目可能要微调。4.4 跟踪器主循环跟踪器的核心循环对每一帧执行三步预测、关联、更新。我把完整的update逻辑贴出来这是整个系统最核心的一段了。class ByteTracker: def __init__(self, fps25): self.tracks [] self.frame_id 0 self.fps fps self.kf None # 共享卡尔曼滤波器在第一次使用时初始化 def update(self, detections): self.frame_id 1 # 过滤掉空检测 detections detections if len(detections) 0 else np.empty((0, 5)) # 预测当前帧所有track的位置 for track in self.tracks: track.predict() # 分离confirmed和unconfirmed confirmed [t for t in self.tracks if t.is_activated] unconfirmed [t for t in self.tracks if not t.is_activated] # 关联匹配 matches_a, u_track_a, u_det_a associate(confirmed, detections) ... # 新track初始化用未匹配的detection创建 ... # 输出被确认的track结果 outputs [] for track in self.tracks: if track.is_activated: outputs.append(track.to_tlwh() [track.track_id, track.cls_id]) return np.array(outputs)这段代码的骨架和ByteTrack官方版没有太大区别但它稳定跑过了我十几个不同场景的测试视频。这里有一个容易踩的坑update函数里传进来的detections一定不能是空数组否则np.array([])的形状会给你整出个(0,)然后各种矩阵运算直接崩。我在前面加了if len 0的三元表达式就是为了处理画面里一辆车都没有的帧。5. 车流量统计与方向判断5.1 基于虚拟检测线的计数方案跟踪是手段计数才是目的。我选的是最接地气的虚拟检测线方案在画面里人为划一条线当一个track的轨迹从线的一侧跨越到另一侧时计数器加一。这个方法相比指定区域计数有两个优点一是物理含义明确车辆越过路口停止线就算通过符合交通管理的语义二是实现简单只需要维护每个track的上一帧中心点坐标和当前帧中心点坐标判断两个点是否分处直线两侧即可。class Counter: def __init__(self, line_start(640, 200), line_end(640, 800)): self.line_start np.array(line_start, dtypenp.float64) self.line_end np.array(line_end, dtypenp.float64) self.count 0 self.counted_ids set() def cross_count(self, track_id, prev_center, curr_center): # 叉积判断是否跨线 d self.line_end - self.line_start prev_side np.cross(d, prev_center - self.line_start) curr_side np.cross(d, curr_center - self.line_start) if prev_side * curr_side 0: if track_id not in self.counted_ids: self.count 1 self.counted_ids.add(track_id)这里有几个要点每个track只需要判断一次。我维护了counted_ids集合一个ID只要跨线计数过一次后面就不重复计。否则一辆车在检测线附近来回蠕动比如排队缓行会被反复计算。但这也带来一个问题车辆跨线后如果跟踪丢失重新被分配了新的ID那就会被计两次。这个问题的根源在于ByteTrack的ID切换目前没法完全消除只能在提升跟踪稳定性上下功夫。线的位置至关重要。虚拟检测线最好设在车辆的必经之路上且前后要有一定的缓冲距离。比如路口场景线放在停止线前一点到人行道附近这段区域最合适。如果线太靠近摄像头车辆刚进入画面就越线那时车辆只露出一角检测框不稳定容易一会儿有ID一会儿没有统计结果会偏低如果线太远车辆已经在画面深处目标小、检测置信度低也会掉ID。我实际试下来把检测线放在画面中下部、车辆完整进入画面后约三分之一的区域准确率最高。5.2 双向流量统计如果只统计一共过了多少辆车那上面的单向计数就够用了。但很多场景需要分方向统计比如北向南多少辆、南向北多少辆那就把单一检测线升级为方向判断。我的做法是设定一条基准线用车辆轨迹的进入方向来判断def get_direction(self, prev_center, curr_center): # 默认线是水平的y减小的方向为向上y增大的方向为向下 if curr_center[1] - prev_center[1] 0: return TO_TOP else: return TO_BOTTOM这是最简化的版本只判断y轴方向。更严谨的做法是用轨迹点拟合成一条向量计算这个向量与虚拟检测线法向量之间的夹角通过角度把方向分成四象限东/南/西/北。但我在实际项目中很少遇到需要四个方向的需求两个方向已经覆盖了绝大多数路口。5.3 密度统计与时间窗口聚合计数只是第一步流量统计还需要做时间维度上的聚合。我定义了一个TrafficStats类以1分钟为窗口把每辆车的越线时间戳存下来每分钟结束时汇总一次。class TrafficStats: def __init__(self, window_seconds60): self.window_seconds window_seconds self.timestamps [] self.hourly_counts {} def add_crossing(self, ts): self.timestamps.append(ts) def get_count_since(self, since_ts): return sum(1 for t in self.timestamps if t since_ts) def hour_summary(self): # 按小时聚合输出{08:00: 120, 09:00: 150, ...} ...这个模块还可以接可视化。把每分钟的流量值实时画成曲线图就能直观看到早晚高峰。我第一次跑出这个曲线的时候发现早高峰出现在8:00-8:30晚高峰出现在18:00-18:30和客户手工统计的数据基本一致那时候真的觉得这套系统的价值体现出来了——以后不用再派人蹲守了。5.4 可视化与结果输出实际部署时不能只输出一个数字客户要看到为什么是这个数字。所以我把检测框、跟踪ID、速度估算、以及检测线都画在画面里实时叠加显示方便人工校验。def draw_boxes(frame, tracks): for track in tracks: x, y, w, h track[0], track[1], track[2], track[3] track_id int(track[4]) cls_id int(track[5]) cv2.rectangle(frame, (int(x), int(y)), (int(x w), int(y h)), (0, 255, 0), 2) cv2.putText(frame, fID:{track_id} {class_names[cls_id]}, (int(x), int(y) - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) return frame如果你后面要做Web展示可以让Python程序把每帧的处理结果以JSON推送到后端帧号、track_id、坐标、置信度再由Web端渲染。这样前端就完全不涉及算法只需要展示数据分工清晰、便于多人协作。我之前做过一版用浏览器直接调用摄像头、WebSocket推流的效果也很不错。6. 性能优化与部署实践6.1 模型推理加速从PyTorch到ONNX再到TensorRT本地开发跑通只是第一步要真正到监控机上跑性能必须再往上提。我针对YOLOv8做了两轮加速先转ONNX再转TensorRT。第一步导出ONNX格式from ultralytics import YOLO model YOLO(yolov8m.pt) model.export(formatonnx, imgsz1280, opset12, simplifyTrue)这一步的主要作用是让模型脱离PyTorch runtime运行后续如果部署到无PyTorch环境比如用C推理也能加载。simplifyTrue会调用onnx-simplifier做计算图简化能去掉一些冗余节点。转完之后用onnxruntime-gpu推理在1660Ti上速度大约能提升10%左右。第二步转TensorRT。这一步复杂度高一些但收益也最明显推理速度能提升30%~50%。核心命令trtexec --onnxyolov8m.onnx --saveEngineyolov8m.engine --fp16--fp16半精度模式对速度提升巨大但注意导出ONNX时要确保模型支持fp16。YOLOv8导出时要加halfTrue参数。如果模型在fp16下出现数值异常比如全部输出NaN就退回fp32或者等模型的检测头做一下Clip层处理。6.2 用GTX 1660Ti跑实时推理的实测数据放一组我实测的数据供你参考输入分辨率1280x720GPU为GTX 166Ti6GB显存模型推理框架平均耗时/帧FPS备注YOLOv8sPyTorch18ms55可实时YOLOv8sTensorRTfp1610ms100完全无压力YOLOv8mPyTorch30ms33刚好实时YOLOv8mTensorRTfp1618ms55推荐使用YOLOv8lTensorRTfp1630ms33画质极高时用我最终选了YOLOv8m TensorRTfp16的组合。原因很简单m模型精度比s高不少虽然模型体积大但fp16优化后速度和s差不多了。加上ByteTrack本身也要消耗一小部分CPU时间每帧约2~3ms流水线总耗时控制在22ms以内能稳定跑满45FPS以上。对实时监控来说这个余量已经够大了。6.3 嵌入式部署从RK3588到Jetson不少客户会把摄像头和计算盒子放在一起不需要单独的PC。这个场景我实测过RK3588和Jetson Orin Nano两条路。RK3588是瑞芯微的旗舰SoC里面有6TOPS的NPU。YOLOv8已经支持通过rknn-toolkit2导出到RKNN格式转换时要注意几点一是RKNN的量化策略很关键不量化fp16精度损失小但速度不如int8二是输入分辨率要固定动态shape在NPU上基本不可用。我实测YOLOv8s在RK3588上int8量化后推理速度约30ms/帧约30FPS和1660Ti跑PyTorch差不多功耗却只有几瓦相当能打。Jetson Orin Nano更省心一些官方提供TensorRT和DeepStream一套解决方案。DeepStream是NVIDIA的流媒体分析框架它内置了对ByteTrack的支持你在配置文件中指定高度为YOLOv8的ONNX模型再设置跟踪器类型为NvByteTracker它内部就会帮你用TensorRT跑检测、用ByteTrack做跟踪基本是开箱即用就是配置流程有点繁琐。无论走哪条路线我的建议是提前把模型导出和精度对齐的脚本写好用同一批测试数据对比各种后端的精度差值。否则部署到嵌入式后才发现检测率掉了10%排查起来会非常痛苦。7. 常见问题与排查技巧实录7.1 跟踪不稳定、ID频繁切换这是被问得最多的一个问题。ID切换多统计数字就容易乱。我排查的顺序是第一步查检测端。如果检测本身就不稳定跟踪一定乱。把检测框的显示打开盯着看同一辆车是不是一会儿有框一会儿没框如果是说明conf_thres设得太高了把它从0.25降到0.15试试。代价是会出现一些误检框但这些误检框在关联阶段通常拿不到稳定ID对统计影响有限。第二步查IoU阈值。ByteTrack的关联阈值默认0.2如果画面里的车很大一帧之间位移也很小因为FPS高IoU阈值可以放宽到0.3甚至0.4如果FPS低、车辆速度快IoU会很小阈值反而要降。这个参数没有一成不变的值要根据你的实际视频去调。第三步看卡尔曼预测是否发散。如果目标在画面里突然消失又出现Kalman预测的误差会越积越大。ByteTrack对lost超过一定帧数的track会直接删掉这个帧数在官方实现里默认30我建议不要调太高否则目标消失了很久还会占用计算资源且重新出现时容易匹配到错误目标。7.2 计数多算或少算多算的原因通常是ID切换。一辆车越过检测线前因为遮挡丢失了一个ID重新出现后拿到新ID又被计了一次。要减少这种情况就是把检测线附近的区域重点优化提高分辨率、降低conf_thres、增大目标裁剪区域的图像增强。还有一个土办法是在counted_ids里不仅记录已计数的ID也记录ID在检测线附近出现的次数如果一个新ID是从旧ID的轨迹延续来的中心点距离很近就不给它计数。这个启发式规则在我们项目里很有效把多算率从5%降到了1%以下。少算的原因通常是漏检。车太远、模糊、光线差都会导致漏检。解决办法是把imgsz调大、启用YOLOv8的augment推理但会变慢不推荐实时场景使用或者换更大的模型。另外虚拟检测线要尽量避开画面边缘边缘区域畸变严重对精确检测很不利。7.3 模型训练数据与场景迁移如果你要在一个全新的场景比如高速收费站、停车场闸口而不是路口预训练模型的表现可能会下降。这时候你需要收集该场景的图片做微调。我提供一个最小可行的数据准备流程从监控视频中截取300~1000帧图片覆盖白天、夜晚、逆光、雨天等不同光照条件。用LabelImg或者Ultralytics的标注工具标注车辆框类别名设为与你的需求一致Car、Truck、Bus等。整理成YOLO格式的txt文件一个图片对应一个同名txt每行格式为cls_id x_center y_center width height坐标是归一化后的0~1。修改data.yaml文件指定train/val路径和类别列表。训练命令yolo detect train datadata.yaml modelyolov8m.pt epochs50 imgsz1280 batch8 device0我用这个流程在客户现场拍的900张图片上微调了50轮mAP从0.52涨到了0.79夜晚场景的漏检率降了一半以上。所以如果你的场景和标准COCO差异大微调是绕不开的。7.4 代码性能瓶颈分析与线程化处理如果你的FPS不够先别急着换模型。把主循环拆开用cProfile跑一遍大概率会发现问题在预处理或者后处理上。import cProfile cProfile.run(main_loop(), sortcumulative)我实测中的典型性能瓶颈是这几处cv2.resize在大分辨率输入时耗CPU很严重如果检测输入是1280但原视频是4K先用cv2.resize降到1280再进模型这部分大概占3~5ms。NMS后处理在目标数量多时会用掉很多时间Ultralytics默认的NMS是CPU上的实现可以考虑改用torchvision.ops.nms的GPU版本。Python的GIL在多线程下会限制多路视频流的并行处理这种场景建议用multiprocessing替代threading每个视频流一个进程。另外有一个优化技巧如果输入是视频文件而不是摄像头实时流可以用多线程预读帧。OpenCV的VideoCapture.read()在某些平台上有几十毫秒的阻塞单线程读帧推理的时候GPU经常在等CPU喂数据。我加了一个双缓冲线程始终保证有一帧图像在队列里等着被取走FPS在视频文件场景下提升了10%~20%。8. 从实验到落地的几点体会做这个项目最大的心得是多目标跟踪类项目调参的优先级远高于换模型。YOLOv8和ByteTrack的组合在绝大多数交通场景下都能有不错的表现但参数没调对conf阈值、IoU阈值、丢失容忍帧数哪怕换成最大的YOLOv8l也会出问题。相反如果你把视频的实际帧率、车辆运动速度、摄像头安装角度都考虑进来用默认的YOLOv8s也能跑出漂亮的数据。还有一个小技巧想分享调试的时候别盯着最终计数看把中间结果先可视化出来。画框、画ID、画轨迹线、画检测线一帧一帧地看很快就能定位到是哪一步出了问题。我很多次以为跟踪算法写错了结果一可视化发现是检测的置信度阈值设高了、车没被识别出来。可视化是最快的调试工具。最后说一句这套系统的代码骨架几乎不依赖具体业务换几个参数、改一下类别过滤条件就能用到行人统计、自行车流量统计、甚至是工厂里AGV小车的计数上。如果你做完车辆版本后想迁移到其他目标流程基本一样踩过的坑也差不多。先跑通再优化最后部署这条路线不会错。本文还有配套的精品资源点击获取
返回列表