☰
YOLO26多路摄像头车流统计实战:从检测到部署的全流程解析
2026/10/19 8:57:01 网站建设 项目流程

路口车流统计这类需求,放在需求文档里就一句话——“要实现机动车和行人的实时检测与流量统计”,可真正做进生产环境,就成了很多团队的劝退项目。整套链路从摄像头取流、GPU推理、目标跟踪,一直到过线计数和结果落库,每一环都可能出问题。YOLO26的检测能力在标准评测集里已经很能打,但真正决定项目成败的,是把它接到真实路口监控画面之后,还能不能保持稳定、不重不漏地把数数出来。

这篇文章基于一个模拟项目X的智能交通场景,记录我在多路摄像头+服务器方案下,从选型、训练、部署到调优的完整过程。核心覆盖四块:为什么选服务器集中处理而不是边缘盒子、数据侧怎么为路口场景准备、检测跟踪统计这条链路怎么落地、以及上线之后最常见的那些坑怎么排。适合正在做项目交付的算法工程师、部署工程师和运维同学参考,也适合打算入行AI视觉落地的朋友了解真实项目长什么样。

1. 场景分析与方案选型思路

1.1 真实路口比评测集难在哪

先说个很现实的问题:YOLO26在公开数据集上跑得再漂亮,放到真实路口的监控画面里,大概率要打七折。我之前在模拟项目X里接了一个十字路口,摄像头安装高度在8到10米,俯拍角度斜得很厉害,白天大逆光,晚上靠路灯补光,画面里全是噪点。这种场景跟公开数据集的差距主要在三个地方。

第一是目标尺度。路口监控下,一辆普通轿车在1080P画面里往往只占几十个像素的高度,行人就更小,有时候一个人就是在画面里移动的20×50像素的小色块。检测器对这类小目标的召回率天然偏低,这也是为什么后面我会把“小目标召回率”单独拉出来作为验收指标。

第二是遮挡和密集。一个红灯周期内,停止线前可以挤下二三十辆车,车与车紧挨着,行人从车前穿过时被车身挡住,电动车从大车后面冒出来。遮挡带来的漏检和ID切换,直接影响计数准确性,而且这个影响在评测集里基本看不出来,因为公开数据集里遮挡样本的比例没那么极端。

第三是画质不稳定。很多路口摄像机虽然标注是200万像素,但实际码率、帧率、白平衡都不可控。雨天镜头上有水渍,夜间车灯过曝,早晚霞光导致整帧偏色。这些变化叠加在一起,检测框就会开始抖。一个框如果在一帧出现、下一帧消失,再下一帧又出现,跟踪和统计逻辑就非常容易崩。

所以真实的交通落地项目,从来不是一个检测模型单独能扛下来的。模型要处理的是“从有噪声的、小目标的、严重遮挡的画面中稳定输出目标框”,这已经超出很多论文实验的讨论范围了。

1.2 服务器集中处理,而不是边缘盒子

交通项目经常会有人问,为什么不用边缘计算盒子?我的答案很直接:一旦路数超过4路,边缘盒子的运维成本反而更高。

边缘盒子的问题在于算力固定。YOLO26这个体量的模型,在消费级GPU上跑都很吃算力,放在低功耗盒子上,推理速度会掉到不可用的程度。想换一个更大的模型,或者提高输入分辨率,盒子就得跟着换硬件。更为致命的是,边缘盒子解码路数通常被锁死,4路盒子要接8路摄像头,就得再买一台设备,然后每一台设备单独维护、单独升级、单独排查故障。

服务器方案的优势在于资源池化。一张GPU可以同时处理多路视频流,哪一路算力不够就把batch调大一点,或者把抽帧率调低一点,不需要动硬件。模型更新也方便,把新的权重文件推到服务器,重启推理服务就能生效,不用跑到现场去刷机。再加上服务器可以统一控制解码、推理、跟踪、落库的每一步,出问题的时候可以在一个地方看全链路日志。

对比一下两种方案的取舍:

