简介:本资源为基于YOLO的机动车乱停乱放检测系统完整项目包,面向人工智能、计算机视觉方向的学生与开发者,尤其适合作为毕业设计或课程实践参考。项目利用YOLO目标检测框架识别车辆并判断违规停放行为,涵盖数据预处理、模型训练、推理与后处理等核心模块,帮助读者理解深度学习在智能交通场景中的落地方式。压缩包共16个文件,以13张png与1张jpeg图片、1个py脚本、1个md说明文档为主,整体约9.34MB,图片可用于展示检测效果与训练过程,脚本与文档则提供代码入口和部署指引。目前已有214人学习下载。通过该资源,读者可获得可运行的源码、模型权重与部署教程,掌握YOLO模型训练、数据增强及视频流实时检测的完整流程,并了解光照变化、遮挡等实际问题的处理思路,对完成毕业设计或开展相关研究具有较高参考价值。
1. 从一段路口监控说起:这套 YOLO 乱停乱放检测系统到底能干什么
路口摄像头拍了一整天,真正需要人工回看的违停片段可能就那么几十秒,但你要在十几个小时的视频里把它找出来。这套「基于 YOLO 的机动车乱停乱放检测系统」解决的正是这件事:它把目标检测模型和一套判定逻辑绑在一起,让机器先替你把画面里的车框出来,再根据车辆停留时长、是否压线、是否占用禁停区域来判断「这辆车是不是乱停」。源码包里包含训练脚本、推理脚本、权重文件和一份部署教程,属于典型的毕业设计级完整工程,但功能链路是通的,拿来做课程设计、二次开发或者当成一个可跑通的 YOLO 落地样板都合适。适合谁?手上有 Python 基础、装过 PyTorch、想找一个「检测 + 业务判定」闭环项目练手的人。如果你只是想跑个官方 demo 看框,那这个包对你偏重;如果你要的是从数据到部署的完整流程,它刚好卡在这个位置上。
2. 拆开这个包:YOLO 检测与乱停判定的技术链路
2.1 为什么是 YOLO,而不是两阶段检测器
乱停乱放检测的本质是「先找到车,再判断这辆车的行为」。第一步是目标检测,第二步是时序和空间逻辑。选 YOLO 做第一步,核心原因是速度。路口视频通常是 25fps 起步,如果用 Faster R-CNN 这类两阶段检测器,单帧推理在普通显卡上就可能吃掉几十毫秒,多路视频一叠加,实时性直接崩掉。YOLO 把检测当成回归问题,一次前向就出框和类别,工程上更容易做到「边拉流边推理」。
另一个原因是部署友好。YOLO 系列从 v5 开始,权重导出 ONNX、TensorRT 的链路非常成熟,源码包里如果带的是.pt权重,你几乎可以无痛转成 ONNX 再上 TensorRT 或 OpenVINO。两阶段检测器导出和量化时踩的坑通常更多,对毕业设计这种「能跑起来比极致精度更重要」的场景,YOLO 是更稳的选择。
至于选哪个版本,常见做法是:如果源码包给的是 YOLOv5 或 YOLOv8,直接用,不要自己换版本。YOLOv5 的生态最全,网上能搜到的部署教程最多;YOLOv8 的 API 更干净,训练脚本更短。两者在乱停检测这种「车 + 背景」的二分类或三分类任务上,精度差距远没有工程便利性差距大。
2.2 乱停判定不是检测,是逻辑层
很多人第一次看这类系统会误以为「检测到车 = 乱停」,这是最大的误解。检测模型只负责输出[x1, y1, x2, y2, conf, cls],它不知道这辆车停了多久、停在哪。乱停判定是在检测结果之上加的一层业务逻辑,通常包含三个维度:
- 停留时长:对同一辆车做跨帧跟踪(常见做法是 ByteTrack 或简单的 IOU 匹配),记录它进入画面的时间,超过阈值(比如 30 秒)才判定为「停」。
- 空间位置:判断车辆框的中心点或底边是否落在预设的禁停区域内。禁停区域一般用多边形标注,存在一个 JSON 或 YAML 配置里。
- 状态过滤:排除正在行驶的车。如果车辆在两帧之间位移超过阈值,说明它在动,不参与乱停判定。
这三条逻辑组合起来,才是「乱停乱放检测」。源码包里如果有一个judge.py或logic.py之类的文件,大概率就是干这个的。理解这一点,你才知道为什么检测框画得再准,系统还是可能误报——问题往往出在逻辑层的阈值上,而不是模型。
2.3 环境配置:从零把依赖装对
部署教程里一般会给requirements.txt,但直接pip install -r经常翻车,因为 YOLO 对 PyTorch 和 CUDA 版本很敏感。我一般会按下面的顺序来,先确认显卡驱动和 CUDA,再装 PyTorch,最后装 YOLO 本体。
# 1. 确认显卡和驱动,能看到 CUDA Version 说明驱动没问题 nvidia-smi # 2. 建一个干净的虚拟环境,别在 base 里装 conda create -n yolo_park python=3.9 -y conda activate yolo_park # 3. 装 PyTorch,版本要和你的 CUDA 对应 # CUDA 11.8 的常见组合,其他版本去 PyTorch 官网查对应命令 pip install torch==2.0.1 torchvision==0.15.2 --index-url https://download.pytorch.org/whl/cu118 # 4. 装 YOLO 本体,如果源码包自带就不装 pip install ultralytics # 5. 装其余依赖 pip install opencv-python numpy pyyaml tqdm逻辑说明:第一步nvidia-smi是排错起点,如果这里就报错,后面全白搭。第二步用 conda 隔离环境,是因为 YOLO 项目经常和别的项目抢 PyTorch 版本,混装是血泪经验里最常见的翻车点。第三步的--index-url是关键,不加这个参数,pip 会去默认源拉 CPU 版,装完发现torch.cuda.is_available()返回 False,很多人卡在这里半天。第四步如果源码包已经带了ultralytics目录,就不要重复装,否则版本冲突。
参数说明:python=3.9是兼容性最好的版本,3.10 以上有些旧版 YOLOv5 会出问题;torch==2.0.1对应 CUDA 11.8,如果你显卡驱动较新,可以上 CUDA 12.x 配 torch 2.1+,但源码包如果写死了旧版本,就跟着源码走。
2.4 推理脚本怎么跑:单图、视频、摄像头三种模式
装完环境,下一步是让模型动起来。源码包里的推理入口通常是一个detect.py或main.py,参数大同小异。下面是一个典型的调用方式:
# 单张图片推理,结果存到 runs/detect/exp python detect.py --source test.jpg --weights best.pt --conf 0.4 # 视频文件推理 python detect.py --source parking.mp4 --weights best.pt --conf 0.4 --save-txt # 调用本地摄像头,实时看效果 python detect.py --source 0 --weights best.pt --conf 0.4 --view-img逻辑说明:--source决定输入源,可以是图片路径、视频路径、摄像头编号(0 是默认摄像头)或者一个目录。--weights指向训练好的权重,源码包一般会带一个best.pt,如果没带,你需要自己训练或者下载预训练权重。--conf是置信度阈值,默认 0.25,乱停检测场景我一般调到 0.4,因为误检一辆不存在的车比漏检更烦人。--save-txt会把检测框坐标存成 txt,方便后面接逻辑层。--view-img是实时预览,调试时用,正式跑批处理时关掉。
参数说明:--conf调高会减少误检但可能漏检,调低反之,0.4 是一个偏保守的起点;--iou控制 NMS 的 IoU 阈值,默认 0.45,车辆密集时如果发现框被吞,可以调到 0.5;--imgsz是推理分辨率,默认 640,如果画面里车很小,调到 1280 能提升召回,但速度会掉。
3. 训练自己的乱停数据集:标注、配置与调参
3.1 数据标注:只标车,还是标状态
这是训练前必须想清楚的问题。有两种标注策略:
- 只标车辆:所有车都标成
car一个类,乱停判定完全交给逻辑层。优点是标注快,模型泛化好;缺点是模型不区分状态,逻辑层压力大。 - 标状态:把车标成
car_normal和car_illegal两类。优点是模型直接输出状态,逻辑层简单;缺点是标注成本高,而且「乱停」是一个时序概念,单帧标注很难标准。
常见做法是第一种,只标车。因为乱停的本质是「车 + 时间 + 位置」,单帧里一辆静止的车和一辆乱停的车长得一模一样,强行让模型学状态,只会让它学偏。源码包如果用的是单类car,说明作者走的是这条路,你跟着走就行。
标注工具用 LabelImg 或 Roboflow 都行,输出 YOLO 格式的 txt,每行是cls x_center y_center width height,坐标归一化到 0-1。标注时注意:车辆被遮挡超过一半的不要标,夜间模糊的不要标,这些样本会拉低模型质量。
3.2 数据集目录结构与 data.yaml
YOLO 对目录结构有固定要求,放错了训练直接报错找不到图片。标准结构如下:
dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── data.yamldata.yaml是数据集配置文件,内容大致是:
# 训练集和验证集路径,可以是绝对路径也可以是相对路径 train: ./dataset/images/train val: ./dataset/images/val # 类别数量 nc: 1 # 类别名称,顺序要和标注时的 cls 编号对应 names: ['car']逻辑说明:train和val指向图片目录,YOLO 会自动去找同级的labels目录。nc是类别数,单类就是 1。names的顺序必须和标注时用的编号一致,如果标注时car是 0,这里就写['car'],写反了模型会把类别学乱。这个文件最容易出的错是路径用了 Windows 反斜杠,YOLO 在 Linux 下读不了,统一用正斜杠。
3.3 训练命令与关键参数
配置好之后,训练命令通常是这样:
# 从预训练权重开始训练,epochs 100,batch 16,图片尺寸 640 python train.py --data data.yaml --weights yolov5s.pt --epochs 100 --batch 16 --imgsz 640 --device 0逻辑说明:--weights yolov5s.pt是从预训练权重开始,这叫迁移学习,比从零训练收敛快得多,也是 yolo 预训练模型下载 这个热搜词背后的实际需求——没人从零训。--epochs 100是训练轮数,乱停数据集通常几千张图,100 轮够用,太多会过拟合。--batch 16是批大小,取决于显存,8G 显存跑 640 尺寸大概能到 16,不够就降到 8。--device 0指定第一块显卡,多卡用0,1。
参数说明:--imgsz 640是训练分辨率,和推理保持一致;--lr0是初始学习率,默认 0.01,如果 loss 震荡厉害可以降到 0.001;--patience是早停轮数,默认 50,验证集指标 50 轮不提升就停,省时间。
3.4 训练过程看什么:loss、mAP 和混淆矩阵
训练开始后,控制台会打印每一轮的box_loss、obj_loss、cls_loss和mAP@0.5。重点看两个:
- box_loss:框回归损失,应该持续下降。如果它不降反升,多半是学习率太大或者标注有问题。
- mAP@0.5:平均精度,越高越好。乱停检测单类任务,mAP 到 0.85 以上就算能用,0.9 以上算好。
训练结束后会生成混淆矩阵。这里有个热搜词叫「yolo 混淆矩阵总合不唯一」,说的就是混淆矩阵对角线之外还有值,说明模型把某些类分错了。单类任务里,混淆矩阵应该接近对角阵,如果出现大量非对角值,检查是不是标注时类别编号写错了。另一个常见现象是「yolo 训练中 bn 崩溃」,表现为 loss 突然变 NaN,原因是 batch 太小或者学习率太大,解决办法是把--batch调大或者加--lr0 0.001。
4. 乱停判定的逻辑层实现:从检测框到报警
4.1 用 IOU 做简易跟踪
检测模型每帧输出一堆框,但系统需要知道「这个框和上一帧的哪个框是同一辆车」。最轻量的做法是 IOU 匹配:计算当前帧每个框和上一帧所有框的 IOU,取最大值,超过阈值就认为是同一辆车。
import numpy as np def iou(box1, box2): # box 格式 [x1, y1, x2, y2] x1 = max(box1[0], box2[0]) y1 = max(box1[1], box2[1]) x2 = min(box1[2], box2[2]) y2 = min(box1[3], box2[3]) inter = max(0, x2 - x1) * max(0, y2 - y1) area1 = (box1[2] - box1[0]) * (box1[3] - box1[1]) area2 = (box2[2] - box2[0]) * (box2[3] - box2[1]) union = area1 + area2 - inter return inter / union if union > 0 else 0 def match_tracks(current_boxes, prev_boxes, iou_thresh=0.3): # 返回 current_boxes 中每个框匹配到的 prev 索引,-1 表示新目标 matches = [] for cb in current_boxes: best_iou, best_idx = 0, -1 for i, pb in enumerate(prev_boxes): v = iou(cb, pb) if v > best_iou: best_iou, best_idx = v, i matches.append(best_idx if best_iou > iou_thresh else -1) return matches逻辑说明:iou函数算两个框的交并比,match_tracks给当前帧每个框找上一帧里最像的那个。iou_thresh=0.3是经验值,太低会把两辆车混成一辆,太高会频繁断跟踪。这个简易跟踪不完美,车辆交叉时会跟丢,但对乱停检测够用,因为乱停的车通常不动,跟踪压力小。
参数说明:iou_thresh在车辆密集场景可以调到 0.4,稀疏场景 0.2 也行;如果发现同一辆车被反复分配新 ID,说明阈值太低。
4.2 停留时长与禁停区域判定
有了跟踪 ID,就可以记录每辆车的首次出现时间,并判断它是否在禁停区域内。
import time from shapely.geometry import Point, Polygon # 禁停区域,实际项目里从配置文件读 no_park_zone = Polygon([(100, 200), (500, 200), (500, 600), (100, 600)]) # 记录每辆车的首次出现时间和位置 track_history = {} def judge_illegal(track_id, box, fps=25, stay_seconds=30): cx = (box[0] + box[2]) / 2 cy = (box[1] + box[3]) / 2 point = Point(cx, cy) if track_id not in track_history: track_history[track_id] = {'start': time.time(), 'frames': 0} return False info = track_history[track_id] info['frames'] += 1 duration = time.time() - info['start'] # 在禁停区域内且停留超过阈值 if no_park_zone.contains(point) and duration > stay_seconds: return True return False逻辑说明:no_park_zone是一个多边形,实际项目里应该从 JSON 配置读,方便不同路口切换。judge_illegal用车辆框中心点判断是否在区域内,同时累计停留时间。两个条件同时满足才报警,避免把路过禁停区的车误判。stay_seconds=30是阈值,路口场景一般 30 到 60 秒,小区门口可以短一点。
参数说明:用中心点判断简单但不够准,车辆压线时中心点可能在区域外,更严谨的做法是用框的底边中点或者计算框与区域的重叠面积;stay_seconds要根据场景调,太短会误报等红灯的车,太长会漏报。
4.3 把逻辑层和检测层接起来
最后一步是把检测输出喂给逻辑层,形成一个完整循环:
import cv2 from ultralytics import YOLO model = YOLO('best.pt') cap = cv2.VideoCapture('parking.mp4') prev_boxes = [] while cap.isOpened(): ret, frame = cap.read() if not ret: break results = model(frame, conf=0.4)[0] current_boxes = results.boxes.xyxy.cpu().numpy().tolist() matches = match_tracks(current_boxes, prev_boxes) for idx, box in enumerate(current_boxes): track_id = matches[idx] if matches[idx] != -1 else len(track_history) if judge_illegal(track_id, box): # 画红框并标注 cv2.rectangle(frame, (int(box[0]), int(box[1])), (int(box[2]), int(box[3])), (0, 0, 255), 2) cv2.putText(frame, 'ILLEGAL', (int(box[0]), int(box[1]) - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 0, 255), 2) prev_boxes = current_boxes cv2.imshow('result', frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()逻辑说明:每帧先跑检测,拿到框列表,然后和上一帧做匹配,给每个框分配 track_id,再交给judge_illegal判断。判定为乱停的画红框。prev_boxes在每帧末尾更新,形成跨帧跟踪。这个循环是整套系统的骨架,源码包里的主程序基本就是这个结构。
参数说明:conf=0.4和前面推理一致;cv2.waitKey(1)控制播放速度,1 毫秒接近实时,调大可以慢放调试。
5. 避坑与排查:那些让系统跑不起来的常见问题
5.1 现象:torch.cuda.is_available()返回 False
原因:装成了 CPU 版 PyTorch,或者 CUDA 版本和 PyTorch 不匹配。这是最高频的翻车点,十个人里有六个卡在这。
解决:先nvidia-smi看驱动支持的 CUDA 版本,然后去 PyTorch 官网查对应安装命令,务必带--index-url。装完在 Python 里跑import torch; print(torch.cuda.is_available()),返回 True 才算过。如果还是 False,卸载重装,别试图修。
5.2 现象:训练 loss 变成 NaN,或者 mAP 一直是 0
原因:学习率太大、batch 太小、标注格式错误三者之一。loss 变 NaN 通常是学习率问题,mAP 为 0 通常是标注问题。
解决:先把--lr0降到 0.001 试一轮;如果还不行,检查标注文件,用labelimg重新打开几张图确认框没画反、类别编号没写错。标注里最常见的错是坐标没归一化,YOLO 要求 0-1 之间,如果写成像素值,训练直接崩。
5.3 现象:推理时框画出来了,但乱停判定从不触发
原因:禁停区域坐标和视频分辨率不匹配。很多人标注区域时用的是原图坐标,但推理时视频被 resize 到 640,坐标对不上,车辆中心点永远落在区域外。
解决:统一坐标系。要么把禁停区域坐标按推理分辨率缩放,要么在推理前把帧 resize 回原尺寸再判定。我一般会在配置里存原图坐标,推理时按比例换算,这样换分辨率不用重标。
5.4 现象:同一辆车被反复分配新 ID,停留时长永远累计不起来
原因:IOU 匹配阈值太低,或者检测框抖动太大。车辆静止时框应该稳定,但如果模型置信度在阈值边缘反复横跳,框会时有时无。
解决:把iou_thresh从 0.3 提到 0.4,同时把检测conf从 0.4 降到 0.35,让框更稳定。如果还不行,加一个「丢失容忍」机制:车辆连续 5 帧没检测到才认为它离开,而不是一帧没检测到就删记录。
5.5 现象:部署到服务器后速度很慢,达不到实时
原因:没用 GPU,或者没用半精度,或者视频解码成了瓶颈。
解决:确认--device 0生效;推理时加half=True用 FP16,速度能快近一倍;如果视频解码慢,用cv2.CAP_FFMPEG或者先把视频转成低分辨率。另一个常见做法是把 PyTorch 权重导出成 ONNX 或 TensorRT,TensorRT 在 NVIDIA 卡上通常比原生 PyTorch 快 2 到 3 倍。
6. 进阶技巧:把检测精度和部署效率再压一压
模型能跑通之后,真正拉开差距的是细节。分享几个我在实际项目里反复验证过的做法。
第一,用切片推理处理小目标。路口摄像头架得高,车辆在画面里可能只有几十像素,直接 resize 到 640 后车更小,召回率掉得厉害。常见做法是 SAHI 切片推理:把大图切成带重叠的小块分别检测,再合并结果。代价是速度慢,但召回能提升十几个点。如果源码包没带这个功能,可以自己加,核心就是把model(frame)换成对切片列表的循环。
第二,导出 ONNX 再上 TensorRT。这一步对部署效率提升最明显。导出命令:
# 导出 ONNX,opset 12 兼容性最好 python export.py --weights best.pt --include onnx --opset 12 --imgsz 640 # 用 trtexec 转 TensorRT,FP16 精度 trtexec --onnx=best.onnx --saveEngine=best.engine --fp16逻辑说明:ONNX 是中间格式,TensorRT 是最终部署格式。--opset 12是算子集版本,太低不支持某些算子,太高有些推理引擎不认,12 是稳妥选择。--fp16开启半精度,精度损失通常不到 1 个点,速度提升明显。转完之后用 TensorRT 的 Python API 加载best.engine推理,延迟能压到原生 PyTorch 的三分之一左右。
第三,用配置文件管理场景参数。禁停区域、停留阈值、置信度这些参数,不要写死在代码里。我一般会建一个config.yaml:
conf_threshold: 0.4 iou_threshold: 0.4 stay_seconds: 30 no_park_zones: - name: "路口A" points: [[100, 200], [500, 200], [500, 600], [100, 600]] - name: "路口B" points: [[600, 300], [900, 300], [900, 700], [600, 700]]这样换一个路口只需要改配置,不用动代码。多场景部署时这个习惯能省大量时间。
第四,验证方法要固定。每次改完参数,不要凭感觉说「好像准了」。固定一段测试视频,跑完之后统计三个指标:误报数(把正常车判成乱停)、漏报数(乱停车没判出来)、平均判定延迟(从车停下到报警的秒数)。这三个数才是判断改动好坏的依据。我见过太多人调了一下午参数,结果只是换了个视频看,根本没量化。
最后说个我自己的教训。早期做这类系统时,我总想着把模型精度往上堆,换更大的模型、加更多数据,结果部署时发现推理速度根本达不到实时,整个方案推倒重来。从那以后我每次做检测项目,都强制先跑一遍端到端延迟测试,再决定模型规模。模型不是越大越好,能稳定跑在目标硬件上的才是好模型。希望帮到你。
本文还有配套的精品资源,点击获取