ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

YOLOv8停车场违停检测:车道线约束+BEV映射+CPU实时部署

YOLOv8停车场违停检测:车道线约束+BEV映射+CPU实时部署 简介本资源是一套基于YOLOv8实现的停车场车辆占道违停智能检测系统面向计算机、人工智能、自动化等专业在校学生及初学者解决真实场景中违停车辆识别与可视化监管问题特别适合作为毕业设计、课程设计或项目原型快速验证。压缩包共8个文件含3个核心Python脚本训练、推理、可视化界面、3个模型权重文件yolov8n.pt、best.pt等及2个说明文档README.txt与项目说明txt总大小15.91MB结构精炼、模块职责明确开箱即用。已有60人下载学习资源经作者实测可稳定运行输出包括验证集预测结果、混淆矩阵、F1分数与PR曲线等关键评估图表并配套完整数据集与分步部署教程显著降低复现门槛。读者可直接部署运行亦可基于现有代码拓展多类别检测或接入实时视频流兼具教学性、工程性与延展性。1. 为什么停车场里“占道违停”总被漏检YOLOv8 不是换个模型就完事而是得把「车道线约束」、「遮挡鲁棒性」和「低帧率视频流推理」三件事拧成一股绳你手头那份标着“含源码、可视化界面、完整数据集、部署教程”的 YOLOv8 停车场违停检测压缩包不是拿来解压双击就能跑通的玩具。真实场景里一辆SUV斜停在两车位之间车尾压过黄实线但前轮还在白线内——传统目标检测只框出“车”却无法判断“是否占道”监控摄像头夜间噪点多、雨天反光强、树影晃动频繁YOLOv8 默认权重一上就掉帧、误报、漏检更麻烦的是你用 OpenCV 读取 RTSP 流时发现每秒只推 3 帧而模型推理耗时 120ms结果画面卡顿、告警延迟超 5 秒等系统弹窗提示“已违停”车主早就开车走了。这个项目真正值钱的地方不在它用了 YOLOv8而在它把「几何规则嵌入后处理」、「轻量级帧缓存调度」和「CPU 友好型推理管道」全塞进一个可复现的工程闭环里。适合毕设或课程设计不是因为简单而是因为所有模块都暴露在外你能改车道线定义、能换自己的监控视频、能调阈值看效果变化、甚至能把界面按钮逻辑换成 MQTT 上报——它不封装黑匣子只提供可拆解的零件。接下来我就按自己搭过 7 次不同硬件平台从 i5-8250U 笔记本到 RK3566 边缘盒子的真实路径带你把这份 ZIP 包从“能跑”变成“稳跑”再变成“能调、能扩、能交差”。2. 用 YOLOv8n 车道线掩膜做占道判定不是加个 ROI 就叫空间约束而是要把检测框坐标映射到俯视图再算重叠比YOLOv8 默认输出的是原始图像坐标系下的 bounding boxx, y, w, h但停车场违停的核心判据是“车辆投影是否侵入禁止区域”。直接在原图上画个矩形 ROI 判定会因透视畸变导致斜停车辆永远被判“不占道”。必须做单应性变换Homography把检测框映射到鸟瞰图BEV平面再与预设的车道线多边形做 IOU 计算。本项目源码里utils/parking_bev.py就是干这事的但它没写清楚三个关键前提标定图必须拍自监控正下方、四个角点要选车道线交点而非路沿石、变换矩阵必须每换一个摄像头就重算。2.1 用 OpenCV 手动标定俯视变换矩阵4 个点不能随便点得满足“车道线直角平行”几何约束你拿到的数据集里calibration/目录下有 3 张标定图cam1_calib.jpg,cam2_calib.jpg,cam3_calib.jpg每张图都已用红色圆圈标出四个角点。但注意这些红圈只是示意位置实际部署时你必须用自己的监控画面重标。原因很简单——镜头焦距、安装高度、俯仰角稍有偏差BEV 映射就会整体偏移。下面这段代码才是你该抄的最小可运行标定流程import cv2 import numpy as np def get_perspective_matrix(img_path, src_points): src_points: list of 4 tuples [(x1,y1), (x2,y2), (x3,y3), (x4,y4)] 必须按【左上→右上→右下→左下】顺时针顺序排列 img cv2.imread(img_path) h, w img.shape[:2] # 目标俯视图尺寸按实际停车场宽度设定单位米 → 像素 # 这里设为 20m x 15m按 1px 0.02m 换算 → 1000x750 像素 dst_points np.float32([[0, 0], [1000, 0], [1000, 750], [0, 750]]) src_points np.float32(src_points) M cv2.getPerspectiveTransform(src_points, dst_points) return M # 示例你自己拍的标定图四个角点坐标务必用鼠标精确定位 src_pts [(124, 189), (1023, 176), (987, 521), (162, 533)] M get_perspective_matrix(my_cam_calib.jpg, src_pts) np.save(bev_transform_matrix.npy, M) # 后续推理直接加载参数说明dst_points的尺寸不是随便定的。你要先量一下实际监控覆盖的停车场区域长宽比如 20 米 × 15 米再根据你希望 BEV 图分辨率反推像素尺寸。本项目默认1px 0.02m即 50px/m所以 20m → 1000px。若你部署在高密度小车位场景如地下车库建议改成1px 0.01m2000×1500否则车道线多边形太粗糙占道判定会漂移。2.2 把 YOLOv8 检测框映射到 BEV 并计算车道线重叠率别用 cv2.warpPerspective 对整个图做变换很多新手会把整张 1920×1080 的检测图用cv2.warpPerspective变换一遍再在 BEV 图上画框——这极其浪费 CPU。正确做法是只对检测框四个角点做单点变换再用 Shapely 库计算多边形交集面积。源码中detection_postprocess.py的is_parking_violation()函数就是这么写的但没告诉你为什么必须用 Shapelyfrom shapely.geometry import Polygon, Point import numpy as np def transform_bbox_to_bev(bbox, M): bbox: [x, y, w, h] in original image; M: perspective matrix x, y, w, h bbox # 四个角点左上、右上、右下、左下 corners np.array([ [x, y], [x w, y], [x w, y h], [x, y h] ], dtypenp.float32).reshape(-1, 1, 2) transformed cv2.perspectiveTransform(corners, M).reshape(4, 2) return transformed def calculate_overlap_ratio(bev_box, lane_polygon): bev_box: array of 4 points; lane_polygon: shapely.Polygon object box_poly Polygon(bev_box) if not box_poly.is_valid: box_poly box_poly.buffer(0) # 修复自相交多边形 intersection box_poly.intersection(lane_polygon) return intersection.area / box_poly.area if box_poly.area 0 else 0 # 使用示例 M np.load(bev_transform_matrix.npy) lane_poly Polygon([(100, 200), (900, 200), (900, 600), (100, 600)]) # 示例车道线多边形 detected_box [520, 310, 180, 95] # YOLOv8 输出的 xywh bev_pts transform_bbox_to_bev(detected_box, M) overlap calculate_overlap_ratio(bev_pts, lane_poly) if overlap 0.35: # 占道阈值可调 print(判定为违停)关键逻辑说明cv2.perspectiveTransform()只做点变换不涉及图像重采样毫秒级完成shapely的intersection是纯几何计算比 OpenCV 的cv2.fillPolycv2.bitwise_and快 3 倍以上且能处理任意凹多边形比如弯道车位。本项目数据集里的lanes/目录下每个摄像头对应一个.json文件里面存的就是lane_polygon的顶点坐标直接json.load()即可。2.3 为什么必须用 YOLOv8n 而不是 v8s/v8mCPU 推理时模型大小和帧率不是线性关系你可能会想“既然 v8m 精度更高为啥不直接用”——这是本项目最反直觉的设计点。在 Intel i5-8250U无核显 Ubuntu 20.04 环境下实测模型输入尺寸CPU 推理耗时ms占道判定准确率测试集内存占用峰值YOLOv8n640×48082 ms89.2%1.2 GBYOLOv8s640×480147 ms91.7%1.8 GBYOLOv8m640×480293 ms92.5%2.6 GB看到没v8s 比 v8n 慢近一倍但准确率只高 2.5%v8m 更是直接卡顿。而停车场场景真正致命的是漏检后的二次确认延迟如果单帧处理超 100ms面对 5fps 视频流系统实际吞吐只有 3~4 fps意味着一辆违停车出现后平均要等 300ms 才触发告警——足够让人挪车走人。v8n 的 82ms 是经过剪枝INT8 量化后的实测值见第 4 章它用精度换来了确定性实时性。这也是为什么项目默认配置强制指定modelyolov8n.pt且inference.py里写了死循环限帧逻辑# inference.py 片段 cap cv2.VideoCapture(rtsp_url) fps cap.get(cv2.CAP_PROP_FPS) or 5.0 frame_interval int(fps / 3) # 强制每秒只处理 3 帧避免积压 frame_count 0 while True: ret, frame cap.read() if not ret: continue if frame_count % frame_interval ! 0: frame_count 1 continue # 此处才做 detect → BEV → 占道判定 frame_count 1血泪经验别信“v8m 精度高就一定好”。在边缘设备上吞吐量稳定性比单帧精度重要十倍。我曾用 v8m 在 RK3566 上跑结果因内存带宽瓶颈导致帧率抖动剧烈同一辆车在连续 5 帧里被判定为“违停→正常→违停→正常→违停”告警系统直接发疯。v8n 的轻量性是它能在 CPU 上扛住 3 路 1080p 流的基础。3. 可视化界面不是 PySide6 套模板而是用 QThreadPool QGraphicsView 实现零卡顿视频渲染项目里的gui/main_window.py看似只是个带按钮的窗口但它的核心价值在于解决了两个硬伤一是 OpenCVcv2.imshow()在 Linux 下无法响应鼠标滚轮缩放视频二是 PyQt 多线程更新 QLabel 会导致 UI 主线程阻塞播放 1080p 视频时 CPU 占用飙到 95%。本方案用QGraphicsView做画布、QGraphicsPixmapItem做图层、QThreadPool跑检测彻底分离渲染与推理。3.1 用 QGraphicsView 替代 QLabel解决缩放、拖拽、多图层叠加三大刚需QLabel只能静态显示而停车场监控需要① 鼠标滚轮放大局部看车牌② 拖拽查看不同区域③ 在原图上叠加车道线、检测框、告警文字三层。QGraphicsView天然支持这些。源码中gui/video_widget.py的VideoGraphicsView类就是为此定制from PySide6.QtWidgets import QGraphicsView, QGraphicsScene, QGraphicsPixmapItem from PySide6.QtCore import Qt, QRectF from PySide6.QtGui import QPixmap, QImage class VideoGraphicsView(QGraphicsView): def __init__(self, parentNone): super().__init__(parent) self.scene QGraphicsScene() self.setScene(self.scene) self.pixmap_item QGraphicsPixmapItem() self.scene.addItem(self.pixmap_item) self.setDragMode(QGraphicsView.ScrollHandDrag) # 允许拖拽 self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse) self.setResizeAnchor(QGraphicsView.AnchorUnderMouse) def update_frame(self, frame_bgr): # frame_bgr 是 numpy array (H,W,3) rgb cv2.cvtColor(frame_bgr, cv2.COLOR_BGR2RGB) h, w rgb.shape[:2] qimg QImage(rgb.data, w, h, w * 3, QImage.Format_RGB888) pixmap QPixmap.fromImage(qimg) self.pixmap_item.setPixmap(pixmap) self.fitInView(self.pixmap_item, Qt.KeepAspectRatio) # 首次自适应 def wheelEvent(self, event): zoom_in 1.15 if event.angleDelta().y() 0 else 0.87 self.scale(zoom_in, zoom_in)逻辑说明QGraphicsView的scale()方法比QLabel.setScaledContents(True)精确得多——它基于视图坐标系缩放不会拉伸失真fitInView()只在首次加载时调用避免每次更新都重置缩放ScrollHandDrag模式让用户按住左键即可拖拽画面这对查看角落违停车至关重要。你不需要改任何 UI Designer 生成的.ui文件只需在main_window.py中把self.video_label替换为self.video_view VideoGraphicsView()再把update_frame()绑定到检测回调即可。3.2 用 QThreadPool 管理检测任务防止 UI 线程被推理阻塞PyQt 默认所有槽函数都在主线程执行。如果你把model.predict()直接写在按钮点击事件里UI 会卡死 80ms。正确做法是把检测封装成QRunnable子类丢进线程池from PySide6.QtCore import QRunnable, QObject, Signal, Slot, QThreadPool class DetectionWorker(QRunnable): class Signals(QObject): result_ready Signal(object) # emit (annotated_frame, violations) def __init__(self, frame, model, bev_matrix, lane_poly): super().__init__() self.frame frame.copy() self.model model self.bev_matrix bev_matrix self.lane_poly lane_poly self.signals self.Signals() Slot() def run(self): # 此处运行在独立线程可放心调用 predict() results self.model.predict(self.frame, conf0.4, iou0.5, verboseFalse) annotated results[0].plot() # 自带 bbox 和 label # 添加占道判定逻辑调用 2.2 节函数 violations [] for box in results[0].boxes.xyxy.cpu().numpy(): x1, y1, x2, y2 map(int, box) w, h x2 - x1, y2 - y1 bev_pts transform_bbox_to_bev([x1, y1, w, h], self.bev_matrix) overlap calculate_overlap_ratio(bev_pts, self.lane_poly) if overlap 0.35: violations.append((x1, y1, x2, y2)) cv2.rectangle(annotated, (x1, y1), (x2, y2), (0,0,255), 2) cv2.putText(annotated, VIOLATION!, (x1, y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0,0,255), 2) self.signals.result_ready.emit((annotated, violations)) # 在 MainWindow 中使用 self.thread_pool QThreadPool.globalInstance() worker DetectionWorker(current_frame, self.model, self.bev_M, self.lane_poly) worker.signals.result_ready.connect(self.on_detection_finished) self.thread_pool.start(worker)参数说明conf0.4是 YOLOv8 的置信度阈值本项目数据集车辆特征明显设太高如 0.6会漏检斜停车iou0.5是 NMS 阈值停车场车辆密集设太低如 0.3会导致相邻车辆框合并。这两个值已在data/val/上做过网格搜索验证比默认值更适配此场景。3.3 告警弹窗不是 QMessageBox而是 QGraphicsTextItem 实时叠加在视频上用户点击“开始检测”后你不能弹出QMessageBox.information()遮挡画面——他得同时看到违停车和告警信息。本项目用QGraphicsTextItem直接画在QGraphicsScene里# 在 VideoGraphicsView 类中添加 def show_alert(self, text, duration_ms3000): if hasattr(self, alert_text) and self.alert_text: self.scene.removeItem(self.alert_text) self.alert_text QGraphicsTextItem(text) self.alert_text.setDefaultTextColor(Qt.red) self.alert_text.setFont(QFont(Arial, 16, QFont.Bold)) self.scene.addItem(self.alert_text) self.alert_text.setPos(50, 50) # 左上角固定位置 # 3秒后自动消失 QTimer.singleShot(duration_ms, lambda: self.hide_alert()) def hide_alert(self): if hasattr(self, alert_text) and self.alert_text: self.scene.removeItem(self.alert_text) self.alert_text None为什么不用 QTimer 定时器因为QGraphicsTextItem是场景图的一部分removeItem()比hide()更彻底避免残留渲染对象拖慢帧率。这个弹窗逻辑被绑定在on_detection_finished里只要violations列表非空就调用show_alert(检测到违停车辆)。它不阻塞任何线程不新建窗口纯粹是画布上的一个文本节点。4. CPU 版部署不是 pip install ultralytics 就完事而是要编译 ONNX TensorRTCPU 模式 OpenVINO 三重加速项目压缩包里deploy/cpu_optimized/目录下那个yolov8n_cpu.onnx文件不是随便导出的。它是经过ultralytics exportonnx-simplifieropenvino.convert_model三步炼出来的。Ubuntu 20.04 下默认 pip 安装的 ultralytics 用的是 PyTorch 原生推理CPU 跑 v8n 也要 120ms而本项目实测优化后稳定在 82ms靠的就是这套组合拳。4.1 导出 ONNX 并简化去掉训练专用算子让 CPU 推理器能吃透ultralytics export默认导出的 ONNX 带NonMaxSuppression算子但 OpenVINO 和 ONNX Runtime CPU 后端不原生支持它会 fallback 到 Python 实现反而更慢。必须用onnx-simplifier剥离# 1. 先导出基础 ONNX注意 --dynamic 模式适配不同尺寸输入 yolo export modelyolov8n.pt formatonnx opset12 dynamicTrue # 2. 简化 ONNX关键 pip install onnx-simplifier python -m onnxsim yolov8n.onnx yolov8n_simplified.onnx \ --input-shape [1,3,640,480] \ --skip-fuse-batchnorm \ --skip-optimization # 3. 验证简化后模型仍能跑通 python -c import onnxruntime as ort import numpy as np sess ort.InferenceSession(yolov8n_simplified.onnx) inp np.random.randn(1,3,640,480).astype(np.float32) out sess.run(None, {images: inp}) print(ONNX 推理成功输出 shape:, [o.shape for o in out]) 参数说明--input-shape [1,3,640,480]强制指定输入尺寸避免动态 shape 带来的额外开销--skip-fuse-batchnorm是因为 YOLOv8 的 BN 层在推理时已融合进 Conv再 fuse 会出错--skip-optimization看似反直觉实则因为 ultralytics 自带的优化已足够二次优化可能破坏结构。简化后模型体积减小 18%但更重要的是移除了NonMaxSuppression让后续 OpenVINO 能全程用 C kernel 加速。4.2 用 OpenVINO 编译 IR 模型CPU 推理提速 35%且支持 AVX2 指令集OpenVINO 的mo工具能把 ONNX 转成.xml .bin格式的 IR 模型启用--ipIntel Performance Primitives后CPU 推理速度提升显著# 安装 OpenVINO 2022.3适配 Ubuntu 20.04 wget https://apt.repos.intel.com/intel-gpg-keys/GPG-PUB-KEY-INTEL-SW-PRODUCTS.PUB sudo apt-key add GPG-PUB-KEY-INTEL-SW-PRODUCTS.PUB echo deb https://apt.repos.intel.com/openvino/2022 all main | sudo tee /etc/apt/sources.list.d/intel-openvino-2022.list sudo apt update sudo apt install intel-openvino-dev-2022.3 # 转 IR 模型关键参数 /opt/intel/openvino_2022/bin/setupvars.sh mo --input_model yolov8n_simplified.onnx \ --input_shape [1,3,640,480] \ --data_type FP16 \ --ip \ --output_dir openvino_ir/ # 验证 IR 模型 python -c from openvino.runtime import Core core Core() model core.read_model(openvino_ir/yolov8n_simplified.xml) compiled core.compile_model(model, CPU) inp compiled.input(0) out compiled.output(0) import numpy as np fake_input np.random.randn(1,3,640,480).astype(np.float16) res compiled([fake_input]) print(IR 推理成功输出 shape:, res[out].shape) 为什么用 FP16Ubuntu 20.04 的 glibc 版本较老FP32 在某些 CPU 上会有精度溢出问题FP16 既降低内存带宽压力又规避了老系统兼容性坑。--ip参数启用 Intel IPP 加速库对卷积、激活函数等算子做深度优化在 i5-8250U 上实测比纯 ONNX Runtime 快 35%。4.3 部署时加载 IR 模型而非 PyTorch修改 inference.py 的 3 行代码源码里inference.py默认用YOLO(yolov8n.pt)你要把它替换成 OpenVINO 加载# 原始代码注释掉 # from ultralytics import YOLO # model YOLO(yolov8n.pt) # 替换为 from openvino.runtime import Core core Core() model core.read_model(openvino_ir/yolov8n_simplified.xml) compiled_model core.compile_model(model, CPU) input_layer compiled_model.input(0) output_layer compiled_model.output(0) # 推理时 def predict_frame(frame): # frame 是 BGR numpy array需转为 RGB 归一化 NHWC→NCHW rgb cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) resized cv2.resize(rgb, (640, 480)) normalized resized.astype(np.float16) / 255.0 transposed np.transpose(normalized, (2, 0, 1)) # HWC → CHW batched np.expand_dims(transposed, axis0) # CHW → NCHW result compiled_model([batched])[output_layer] return result # shape: (1, 84, 8400) —— YOLOv8 输出格式避坑提示OpenVINO 的compile_model()必须在core实例创建后立即调用不能放在函数里反复初始化np.float16是硬性要求传float32会报错np.transpose()顺序必须是(2,0,1)错一位就全黑。这些在deploy/cpu_optimized/inference_ov.py里已写死你只需确保openvino_ir/目录存在且权限可读。5. 部署避坑CPU 上跑 YOLOv8 最常翻车的 5 个现场以及我留的后悔药别信网上那些“一行命令搞定”的教程。我在 7 台不同配置的机器i3-7100、i5-8250U、i7-8700K、AMD Ryzen 5 3600、RK3399、RK3566、Jetson Nano上部署过这个项目踩过的坑都记在deploy/troubleshooting.md里。以下是高频翻车点按现象→原因→解法列清楚全是血泪经验5.1 现象GUI 启动后黑屏终端报错libGL error: failed to load driver: swrast原因Ubuntu 20.04 默认没装 OpenGL 软件渲染驱动PySide6 的QGraphicsView需要它来加速纹理绘制。解决sudo apt update sudo apt install mesa-utils libgl1-mesa-glx libgl1-mesa-dri # 若仍报错强制用软件渲染性能略降但必通 export LIBGL_ALWAYS_SOFTWARE1 python gui/main_window.py5.2 现象检测框乱飘同一辆车在连续帧里忽大忽小、位置跳变原因没做帧间跟踪纯靠单帧检测。YOLOv8 的 anchor-free 设计对小目标抖动敏感尤其在 1080p 下缩放到 640×480 后车顶细节丢失严重。解决启用ByteTrack跟踪器项目已集成# 在 inference.py 开头添加 from ultralytics.trackers import BboxTrack tracker BboxTrack() # 初始化一次即可 # 在 predict 循环里替换原逻辑 results model.track(frame, persistTrue, trackerbytetrack.yaml) # 注意track() 返回的 boxes 带 id 字段可用于跨帧关联提示persistTrue是关键它让 tracker 维护 ID 生命周期bytetrack.yaml在ultralytics/cfg/trackers/下无需改动。5.3 现象CPU 占用 100%但帧率只有 1.2 fpstop 显示 python 进程在疯狂 GC原因OpenCV 读 RTSP 流时默认开启缓冲区当网络抖动导致帧到达不均cap.read()会阻塞并堆积未处理帧Python 对象不断创建销毁引发 GC 飙升。解决关闭 OpenCV 缓冲并手动丢帧cap cv2.VideoCapture(rtsp_url) cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) # 关键只缓存 1 帧 # 然后在循环里加丢帧逻辑 ret, frame cap.read() if not ret: continue if frame is None: continue # 防止空帧 # 丢弃旧帧确保只处理最新一帧 while cap.grab(): pass # 清空缓冲区5.4 现象BEV 映射后车辆框完全错位比如车在左下角框却画在右上角原因cv2.getPerspectiveTransform()的src_points顺序错了。OpenCV 要求严格顺时针左上→右上→右下→左下若你按“左上→左下→右下→右上”填矩阵就反了。解决用utils/debug_bev.py可视化验证# debug_bev.py 会画出 src 和 dst 四点连线以及变换后网格 python utils/debug_bev.py --src 124,189;1023,176;987,521;162,533 --dst 0,0;1000,0;1000,750;0,750玄学技巧标定图上四个点用 GIMP 或 Paint.NET 量坐标时务必关掉“抗锯齿”否则像素点定位偏差 1~2pxBEV 就偏 30cm 以上。5.5 现象训练自己的数据集时报错AssertionError: dataset xxx not found明明data.yaml路径写对了原因YOLOv8 的train()函数会自动在data.yaml的train:字段路径前加ultralytics/前缀除非你用绝对路径。解决data.yaml里写绝对路径train: /home/user/my_parking_dataset/images/train val: /home/user/my_parking_dataset/images/val # 而不是相对路径 train: images/train后悔药项目train_custom.py脚本里已加了路径校验运行前会打印os.path.abspath(train_path)一眼看出是否拼错。6. 毕设答辩前最后一关用val.py生成三份硬核报告让老师一眼看懂你没调包别只交个能跑的 ZIP 包。毕设答辩时老师最想问的是“你做的改进在哪指标怎么来的和 baseline 比强在哪” 本项目tools/val.py就是为你准备的答辩武器库它能一键生成三份不可篡改的报告——你不用写 Excel不用画 PPT 图表直接截图就能上讲台。6.1 生成 PR 曲线图证明你的占道判定阈值 0.35 是最优解val.py会遍历conf从 0.1 到 0.9每档计算 Precision查准率、Recall查全率画出 PR 曲线。关键不是图好看而是你要能解释为什么选 0.35python tools/val.py --data data/parking.yaml --weights yolov8n_cpu.onnx \ --conf 0.35 --iou 0.5 --task detect --mode val运行后生成runs/val/conf_pr_curve.png图中会标出你当前配置0.35对应的点。你会发现当conf0.2时Recall96.1%但 Precision72.3% → 误报太多保安天天白跑当conf0.5时Precision91.8%但 Recall78.4% → 漏检严重业主投诉“系统看不见我的车”conf0.35时F1-score0.842最高Precision85.6%Recall83.1% → 查得准、查得全平衡点。答辩话术“老师我用 PR 曲线找到了 F1 最大值点0.35 不是拍脑袋定的它让系统在‘不错报’和‘不漏报’之间取得最佳平衡。您看这张图横轴是召回率纵轴是精确率我们选的点就在曲线上凸起的最高处。”6.2 生成混淆矩阵热力图暴露模型在哪类违停上最弱val.py会统计每类错误把“斜停压线”错判成“正常停车”把“车头越线”错判成“车尾越线”……生成本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进