维度边缘盒子方案服务器方案
初始成本较低较高
多路扩展受设备通道数限制靠单机算力扩展
模型迭代现场刷机替换远程推送权重
调参灵活度低高
故障排查需要逐台登录统一日志与监控
适合场景单点、小规模、离线多路口、集中平台、持续迭代

模拟项目X的场景是“路口+多摄像头+持续运行”,所以服务器集中部署几乎是必然选择。如果有一天项目小到只有一两路摄像头,那再考虑边缘盒子也不迟。

1.3 整体链路与算力估算方法

整个系统的数据流可以拆成:RTSP取流 → 解码 → 抽帧 → 图像预处理 → YOLO26检测 → 目标跟踪 → 过线/区域统计 → 结果落库 → 可视化展示。每一环的资源消耗重点不同。

拉流和解码消耗CPU为主,图像缩放、色彩转换也有CPU开销;YOLO26推理消耗GPU;跟踪和统计消耗CPU且依赖检测结果;落库要看数据库写入频率。这里面最容易被人忽略的就是解码。很多团队的GPU推理扛住了,结果CPU拉流和解码先满了,反而成了瓶颈。所以在部署时要注意给解码单独预留CPU核心。

算力估算有个很实用的方法:先测单路基准,再乘路数系数。假设你的服务器上YOLO26模型在1280输入分辨率、TensorRT FP16下,单路1080P视频实测能跑40FPS,而实际业务只需要每路10FPS抽帧就够了,那么单路推理只占GPU算力的四分之一,理论上能扛4路。留出30%的冗余,实际配置4到6路就相对安全。这个估算方法比看算力表格靠谱,因为不同摄像头画质、不同模型尺寸、不同输入分辨率,对最终性能的影响非常大。

还有一点值得提前说:交通统计业务真的用不上25FPS的抽帧率。车辆从一个方向完全通过路口,最少需要两三秒,行人更慢,10FPS已经足够覆盖过线判断的精度。把帧率从25降到10,等于把可用算力放大了2.5倍,这个买卖非常划算。

2. 数据准备与模型初始化

2.1 先用预训练模型“裸跑”,再决定要不要标数据

很多人拿到项目第一反应就是找数据、标数据、训练模型,其实顺序反了。正确的做法是先拿一份现成的预训练权重,在路口录制的原始视频上直接跑一遍,把检测结果可视化保存下来。这一步的目的不是看效果多好,而是建立“负样本库”。

我在模拟项目X里第一次裸跑时,白天效果还算能看,到了夜间,行人的漏检率非常高,车辆轮廓在路灯下经常只被检测出一半。把这些片段截出来、按时间段分类之后,才明确了真正需要优化的场景。反过来,如果一开始就盲目采集数据,你可能标了大量白天正常光线下的样本,而真正拖后腿的夜间、逆光、雨天样本却没有覆盖到。

另外,预训练权重本身带着大规模数据集学到的通用特征,对轿车、行人、卡车这些常见类别已经有不错的识别能力。你真正要做的不是从零训练一个模型,而是在通用权重基础上适配路口的视角和画质。这决定了后面的微调策略可以做到成本很低。

2.2 标什么、怎么标的规范

交通场景的类别定义不能直接照搬目标检测公开数据集的80类,要按业务需要精简。模拟项目X里我最终只保留了这几类:

类别说明
car轿车、SUV、面包车
bus客车、公交车
truck卡车、厢式货车
person行人
rider电动车/自行车骑行人

为什么不加更多细分类?因为类别越细,混淆错误越多。洒水车和部分货车长得像,三轮车和电动车也容易混,一旦类别混淆,统计报表就要开始“打架”。先做粗粒度,保证数量准确,细分类完全可以后续再加。

标注规范这里立几条铁律:

  • 用完整包络框,不能只框目标可见部分。车辆被树挡一半、行人被车身挡一半,只要是可辨认的目标,就要按完整外轮廓标注。这一点特别影响跟踪,因为如果训练时框的是可见部分,推理时遮挡角度一变框就会跳。
  • 小目标也要标。路上30像素大小的行人在很多人眼里“不值得标”,但恰恰是这种不起眼的目标才是漏检重灾区。标注时只要人眼还能辨认出轮廓,就要标出来。
  • 模糊到人眼都判断不了的不标。这类样本标进去等于给模型注入噪声,它学到的不是“目标长什么样”,而是“模糊区域要出框”。
  • 电子屏幕里的车辆不标。路口附近如果有LED屏幕播放汽车广告,画面里会出现完整车辆影像,标注时必须忽略,否则模型会把屏幕里的虚拟车当成真车。

