资讯详情

资讯详情

Python与YOLOv8实现高速公路车辆违规停车检测系统

简介面向高速公路智能监控场景的Python毕业设计项目基于OpenMMLab生态MMDetection、MMRotate、MMSegmentation实现无人机视角下车辆目标检测、多目标跟踪、禁停区语义分割、速度估计与违规停车报警。适合计算机相关专业毕业设计、课程设计及实战学习者代码完整且附带文档说明可快速跑通并作为二次开发基础。压缩包共27个文件包含20个Python源码文件、2个模型权重文件pth、配置与说明文档md/txt及演示视频mp4整体大小53.53MB覆盖检测、分割、跟踪、工具与配置等模块。目前已有66人学习下载属于实用型项目资料。读者可获得完整项目源码、预训练权重、运行说明与效果演示视频支持从数据准备、模型训练到评估推理的全流程复现尤其适合需要完成高质量毕业设计或提升工程能力的学生。1. 高速公路车辆违规停车检测难点不在检测框而在“判定”把一段高速公路监控视频丢给 Python 程序让它自动框出停在应急车道上的车、记下停车时间并输出一张带车牌区域的截图——这就是“Python实现高速公路车辆违规停车检测系统毕业设计项目”要交付的东西。网上能找到的车辆检测源码不少但多数只做到“画出检测框”就结束了离“违规停车”还差着两层一是不知道画面里这辆车是刚路过还是已经停下二是不知道它停的位置算不算违规。这套系统的真实工作量其实集中在把“检测”升级成“判定”的过程里。适合做它的是已经有 Python 和 OpenCV 基础、想用视频目标检测方向做毕设的同学或者需要快速搭一套路侧监控 Demo 的工程师。下面按我自己的实现习惯把选型理由、核心代码和踩坑点一次讲清楚。2. 选型立住YOLOv8 ByteTrack 时间阈值才能回答“停了多久”2.1 检测模型为什么是 YOLOv8 而不是更老的检测器车辆检测是整个系统的地基这一步做不实后面所有判定都是空中楼阁。毕设项目里最常见的选型是 YOLOv8主要理由是它在精度、推理速度和部署难度之间最平衡。相比 YOLOv5v8 的 Anchor-Free 设计省掉了锚框聚类换数据集训练时少一个步骤相比 Faster R-CNN 这类两阶段检测器它在普通笔记本上用 CPU 也能跑到可以接受的帧率对没有 GPU 服务器的学生来说很关键。YOLOv8 按规模分成 n/s/m/l/x 几个版本高速监控这种场景我一般从yolov8n或yolov8s起步。原因很直接监控视频分辨率高如果把每帧都缩到 640×640 再推理n 模型在 GTX 1660 上大概能到 30 FPS 上下s 模型会掉到 20 FPS 左右。停车检测不需要每帧都跑实际工程里常见做法是每 2~3 帧抽一帧做推理所以推理速度余量是够的。真正要注意的不是模型选哪个版本而是后续跟踪模块能不能接住检测框。检测器本身是逐帧独立的它不记得上一帧发生了什么。画面上有一辆白色轿车停在应急车道第 100 帧检测到了第 101 帧也检测到了但检测器并不知道这两帧里是同一辆车。要回答“停了多久”必须引入目标跟踪模块。这就是为什么任何完整的停车检测系统都不可能只靠一个检测模型完成。2.2 跟踪模块单帧检测框不构成证据跨帧 ID 才是目标跟踪要解决的是“给每一帧出现的车辆一个稳定 ID并让同一个 ID 在连续帧里指向同一辆车”。常用方案是 DeepSORT 和 ByteTrack毕设项目我更推荐 ByteTrack。DeepSORT 依赖外观特征提取器需要单独加载一个 ReID 模型车辆外形相似时容易把 ID 互相换掉调参步骤多翻车概率不低。ByteTrack 的策略更直接不只看置信度高的检测框还把低置信度框一起纳入匹配然后用 IoU 和位置代价做关联。高速场景里车辆遮挡少、运动规律ByteTrack 的 ID 稳定性足够用而且实现简单、参数少很适合作为毕设的核心模块。跟踪输出的 track_id 为什么这么重要以“停车时长”为例假设车辆在第 300 帧进入画面在第 900 帧驶离帧率 30 FPS那它在画面里存在了 20 秒。如果这 20 秒里 ID 一直是 7程序就能统计出它从哪一帧开始静止不动、静止了多少帧如果 ID 从 7 变成了 9程序就会当成另一辆车重新计时真实停车时间被撕成两段漏报就出现了。后面我会专门讲 ID 漂移的排查这里先记住结论跟踪器稳定与否直接决定判定逻辑可不可信。2.3 “违规”的判定标准位置、静止、时长三个要素怎么落到参数实现违规判定前先要把“违规停车”翻译成程序能计算的三个条件一是位置条件。高速公路违规停车主要发生在应急车道和行车道程序里需要提前标定一个或多边形 ROI 区域只有检测框中心点落在 ROI 内才进入判定流程。ROI 是标在原始图像坐标上的换摄像头位置或角度就要重新标这是毕设答辩时经常被问到的点。二是静止条件。车辆从行驶到停止最直观的变化是检测框中心点不再移动。程序中把这个变化量化成“中心点位移小于某个像素阈值”连续满足就算静止。这里有个细节不是每帧都算位移因为模型推理有耗时视频帧率也未必等于处理帧率更稳的做法是按固定间隔抽样取检测结果再计算。三是时长条件。应急车道短暂停靠和长时间停放性质完全不同。毕设里一般把“持续静止超过 T 秒”判为违规T 取 5~10 秒比较常见。时长不是直接数帧数而是用“静止帧数 × 抽样间隔”折算否则帧率一变时长就失真。把这三个条件组装起来就是一个完整的判定规则。下面是系统的主流程读取视频帧 → 目标检测 → 目标跟踪 → 重心点轨迹 → ROI 内含 → 静止帧计数 → 阈值判断 → 告警截图。这个链路里每一步都有坑但先把主流程想清楚后面写代码才不会绕。3. 把检测和追踪跑通最小代码与必调的五个参数3.1 环境与最小检测代码环境准备上需要 Python 3.9 以上版本依赖主要是ultralytics、opencv-python、numpy跟踪部分如果不想自己实现可以装boxmot这类集成库。装opencv-python时有个常见问题pip 默认装的是 opencv-python读视频和显示窗口够用如果后面要做图像处理增强注意它和 opencv-contrib-python 不要同时装容易冲突。先写一段最小检测代码确认模型权重能加载、视频能正常读取import cv2 from ultralytics import YOLO # 加载预训练权重首次运行会自动下载 yolov8n.pt model YOLO(yolov8n.pt) # classes: COCO 中 2car, 5bus, 7truck # 高速公路场景只需要载具类过滤掉行人、自行车等无关目标 cap cv2.VideoCapture(highway_clip.mp4) while True: ret, frame cap.read() if not ret: break # conf0.5 表示低于 0.5 置信度的检测框直接丢弃 results model(frame, classes[2, 5, 7], conf0.5, verboseFalse) for result in results: for box in result.boxes: x1, y1, x2, y2 map(int, box.xyxy[0].tolist()) conf float(box.conf[0]) cls int(box.cls[0]) cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imshow(detection, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()这段代码里model(frame, classes[2, 5, 7], conf0.5)是修改模型的三个关键入口。classes限定只检测车类目标能明显降低误检率conf0.5是置信度阈值值越高漏检越多值越低误检越多。高速场景下建议先用 0.5 起步等后面接上跟踪模块再根据效果回调。verboseFalse是关闭推理日志避免每帧向控制台打印信息拖慢处理速度。3.2 挂接 ByteTrack从检测框到持续 track_id检测框只是每帧的独立结果现在把它接入跟踪器。用集成库时ByteTrack 的调用通常长这样import numpy as np from boxmot import ByteTrack # frame_rate 与视频实际帧率保持一致影响运动模型的时间步长 tracker ByteTrack(track_thresh0.5, match_thresh0.8, frame_rate30) while True: ret, frame cap.read() if not ret: break results model(frame, classes[2, 5, 7], conf0.5, verboseFalse) dets [] for result in results: for box in result.boxes: x1, y1, x2, y2 map(float, box.xyxy[0].tolist()) conf float(box.conf[0]) cls int(box.cls[0]) # ByteTrack 的输入格式是 [x1, y1, x2, y2, conf, cls] dets.append([x1, y1, x2, y2, conf, cls]) if len(dets) 0: dets np.array(dets) # update 返回带 track_id 的检测结果 tracked tracker.update(dets) for track in tracked: track_id int(track.id) x1, y1, x2, y2 map(int, track[:4]) conf float(track[4]) cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(frame, fID {track_id}, (x1, y1 - 8), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)boxmot是开箱即用的多目标跟踪集成库它为 ByteTrack、DeepSORT、BoT-SORT 提供了统一的调用接口。如果你不想引入这个库也可以直接读 ByteTrack 源码跑但毕业设计一般没必要重复造轮子。tracker.update()每帧调用一次输入是检测框数组输出里每行前 4 个值是框坐标第 5 个是置信度后面的id字段就是跨帧稳定的 track_id。参数说明track_thresh0.5是第一轮匹配的高置信度阈值低于这个值的检测框进入第二轮低置信度匹配match_thresh0.8是 IoU 匹配的阈值值越大匹配越严ID 切换越频繁值越小越容易把不同车关联成同一个 ID。这两个值需要配合调我一般先固定match_thresh0.8再根据 ID 漂移现象反向调。3.3 关键参数速查表把检测和跟踪阶段的参数整理成一张表方便复现时对照参数建议初始值作用调参方向modelyolov8n.pt检测模型权重追求精度换 s/m 版追求速度用 nclasses[2, 5, 7]限定 car/bus/truck需要检测货车可加类别 8conf0.5置信度阈值漏检多则降到 0.3误检多则升到 0.6track_thresh0.5跟踪第一轮匹配阈值目标小而远时降到 0.4match_thresh0.8IoU 匹配阈值ID 频繁切换时升到 0.9frame_rate视频实际帧率跟踪器的运动时间基准必须与视频 fps 一致这里有一个经常被忽视的点frame_rate不是随便填的。ByteTrack 内部会用它推算目标在当前帧的运动预测范围如果视频实际是 25 FPS 你填了 30预测步长不准高速上车流量大的路段就容易把 ID 跟丢。拿不准时先用cv2.VideoCapture.get(cv2.CAP_PROP_FPS)读一下真实帧率再传入。4. 违规判定与告警留证从“静止帧数”到一条完整违规记录4.1 先画禁区用多边形 ROI 限定可停车区域跟踪链路跑通后下一步是判定“停在哪儿”。高速公路监控画面里行车道和应急车道的分界线通常是一道实线程序不可能自动理解交通标线所以需要一个离线标定的 ROI。常见做法是运行时用鼠标在画面上点几个点程序自动连成多边形再把坐标保存到配置文件。判定规则是车辆检测框的底边中心点注意不是框中心落在 ROI 内才认为车辆“位于嫌疑区域”。为什么用底边中心点因为检测框通常包含车头阴影和车顶框中心点会偏高容易在车辆刚进入 ROI 边缘时产生误差而底边中点更接近车轮与地面接触的位置落在车道上的物理含义更准确。import cv2 # 手动标定 ROI点的顺序按顺时针或逆时针都行但要构成凸多边形 # 这里用六个点模拟高速路应急车道区域 roi_points [ (560, 380), (720, 380), (1020, 620), (1020, 720), (560, 720), (380, 620) ] def is_in_roi(x, y, roi_points): 判断点是否落在 ROI 多边形内。 import numpy as np roi np.array(roi_points, dtypenp.int32) result cv2.pointPolygonTest(roi, (int(x), int(y)), False) return result 0cv2.pointPolygonTest返回 1 表示点在多边形内-1 表示在外0 表示在边上。这里用“底边中心点”((x1 x2) / 2, y2)作为传入坐标。ROI 区域不要画得太贴边留出 10 到 20 像素的缓冲否则检测框轻微抖动时判定结果会在“进入/退出”之间反复横跳。4.2 静止判定逻辑中心点位移 持续帧数停车判定的核心是一个状态记录器。它要为每一个 track_id 维护“上一帧位置”和“静止计数”然后在每次迭代里决定这个 ID 的状态是行驶中、疑似停车还是确认违规。下面是一段可直接落地的实现class ParkingDetector: def __init__(self, fps30, sample_interval2, move_thresh3.0, violation_time5.0): # sample_interval: 每隔多少帧做一次判定 # move_thresh: 中心点位移小于该像素数视为静止 # violation_time: 静止超过该秒数判定为违规 self.fps fps self.sample_interval sample_interval self.move_thresh move_thresh self.violation_frames int(violation_time * fps / sample_interval) # 每个 track_id 的记录: {last_pos: (x, y), stationary: 0} self.track_state {} def update(self, tracks): events [] # 收集违规事件 for t in tracks: track_id int(t.id) x1, y1, x2, y2 t[:4] # 使用底边中点作为车辆位置 cx, cy (x1 x2) / 2.0, y2 if track_id not in self.track_state: self.track_state[track_id] {last_pos: (cx, cy), stationary: 0} continue prev self.track_state[track_id][last_pos] displacement ((cx - prev[0]) ** 2 (cy - prev[1]) ** 2) ** 0.5 stationary self.track_state[track_id][stationary] if displacement self.move_thresh: stationary 1 if stationary self.violation_frames: events.append({ track_id: track_id, frame: None, # 由调用方回填帧号 stationary_frames: stationary, box: (x1, y1, x2, y2), }) else: stationary 0 self.track_state[track_id][last_pos] (cx, cy) self.track_state[track_id][stationary] stationary return events这段代码把“静止”和“违规”拆成了两个阈值。move_thresh3.0表示两帧之间车辆底边中心点位移小于 3 像素就记为静止。高速监控摄像头通常装在很高的杆子上远景车辆移动超过 3 像素是很容易的如果车辆在画面上真实停着位移会在 0 到 1 像素之间波动。violation_frames是违规所需的累计静止次数这里用“秒数 × 帧率 ÷ 抽样间隔”折算而不是直接数帧是为了让时长判定不依赖视频帧率。注意代码里每次判定只抽样一次即每隔sample_interval帧调用一次update。原因是检测和跟踪都有耗时实际处理往往跟不上原视频帧率如果按每一帧都算位移中间跳帧会导致位移被夸大原本停着的车会被判定成“一直在动”。抽样间隔取 2代表每隔 2 帧判定一次也就是 30 FPS 视频里每秒做 15 次判定这对静止检测来说足够细了。4.3 告警去重与事件表事件被触发后不能直接循环报否则一辆车在画面里停 1 分钟系统会每秒都在告警日志根本无法看。常见做法是加两层限制一是同一 track_id 在“未驶离或未消失前”只记录一次首次告警二是如果持续停车超过较长时间允许按固定周期复报一次。我实际项目里用的是一个去重表记录每个 track_id 首次违停帧号、上次复报帧号只有当“当前帧 - 上次复报帧号”超过复报周期时才再次写入事件。告警记录建议落成两个部分截图存证 结构化事件条目。截图用cv2.imwrite把当前帧保存为 JPEG文件名带上时间信息能直接看出是哪天的哪一段事件条目至少包含以下字段字段示例值说明event_id20240617_143005_007全局唯一事件号track_id7跟踪器输出 IDtimestamp2024-06-17 14:30:05事件触发时间frame_idx4523触发时的视频帧号box(960, 540, 1120, 680)车辆检测框stationary_seconds8.4累计静止秒数image_path./events/20240617_143005_007.jpg截图路径事件表用 CSV 还是 SQLite 都行单机演示用 CSV 更直观答辩展示时打开表格一目了然。如果是部署到路口做连续采集建议用 SQLite 或 MySQL避免单文件越来越大。核心原则是每条事件必须能回溯到对应的原始视频帧否则这个系统的输出不具备可信度答辩时会被追问到无话可说。5. 落地避坑排查夜间闪烁、ID 漂移、重复告警与误检误判5.1 夜间灯光的红外闪烁让停车判定翻车现象夜间视频里车辆大灯和路灯会在检测框周围形成忽明忽暗的光晕有时车已经完全停稳但检测框的边缘随灯光闪烁不断变化中心点位移频繁超过阈值导致静止计数一直无法累计。原因红外补光下车辆轮廓可能每帧微变检测框在宽度和高度上轻微抖动另外夜间低光照下模型置信度下降检测框时而收缩时而放大底边中点随之移动。解决第一把move_thresh从 3 像素提高到 5~6 像素给检测框抖动留出余量第二对检测框坐标做平滑用指数移动平均处理x1, y1, x2, y2后再传入判定逻辑第三夜间光线差时适当降低conf到 0.35 左右避免车身部分漏检导致框突然消失又出现。5.2 阴影和绿化带导致的误检误判现象晴天时车辆旁边的深色阴影被识别成车辆或者树影在地面上晃动导致静止帧计数异常更常见的是应急车道旁的绿化带、路肩护栏被当成背景噪声灌入跟踪器。原因YOLO 模型对光照变化敏感深色阴影与车辆底盘区域颜色高度相似绿化带和护栏在低置信度下产生大量假正例框ByteTrack 的“低置信度框也会参与匹配”策略会把一些假框当成真实目标跟住。解决锁定车辆类别忽略其他类别对检测框施加最小尺寸过滤例如宽小于 40 像素或高小于 60 像素的框直接丢弃ROI 范围内做背景差分如果该区域本来就有固定物体护栏可以先把这些区域从 ROI 中挖掉。还有一个经验值把match_thresh调高到 0.85 以上假框和目标之间的 IoU 不稳定很难持续匹配成功跟踪器会更快丢掉它们。5.3 跟踪 ID 漂移车没变ID 换了现象一辆白色轿车从远处驶入画面停车 10 秒后程序输出的 track_id 从 12 变成了 18前面累计的 10 秒静止计数全部清零违规漏报。原因车辆在画面中停久了ByteTrack 会把它标记为“丢失”再重新找到重新找到时分配新 ID。这种情况在车辆静止而且外观特征不明显时特别容易发生因为跟踪器的运动预测默认目标是持续运动的一旦目标静止运动模型和真实状态不匹配。解决在跟踪循环外维护一个“历史车辆表”当新 track_id 出现时先判断它是否与某个已存在的历史帧位置高度重合。如果新 ID 的检测框与某个已消失 ID 的最后一帧检测框 IoU 超过 0.6就认为是同一个目标把新 ID 重映射回旧 ID。这一步是停车检测系统能稳定工作的关键修复比调任何跟踪参数都有效。5.4 告警风暴同一辆车重复报警现象一辆违停车辆在画面里停了 20 秒系统在这 20 秒里生成了 30 条告警每条事件都附一张截图事件表瞬间被刷屏。原因告警逻辑直接在静止计数超过阈值后无条件追加没有做“首次触发后进入告警态、车辆驶离后退出告警态”的状态管理。解决给每个 track_id 增加一个auto_state字段取值包括normal、warning、left。首次触发违规时写入事件并置为warning车辆位移重新超过阈值或 ID 消失超过 60 帧后回到normal只有normal状态下触发违规才允许写入事件。这样一辆车一次违停只产生一条记录如果需要周期复报单独加一个定时器字段即可。5.5 BGR 顺序与坐标体系不一致现象ROI 画好之后用鼠标点击坐标测试发现点击位置和屏幕上显示的位置不是同一个点或者画布颜色异常红框变成蓝框。原因OpenCV 读入的图像是 BGR 格式而很多标注工具和 matplotlib 使用 RGB直接在 OpenCV 窗口上画图其实没有 BGR/RGB 问题但一旦混用 PIL 或 matplotlib 显示图像颜色就会互换。坐标问题则是因为 ROI 的坐标是基于缩放后窗口的而检测框坐标是基于原始帧的两者没有做比例映射。解决统一所有图像操作都在 OpenCV 里完成显示和画框都用 BGR如果要在 matplotlib 里看效果用cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)先转格式。ROI 标定时先记录窗口尺寸再乘以缩放比例换算回原始坐标。可以在 ROI 标定完成后立刻把点和原始帧背景叠加导出预览图人工核对位置后再启用这一步能省掉后面大量调试时间。6. 用一套“能答辩”的验证流程收尾自录视频、可视化调试与参数边界6.1 快速制作验收数据集不用去网上找大型数据集。毕设验收时最有说服力的做法是用一段自己拍摄的高速公路监控画面或公开的交通监控视频作为验收素材抽取 200 到 300 帧手动标注每帧是否存在违停车辆、车牌是否可见然后统计系统的检出率。这个流程做下来比引用任何公开数据集都更贴近题目里的“高速公路”场景。标注不一定用 LabelImg可以写一个快速的标注脚本对每帧保存车辆框坐标框架简单点也够用。评估指标先用两个核心数违停事件检出率真实违停事件有多少被程序触发和误报率非违停时段触发了多少次告警。毕设答辩时这两个数比单帧检测的 mAP 更有说服力。6.2 调试可视化把判定过程画到帧上调试停车判定逻辑时最大痛点是“黑匣子”——程序知道自己在想什么你看不到。我会在调试版程序里把判定状态直接画到帧上每个 track_id 显示当前的静止计数和状态ROI 边界用红色线条画出处于疑似状态的车辆框用黄色确认违规后用红色并叠加累计静止秒数。这样一眼就能看到是哪一辆车、在哪个位置、计数器卡在哪一步没有继续上涨。条件允许的话把判定过程导出成视频而不是只看截图。导出视频能回放 ID 换号、告警触发点、车辆驶离时刻排查问题时比逐帧翻截图高效得多。6.3 调参的三个雷区调参时最容易踩的雷区有三个。第一个是只调一个参数就期望全局变好实际上一套参数只在某一段视频上表现得“玄学般理想”换一段路况就失效。第二个是把violation_time设得太短比如 1 秒结果刹车让行的车辆全被误判成违停视频里全是假告警。第三个是忽略了抽样间隔和帧率的耦合改过视频帧率却忘了同步更新frame_rate参数导致运动预测的时间基准全乱。我的习惯是备三段不同的测试视频一段白天车流正常、一段夜间、一段雨天。每次调参后先在三段上都跑一遍记录每个视频的检出率和误报率。把某段视频调到完美不算完成三段都能达到可用水平才算阶段性结束。当前这套代码里move_thresh5.0、violation_time5.0、match_thresh0.85的组合在多数白天高速场景下是一个不错的起点夜间场景需配合置信度下调和平滑处理一起改。最后提醒一句项目源码可以用你已经训练好的模型但权重和配置别封装成不可改的黑匣子。答辩时老师一定会问“这个参数为什么是 5 不是 10”能现场改参数并复现效果变化比准备再多的 PPT 都管用。这套系统做到能稳定输出“哪辆车、在哪个区域、停了多久、触发了告警”这四件事就算落地了。希望帮到你。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →