
简介这份PDF教程面向智能交通领域开发者、计算机视觉学习者与自动驾驶感知方向研究人员围绕YOLOv11在实时车辆速度估计与轨迹追踪中的落地应用展开共45页内容完整、条理清晰支持目录跳转与阅读器左侧大纲快速定位。压缩包内仅含1个PDF文件大小约2.29MB便于下载后直接查阅。教程从智能交通需求分析切入系统讲解YOLOv11网络架构、预测机制与损失函数并延伸至系统架构设计、数据采集与处理、模型训练优化、速度估计算法实现及轨迹追踪算法实现等模块涵盖SIFT与ORB特征点匹配、Lucas-Kanade光流法、卡尔曼滤波、匈牙利算法、SORT与DeepSORT等关键技术同时讨论多传感器融合、抗遮挡处理与实时性优化思路。目前已有77人学习适合希望将YOLOv11应用于车辆检测、测速与多目标跟踪实战的读者参考文档仅供学习使用。1. YOLOv11 上车实时车辆速度估计与轨迹追踪到底难在哪很多人第一次做智能交通项目都会把问题想简单YOLOv11 检测框一画卡尔曼滤波一挂速度不就出来了真上路跑一遍就知道检测框在抖、ID 在跳、像素到米的换算全靠猜最后速度曲线像心电图。这个标题真正要解决的不是「怎么调 YOLOv11」而是把检测、追踪、标定、测速串成一条能实时跑、误差可控的流水线。它适合两类人一类是已经会用 ultralytics 跑通 YOLOv11 推理、想往交通场景落地的算法同学另一类是做路侧感知、卡口分析的工程同学需要一套能复现、参数可解释的方案。整条链路里YOLOv11 只负责「看见车」剩下「这辆车是谁、它跑多快」全靠追踪器和标定撑起来这也是后面几章要一层层拆开的东西。2. 从检测到测速的链路拆解YOLOv11 负责什么追踪器负责什么2.1 为什么单靠 YOLOv11 出不了速度YOLOv11 输出的是每一帧的检测框帧与帧之间没有身份关联。同一辆车在第 10 帧是[x1,y1,x2,y2]第 11 帧还是类似一个框但模型不知道这两个框是同一辆车。速度的本质是位移除以时间位移又依赖「同一个目标在连续帧里的位置差」所以没有身份关联就没有位移没有位移就没有速度。常见做法是引入多目标追踪MOT把检测框按 ID 串成轨迹。工业界最稳的组合是 YOLOv11 做检测、ByteTrack 或 BoT-SORT 做关联。ByteTrack 的思路是先把高分框和已有轨迹匹配再用低分框去补漏对遮挡和漏检的容忍度比纯 IoU 匹配高不少。这也是为什么很多交通项目里检测模型换了一茬又一茬追踪器却一直是 ByteTrack 系。选型上有个容易忽略的点YOLOv11 的检测频率和追踪器的匹配频率要一致。如果你用track模式跑ultralytics 内部已经帮你接了 ByteTrack直接出带 ID 的框省掉自己写关联逻辑。但如果你要改匹配阈值、要接自己的卡尔曼实现就得把检测和追踪拆开跑。2.2 用 ultralytics 的 track 模式跑通带 ID 的检测先确认环境。YOLOv11 在 ultralytics 8.3 之后的版本里已经内置不需要单独装包。下面是最小可跑的命令行和 Python 两种方式。# 安装或升级 ultralyticsYOLOv11 权重会自动下载 pip install -U ultralytics # 命令行直接跑追踪source 换成你的视频 yolo track modelyolo11n.pt sourcetest_traffic.mp4 trackerbotsort.yaml saveTruefrom ultralytics import YOLO # 加载 YOLOv11 检测权重n 是最小号交通场景建议 s 或 m model YOLO(yolo11s.pt) # track 模式检测 追踪一步到位persistTrue 保证视频帧间 ID 连续 results model.track( sourcetest_traffic.mp4, trackerbytetrack.yaml, # 也可换 botsort.yaml persistTrue, # 关键参数不加会导致每帧 ID 重置 conf0.3, # 交通场景建议 0.25~0.4太低会引入误检 iou0.5, classes[2, 5, 7], # COCO 里 2car 5bus 7truck只留车辆 streamTrue, # 视频流式推理省内存 saveTrue ) for r in results: boxes r.boxes if boxes.id is not None: # xyxy 是框坐标id 是追踪 IDcls 是类别 for xyxy, tid, cls in zip(boxes.xyxy, boxes.id, boxes.cls): print(xyxy.tolist(), int(tid), int(cls))逻辑说明model.track在内部对每一帧先做 YOLOv11 推理再把检测结果喂给追踪器做关联输出的boxes.id就是追踪 ID。persistTrue是血泪经验不加的话每调用一次 track 追踪状态就重置ID 会从 1 重新开始轨迹直接断掉。classes过滤掉行人、非机动车减少无关目标对追踪资源的占用。参数说明conf控制检测置信度阈值交通卡口场景光照稳定可以设 0.4路侧远景建议 0.25 左右因为远处车辆像素少、置信度天然偏低。iou是 NMS 的 IoU 阈值车辆密集时调低到 0.45 能减少漏检。tracker选 bytetrack 还是 botsort取决于场景ByteTrack 快、对遮挡鲁棒BoT-SORT 带 ReID 特征、跨帧身份更稳但更吃算力路口多车交织建议 BoT-SORT。2.3 轨迹数据怎么存后面测速才用得上追踪出来的 ID 和框不能只打印要按「ID → 时间序列」的结构存下来测速和轨迹平滑都依赖它。常见做法是用字典按 ID 聚合每个 ID 存一个列表元素是(frame_idx, timestamp, cx, cy)cx、cy 是框底边中心点——注意是底边中心不是框中心因为车辆接地点在底边用底边做像素位移更接近真实运动。import time from collections import defaultdict tracks defaultdict(list) # key: track_id, value: [(frame, ts, cx, cy), ...] frame_idx 0 t0 time.time() for r in model.track(sourcetest_traffic.mp4, persistTrue, classes[2,5,7], streamTrue): frame_idx 1 ts time.time() - t0 # 用真实时间戳别用 frame_idx/fps 估算 if r.boxes.id is None: continue for xyxy, tid in zip(r.boxes.xyxy, r.boxes.id): x1, y1, x2, y2 xyxy.tolist() cx (x1 x2) / 2.0 cy y2 # 底边中心 tracks[int(tid)].append((frame_idx, ts, cx, cy))逻辑说明用time.time()记录真实时间戳而不是用帧号除以 FPS是因为视频解码和推理会有抖动帧号推出来的时间在丢帧时误差会累积。defaultdict(list)让每个新 ID 自动建列表省掉判断。存底边中心点是为了后面做透视变换时接地点能直接映射到地面坐标系。参数说明如果视频是固定帧率且不丢帧用帧号算时间也可以但要在代码里显式记录 FPS 并校验实际解码帧数。轨迹列表建议加长度上限比如只保留最近 150 帧防止长视频内存爆掉。3. 像素位移换算成真实速度标定和透视变换怎么做3.1 为什么不能直接用像素速度乘系数新手最容易翻车的地方就是拿像素位移除以时间再乘一个「大概每像素多少米」的系数。这个系数在画面不同位置完全不一样近处一辆车占 200 像素远处只占 30 像素同样的真实位移在像素上差好几倍。直接乘固定系数近处速度偏低、远处速度偏高误差能到 50% 以上。正确做法是做逆透视变换IPM把图像平面映射到地面俯视图在俯视图里像素和米的换算才是线性的。前提是相机固定、路面近似平面路侧卡口和固定监控基本满足。核心是找四个在真实世界里构成矩形的点比如车道线的四个角量出它们的真实世界坐标算出单应性矩阵。3.2 单应性矩阵的求解与四个点的选法选点原则四个点要尽量分散、覆盖测速区域、共面都在路面上。常见做法是选一段已知长度的车道量出车道宽度和这段长度取四个角点。import cv2 import numpy as np # 图像上的四个点像素坐标按 左上、右上、右下、左下 顺序 src_pts np.float32([ [320, 480], # 左上近处左车道线 [960, 480], # 右上近处右车道线 [780, 300], # 右下远处右车道线 [500, 300], # 左下远处左车道线 ]) # 对应的真实世界坐标米俯视图里的矩形 # 假设这段路长 20 米、单车道宽 3.5 米 dst_pts np.float32([ [0, 20], [3.5, 20], [3.5, 0], [0, 0], ]) # 求单应性矩阵 H, status cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0) print(单应性矩阵:\n, H) def to_bird_eye(x, y, H): 把图像坐标映射到俯视图坐标米 pt np.array([x, y, 1.0]) mapped H pt mapped / mapped[2] # 齐次坐标归一化 return mapped[0], mapped[1]逻辑说明findHomography用四对点求 3x3 单应性矩阵RANSAC在点多于四对时能剔除误匹配这里只有四对点RANSAC 主要起校验作用。to_bird_eye做齐次坐标变换除以第三维完成归一化得到的就是以米为单位的俯视图坐标。参数说明src_pts的顺序必须和dst_pts一一对应顺序错了矩阵就废了。真实世界坐标的尺度由你自己定义只要 dst_pts 里两点间的距离等于真实米数即可。车道宽度按国标取 3.5 米这段长度建议选 15~30 米太短标定误差被放大太长远处点定位不准。提示标定一次不够相机如果有轻微震动或云台漂移单应性矩阵会失效。固定相机建议每周校验一次用已知长度的参照物比如车道虚线国标虚线长 6 米、间隔 9 米反推验证。3.3 从俯视图坐标算速度差分、平滑与单位换算有了俯视图坐标速度就是相邻两帧的欧氏距离除以时间差。但原始差分噪声大必须做平滑。常见做法是滑动窗口线性拟合用窗口内所有点拟合一条直线斜率就是速度比两点差分稳得多。import numpy as np def estimate_speed(track, H, window7): track: [(frame, ts, cx, cy), ...]返回速度序列km/h if len(track) window: return None speeds [] for i in range(window, len(track)): seg track[i-window:i] pts np.array([to_bird_eye(p[2], p[3], H) for p in seg]) # 俯视图坐标 ts np.array([p[1] for p in seg]) # 分别对 x、y 关于时间做一次多项式拟合取一次项系数为速度分量 vx np.polyfit(ts, pts[:, 0], 1)[0] vy np.polyfit(ts, pts[:, 1], 1)[0] v np.hypot(vx, vy) # 合速度单位 m/s speeds.append(v * 3.6) # 转 km/h return speeds逻辑说明对窗口内的时间序列分别拟合 x(t) 和 y(t)一次项系数就是该方向的速度分量合速度用勾股定理。相比首尾两点差分拟合用上了窗口内所有点对单帧检测抖动有抑制作用。窗口大小 7 是经验值25 FPS 下约 0.28 秒既能平滑又不至于把加减速抹平。参数说明window太小平滑不够太大速度响应滞后。车辆匀速场景可以到 9~11路口起步停车场景建议 5~7。速度单位换算 1 m/s 3.6 km/h别漏乘。如果轨迹点时间戳不均匀polyfit 依然成立因为它按实际 ts 拟合这也是前面坚持存真实时间戳的原因。4. 避坑与排查轨迹追踪和测速里最容易翻车的 5 个点4.1 ID 频繁跳变同一辆车换了三四个 ID现象一辆车从画面左侧开到右侧追踪 ID 从 3 变成 17 又变成 42轨迹被切成几段速度算出来全是断点。原因检测框在遮挡、远小目标、光照突变时置信度掉到阈值以下追踪器匹配不上就新建 ID。另外persist没开、或者每帧重新初始化追踪器也会导致 ID 重置。解决换 BoT-SORT 并开启 ReID让追踪器用外观特征做二次匹配把conf适当调低到 0.25 保住弱检测确认persistTrue如果自己写追踪循环追踪器实例要在循环外创建不能每帧 new 一个。4.2 速度整体偏大或偏小误差稳定在某个比例现象所有车速度都偏高 20%或者都偏低 15%误差方向一致。原因单应性矩阵的尺度错了通常是 dst_pts 里定义的真实距离和实际不符比如把车道宽度 3.5 米写成了 3 米或者选的四点实际不共面。解决用已知长度的参照物反推验证。找一段车道虚线国标虚线长 6 米、间隔 9 米在俯视图里量它的长度和 6 米对比按比例修正 dst_pts。四点共面问题靠选点解决别选到路沿、护栏上。4.3 远处车辆速度乱跳近处很稳现象画面下半部分的车速度曲线平滑上半部分的车速度忽高忽低。原因远处车辆像素少检测框抖动一两个像素映射到俯视图就是几十厘米的位移除以短时间差速度就爆了。这是透视变换的固有放大效应。解决对远处目标加大平滑窗口或者按画面位置设置速度有效范围超出物理合理值比如 0~180 km/h的直接丢弃。更彻底的做法是限制测速区域只在画面中下部、像素密度够的区域测速远处只做轨迹不做速度。4.4 多车并排时轨迹串了A 车的速度算到 B 车上现象两车并行或前后紧跟时ID 互换速度曲线出现不合理的突变。原因IoU 匹配在框重叠时容易错配尤其是同型号同颜色车辆外观特征也区分不开。解决BoT-SORT 的 ReID 能缓解但并排遮挡严重时仍会错。工程上常见做法是加运动方向约束用卡尔曼预测的位置做门控预测位置和检测框距离超过阈值的匹配直接拒绝。另外可以在测速前对轨迹做一次合理性校验速度突变超过物理加速度上限比如 10 m/s²的段标记为可疑。4.5 实时性不够25 FPS 视频跑出来只有 8 FPS现象离线跑还行实时流一接就掉帧速度估计延迟越来越大。原因YOLOv11 模型太大、输入分辨率太高或者追踪器 ReID 特征提取拖慢了整体。另外每帧都做全图推理没有做区域裁剪。解决交通场景用 yolo11s 或 yolo11n输入尺寸从 640 降到 416 或 320速度能翻倍只在 ROI 区域推理把画面裁到车道范围追踪器和检测解耦检测每帧跑、追踪按需跑用 TensorRT 或 ONNX Runtime 加速推理。实测 yolo11n 416 输入在主流 GPU 上能到 60 FPS 以上足够实时。5. 让速度估计更稳的两个进阶技巧轨迹补全与多帧投票前面把链路跑通了但真实路侧场景里遮挡是常态轨迹断一段速度就没了。我一般会加两层处理轨迹补全和多帧投票。轨迹补全用卡尔曼滤波在丢检帧做预测。ByteTrack 内部有卡尔曼但只用于匹配不对外输出补全后的轨迹。自己补的话对每个 ID 维护一个卡尔曼状态[x, y, vx, vy]丢检时用预测值填充重新检测到时用观测值更新。这样轨迹连续速度不会因为几帧漏检就断掉。from filterpy.kalman import KalmanFilter import numpy as np def make_kf(): kf KalmanFilter(dim_x4, dim_z2) kf.F np.array([[1,0,1,0],[0,1,0,1],[0,0,1,0],[0,0,0,1]]) # 匀速模型 kf.H np.array([[1,0,0,0],[0,1,0,0]]) # 只观测位置 kf.R * 0.5 # 观测噪声检测框抖动大就调大 kf.Q * 0.1 # 过程噪声车辆机动性强就调大 return kf逻辑说明状态量是位置和速度匀速模型假设短时间内车辆速度不变对交通场景够用。观测只有位置速度靠卡尔曼从位置序列里估计。R是观测噪声检测框越抖越大Q是过程噪声车辆急加速急减速时调大让滤波器更信观测。多帧投票的思路是单帧算出的速度不可信用最近 N 帧的速度估计做中位数滤波输出中位数作为当前速度。中位数比均值抗离群点偶尔一帧检测框跳变不会污染结果。N 取 5~9和前面的拟合窗口配合使用。实测这套组合下来匀速场景速度误差能压到 ±3 km/h 以内加减速场景 ±6 km/h 左右对卡口和路侧分析够用了。最后说个习惯每次换相机、换路段我一定重新标定并跑一段已知速度的参照车验证别信「上次标定还能用」。标定和验证这步偷懒后面所有速度数据都是玄学。希望帮到你。本文还有配套的精品资源点击获取