2.3 微调配置与训练参数建议

数据准备完成之后,微调策略可以走“先冻结后解冻”的路线。前若干个epoch冻结模型骨干,只训练检测头,让模型先适应新的类别映射;之后再解冻全部参数,用较小学习率整体微调。这样既避免灾难性遗忘,又不会出现训练初期损失剧烈震荡的问题。

输入分辨率建议用1280甚至1536。路口场景的核心难点是小目标,分辨率提上去比换更大模型更直接。当然代价是显存占用和推理耗时上升,这个需要结合服务器算力来平衡。

类别不均衡也要处理。行人和轿车的样本数可能差好几倍,训练时按类别出现频率设置损失权重,否则模型会倾向于把所有东西都预测成占多数的类别。我见过一个项目,因为没做均衡,夜间把路灯灯杆全部识别成行人,就是这种失衡的典型表现。

训练完成之后,除了看mAP@0.5,一定要单独看小目标类别的召回率。交通场景中,漏一辆车的代价远大于把一棵树的倒影框出来。建议在验证集上把目标按像素面积分桶统计,如果那些高度小于80像素的车辆召回率低于80%,宁可继续加数据,也不要直接上线。

2.4 权重导出与推理加速准备

训练出来的PyTorch权重不能直接在服务器推理服务里用,需要先导出为ONNX,再转成TensorRT的engine文件。这里有个细节:ONNX导出时不要把NMS写进模型内部。原因很简单,部署侧一般需要拿到的是未过滤的原始框,用于后续的跟踪和自定义后处理。如果NMS已经固化在模型里,跟踪遇到遮挡时想调整阈值会很别扭。

TensorRT转换可以先用命令行工具验证精度和性能:

trtexec --onnx=yolo26.onnx \ --saveEngine=yolo26.engine \ --fp16 \ --minShapes=images:1x3x1280x1280 \ --optShapes=images:4x3x1280x1280 \ --maxShapes=images:8x3x1280x1280 \ --workspace=2048

动态shape配置min/opt/max三档是给后面的多路batch推理准备的。实际跑的时候能把4路甚至8路视频帧拼成一个batch送进GPU,吞吐量提升非常明显。FP16精度在交通场景下基本够用,但有小目标密集的路段,建议转换后跑一遍夜间测试集,确认召回率没有明显下降。

3. 检测、跟踪与统计核心实现

3.1 多路RTSP取流与抽帧调度

多摄像头接入的第一步,是把RTSP流稳定拉进来。这里最大的坑是:不能在主线程里同步读帧。某个摄像头网络抖动一下,整条处理链路都会被卡住。正确做法是每个摄像头单独一个拉流线程,线程只管读取最新帧、放入队列,真正耗时的事情交给下游消费者处理。

我用的拉流结构大致是这样:

import cv2 import time import queue import threading class RTSPStream: def __init__(self, url, name, target_fps=10): self.name = name self.url = url self.target_fps = target_fps self.cap = cv2.VideoCapture(url) self.frame_queue = queue.Queue(maxsize=30) self.running = True self.thread = threading.Thread(target=self._read_loop, daemon=True) self.thread.start() def _read_loop(self): while self.running: ok, frame = self.cap.read() if not ok: self.cap.open(self.url) time.sleep(1.0) continue # 按目标帧率抽帧 now = time.time() if now - self.last_push >= 1.0 / self.target_fps: if self.frame_queue.full(): self.frame_queue.get() # 丢旧帧 self.frame_queue.put(frame) self.last_push = now self.cap.release() def get_frame(self): try: return self.frame_queue.get(timeout=1.0) except queue.Empty: return None

