拓冰建站拓冰建站
首页 / 资讯中心 / 正文

基于YOLOv8和ByteTrack的Python视频车辆测速实战

简介这是一套基于Python与OpenCV实现的车辆速度检测项目资源面向计算机视觉初学者、车流量分析爱好者及进行相关课程设计的开发者。压缩包共10个文件整体大小64.69MB主要包含可执行的Python检测脚本、Haar级联车辆识别模型、依赖说明与使用文档并配有4段MP4视频素材、AVI输出视频和GIF动态效果图覆盖输入道路视频与测速结果展示便于逐步骤对照实际效果。项目借助车辆检测与帧间位置变化估算速度直观演示从视频读取、目标识别、边缘框选到速度计算的完整技术流程难度适中适合作为智能交通算法入门的练手案例。CSDN上已有2786人学习下载说明该主题具有较高关注度。下载后既可以按文档快速跑通现有代码也能自行更换测试视频或微调检测参数进一步体会传统视觉方法在车辆测速中的局限与优化空间从而获得从理论到工程的实战经验。1. 视频车辆测速为什么说这是最值得先跑的交通视觉项目如果你手头有一段普通监控拍到的道路画面想算出每辆车大概开多快又不想买雷达、不想碰 GPS 定位那 python 视频车辆测速就是最务实的方案用 YOLO 系列做检测用跨帧跟踪把同一辆车串起来再通过地面标定把像素位移换算成真实米数最终得到速度。这个方向能直接落地在交通事故责任分析、小区/园区车速预警、交通流量调研这些场景并且单人用一台普通电脑就能在两三天内跑出可用结果。适合正在做毕设的学生、刚接触视觉开发的工程师以及想给现有监控系统增加车速感知能力的团队。它最大的价值不是算法多新颖而是把检测、跟踪、几何换算这三块成熟技术串成了一条完整链路。2. 核心原理与选型先搞懂测速是怎么算出来的2.1 帧间位移除以时间测速的底层公式任何视频测速最终都逃不开这个公式速度 位移 / 时间。有两层单位要从源头理清。第一层是时间一段 30 FPS 的视频相邻两帧间隔约 0.033 秒如果你用的是手机录制的“动态帧率”视频实际帧间隔会在 0.02 到 0.05 秒之间跳动这时候直接用 1/FPS 计算就会引入 30% 以上的误差。第二层是位移目标在画面里移动了 100 个像素这 100 像素对应真实世界的多少米取决于摄像机距离地面的高度、俯仰角、焦距以及目标当前在画面里的位置。远处的车移动 100 像素可能实际走了 10 米近处的车移动 100 像素可能只走了 0.5 米。所以正确做法是把检测框底部的中心点作为车辆接地点的投影先做透视变换映射到真实地面坐标再计算位移。检测框底部比中心点靠谱因为遮挡、车高差异对中心点影响大而底部中心能更稳定地落在路面上。2.2 检测模型选型从 YOLOv5 到 YOLOv8 怎么选检测器是这条链路的起点它的稳定性直接决定测速结果是否可看。我的建议是优先使用 YOLOv8不是因为它精度碾压而是因为它的使用成本最低、输出接口最顺。YOLOv8 的model(frame)返回结果里能直接拿到类别、置信度和归一化的框坐标不用像 YOLOv5 那样自己解析 txt 输出或额外处理 anchor这对快速跑通流程非常关键。如果分析的是夜间红外监控或低照度场景可以把模型换成 YOLOv8s 甚至 YOLOv8m如果只关注车辆、不关心行人分得细不细用 COCO 预训练权重即可类别里包含 car、truck、bus、motorcycle。从指标上看YOLOv8s 在 COCO 上的 mAP50 约 44.9YOLOv5s 约 37.4但测速场景不追求分类细致更看重框的稳定性。我实际跑下来同样是 640 输入YOLOv8 的框在高动态运动目标上抖动明显更小这能让后续速度曲线更平滑。至于 RT-DETR 这类 transformer 检测器精度虽然高但推理延迟对 CPU 和低端 GPU 不友好视频测速是逐帧处理单帧延迟会被放大成持续卡顿不值得为了零点几个点的精度牺牲实时性。2.3 跟踪器选型为什么跨帧匹配用 ByteTrack 更省事检测器给出每一帧的框但测速需要“同一辆车持续被识别”。这个任务交给跟踪器。常见选择有 DeepSORT 和 ByteTrack。DeepSORT 需要额外加载一个 ReID 特征提取模型用来判断前后两帧的框是不是同一辆车好处是抗遮挡能力强坏处是多一个模型就多一个黑匣子特征提取模型的尺寸、训练数据分布都会影响匹配结果调试成本偏高。ByteTrack 的思路完全不同它不依赖外观特征而是用高置信度检测框做主要匹配再用低置信度框补匹配纯靠位置 IoU 和运动预测就能把目标串起来。这个特性特别适合车辆测速因为同一条车道上车辆外观相似度极高ReID 反而容易把前后车混淆。ByteTrack 只有一个可调参数track_thresh通常 0.4 到 0.6默认值就能跑得很好。真遇到车与车紧贴并行导致的 ID 切换靠后面讲的速度平滑策略可以兜住大部分异常。2.4 透视变换与地面标定像素距离换算成米这是整个测速方案里最容易被敷衍、也最容易翻车的一步。很多人直接用一个固定比例系数比如 1 像素 0.05 米去算速度这在摄像机平行于路面的理想假设下才成立。实际监控都是斜着俯拍画面下方近处 1 像素对应 0.02 米画面远处可能对应 0.5 米比例系数随纵坐标变化差出几十倍用固定系数算出的速度基本等于随机数。正确做法是用单应变换。在画面里选一条车道上的四个点构成一个矩形量出这个矩形对应的真实长宽比如宽 3.7 米长 30 米然后用cv2.getPerspectiveTransform求出一个 3x3 矩阵。后续每个检测框底部中心点都用这个矩阵映射到真实地面坐标单位是米速度计算直接按米每秒处理即可。这里有一个关键约束四个点必须落在同一个平面也就是路面上不能选到车辆侧面、绿化带或路灯杆上。标定矩形最好顺着车道延伸方向放覆盖车辆从画面远处到近处的行驶区间。3. 用 YOLOv8 ByteTrack 跑通车辆测速完整实现3.1 环境准备装好 Python 并配置编辑器环境建议用 Python 3.9 或 3.10 建一个独立虚拟环境避免和系统 Python 打架。如果你还没装 Python可以直接去 python 官网下载安装包安装时勾选 Add Python to PATH装好之后在 PyCharm 或 VS Code 里配置 python 环境指向这个虚拟环境即可。以下命令在终端里逐行执行python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate pip install ultralytics boxmot opencv-python pip install numpy逻辑说明第一行创建虚拟环境第二行激活第三行安装核心依赖。ultralytics自带 YOLOv8 模型权重下载和推理接口boxmot封装了 ByteTrack 跟踪器opencv-python负责视频读取、绘制和透视变换。如果下载模型权重很慢可以手动把yolov8s.pt下载后放到项目目录代码里直接指定本地路径。参数说明Python 版本不要用 3.12 之前的太老版本boxmot在某些 Python 3.8 环境下会出现依赖冲突。安装完可以用python -c import cv2, ultralytics, boxmot验证是否全部导入成功。3.2 视频预检先确认帧率和编码跑测速前必须先确认视频能不能被 OpenCV 正常读取以及帧率是否稳定。很多手机录制的视频是 HEVC 编码直接cv2.VideoCapture会读取失败或只有声音没有画面这是因为系统缺少 HEVC 视频扩展或 OpenCV 没有对应解码器。建议统一转成 H.264 编码的 MP4 再处理ffprobe -v error -select_streams v:0 -show_entries streamr_frame_rate,avg_frame_rate -of defaultnoprint_wrappers1 input.mp4 ffmpeg -i input.mp4 -c:v libx264 -r 30 -fps_mode cfr output.mp4逻辑说明第一条命令用 ffprobe 查看视频的真实帧率重点看两个字段——r_frame_rate是基础帧率avg_frame_rate是平均帧率两者不一致说明视频存在变帧率情况。第二条命令把视频重编码为固定 30 FPS-fps_mode cfr强制恒定帧率这是后续时间戳计算能成立的前提。参数说明如果你的视频是普通监控摄像头抓出来的帧率本来就是固定的 25 或 30可以跳过转码直接在代码里用cap.get(cv2.CAP_PROP_FPS)获取 FPS。但只要是手机拍摄、短视频平台下载或视频拼接软件输出的素材强烈建议先做一次转码。转码会损失一点画质但测速场景里帧率稳定比画质更重要。3.3 四点标定点击车道矩形并生成透视矩阵打开视频第一帧用鼠标依次点击车道区域的四个角点左下、右下、左上、右上。代码会把点击坐标打印出来并存储方便你微调后重新运行。import cv2 import numpy as np points [] def on_mouse(event, x, y, flags, param): if event cv2.EVENT_LBUTTONDOWN: points.append((x, y)) print(f点 {len(points)}: ({x}, {y})) if len(points) 4: cv2.destroyWindow(calibrate) cap cv2.VideoCapture(output.mp4) ret, frame cap.read() cv2.imshow(calibrate, frame) cv2.setMouseCallback(calibrate, on_mouse) while len(points) 4: cv2.waitKey(1) cv2.destroyAllWindows() src np.array(points, dtypenp.float32) # 按 左下、右下、左上、右上 的顺序排列对应真实坐标矩形 dst np.array([[0, 30], [3.7, 30], [0, 0], [3.7, 0]], dtypenp.float32) M cv2.getPerspectiveTransform(src, dst)逻辑说明M就是 3x3 透视变换矩阵它把图像像素坐标映射到以“米”为单位的地面坐标。src是从画面上点选的四个像素坐标dst是对应的现实地面坐标——这里假设车道宽 3.7 米、标定区域长 30 米。如果你的场景是双向车道或非标准宽度只需改dst里的数值。参数说明标定矩形的宽度要量实际车道线内侧宽度一般城市车道 3.5 到 3.75 米高速车道 3.75 米纵向长度 20 到 50 米都行越长覆盖范围越大但四点取景越大近处选点越容易受透视变形影响建议先取 25 到 30 米。四个点的点击顺序必须和dst一一对应否则算出来的矩阵会把路面扭曲成奇怪形状。3.4 主循环检测、跟踪、坐标变换下面是对每一帧视频的核心处理逻辑包括 YOLOv8 检测、ByteTrack 跟踪和透视变换。代码会持续输出每个跟踪目标的 ID 和速度。import cv2 import numpy as np from ultralytics import YOLO from boxmot import ByteTrack model YOLO(yolov8s.pt) tracker ByteTrack(frame_rate30) cap cv2.VideoCapture(output.mp4) prev_ground {} # track_id - (x, y) prev_time {} # track_id - timestamp while True: ret, frame cap.read() if not ret: break results model(frame, imgsz640, verboseFalse)[0] dets [] for box in results.boxes: x1, y1, x2, y2 box.xyxy[0].cpu().numpy() conf float(box.conf[0]) cls int(box.cls[0]) if cls in [2, 5, 7]: # car, bus, truck dets.append([x1, y1, x2, y2, conf, cls]) dets np.array(dets) if len(dets) 0 else np.empty((0, 6)) tracks tracker.update(frame, dets) now cap.get(cv2.CAP_PROP_POS_MSEC) / 1000.0 for track in tracks: x1, y1, x2, y2, track_id, conf, cls track.astype(float) cx (x1 x2) / 2.0 bottom y2 # 用框底部中心点代表车辆接地点 ground_point cv2.perspectiveTransform( np.array([[[cx, bottom]]], dtypenp.float32), M)[0][0] if track_id in prev_ground: old_x, old_y prev_ground[track_id] dt now - prev_time[track_id] if dt 0: dist np.linalg.norm(ground_point - np.array([old_x, old_y])) speed_kmh dist / dt * 3.6 if dist 2.0: # 单帧最大位移不超过 2 米过滤跳变 print(fID {track_id}: {speed_kmh:.1f} km/h) prev_ground[track_id] ground_point prev_time[track_id] now cap.release()逻辑说明代码先把 YOLOv8 输出的检测框整理成[x1, y1, x2, y2, conf, cls]格式传给 ByteTrack跟踪器返回带 ID 的轨迹。bottom y2取的是检测框底部 y 坐标因为框底比框中心更接近车辆与地面的接触点抗车身高度差异影响的能力更强。cv2.perspectiveTransform把该点映射到地面坐标单位是米。速度等于当前帧与上一帧的地面坐标距离除以时间差再乘 3.6 转成 km/h。参数说明imgsz640是推理输入尺寸如果画面中车辆很小可以改成 960 或 1280精度会提升但速度变慢。cls in [2, 5, 7]对应 COCO 数据集的 car、bus、truck如果想统计摩托车把 3 加进去但如果车道较窄摩托车框小且抖动厉害测速误差会偏大。dist 2.0是单帧位移阈值正常车速下单帧位移不会超过 2 米超过说明目标跟踪跳变或透视变换出现异常点直接丢弃最安全。3.5 速度平滑与可视化输出直接把每秒算出的瞬时速度展示出来会非常抖因为检测框本身有 1 到 2 像素的抖动映射到地面坐标后就是 5 到 20 公分的跳动。常见做法是加一个一阶低通滤波也就是指数移动平均smoothed_speed {} alpha 0.5 if track_id in smoothed_speed: smoothed_speed[track_id] alpha * speed_kmh (1 - alpha) * smoothed_speed[track_id] else: smoothed_speed[track_id] speed_kmh cv2.putText(frame, f{smoothed_speed[track_id]:.1f} km/h, (int(x1), int(y1) - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)逻辑说明alpha越大当前帧速度的权重越高响应越快但抖动也越大alpha越小曲线越平滑但速度变化响应越慢。车辆匀速行驶时alpha0.3到0.5都很合适如果场景里有明显加速或刹车alpha0.6更能还原真实速度变化。参数说明对于入库检测框只有 5 到 10 帧的车辆平滑窗口太大会导致它还没到目标速度就消失所以短轨迹车辆可以用alpha0.7减少滞后。另外如果检测到 ID 发生切换一定记得清理prev_ground和smoothed_speed里对应 ID 的历史缓存否则新 ID 会继承旧 ID 的位置产生一个虚假的瞬时高速度。4. 避坑指南这些坑不处理测出来的速度就是摆设4.1 现象车速忽快忽慢像坐过山车原因视频帧率不稳定或者代码直接使用cap.get(cv2.CAP_PROP_FPS)作为时间间隔但实际每帧间隔在跳动。尤其是手机拍摄的“省电模式”视频画面静止时只有 10 FPS画面有车经过时自动提升到 30 FPS用固定值算时间差必然导致速度结果严重失真。解决按第二章的方法先把视频用 FFmpeg 强制转为恒定 30 FPS。处理后再跑一遍测速结果通常会立刻稳定。这条几乎能解决 80% 的“速度乱跳”问题是我的血泪经验。4.2 现象车还没开出画面速度突然变成 0原因跟踪目标短暂丢失比如一辆车被旁边的大货车遮挡ByteTrack 在遮挡期间输出的 ID 被判定为同一个目标但位置长时间没有更新或者目标确实换了 ID 而旧 ID 的轨迹还在保留。解决设置超时机制超过 0.5 秒没有位置更新的轨迹直接删除并清理缓存。更稳妥的是在更新prev_ground时记录最后更新时间计算速度前先检查now - prev_time[track_id]是否大于 0.5 秒大于就跳过本次速度计算而不是输出 0。同时给每个 ID 维护一个速度滑动窗口取最近 5 个有效速度的中位数作为最终输出能进一步免疫单帧抖动。4.3 现象远处车辆速度偏大近处偏小原因透视标定的四个点没有贴地或者标定矩形没有沿着车道方向放置。常见错误是把角点取到路肩上的树桩、电线杆底座或对向车道边缘导致远处区域被放大近处区域被压缩。解决回到第一步重新标定确保四个点都在同一条车道的路面延伸线上。验证方法很简单拍摄时在画面里放一辆已知长度的小轿车约 4.5 米用代码量一下它在地面坐标系里的宽度或长度如果换算结果明显偏离 4.5 米说明标定矩阵有问题。这个校准步骤只要在正式跑数据前花两分钟就能避免后面所有结果变成废数据。4.4 现象夜间或雨天车辆频繁丢失原因YOLOv8 在低照度下检测性能明显下降尤其是远光灯直射摄像头的车辆检测框会框住光晕而不是车身导致跟踪点在地面坐标里剧烈跳动。雨天地面积水会反射车灯也会出现大量误检框。解决先做图像增强再送进模型常见做法是在model(frame...)之前加一行预处理把帧灰度化后做 CLAHE 直方图均衡化再转回 BGR。代价是增加少量延迟但夜间检测召回率能提升一个档次。如果画面里灯光过曝区域集中可以对这部分区域做 mask把高光像素替换为周围环境均值避免模型产生假目标。夜间场景建议直接用 YOLOv8m 或 YOLOv8l精度提升比白天明显得多。4.5 现象一辆电动车测出 200 km/h原因目标匹配错误。电动车体积小、速度变化快检测框容易在相邻帧之间跳变尤其是跟行人一起出现在人行横道附近时跟踪器可能接错了目标。另一个常见原因是目标跨越标定矩形边界在矩形外的区域透视变换结果失控坐标出现巨大偏移。解决只对标定矩形覆盖范围内的检测结果计算速度超出范围的目标直接跳过同时过滤速度极大值比如超过 160 km/h 或低于 3 km/h 的目标正常监控场景下不符合常识的结果直接剔除。保险起见目标必须在连续 5 帧以上都被跟上才输出速度这样能过滤掉大多数跳变产生的假轨迹。5. 进阶与验证测出来的数据怎么证明可信先说一个最关键的习惯任何一套测速代码在跑真实数据之前先跑一段“已知速度的合成视频”验证误差。常见做法是自己写脚本生成一段模拟视频一个黑色矩形在画面上匀速移动移动速度是已知值然后拿同一套代码去测它。具体做法是新建一个脚本用 OpenCV 绘制一个固定大小的矩形每个画面向右平移固定像素假设 1 像素等于 0.1 米、帧率 30那么实际速度就是已知的。把自己的测速代码跑上去如果误差超过 5%说明不是标定问题就是平滑参数问题这才是该调代码的时候而不是拿到真实视频后边跑边猜。真实场景登录验证也有一个替代思路找一段自己开车用行车记录仪拍的同一路段视频用 GPS 车速比对。但没有 GPS 时可以观察画面里的固定距离标线——比如收费站前的实线区域长度在图纸上可查车辆通过这段线的时间可以用秒表量两者相除得到平均速度再与你的测速结果对比。采样 10 辆车误差控制在 ±5 km/h 内这套流程就可以放心用了。输出方面建议把每个 ID 在每帧的速度、位置、时间戳写进 CSV而不是只输出视频画面。因为视频画面是给人看的CSV 才是能拿去分析的数据。画面上可以按速度区间着色——超过 80 km/h 显示红色40 到 80 显示黄色低速显示绿色这样生产环境里一眼能看出超速目标。代码里只要在绘制cv2.putText时根据speed_kmh切换颜色即可逻辑简单但价值明显。最后给你一个我自己踩出来的习惯每次跑新视频前先标定再从视频中间截取 10 秒的片段跑一遍全程肉眼观察匀速车辆的速度曲线有没有明显异常没有异常才处理完整视频。处理完成后保留中间结果 CSV方便后续排查——测速这种场景后悔药就是逐帧记录。希望帮到你。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门