
简介这是一份面向计算机视觉初学者与OpenCV实战练习者的Python车辆测速项目资源围绕视频中车辆速度检测这一典型场景展开可用于课程设计、毕业设计或自学练手。压缩包共10个文件约64.69MB包含4个mp4与1个avi演示视频、1个gif效果预览、1个py测速脚本、1个xml车辆检测模型文件以及requirements.txt、README.md等环境与说明文档覆盖从素材到代码的完整链路。目前已有2787人学习下载说明该案例在入门群体中认可度较高。读者可借助现成脚本与示例视频理解视频读取、目标检测、帧间位移与速度换算的基本流程并参考README快速配置依赖、复现测速效果适合作为OpenCV视频处理与车辆测速方向的入门实践素材。1. 从一段路口监控说起python视频车辆测速到底在测什么路口摄像头拍下的画面里一辆车从画面左侧驶入、右侧驶出整个过程不过两三秒。如果我想知道它有没有超速靠肉眼盯屏幕是没用的——我需要把这段视频拆成帧在每一帧里找到车把车在画面里的像素位移换算成现实世界的米再除以时间。这就是 python视频车辆测速 要干的事用 Python 把「视频 → 车辆目标 → 像素轨迹 → 真实速度」这条链路串起来。它解决的不是「识别车牌」那种静态问题而是带时间维度的运动估计问题。适合谁做智慧交通 demo 的算法工程师、想用 python代码 练手计算机视觉的学生、需要给园区或停车场做低速测速原型的开发者。它不需要雷达不需要地感线圈一段普通监控视频加一台能跑 Python 的机器就能起步。但要注意单目视频测速天生有精度天花板标定做不好误差能到 30% 以上后面我会把这块讲透。2. 测速链路的四个环节从视频帧到公里每小时2.1 为什么是「检测 跟踪 标定 换算」而不是端到端很多人第一反应是找个模型直接回归速度。这条路在学术数据集上能跑落到真实监控视频里基本翻车。原因是速度不是视觉特征它是位移对时间的导数模型必须隐式学会「像素 → 米」的映射而这个映射随摄像头安装高度、俯仰角、焦距变化换一个机位就失效。常见做法是拆成四步每步可独立调试环节输入输出常用工具目标检测单帧图像车辆 bboxYOLOv8 / YOLOv5多目标跟踪连续帧 bbox带 ID 的轨迹ByteTrack / DeepSORT相机标定标定参照物像素-米比例透视变换 / 手工标尺速度换算轨迹 比例 帧率km/h位移差分这样拆的好处是检测不准换模型ID 跳变调跟踪器速度整体偏大回头查标定。每一环都有明确的排错入口而不是面对一个黑匣子干瞪眼。2.2 用 YOLOv8 在本地跑通车辆检测的最小命令先保证环境能跑。python安装 这一步如果还没做去 python官网下载 装 3.9 以上版本装的时候勾选 Add to PATH。然后建虚拟环境别把依赖装到全局这是血泪经验python -m venv venv # Windows venv\Scripts\activate # Linux / macOS source venv/bin/activate pip install ultralytics opencv-python numpyultralytics这个包自带 YOLOv8 的推理接口和预训练权重下载逻辑第一次运行会自动拉yolov8n.pt。opencv-python负责读写视频帧numpy做矩阵运算。三个包装完大概几百 MB如果 pip 慢换国内镜像源即可。检测代码本身很短from ultralytics import YOLO import cv2 # 加载 nano 版本速度优先测速场景对精度要求没车牌识别那么高 model YOLO(yolov8n.pt) cap cv2.VideoCapture(road.mp4) fps cap.get(cv2.CAP_PROP_FPS) # 后续速度换算必须用到 print(f视频帧率: {fps}) while cap.isOpened(): ret, frame cap.read() if not ret: break # classes[2,5,7] 分别对应 COCO 的 car/bus/truck过滤掉行人 results model(frame, classes[2, 5, 7], conf0.4, verboseFalse) for box in results[0].boxes: x1, y1, x2, y2 box.xyxy[0].tolist() cv2.rectangle(frame, (int(x1), int(y1)), (int(x2), int(y2)), (0, 255, 0), 2) cv2.imshow(detect, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()逻辑说明classes[2,5,7]是 COCO 数据集的类别编号2 是 car5 是 bus7 是 truck把行人、自行车过滤掉能减少误检。conf0.4是置信度阈值调低会召回更多但误检增加调高会漏检远处小车。verboseFalse关掉每帧日志不然控制台刷屏。参数怎么改如果视频里车很小比如高空俯拍把imgsz设成 1280 甚至 1920默认 640 会丢小目标如果机器没 GPUyolov8n是唯一现实选择yolov8x在 CPU 上一帧要好几秒根本没法处理视频。2.3 ByteTrack 接上检测结果拿到稳定 ID光有 bbox 没用我需要知道「这一帧的车」和「上一帧的车」是不是同一辆。ByteTrack 的优势是不依赖外观特征纯靠运动预测和 IoU 匹配速度快适合实时场景。ultralytics 已经把跟踪器集成进去了改一个参数就行from ultralytics import YOLO import cv2 model YOLO(yolov8n.pt) cap cv2.VideoCapture(road.mp4) fps cap.get(cv2.CAP_PROP_FPS) # persistTrue 让跟踪器在帧之间保持状态 results model.track( sourceroad.mp4, trackerbytetrack.yaml, classes[2, 5, 7], conf0.4, persistTrue, streamTrue # 流式读取避免一次性加载整个视频 ) for r in results: if r.boxes.id is None: continue ids r.boxes.id.tolist() boxes r.boxes.xyxy.tolist() for tid, box in zip(ids, boxes): x1, y1, x2, y2 box # 用 bbox 底边中点作为「车辆接地点」比中心点更贴近真实位置 cx (x1 x2) / 2 cy y2 cv2.putText(r.orig_img, fID{int(tid)}, (int(x1), int(y1) - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)关键点persistTrue必须开否则每帧跟踪器重置ID 永远从 1 开始。streamTrue对长视频很重要不然内存会被帧缓存吃满。取cy y2而不是 bbox 中心是因为车辆与地面的接触点在底边用底边做位移计算更接近真实运动轨迹这个细节能减少几个百分点的误差。3. 像素到米的换算标定做不对后面全白费3.1 单目测速的几何本质与两种标定路线摄像头把三维世界投影到二维平面深度信息丢了。要恢复「米」必须引入先验。两条路线一是透视变换IPM假设路面是平面选四个已知真实坐标的地面点算出单应矩阵把图像投影成鸟瞰图鸟瞰图里像素和米是线性关系。适合摄像头固定、路面平坦的场景。二是手工标尺在画面里找一条已知长度的参照物车道线虚线 6 米、斑马线宽度、路边护栏间距直接算「每像素多少米」。简单粗暴但只在参照物所在的深度平面上准确车离参照物远近不同比例会变。我一般先用第二种快速验证跑通了再上第一种做正式版。因为 IPM 需要标定点坐标现场量取很麻烦而车道虚线长度是国标查一下就知道。3.2 用 cv2.getPerspectiveTransform 做鸟瞰图变换假设我在画面里选了四个点对应现实世界一个矩形区域。代码import cv2 import numpy as np # 图像里的四个点顺时针左上、右上、右下、左下 src np.float32([[320, 400], [960, 400], [1200, 700], [80, 700]]) # 对应的真实世界坐标单位米假设这个区域宽 12 米、长 20 米 dst np.float32([[0, 0], [12, 0], [12, 20], [0, 20]]) M cv2.getPerspectiveTransform(src, dst) print(单应矩阵:\n, M) # 反算鸟瞰图里 1 像素 多少米 scale_x 12 / 1200 # 假设鸟瞰图宽 1200 像素 scale_y 20 / 1200 print(f横向比例: {scale_x:.4f} 米/像素)逻辑说明src是你在原图上点的四个地面点dst是你希望它们映射到的矩形坐标。getPerspectiveTransform解出 3x3 单应矩阵。之后任何图像点乘这个矩阵就落到鸟瞰图坐标系单位是米。参数怎么改src的四个点必须共面都在路面上如果点到车身或天空上矩阵就错了。选点技巧是找车道线的拐角、停止线端点这类清晰的地面标记。dst的宽高按实际测量填不要随便写它决定了比例尺。提示标定完一定要验证。在鸟瞰图上量一下车道宽度标准车道 3.5 米左右如果量出来 5 米或 2 米说明选点或实际尺寸填错了。3.3 速度公式与帧率、时间戳的坑拿到鸟瞰图坐标后速度计算就是差分import numpy as np def compute_speed(track_history, fps, scale0.01): track_history: [(frame_idx, x_m, y_m), ...] 鸟瞰图坐标单位米 fps: 视频帧率 scale: 米/像素如果坐标已经是米则设 1 返回 km/h if len(track_history) 2: return 0.0 (f0, x0, y0), (f1, x1, y1) track_history[0], track_history[-1] dt (f1 - f0) / fps # 秒 if dt 0: return 0.0 dist np.sqrt((x1 - x0) ** 2 (y1 - y0) ** 2) * scale speed_ms dist / dt return speed_ms * 3.6 # 转 km/h逻辑说明取轨迹首尾两点算位移除以时间差。scale是米/像素如果前面已经做了 IPM 变换坐标单位是米scale1。坑在哪fps不能信cap.get(cv2.CAP_PROP_FPS)的返回值。很多监控视频的容器帧率和实际帧率不一致比如标称 25fps 实际 23.976fps跑几分钟就累积出几秒误差。稳妥做法是用帧序号除以真实时长或者读视频的 PTS 时间戳。另一个坑是丢帧如果处理速度跟不上cap.read()会跳过帧但帧序号还在涨算出来的 dt 偏大速度偏小。解决办法是记录每帧的实际处理时间或者用frame_idx而不是处理次数。4. 避坑与排查那些让测速结果离谱的细节4.1 速度忽大忽小ID 频繁跳变现象同一辆车在连续几帧里速度从 30 跳到 80 又掉回 20。原因ByteTrack 在车辆被遮挡比如过电线杆、被大车挡住时会丢失 ID重新出现时分配新 ID轨迹断裂。如果代码里把新 ID 当成新车首尾两点可能跨越了遮挡期间的大位移dt 又小速度就爆了。解决给轨迹加最小长度和最大时间跨度限制。少于 5 帧的轨迹不输出速度首尾帧间隔超过 1 秒的轨迹标记为不可信。另外可以调tracker的track_buffer参数默认 30 帧遮挡严重时调到 60。4.2 所有车速度系统性偏大 20%现象明明限速 60 的路段测出来都是 70 多。原因标定的dst尺寸填大了或者src选点选到了车道线外侧。也可能是 fps 读出来偏小导致 dt 偏小。解决找一辆已知速度的车比如自己开车按 40 匀速过反推比例系数。如果所有速度都乘以同一个系数能对上那就是标定问题直接修正scale。如果比例不一致那是透视变换选点有误重新标。4.3 远处车辆速度明显偏小现象画面近处的车速度正常远处的车测出来只有十几。原因单目透视的固有特性。远处一个像素代表的实际距离比近处大得多但检测框在远处的定位误差也大底边中点的抖动被放大。加上远处车辆像素少bbox 底边可能落在车顶而不是地面。解决限制测速区域ROI只对画面下半部分、距离摄像头 50 米内的车输出速度。远处车辆只做检测不做测速。这是工程上最务实的做法别追求全画面覆盖。4.4 视频播放正常但程序读出来是 0 帧现象cap.isOpened()返回 True但read()一直返回 False。原因OpenCV 的视频解码后端对某些编码格式尤其是 H.265 或某些厂商的私有封装支持不好。python下载cv2 装的是预编译版解码器有限。解决先用 ffmpeg 把视频转成 H.264 的 mp4再喂给 OpenCV。命令ffmpeg -i input.mp4 -c:v libx264 -crf 23 output.mp4。这一步能解决 90% 的读帧问题。4.5 处理速度只有 2 fps根本跑不了实时现象视频 25fps程序处理一帧要 0.5 秒。原因CPU 推理 YOLOv8n 在 640 分辨率下大概就是这个速度。加上跟踪和绘图更慢。解决三条路。一是降分辨率到 416精度掉一点但速度翻倍二是跳帧处理每 3 帧检测一次中间帧用跟踪器预测位置速度计算用实际时间戳三是上 GPU装 CUDA 版 PyTorchmodel.to(cuda)速度能到 30fps 以上。如果只是离线分析跳帧是最省事的。5. 把误差压到 10% 以内的三个进阶技巧第一个技巧是多帧拟合代替两点差分。两点差分对 bbox 抖动极其敏感一帧定位偏 5 像素速度就跳。改成对最近 10 帧的轨迹做最小二乘线性拟合斜率就是速度。代码上就是把track_history喂给np.polyfitimport numpy as np def fit_speed(history, fps, scale1.0): # history: [(frame_idx, x_m, y_m), ...] if len(history) 5: return None frames np.array([h[0] for h in history], dtypefloat) xs np.array([h[1] for h in history], dtypefloat) * scale ys np.array([h[2] for h in history], dtypefloat) * scale t frames / fps # 分别拟合 x-t 和 y-t斜率是分速度 vx np.polyfit(t, xs, 1)[0] vy np.polyfit(t, ys, 1)[0] return np.sqrt(vx**2 vy**2) * 3.6这样算出来的速度是整段轨迹的平均速度抖动被平滑掉。代价是响应变慢车辆急加速时会有滞后但对交通测速场景完全够用。第二个技巧是用车辆底边中心而不是 bbox 中心。前面提过这里展开说bbox 中心会随车辆远近和遮挡变化而底边中心更接近车辆与地面的接触点。如果检测框在垂直方向有 10 像素抖动中心点抖动 10 像素底边中心只抖动 5 像素左右因为底边受车顶检测误差影响小。第三个技巧是时间戳对齐。如果跳帧处理不要用「处理帧数 / fps」算时间要用「原始帧序号 / fps」。因为跳过的帧时间照样在流逝。维护一个frame_idx计数器每读一帧加一不管有没有做检测。速度计算全部基于frame_idx这样即使跳帧dt 也是准的。技巧误差改善代价多帧拟合抖动降低 50%响应滞后 0.3s底边中心垂直抖动减半需确保底边可见帧序号计时消除跳帧误差需维护计数器最后说个我自己的习惯每换一个摄像头机位先拿一辆已知速度的车跑一遍验证把实测速度和 GPS 速度对比误差超过 15% 就重新标定绝不凑合。测速这东西标定是地基地基歪了后面算法再花哨都是白搭。希望帮到你。本文还有配套的精品资源点击获取