队列长度必须设置上限。拉流线程只是取流,推理一旦跟不上,队列里积压的都是旧画面,统计出的数据就失去了实时性。所以宁可丢弃旧帧,也不要把队列变成无底洞。抽帧率方面,前面说过先按10FPS配置,后期算力有余可以再调高。

3.2 YOLO26推理与类别过滤

推理接口的具体写法取决于你用的推理后端,这里给出一段逻辑形态示意,重点在后面几个业务参数:

import torch model = load_yolo26_engine("yolo26.engine") # 交通场景里最常见的四类 # 类别映射:person=0, car=2, bus=5, truck=7 ALLOWED_CLASSES = {0, 2, 5, 7} def detect_frame(frame): results = model.infer(frame, conf_thres=conf_thres, iou_thres=iou_thres, imgsz=1280) boxes = results.boxes.xyxy.cpu().numpy() scores = results.boxes.conf.cpu().numpy() cls_ids = results.boxes.cls.cpu().numpy().astype(int) # 只保留业务需要的类别 keep = [i for i, cid in enumerate(cls_ids) if cid in ALLOWED_CLASSES] return boxes[keep], scores[keep], cls_ids[keep]

业务上为什么只留这四类?因为统计报表只关心机动车和行人。自行车、电动车、摩托车这类小目标在路口场景又密集、又互相遮挡、ID极不稳定,第一版先不碰它们,把主流程跑稳定再扩展。每多一个类别,跟踪和统计的复杂度不是线性上升,而是组合爆炸式上升。

置信度阈值和NMS阈值怎么定?

参数建议范围说明
conf_thres0.25~0.45夜间调低,白天调高
iou_thres0.45~0.55车流密集可调低,防止两帧之间框过度合并
imgsz1280或1536小目标多就上1536

3.3 ID稳定是统计准确的地基

检测模型输出的每一帧框本身没有身份信息,上一帧的A车和这一帧的A'车是不是同一辆车,需要靠跟踪算法关联。在交通场景里,我建议优先用ByteTrack这套思路:它区分高置信度和低置信度的检测框,低置信度框在遮挡时能起到“续命”作用,比单纯用匹配IoU的跟踪器抗遮挡能力强不少。

实际使用时需要注意几个参数:

  • 轨迹保留帧数。这个值决定目标被遮挡多久之后还允许恢复ID。帧率10FPS的场景,保留30帧左右才够大车从行人身前经过的时间。
  • 匹配阈值。画面里目标检测框抖动比较大的时候,IoU匹配阈值要稍微放宽,否则同一个目标会频繁生成新ID。
  • 最小显示帧数。一个新轨迹至少要连续出现5到10帧才允许参与统计,避免单帧误检产生“幽灵目标”。

端口配置完后,要经常看可视化效果:把每个目标的ID号画在框上,如果发现同一个车从画面这头走到那头换了3个ID,说明跟踪参数还不对,这种状态下做统计,结果一定是乱的。

3.4 过线统计算法这样写不会重不漏

车流统计和行人统计我用了两种逻辑。车流用“虚拟线”方案,在画面里给每条车道画一条横向计数线;行人和非机动车用“区域”方案,在路口人行横道区域附近统计进出。

虚拟线方案的判定逻辑不复杂:

def detect_line_cross(last_pt, cur_pt, line_y): up = last_pt[1] > line_y and cur_pt[1] <= line_y down = last_pt[1] < line_y and cur_pt[1] >= line_y if up: return "up" if down: return "down" return None

中心点从线上方跨到线下方,算一个方向;从下方跨到上方,算另一个方向。但只做这一步的话,同一个目标在线的边缘反复抖动,会被重复计数。所以每个跟踪目标要维护一个“已计数”状态,第一次跨线加1,之后只要这个ID没有消失,即使它在线附近来回摆动也不再计入。

区域方案的思路类似:定义一个人行横道多边形ROI,行人目标中心点从未在区域内变为在区域内,记一次进入;从在区域内变为不在区域内,记一次离开。区域方案的好处是行人在人行横道上逗留、折返,只要不离开区域就不会重复计数。

3.5 结果落库与实时推送

