简介:基于YOLOv8的雾天车辆行人检测系统,面向计算机视觉学习者及安防、自动驾驶等应用场景,解决雾天条件下目标检测精度下降的问题,提供从训练权重到可视化界面的完整落地路径。压缩包共41个文件、约20.72MB,核心包含RTTS数据集上训练得到的best.pt和last.pt权重、基于PyQt5的GUI推理界面,以及支持图片、视频、摄像头输入的检测代码,代码内置目标数量计数逻辑;另附训练曲线、混淆矩阵等评估图表与配置、依赖清单和说明文档。已有1527人学习下载,适合课程设计、项目演示及算法研究,可快速体验从数据集训练到界面部署的完整流程。除可直接运行的推理系统外,资源还提供完整训练日志与可视化分析结果,便于对比不同epoch下的模型性能;作者同时提供安装、跑通、环境搭建和使用教学等售后服务,能帮助初学者节省大量调试时间,真正将雾天检测模型应用落地。
1. 雾天车辆行人检测不是“加个模型”就行:这套系统的完整形态
“基于YOLOv8的雾天车辆行人检测系统,包含训练好的权重和推理代码、GUI界面,支持图片、视频、摄像头输入,支持检测结果导出”——这个标题看着像一个毕业设计题,但真正做起来,它是一条完整的视觉应用流水线:雾天图像退化处理、目标检测模型、权重适配、推理封装、界面交互、结果持久化,任何一个环节掉链子,整套东西都没法交付。
这套系统解决的是“晴天跑得好好的模型,一到雾天就漏检、误检”的实际问题。雾天环境下车辆和行人的对比度低、边缘模糊,尤其是远处小目标,对模型特征提取是很大挑战。所以系统里通常不止有YOLOv8推理代码,还要有雾天相关的处理策略和针对性训练的权重。
适合谁做?准备拿它做毕业设计的同学、要给项目演示做原型的工程师、或者要把车载/安防场景做雾天检测验证的团队。你需要懂一点Python和OpenCV,不需要从零写网络结构。下面按“原理 → 推理代码 → GUI开发 → 踩坑 → 进阶”这条路线,把整个方案拆开讲清楚。
2. 雾天场景下YOLOv8为什么会翻车:退化模型、权重选择与前处理取舍
2.1 雾天影像退化模型:检测器面前的输入到底长什么样
雾天图像退化可以用经典的大气散射模型来描述:
I(x) = J(x) * t(x) + A * (1 - t(x))
其中 J(x) 是清晰场景辐射,A 是大气光,t(x) 是介质透射率。雾越浓,t(x) 越小,图像对比度越低,颜色整体向大气光偏移。对检测器来说,这不是简单的“变模糊”,而是目标与背景的差异被压缩,骨干网络提取不到足够强的高频特征。
行人这类小目标尤其吃亏。车辆还有明显的轮廓和车灯等强局部特征,行人本身尺寸小、纹理少,雾天透射率低时,特征图里可能只剩一个灰斑。这也是大家在雾天用YOLOv8经常看到“车能检测到,行人丢了”的原因。
import cv2 import numpy as np def simulate_fog(image, intensity=0.6): """给清晰图像加模拟雾,方便做雾天增强训练""" h, w = image.shape[:2] fog = np.full_like(image, 180, dtype=np.uint8) # 大气光A,灰度值可调 alpha = intensity # 透射率反比因子,越大雾越浓 t = np.random.uniform(0.3, 0.8, (h, w, 1)) # 逐像素随机透射率 t = np.clip(t, 0.1, 1.0) fogged = (image * t + fog * (1 - t)).astype(np.uint8) return fogged这段代码用简化模型模拟雾天退化。intensity控制整体雾的浓度,fog是大气光颜色,实际场景里可能偏灰白或偏黄,可以根据数据集情况调整。逐像素随机透射率是为了避免全图均匀加雾导致的过度理想化。
这里要说明:真实雾的透射率与深度有关,近处目标清晰、远处模糊是它的物理规律。如果只是均匀加雾,模型学到的是全局对比度下降,而不是“距离越远越看不见”。所以做雾天数据增强时,最好基于深度信息或至少做区域渐变,否则训练出来的权重在真实雾天场景上效果会打折扣。
2.2 权重选择:自称“训练好的权重”到底该怎么用
标题里的“训练好的权重”需要拆开看。一套正经的雾天车辆行人检测系统,通常包含两类权重:
一类是官方通用预训练权重,比如yolov8n.pt、yolov8s.pt、yolov8m.pt。它们在大规模通用数据集上训练过,能识别人、车、公交、卡车等常见目标类别,但没针对雾天优化。用于晴天演示没问题,雾天场景就得看运气。
另一类是自训练或微调权重,比如best.pt。它是拿官方权重做初始值,再用自己的雾天车辆行人数据集接着训练得到的,类别数和标签顺序可能和官方不完全一致。加载时要注意类别名称是否对得上,否则会出现“检测到person但显示成car”这种低级错误。
| 权重类型 | 适用场景 | 注意点 |
|---|---|---|
| yolov8n.pt / s / m / l / x | 快速验证、资源受限设备 | 模型小,GPU显存占用低,精度稍弱 |
| 自训练 best.pt | 雾天专用、项目交付 | 必须确认类别名称和标签顺序 |
| 在清晰天气数据上训练的权重 | 晴朗场景 | 直接用到雾天前建议先跑评测看mAP |
我自己的经验:只要是“雾天检测”,别拿官方COCO权重硬抗。至少要做雾天数据增强后的微调,哪怕只训练几十个epoch,漏检率都能明显下降。你拿到别人的系统时,第一步就是打印权重的类别信息。
from ultralytics import YOLO model = YOLO("weights/best.pt") names = model.names print("模型类别:", names) print("输入尺寸:", model.model.args.get("imgsz", 640))这段代码用YOLO对象的names属性直接读取类别表,确认权重到底是COCO的80类还是仅车辆的2类。imgsz参数能从模型配置里读出来,如果训练时用的是640,推理时强制拉到1280,结果不一定更好,反而可能因为特征尺度错位导致误检。
2.3 去雾前处理是否值得做:暗通道先验与直方图均衡的取舍
不少方案喜欢在检测前加去雾模块,常见的有暗通道先验和直方图均衡化。先说结论:去雾不一定能提升YOLOv8的检测精度,还会吃掉大量算力,需要先做离线对比再决定。
暗通道先验去雾对浓雾图像的处理效果明显,但它假设局部区域至少有一个通道的强度接近0,而白色车辆、白色墙体这类目标会违背假设,产生色偏。直方图均衡化快,但会放大噪声,对行人检测的增益有限。
import cv2 def dehaze_dark_channel(image): """暗通道先验去雾:适合离线预处理,不适合实时推理""" dark = cv2.min(cv2.min(image[:, :, 0], image[:, :, 1]), image[:, :, 2]) blur_dark = cv2.medianBlur(dark, 15) # 简化透射率估计:1 - 暗通道 / 大气光 A = np.max(dark) t = 1 - 0.95 * (blur_dark / (A + 1e-6)) t = np.clip(t, 0.1, 1) restored = np.empty_like(image) for i in range(3): restored[:, :, i] = (image[:, :, i].astype(float) - A) / t + A return np.clip(restored, 0, 255).astype(np.uint8)这个简化版暗通道去雾在中等雾量下能看到对比度恢复,但对视频流逐帧跑,720p分辨率的耗时可能在几十毫秒到上百毫秒之间,会把整个推理流程拖垮。而且它假设透射率在局部平滑,实际实时帧里运动模糊和噪声会让恢复结果出现光晕。
所以我的做法是:先分别跑“原图直检”和“去雾后检测”两组实验,统计mAP。如果去雾带来的提升小于1%,就不上这个模块。如果确实需要,把去雾做一个可开关的预处理选项,而不是默认打开。雾天系统工程里真正稳的增益来源,还是让模型本身去适应雾天分布,也就是下一章要讲的训练和权重适配。
3. 推理代码实战:从权重文件到图片、视频、摄像头三路输入
3.1 yolov8环境配置:GTX1660Ti和纯CPU都能跑的安装顺序
先解决环境。很多人卡在yolov8环境配置上,其实核心就两步:装PyTorch,装ultralytics包。
# 创建虚拟环境,Python 3.8 - 3.11 都兼容 conda create -n yolov8 python=3.9 -y conda activate yolov8 # 先安装CPU版PyTorch,能保证推理跑通 pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu # 再装ultralytics,YOLOv8的官方训练推理库 pip install ultralytics # 验证环境 python -c "from ultralytics import YOLO; print('ok')"提示:
--index-url指定的是CPU版PyTorch的官方索引地址,如果你的显卡是NVIDIA且有CUDA,可以把它换成对应CUDA版本的PyTorch安装命令,比如CUDA 11.8或12.1。GTX1660Ti这种6GB显存的卡,跑yolov8s完全没问题,显存不够就降到yolov8n。
GTX1660Ti跑YOLOv8大概什么水平?我自己的实测里,用yolov8s、输入640分辨率,批量1的推理速度大约在10到20毫秒一帧,也就是50到60 FPS左右,完全够视频和摄像头实时用。关键是别把输入分辨率拉太高,别开过多的预处理。
纯CPU环境也有办法。用yolov8n在CPU上跑640像素图像,大约每张图100毫秒到300毫秒,做图片批量检测还能忍,摄像头实时性就差了。如果必须在CPU上做实时检测,把输入尺寸降到416或者320,能换来明显提速,代价是召回率下降。
3.2 最小推理代码:加载best.pt并输出检测框坐标
环境就绪后,推理代码非常短。这里的“推理代码”指的是从加载权重到输出结果列表的一套调用逻辑。
from ultralytics import YOLO model = YOLO("weights/best.pt") results = model.predict( source="demo.jpg", conf=0.35, iou=0.5, imgsz=640, save=True, name="output", classes=None, # None表示用模型全部类别 device="0", # "0"为第一块GPU,"cpu"为纯CPU ) for r in results: for box in r.boxes: x1, y1, x2, y2 = box.xyxy[0].cpu().numpy() score = box.conf.item() cls = int(box.cls.item()) name = model.names[cls] print(f"{name} {score:.2f} ({x1:.0f},{y1:.0f})-({x2:.0f},{y2:.0f})")这段是核心推理代码。results里保存了所有检测框,box.xyxy是左上角和右下角坐标,conf是置信度,cls是类别ID。predict里的参数为什么这样设?
conf=0.35:雾天场景下目标对比度低,置信度普遍偏低。如果按默认的0.25太激进、误检多,我一般先设0.35再调,低于0.3时会对小目标漏检非常敏感。iou=0.5:非极大值抑制的IoU阈值。车辆密集场景可以降到0.45,减少重叠框。imgsz=640:推理输入尺寸。取决于模型训练时的分辨率,不要盲目改大。save=True:会在输出目录自动保存带标注的图片,适合快速验证。device="0":用GPU跑还是CPU跑,Windows上也可以用device="cpu"。
3.3 统一三个输入源:图片、视频、摄像头
系统要支持图片、视频、摄像头三种输入,所以在工程上做一个统一的输入源抽象是必须的。不要为每种输入写一套循环,那样代码会重复且难维护。
import cv2 import os from ultralytics import YOLO model = YOLO("weights/best.pt") def detect_frame(frame): """对单帧做检测,返回绘制好结果的帧""" results = model.predict(frame, conf=0.35, iou=0.5, imgsz=640, verbose=False) rendered = results[0].plot() return rendered def run_image(path): frame = cv2.imread(path) rendered = detect_frame(frame) cv2.imwrite("result.jpg", rendered) def run_video(path): cap = cv2.VideoCapture(path) out = cv2.VideoWriter("result.mp4", cv2.VideoWriter_fourcc(*"mp4v"), 30, (int(cap.get(3)), int(cap.get(4)))) while cap.isOpened(): ok, frame = cap.read() if not ok: break out.write(detect_frame(frame)) cap.release() out.release() def run_camera(rtsp_url): cap = cv2.VideoCapture(rtsp_url) while cap.isOpened(): ok, frame = cap.read() if not ok: break detect_frame(frame) cap.release()这个统一思路的关键在detect_frame:它对输入要求就是一张BGR格式的numpy数组,图片、视频帧、摄像头帧都是同一类型。results[0].plot()是UI显示时最常用的方法,它把检测框、类别名、置信度一并画在副本上。
实际项目里我一般还会把detect_frame改成可配置参数,比如conf、imgsz通过字典穿进去,而不是写死在函数里。否则调参时要改源码,不优雅。
视频和摄像头的区别在读取端。摄像头用cv2.VideoCapture(0)代表本地摄像头,传RTSP地址就是IP摄像头。这里有个坑:摄像头输入分辨率通常不稳定,读出来的帧宽高可能会变,所以VideoWriter一定要在拿到第一帧后再初始化,不要提前写死分辨率。
3.4 推理参数调优:conf、iou、imgsz、device怎么设置不玄学
写推理代码时,这几个参数是最容易“玄学调参”的地方,但在雾天场景下其实有一些规律可循。
conf(置信度阈值)在雾天要适当降低。因为远处行人被雾掩盖后,模型输出的置信度会整体下降。如果你想保住召回率,可以从0.25降到0.15,然后接受更多误检;如果应用场景对误检容忍度低,比如夜间安防,那还是保持0.4以上更稳。
iou在车辆密集、互相遮挡时影响很大。高速路口车辆排队,IoU阈值太高会让同一个车出现多个框;太低又会漏掉真正重叠的另一个目标。一般默认0.5,密集场景降到0.45。
imgsz决定输入图像的缩放尺寸。模型训练用的640,你推理时用960,不代表能检测到更小的目标,反而会引入训练时没见过的目标尺度,可能掉点。我想强调:推理分辨率优先跟随训练配置,而不是想当然地用更大值。
device选GPU还是CPU,不只是一个速度问题。GPU显存不够时会爆显存崩溃,CPU单帧耗时会拖垮视频处理线程。稳妥做法是启动时自动检测GPU可用性,不可用时回退到device="cpu",并同时把imgsz降到480或416,保住实时性。
import torch device = "0" if torch.cuda.is_available() else "cpu" imgsz = 640 if torch.cuda.is_available() else 416这种动态策略适合做交付,用户在没有任何显卡的机器上也能跑图片检测,只不过会慢一些。至于X86机器上用OpenVINO加速、树莓派上用NCNN,那是部署阶段再考虑的事,和本系统的GUI推理核心耦合度不高。
4. GUI界面设计:把推理代码包成可演示、可导出结果的工具
4.1 界面框架选型:PySide6适合做交付,Tkinter适合快速演示
标题里的GUI界面要支持图片、视频、摄像头输入以及结果导出,选型很重要。我常见两种方案:PySide6(Qt的Python绑定)和Tkinter(Python自带)。
| 框架 | 界面美观度 | 线程支持 | 打包体积 | 适用场景 |
|---|---|---|---|---|
| Tkinter | 一般,原生控件 | 弱,需要自己写线程桥 | 小 | 毕业设计、内部快速演示 |
| PySide6 | 现代,QSS可定制 | 强,信号槽天然支持跨线程 | 较大 | 项目交付、产品原型 |
做“可以给别人演示、还能导出结果”的系统,我首选PySide6。它自带文件对话框、表格控件、信号槽机制,能比较规范地把推理放到后台线程,界面不会因为检测一帧就卡死。用Tkinter硬做,虽然也能跑,但你要额外处理线程安全和界面刷新,反而省不了多少时间。
4.2 工作线程与信号槽:避免“点一下按钮界面就未响应”
GUI推理最大的坑是把model.predict放进按钮的回调函数里。一下按钮,主线程被拖住,窗口转圈,严重时直接被系统判定为“未响应”。解决办法是把推理放到QThread,通过信号把结果传回主线程。
from PySide6.QtCore import QThread, Signal from ultralytics import YOLO import cv2 class InferenceThread(QThread): result_ready = Signal(object) # 把渲染后的帧传回UI log_message = Signal(str) # 日志信号 def __init__(self, model_path, conf=0.35): super().__init__() self.model = YOLO(model_path) self.conf = conf self.running = True def run(self): cap = cv2.VideoCapture(0) # 这里以摄像头为例 while self.running and cap.isOpened(): ok, frame = cap.read() if not ok: break results = self.model.predict(frame, conf=self.conf, verbose=False) rendered = results[0].plot() self.result_ready.emit(rendered) cap.release()这段代码演示了如何在后台线程里持续读取摄像头并推理。result_ready信号把绘制好的帧发射给主线程,主线程槽函数只负责把图像显示到QLabel上,不做任何耗时操作。
线程停止要处理干净。自己写一个stop()方法设置self.running = False,并在run()的while循环里检查它,否则关窗口时后台线程还在读摄像头,会导致Python进程退不干净。另外,YOLO对象在子线程创建后不要挪到主线程用,每个线程自己维护一个模型实例是最稳妥的。
4.3 导出结果三通道:标注图片、标注视频、CSV坐标明细
“检测结果导出”在项目交付里往往是验收重点。最少要做三类导出:标注图片、标注视频、结构化坐标文件。图片和视频直接用OpenCV写,坐标明细用CSV或TXT。
import csv def export_results(results, frame, csv_path): rows = [] for box in results.boxes: x1, y1, x2, y2 = box.xyxy[0].cpu().numpy() score = box.conf.item() cls = int(box.cls.item()) rows.append([model.names[cls], f"{score:.4f}", int(x1), int(y1), int(x2), int(y2)]) with open(csv_path, "w", newline="", encoding="utf-8") as f: writer = csv.writer(f) writer.writerow(["class", "conf", "x1", "y1", "x2", "y2"]) writer.writerows(rows)CSV是通用的。如果项目要求兼容VOC格式,还要把坐标转换成VOC的XML或者YOLO的txt归一化格式。视频导出注意编码器:mp4v兼容性最好,Windows上有些播放器不认avc1。导出前要把渲染后的帧转换回BGR再写,因为OpenCV默认BGR,Qt显示则需要RGB,别把通道顺序搞混。
4.4 界面布局最小可用:文件选择、摄像头按钮、进度条、结果表格
一个能上台演示的界面,不需要花哨,但要有清晰的交互流。
顶部放“打开图片”“打开视频”“开启摄像头”三个按钮;中间是显示图像的大区域;右边一个表格实时刷新检测结果;底部放“导出图片”“导出视频”“导出CSV”和进度条。用PySide6的QFileDialog选文件,摄像头按钮切换文本为“停止摄像头”,这样用户不会迷路。
from PySide6.QtWidgets import QMainWindow, QFileDialog, QLabel, QVBoxLayout, QPushButton class MainWindow(QMainWindow): def select_image(self): path, _ = QFileDialog.getOpenFileName(self, "选择图片", "", "Images (*.jpg *.png *.bmp)") if path: self.run_image_detection(path)select_image里不需要自己读文件,把路径交给后台线程去读,避免界面等一下图片加载。进度条可以用信号更新,比如每处理完一帧就把当前帧数发出来。这样用户能看到“10帧 / 200帧”而不是干等。
5. 雾天检测系统落地避坑记录:现象、根因与解决方案
5.1 现象:官方COCO权重在雾天图片上漏检行人严重
有人喜欢直接用yolov8n.pt跑雾天,结果车辆勉强能检测出,行人却大面积漏掉。这不是模型坏了,是训练分布与测试分布不一致。
原因:COCO数据集里大多是清晰天气图像,模型学到的行人特征包含大量清晰纹理。雾天把纹理抹掉后,特征匹配强度下降,置信度低于阈值就被过滤。
解决:首先调低confidence阈值看召回率是否回升,如果回升说明模型还有能力只是阈值太高。如果还是漏检,必须用雾天数据进行微调。可以在公开清晰数据集上合成不同浓度的雾,按第二章的simulate_fog方法扩充训练集,再训练30到50个epoch。这个步骤是治本。
5.2 现象:GUI里一开摄像头就卡死,CPU占用跑满
明明单张图片检测很流畅,一开摄像头界面就卡住,连拖动窗口都困难。
原因:摄像头读取、模型推理、界面刷新全在同一个主线程里。摄像头帧率通常30FPS,每帧推理需要几十毫秒,主线程既要读帧又要画界面,自然卡死。
解决:按第四章的方式把摄像头读取放到独立线程,推理再放一个线程,两个线程之间用队列传递帧。这样界面线程只负责显示最新一帧。如果CPU吃紧,还可以把摄像头读到的帧做缩放再送进推理,比如720P的画面先缩到640,推理耗时会明显下降,对检测结果影响不大。
5.3 现象:导出的标注视频在播放器里黑屏或无法打开
用cv2.VideoWriter写MP4,导出完本机能播,别人电脑打开黑屏;或者干脆写入失败生成0字节文件。
原因:常见有三个。视频编码器不对,OpenCV在某些系统上默认不支持H264;分辨率没有取到实际帧的宽高,写成了0或奇数值;画面通道顺序错误导致颜色异常被误认为是视频损坏。
解决:统一使用mp4v编码器,分辨率在视频第一帧读出后再创建VideoWriter。如果还要兼容老旧播放器,可以使用avi+XVID编码器。写入前使用cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)转换,确保可视化结果正常。
5.4 现象:同一个权重命令行有框,GUI界面里一个框都不出
控制台跑YOLOv8正常,把同样的权重嵌进GUI后,界面上不显示任何检测框。
原因:问题几乎不出在模型上,而在图像显示链路。一种情况是GUI里把BGR图像直接塞给QLabel,Qt把BGR当成RGB解析,颜色错乱但依然能看到图;另一种是加载路径有中文,Windows下OpenCV和PyTorch读取路径都可能失败;还有一种是回调里拿错对象,取了results而不是results[0],导致循环不到框。
解决:在GUI里统一打印len(results[0].boxes),确认检测框数量是否为零。如果数量正常,问题在显示;如果为零,就从路径和输入图像开始排查。路径规范化用os.path.abspath()或直接让用户通过文件对话框选择,避免手动输入中文路径。
5.5 现象:模型“看起来”很优秀,但评测mAP画不出PR曲线
有些人导出的权重在几组图上效果很好,但做正式评估时,mAP和PR曲线一张都出不来,或者数字低得离谱。
原因:评测脚本和标签格式不匹配。YOLOv8的val模式要求标签放在和图片同名的txt文件里,并且类别ID从0开始、坐标归一化。如果标签是JSON或XML就算不出正确mAP。
解决:先用自定义数据集跑model.val(data="fog.yaml"),先确认fog.yaml里的路径和类别数正确。PR曲线会自动生成到runs/val/目录。如果还是没有曲线,多半是标签文件名对不上。检查几张样本,把标注为0 0.5 0.5 0.1 0.2这样的YOLO格式与图片对应起来。
6. 进阶验证与交付:合成雾重训、批量评估与GUI打包
6.1 用合成雾数据重训:让权重真正适配雾天
如果手里已有清晰天气的车辆行人数据集,最快提升雾天效果的办法是合成雾数据增强。
from pathlib import Path source_dir = Path("datasets/train/images") target_dir = Path("datasets/fog/train/images") target_dir.mkdir(parents=True, exist_ok=True) for img_path in source_dir.glob("*.jpg"): img = cv2.imread(str(img_path)) fogged = simulate_fog(img, intensity=0.4 + 0.4 * np.random.rand()) cv2.imwrite(str(target_dir / img_path.name), fogged)这里把清晰图片按随机浓度加雾,生成一套平行的雾天训练目录。标签文件直接复制一份,因为加雾不改变目标位置。要提醒一个细节:训练时不要只给模型看雾天图片,最好按比例混合清晰图片和雾天图片,否则模型会把“有雾”当成一种固定风格,反而降低晴天泛化能力。
训练命令和参数含义:
yolo train data=datasets/vehicle.yaml model=yolov8s.pt epochs=80 imgsz=640 batch=8epochs如果数据集小,30到80即可,太大容易过拟合到合成雾的纹理上。batch由显存决定,GTX1660Ti 6GB显存跑yolov8s用batch=8是安全线。训练过程中观察loss曲线,雾天增强的模型会在前10个epoch快速下降,之后进入平台期。
6.2 批量评估:val模式输出mAP和PR曲线
完成重训后,不要在几张图片上“眼睛看效果”,要跑完整验证集。
from ultralytics import YOLO model = YOLO("runs/train/exp/weights/best.pt") metrics = model.val(data="datasets/vehicle.yaml", imgsz=640, split="val") print("mAP50:", metrics.box.map50) print("mAP50-95:", metrics.box.map)map50是IoU阈值0.5下的平均精度,适合看日常检测效果;map50-95是更严格的多IoU平均值,能反映框定位精度。雾天检测系统交付时这两项都要记录,前者证明“能不能检测到”,后者证明“框得准不准”。如果mAP50高但mAP50-95低,说明目标定位不稳定,可尝试把推理iou调低,或者训练时增加回归损失权重。
6.3 打包交付:PyInstaller打包GUI与权重文件外置
系统做完要交付给别人的时候,推荐用PyInstaller打包成可执行程序。
pyinstaller --windowed --onefile --name FogDetector main.py这个命令会生成单个exe,但直接把权重文件塞进exe里会导致启动极慢且更新权重困难。我会在代码里按相对路径找权重:
import sys, os base_dir = os.path.dirname(os.path.abspath(sys.argv[0])) model_path = os.path.join(base_dir, "weights", "best.pt")打包后目录结构就是“exe + weights文件夹”。用户要更新模型,只需替换best.pt,不需要重新打包。注意PyInstaller--onefile会把文件解压到临时目录,如果自己写的代码用了相对路径会找不到权重,所以一定用上面这种基于可执行文件所在目录的定位方式。
最后一个经验:交付前一定要在一台没有安装Python的干净Windows机器上测试打包结果。我自己曾因为漏掉ultralytics的隐藏数据文件,打包后的exe在别人电脑上打开就闪退。现在习惯用--collect-all ultralytics显式收集库的配置和数据文件,虽然exe会变大,但至少能跑。希望帮到你。
本文还有配套的精品资源,点击获取