简介这是一份面向计算机视觉初学者与OpenCV实战练习者的Python车辆测速项目资源围绕视频中车辆速度检测这一典型场景展开可用于课程设计、毕业设计或自学练手。压缩包共10个文件约64.69MB包含4个mp4与1个avi演示视频、1个gif效果预览、1个py测速脚本、1个xml车辆检测模型文件以及requirements.txt依赖清单、README.md说明文档和txt辅助文件覆盖从环境配置到运行验证的完整链路。目前已有2787人学习下载说明该案例在入门群体中具备一定参考价值。读者可借助现成脚本与示例视频理解视频读取、目标检测、帧间位移与速度换算的基本流程并对照效果动图快速验证运行结果适合作为OpenCV视频处理与车辆测速方向的入门实践素材。1. 从一段路口监控说起python视频车辆测速到底在测什么手里有一段路口监控视频画面里车来车往现在要回答一个很具体的问题那辆白色轿车经过时速度是多少这就是 python视频车辆测速 要解决的事。它不是让模型去“猜”车速而是从视频里把车辆一帧一帧框出来跟踪同一辆车在连续帧里的位置变化再用像素位移换算成真实世界的位移除以时间得到速度。整套链路里python 负责把检测、跟踪、标定、计算串起来车辆速度检测 的精度取决于标定准不准、跟踪稳不稳、时间戳对不对。适合谁看做过一点 python、装过 opencv、想把手里的监控视频变成可量化数据的同学也适合已经在做车辆检测、但速度算出来忽高忽低、想搞清楚问题出在哪的工程师。下面按“先跑通最小闭环再抠精度最后避坑”的顺序讲代码可以直接抄参数可以按自己场景改。2. 把视频跑成速度检测、跟踪、标定的最小闭环2.1 为什么选“检测 跟踪 标定”三段式而不是端到端回归常见做法有两类。一类是端到端拿视频直接回归出速度听起来省事但训练数据要带真实速度标签标注成本极高换个路口、换个相机角度就得重标泛化基本靠玄学。另一类是“检测 跟踪 标定”检测负责每帧找出车在哪跟踪负责把同一辆车在帧间串起来标定负责把像素坐标映射到地面真实坐标最后用位移除以时间。这条路每一段都可解释、可单独调出问题能定位到具体环节。我一般选第二类。原因很直接车辆速度检测 的误差来源是分层的检测框抖动、跟踪 ID 跳变、标定矩阵不准各自贡献不同量级的误差。端到端模型把这些揉在一起调参时你根本不知道改哪里。三段式里检测用现成的 YOLO 系列就够跟踪用轻量的 SORT 或 ByteTrack标定用透视变换每一块都有成熟实现。选型上检测模型优先选能在你机器上实时跑的版本。如果只是离线处理一段视频不追求实时可以用大一点的模型换精度如果要接实时流就得在精度和帧率之间取舍。跟踪器优先选 ByteTrack它对遮挡和短暂丢失的处理比原始 SORT 稳ID 切换少这对速度曲线平滑度影响很大。2.2 环境准备与依赖安装先把环境搭起来。python 版本建议 3.8 到 3.10太新的版本有些视觉库轮子还没跟上。用虚拟环境隔离避免和系统里的包打架。# 创建虚拟环境 python -m venv venv # Linux / macOS 激活 source venv/bin/activate # Windows 激活 venv\Scripts\activate # 安装核心依赖 pip install opencv-python numpy ultralytics filterpy scipyopencv-python负责读写视频、画框、做透视变换numpy做矩阵和数组运算ultralytics提供 YOLO 检测和内置跟踪filterpy用来做卡尔曼滤波平滑速度scipy做数值处理。如果装opencv-python报错先确认 pip 是不是最新再确认 python 位数和系统匹配。装完用下面这段验证import cv2 import numpy as np from ultralytics import YOLO print(opencv:, cv2.__version__) print(numpy:, np.__version__) model YOLO(yolov8n.pt) # 首次运行会自动下载权重 print(model loaded)这段代码做三件事打印两个核心库版本确认能正常导入加载 YOLO 的 nano 权重确认模型文件能就位。如果卡在下载权重检查网络或者手动把权重放到当前目录再指定路径。版本号不用和我完全一致能正常导入、能加载模型即可。2.3 用 YOLO ByteTrack 把车辆框和 ID 稳定下来检测和跟踪这一步目标不是“框得漂亮”而是“同一辆车在连续帧里 ID 不变”。ID 一变速度就会算出跳变。下面是最小可运行脚本import cv2 from ultralytics import YOLO # 加载检测模型n 版轻量适合先跑通 model YOLO(yolov8n.pt) # 打开视频 cap cv2.VideoCapture(traffic.mp4) fps cap.get(cv2.CAP_PROP_FPS) print(视频帧率:, fps) # 只保留车辆类别COCO 里 car2, motorcycle3, bus5, truck7 vehicle_classes [2, 3, 5, 7] while cap.isOpened(): ret, frame cap.read() if not ret: break # persistTrue 让跟踪器在帧间保持状态 results model.track( frame, persistTrue, classesvehicle_classes, trackerbytetrack.yaml, verboseFalse ) if results[0].boxes.id is not None: boxes results[0].boxes.xyxy.cpu().numpy() ids results[0].boxes.id.cpu().numpy().astype(int) for box, tid in zip(boxes, ids): x1, y1, x2, y2 box.astype(int) cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(frame, fID {tid}, (x1, y1 - 8), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imshow(track, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()关键参数说明persistTrue是跟踪能跨帧保持的前提漏了它每帧都会重新分配 IDclasses限定只检测车辆减少行人、交通标志干扰tracker指定 ByteTrack 配置ultralytics 自带这个配置文件不用自己写。fps从视频元数据读出来后面算速度要用如果读出来是 0 或异常值说明视频元数据缺失得手动指定。跑通后你会看到每辆车带一个稳定 ID。如果 ID 频繁跳变先看是不是检测框抖动太大可以适当调低检测置信度阈值或者换稍大一点的模型。跟踪稳了速度才有稳的基础。2.4 透视标定把像素位移换成米这是整套流程里最容易被忽略、但对精度影响最大的一步。视频里的“像素每秒”不是速度必须知道一个像素对应地面多少米。做法是在画面里找一块已知真实尺寸的矩形区域比如车道线之间的宽度、或者地面上画出的标定框取它的四个角点像素坐标映射到一个矩形平面上。import cv2 import numpy as np # 画面里标定区域的四个角点顺序左上、右上、右下、左下 src_points np.float32([ [320, 480], [960, 480], [1100, 700], [180, 700] ]) # 对应的真实世界坐标单位米假设这块区域宽 7 米、长 20 米 dst_points np.float32([ [0, 0], [7, 0], [7, 20], [0, 20] ]) # 计算透视变换矩阵 matrix cv2.getPerspectiveTransform(src_points, dst_points) def pixel_to_world(px, py): 把像素坐标转成地面米制坐标 point np.array([[[px, py]]], dtypenp.float32) world cv2.perspectiveTransform(point, matrix) return world[0][0][0], world[0][0][1]src_points是你在画面上量出来的像素坐标dst_points是这块区域对应的真实尺寸。顺序必须一一对应左上对左上不能错位错位了矩阵就是错的。pixel_to_world把任意像素点映射到地面坐标。标定准不准直接决定速度的绝对误差。如果条件允许用卷尺量一段真实距离在画面里找到对应像素距离反推比例比目测靠谱得多。注意透视变换假设地面是平的。如果路面有明显坡度或者相机俯仰角很大这个假设会引入误差需要更复杂的标定模型。3. 速度计算与平滑从位移到 km/h 的完整实现3.1 用帧间位移算瞬时速度有了跟踪 ID 和像素到米的映射速度计算就是同一 ID 在前后两帧的地面坐标距离除以两帧时间间隔。时间间隔用1/fps。但直接用相邻帧算噪声会很大因为检测框中心点每帧都在抖。import cv2 import numpy as np from collections import defaultdict, deque from ultralytics import YOLO model YOLO(yolov8n.pt) cap cv2.VideoCapture(traffic.mp4) fps cap.get(cv2.CAP_PROP_FPS) dt 1.0 / fps # 标定矩阵沿用上一节的 matrix src_points np.float32([[320, 480], [960, 480], [1100, 700], [180, 700]]) dst_points np.float32([[0, 0], [7, 0], [7, 20], [0, 20]]) matrix cv2.getPerspectiveTransform(src_points, dst_points) def pixel_to_world(px, py): point np.array([[[px, py]]], dtypenp.float32) world cv2.perspectiveTransform(point, matrix) return world[0][0][0], world[0][0][1] # 每个 ID 保存最近若干帧的地面坐标 history defaultdict(lambda: deque(maxlen10)) vehicle_classes [2, 3, 5, 7] while cap.isOpened(): ret, frame cap.read() if not ret: break results model.track(frame, persistTrue, classesvehicle_classes, trackerbytetrack.yaml, verboseFalse) if results[0].boxes.id is not None: boxes results[0].boxes.xyxy.cpu().numpy() ids results[0].boxes.id.cpu().numpy().astype(int) for box, tid in zip(boxes, ids): x1, y1, x2, y2 box # 用框底边中点作为车辆接地点比中心点更贴近地面 cx (x1 x2) / 2 cy y2 wx, wy pixel_to_world(cx, cy) history[tid].append((wx, wy)) if len(history[tid]) 2: (px, py), (qx, qy) history[tid][-2], history[tid][-1] dist np.hypot(qx - px, qy - py) speed_mps dist / dt speed_kmh speed_mps * 3.6 cv2.putText(frame, f{speed_kmh:.1f} km/h, (int(x1), int(y1) - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2) cv2.imshow(speed, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()这里有两个关键选择。第一用框底边中点而不是框中心点作为接地点。车辆在画面里是有高度的框中心点对应的是车身中部投影到地面会偏底边中点更接近轮胎接触地面的位置透视变换后更准。第二history用deque(maxlen10)保存最近 10 帧为后面的平滑做准备。直接相邻两帧算出来的速度会跳因为检测框每帧都有几个像素的抖动换算成米再除以很小的时间间隔噪声被放大。3.2 卡尔曼滤波平滑让速度曲线不再锯齿瞬时速度的锯齿来自检测噪声。工程上常用卡尔曼滤波做平滑把“位置 速度”作为状态量用观测到的位置去修正。filterpy里现成的实现可以直接用。from filterpy.kalman import KalmanFilter import numpy as np def create_kf(): 状态量 [x, y, vx, vy]观测量 [x, y] 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]], dtypefloat) kf.H np.array([[1, 0, 0, 0], [0, 1, 0, 0]], dtypefloat) kf.R * 0.5 # 观测噪声检测越抖调越大 kf.Q * 0.01 # 过程噪声运动越剧烈调越大 kf.P * 10 # 初始协方差 return kf # 每个 ID 一个滤波器 filters {} # 在循环里拿到 wx, wy 之后 if tid not in filters: filters[tid] create_kf() filters[tid].x np.array([wx, wy, 0, 0], dtypefloat) filters[tid].predict() filters[tid].update(np.array([wx, wy])) vx, vy filters[tid].x[2], filters[tid].x[3] speed_kmh np.hypot(vx, vy) * 3.6F是状态转移矩阵这里假设匀速运动位置按速度累加。H是观测矩阵只观测位置。R是观测噪声协方差检测框越抖这个值要调大滤波器才会更信任预测而不是观测。Q是过程噪声车辆加速、减速越频繁这个值要调大否则滤波器反应迟钝。这两个参数没有万能值得看你的视频。一般先设R0.5、Q0.01看速度曲线抖就加大R滞后就加大Q。卡尔曼滤波的好处是它同时给出平滑后的位置和速度速度直接从状态量里取不用再对速度序列做二次平滑。代价是引入一点滞后急加速场景下速度会晚几帧跟上。如果做的是离线分析可以用双向滤波或者样条平滑消除滞后如果做实时这点滞后通常可以接受。3.3 参数怎么调一份可对照的调参表调参没有银弹但可以按现象定位。下面这张表是我在实际视频里反复试出来的经验值范围不同场景需要微调。参数作用调大后果调小后果建议起点检测置信度过滤低质量框漏检ID 断误检ID 乱0.3~0.5标定角点精度决定米制换算速度整体偏大或偏小同左误差控制在 2 像素内卡尔曼 R观测噪声速度平滑但滞后速度跟得紧但抖0.3~1.0卡尔曼 Q过程噪声反应快但抖平滑但滞后0.005~0.05历史帧数平滑窗口平滑但滞后灵敏但抖5~15调参顺序建议先把检测和跟踪调稳ID 不跳再调标定用已知距离验证最后调卡尔曼看速度曲线。顺序反了前面不稳后面怎么调都是白费。提示如果速度整体偏大或偏小先怀疑标定不要急着调滤波。标定错了滤波只能让错误的值更平滑。4. 避坑与排查速度算不准时先看这几处4.1 现象速度数值整体偏大两倍以上原因标定的真实尺寸填错了或者src_points和dst_points顺序错位。透视变换对点的对应关系极其敏感错一个点整个矩阵就歪了。解决回到画面重新量标定区域的像素坐标确认四个角和真实矩形的四个角一一对应。用一段已知真实距离验证在画面里选两个点量像素距离用矩阵换算成米和卷尺量出来的对比误差超过 10% 就重新标。4.2 现象同一辆车速度忽高忽低曲线像锯齿原因检测框每帧抖动相邻帧位移被噪声主导或者跟踪 ID 在相邻帧之间发生了切换把两辆车的位移算到了一起。解决先确认 ID 是否稳定如果 ID 跳变调低检测置信度或换跟踪器配置。ID 稳定后用卡尔曼滤波平滑把R调大。如果还抖检查是不是用了框中心点而不是底边中点中心点受车身高度影响透视后抖动更大。4.3 现象车辆静止时速度不为零原因检测框在静止车辆上仍有微小抖动除以很小的时间间隔后放大成非零速度或者标定矩阵在远处区域误差大静止车辆的位置换算后仍在漂移。解决设一个速度死区低于某个阈值比如 2 km/h直接置零。同时检查标定远处区域的像素对应米数很大一点点像素误差就是很大的米制误差尽量把标定区域放在车辆实际行驶的车道附近。4.4 现象视频帧率读出来是 0 或异常值原因视频容器元数据缺失cv2.CAP_PROP_FPS拿不到有效值。很多手机录制或转码后的视频会有这个问题。解决手动指定帧率用已知的时间段和帧数反推。比如视频里有一段 10 秒的片段数一下多少帧除一下就是实际帧率。或者用ffprobe之类的工具先查清楚再写进代码。帧率错了速度会整体按比例错。4.5 现象车辆被遮挡后重新出现速度突变原因遮挡期间跟踪器丢失目标重新出现时分配了新 ID历史位置断档第一帧算出来的位移是跨越遮挡的大位移。解决对每个 ID 记录最后一帧的时间戳如果新 ID 的第一帧和上一帧间隔超过阈值跳过这次速度计算等历史积累够了再算。ByteTrack 本身对短遮挡有一定处理但长遮挡仍会断。也可以适当放宽跟踪器的丢失容忍帧数。5. 把精度再往上推几个我常用的验证与进阶技巧速度算出来只是第一步怎么知道它准不准才是能不能交付的关键。我一般用两个办法交叉验证。第一个是找一段有已知速度参照的视频比如高速公路上车流速度大致稳定或者用一辆装了 GPS 的车跑一段把 GPS 速度作为真值和视频算出来的对比。第二个是在画面里选一段已知长度的路段记录车辆通过的时间手算平均速度和程序输出的瞬时速度对比。两个办法都能快速暴露标定或帧率的问题。如果要做多相机、多路视频标定就不能每路手动量了。常见做法是在地面放标定物用角点检测自动找或者用车道线的消失点做自标定。自标定精度不如手动量但胜在能批量处理。我一般对精度要求高的固定点位用手动量对临时部署用自标定先跑起来后面再补。还有一个容易被忽略的点车辆接地点在画面里的位置会随着车辆在画面中的横向位置变化。透视变换假设地面是平的但相机镜头有畸变画面边缘的直线在实际中可能是弯的。如果车辆主要出现在画面边缘先做镜头畸变校正再标定速度会稳很多。校正用cv2.calibrateCamera需要拍棋盘格一次标定长期使用。最后说一个习惯每次换相机、换点位、换视频源都重新标定不要复用上一次的矩阵。我吃过这个亏换了个安装高度差不多的相机觉得矩阵能凑合用结果速度整体偏了百分之十几排查了半天才发现是标定没更新。标定这件事宁可多花十分钟不要事后返工。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?