统计结果不是打印到控制台就行,要按“路口ID + 方向 + 类别 + 时间粒度”组织起来落库。我一般把原始统计按分钟聚合写入数据库,比如“某路口东进口,1分钟内通过轿车32辆、行人18人”。这样后续做报表、按小时分析潮汐流量都很方便。

如果业务场景需要实时预警,比如行人区域拥挤、车辆排队过长,可以通过消息队列把事件推给下游平台。消息体里建议带上抓拍时间、路口ID、摄像头编号和目标框坐标,方便对面做实时展示或者人工复核。有一点容易被忽略:所有服务器和摄像头时间源必须统一,否则跨摄像头统计出来的时序数据会错位,后面的轨迹分析、绿信比优化全都对不上。

4. 服务器部署与多路调优

4.1 单卡批量推理,先把吞吐提上去

把很多路视频帧逐张送进GPU是最浪费算力的做法。GPU的并行能力只有在一个batch里有足够多张图时才能发挥出来。我在模拟项目X里把4路摄像头的帧攒成一个batch送进TensorRT推理,单路FPS提升非常明显。

批量推理的代码语义大概是:

batch = [] batch_meta = [] for cam_id, frame in ready_frames.items(): batch.append(preprocess(frame)) batch_meta.append(cam_id) # 拼成一个 batch 送入模型 batch_tensor = torch.stack(batch) results = engine.infer(batch_tensor) # 按 batch_meta 把结果拆回各摄像头 for i, cam_id in enumerate(batch_meta): boxes = extract_boxes(results[i])

拼接之前每路帧大小要统一到同一个分辨率,所以预处理统一走1280×1280的resize。这个操作有一定的画面裁切,但对交通监控影响不大,因为目标基本集中在画面中下方。

4.2 多路并发不要踩的坑

并发架构上,最容易犯的错是“一路摄像头一个GPU推理进程”。如果每路摄像头都加载一份模型,显存会翻倍甚至翻四倍,一个16G显存根本扛不住几路。正确的做法是单个GPU进程加载一份模型,内部统一管理多路的推理请求。

解码和推理要分开。多路视频解码虽然是CPU密集操作,但它本身有阻塞风险,网络一卡就可能卡住整个进程。我建议拉流和解码放在独立进程里,通过共享队列把帧交给GPU推理进程。推理进程只消费队列,不直接碰网络、不直接碰摄像头。

还要设计降级策略:当某一路摄像头画面长时间无法解码,或者队列积压超过阈值,系统自动降低该路的抽帧率,从10FPS降到5FPS,保证其他路不受影响。宁可牺牲单路实时性,也不能让一路故障拖垮整套系统。

4.3 压测方法和资源规划

上线之前做一次压测很有必要。压测不是简单开一路看看FPS,而是要把业务流量模拟到真实水平,记录系统表现。

压测记录表大概长这样:

测试项配置结果
单路推理吞吐1080P、10FPS抽帧、FP16约48FPS
4路并发吞吐每路10FPS、batch=4总吞吐约30FPS
端到端延迟从摄像头到统计结果约0.8秒
CPU占用4路解码+跟踪约65%
GPU显存占用batch=4、FP16约5.2GB

如果端到端延迟超过2秒,就得检查是不是解码队列堆帧、批处理等待时间过长,或者跟踪后处理拖慢了主循环。解决手段无非三个:降抽帧率、降低输入分辨率、换更小的模型变体。

4.4 保活、重连与日志复盘

长时间运行的项目,服务器上必须有一套保活机制。RTSP流经常会因为网络波动断掉,拉流线程要能在断流后自动重连,重连间隔1到3秒,同时记录断流时长。连续重连失败超过一定次数,要能主动告警,而不是默默在后台无限重试。

GPU推理进程也需要有看门狗。模型推理如果连续输出空结果,或者显存突然暴涨,看门狗要能重启进程,并自动加载最新权重。所有关键日志按“摄像头ID + 日期”分目录存储,检测置信度、跟踪ID数量、统计结果都留痕。这个习惯在追查线上问题时特别有用——哪一路数据异常,直接回看当天的日志和可视化录像,几分钟就能定位原因。

5. 常见问题排查与调优实录

5.1 高频问题速查表

真实项目里遇到的问题,很多不是模型本身的问题,而是系统参数、数据、环境之间互相影响的结果。我先给一张问题排查表,下面再展开讲几个高频坑。

现象可能原因处理办法
同一辆车换几个ID跟踪匹配阈值过严、帧率太低放宽IoU匹配阈值、提高抽帧率
夜间行人漏检严重置信度阈值过高、夜间样本不足夜间时段调低阈值、补充夜间数据
白天把路牌、树影检成车置信度过低、负样本不够提高置信度阈值、收集误检图迭代
计数一下子多很多重复计数逻辑不完善检查“已计数”状态、增加冷却帧数
某一摄像头画面停滞RTSP断流、解码进程卡死自动重连、看门狗重启
显存OOM每路独立建了推理进程改单进程多路批处理
多路并发延迟越来越大队列积压、丢帧策略失效限制队列长度、降级抽帧率

5.2 阈值调优不能一把尺子量到底

很多团队喜欢全局写死一个置信度阈值,这在大规模多路场景里不是最优解。交通场景的光线变化是有规律的:白天和夜间、晴天和雨天,最优阈值差异很大。我最后做成了分时段阈值:白天conf=0.4,夜间conf=0.25,雨夜再降一点。这样白天误检可控,夜间漏检也能缓解。

NMS阈值的影响比较隐蔽。行人密集时,几个行人靠得近,如果IoU阈值太高,框会被合并成一个,导致漏检;调到太低又会导致同一个目标输出多个重叠框,跟踪会乱。比较稳的做法是保持0.5附近,如果发现同目标重复输出框,优先检查跟踪逻辑而不是继续调NMS。

5.3 计数漂移的深层原因

计数漂移就是统计数字和人工数出来的结果对不上。最常见的原因是跟踪ID切换:车辆在过线前被遮挡,旧ID丢失,重新出现后生成新ID,系统就当成两辆车。解决思路是在过线统计里加“冷却期”和“最小轨迹长度”:一个跟踪目标至少要稳定出现10帧,且过线前已经存在了足够长的轨迹,才允许计数。

还有一种漂移来自过线点的抖动。车辆慢速排队时,目标中心点停在虚拟线附近,上下抖动就会反复触发计数。处理办法是在目标第一次过线后,把它的ID标记为“已统计”,即使之后它退回线另一侧也不再重复计。配合方向判断,同一辆车从对向绕回来过第二次线,那才允许计入另一个方向。

5.4 跨路口复用与持续迭代

模拟项目X里面,我一开始只在几个路口采集了数据,后来要扩展到另一个完全不同的路口。新路口的摄像头安装角度更斜,画面里多了绿化带遮挡。发现这个问题后,日间模型在新路口表现明显下滑。

处理思路是用已有数据做更强的数据增强来模拟新场景,重点加随机透视变化和亮度扰动,然后拿少量新路口的视频做快速验证。如果误检集中在某一类目标,比如把绿化带里的灌木丛检成人,那就针对这类误检采集负样本,加到数据包里重训。这样比重新从零采数据、标数据快得多。

另外,模型迭代不能只靠训练一次就结束。我习惯每两周做一次“线上抽检”:随机抽几个时段的路口录像,用当前模型跑一遍,人工对比检测结果和真实情况。发现新问题就形成负样本补丁,持续滚进下一轮训练。这套流程坚持下去,模型的稳定性是远好于一次性训练完就不动的。

最后分享一个实际体会,这类项目真正交付时,算法工程师手里一定要有一套可视化回放工具。每一帧的检测框、跟踪ID、过线判定线、计数结果,全部叠加在原始画面上。这样一来,现场人员说“数据不对”的时候,你按下暂停就能看到问题发生在哪一秒。真实项目里,“能解释清楚每一个统计数字怎么来的”,可能比模型精度高两三个点更让人觉得你靠谱。如果想在现有基础上再扩展,往非机动车分类、违章检测、排队长度分析这几个方向走,前面的取流、跟踪、统计模块都不用推倒重来,这是这套设计留给后续最大的红利。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询