首页 / 资讯中心 / 文章详情

实战:YOLOv8+ONNX+PyQt5战斗机机型识别系统搭建与避坑

实战:YOLOv8+ONNX+PyQt5战斗机机型识别系统搭建与避坑 ★ FEATURED ARTICLE
简介基于YOLOv8的战斗机类型识别检测系统以Python源码、ONNX模型、评估指标曲线和精美GUI界面的形式成套打包面向深度学习初学者、目标检测开发者及毕业设计人员用于快速搭建战斗机图像的识别与检测演示环境。压缩包共264个文件约19.25MB主要包含240张测试图片、6个XML标注文件、6张训练评估曲线图、3个TXT配置文件、3个Python源码文件以及1个yolov8n.onnx模型并有界面资源与项目配置等辅助文件结构紧凑便于直接启动体验。包内提供的模型为ONNX推理格式配合自带Python源码与PyQt5界面可完成图像检测、结果展示与指标查看等操作评估曲线与测试图片则有助于理解模型在战斗机类别上的训练效果。目前已有260人学习下载适合需要现成YOLOv8检测系统或希望参考界面实现与部署流程的读者。1. 战斗机机型识别不是通用检测YOLOv8ONNXGUI这套源码包到底解决了什么问题细粒度目标检测尤其战斗机这种外观相近的机型识别比COCO 80类通用检测要求高得多难在类间差异小、类内姿态差异大。这套基于YOLOv8的识别系统把训练好的yolov8n.onnx模型、mAP/P/R评估曲线、PyQt5 GUI界面打成一个可直接运行的包装好依赖执行 python main.py就能看到测试图上标注了机型标签和置信度框。它适合三类人需要快速出可演示成果的深度学习初学者、想在PyQt5里集成检测引擎的桌面端开发以及想验证onnx部署可行性的算法工程师。省掉的不是训练过程而是把推理、后处理、界面联动串起来的那些返工时间。2. 从装环境到跑通main.py依赖链上的三个关键验证这一章把入口链路走通目的是让你在修改任何代码之前先看到PyQt5窗口和检测框真的出现。项目本身是“源码onnx模型评估曲线GUI”的组合依赖不复杂但错一个环境就够你折腾半天。2.1 装ultralytics框架先装torch还是直接pip install看到官方安装教程大多数人会先执行 pip install ultralytics。这条命令本身没有错但它会把一套CPU版torch也自动带上来。如果你后续打算用显卡推理最好先装好匹配自己CUDA版本的torch再执行这条命令依赖解析器检测到torch已满足版本要求就不会重复下载覆盖。一个比较稳的安装过程是这样conda create -n yolov8 python3.9 -y conda activate yolov8 # 机器有NVIDIA显卡先装CUDA版torch纯CPU机器跳过这行 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121 pip install ultralytics python -c import ultralytics, torch; print(ultralytics.__version__, torch.__version__)逻辑说明conda创建独立虚拟环境避免torch、onnxruntime这类重依赖污染系统Python。先装torch再装ultralytics让安装器跳过对torch依赖的重复下载纯CPU环境可以不提前装。验证命令同时打印两个版本号确保当前环境里的torch确实是提前装的那个。参数说明CUDA 12.1机器把index-url换为cu121CUDA 11.8则用cu118不确定的用nvidia-smi查看右上角版本。Python版本选择3.9或3.10兼容性最稳3.12上部分旧版onnxruntime没有对应wheel装都装不进去。这里有个经常被忽略的点官方仓库安装时会顺带把opencv-python拉进来但某些源会把opencv装成不带GUI支持的headless版本。如果你后面要用cv2.VideoCapture打开摄像头需要确认当前环境里opencv能正常读摄像头。验证方法很简单跑一句 python -c import cv2; print(cv2.getBuildInformation())看输出里有没有摄像头相关支持。这个和PyQt5是两回事cv2负责图像采集和预处理PyQt5只负责界面显示。2.2 补装PyQt5并验证GUI程序的头号依赖坑项目说明里明确写了需要pyqt5但ultralytics不会自动带上它。这两个依赖必须存在同一个环境里。补装和验证命令如下pip install PyQt5 python -c from PyQt5.QtWidgets import QApplication; print(PyQt5 ok)逻辑说明PyQt5只负责界面侧不参与模型推理单独安装不会影响yolov8环境。验证命令与main.py解耦能明确分辨是PyQt5有问题还是项目代码有问题。如果是在无桌面显示器的Linux环境跑验证阶段可能看到qt.qpa.plugin相关报错这是缺少图形环境xcb库不是安装问题Windows桌面端一般不会遇到。参数说明PyQt5不需要刻意指定旧版本默认最新版即可。项目代码没有用到PyQt6的独有API不用画蛇添足装PyQt6。有经验的开发者会在这一步多花十秒把cv2、numpy、onnxruntime也一并验证掉python -c import cv2, numpy, onnxruntime; print(deps ok)这样你在执行main.py之前已经排除了四类最常见的依赖缺失。后面如果真的闪退判断范围就能从环境污染缩小到项目代码本身。2.3 切环境、切目录、再运行启动的三条死规矩官方使用步骤写得很简洁但两条执行细节是新人必踩的位置。完整启动命令是conda activate yolov8 cd 你解压源码包的目录 python main.py逻辑说明conda activate必须执行否则调用的是系统自带PythonPyQt5和onnxruntime大概率不在里面。cd进源码根目录是因为main.py里大概率用相对路径读取weights/yolov8n.onnx和test_img/的图片。终端工作目录不在根目录界面一启动就会因为找不到模型或资源而闪退。用PyCharm的话先在设置里把项目解释器切换到yolov8环境再运行main.py效果和命令行一致。启动后正常现象是PyQt5窗口弹出控制台打印模型路径、置信度阈值和一句加载完成。如果窗口有了但图片区域是黑的多半是test_img目录和当前工作路径对不上如果窗口根本没有优先回到2.2里的验证命令去判断环境。下面这个表格是解压后需要重点核对的核心文件路径对得上再启动路径作用main.pyPyQt5入口启动GUI与检测流程weights/yolov8n.onnxonnxruntime加载的模型weights/results.png训练的mAP、P、R曲线图test_img/推理测试图片目录有一点要提前说明这个模型跑的是onnx推理不要求你从头训练。所以整个第一步只是在确认“onnx能加载、图像能读、按钮能点”。做完这些后面的所有调参都有明确的判断基准。2.4 跑通后怎么确认模型真的在工作第一次点击“开始检测”界面上如果出现了机型标签和0.8以上的置信度数字说明模型加载、onnx推理、后处理、GUI绘制整条链路已经通了。这时可以手动做两个小实验验证链路稳定性换test_img目录下的另一张战斗机图片检查标签和置信度是否跟着变化。换一张自己找的战斗机图片看能否稳定检出。做这两个实验时建议把检测结果截图保存作为后面调参的对照基准。如果换了图片后置信度暴跌到0.4以下说明界面阈值设高了回GUI把置信度参数下调到0.25再试一次。3. 读懂yolov8n.onnx推理前必须搞明白的输入输出与后处理环境跑通之后下一件事是把onnx模型本身看透。很多项目把推理代码封在GUI里看起来是黑匣子其实核心逻辑就三步预处理、session.run、后处理。这一步搞懂后面换模型、改阈值、做部署都不会慌。3.1 为什么交付onnx而不交pytorch权重这个包只提供yolov8n.onnx不提供pytorch .pt权重。这不算缺失反而是更接近交付状态的配置。onnx模型不依赖torch就能被onnxruntime加载部署端环境里少掉一整个torch依赖栈体积和内存占用都更轻同时onnx格式可以和TensorRT、OpenVINO、RKNN这些加速工具直接对接后续跨平台迁移成本低。从复现角度看用onnx还有个好处网络结构和权重在导出那一刻就被固定不会因为ultralytics版本升级导致模型行为变化。对想快速跑通GUI演示的开发来说这意味着环境装好结果就可复现不需要关心训练端版本兼容问题。代价是你拿不到训练时的网络结构细节想继续训练得另找pytorch权重但这个包的定位本来就是“已有训练结果做推理与界面展示”。3.2 onnxruntime三步推理预处理、session.run、NMSyolov8n.onnx的输入张量一般是[1, 3, 640, 640]输出维度是[1, 4num_cls, 8400]。8400来自三个尺度特征图的锚点总数80×80 40×40 20×20。后处理要做的事情是把8400个锚点里低置信度的过滤掉再用NMS去掉重叠框。这类细粒度模型里的num_cls不是COCO的80类而是战斗机的类别数具体值可以用输出维度减4推算。一个可以直接复制使用的onnx推理脚本如下import cv2 import numpy as np import onnxruntime as ort session ort.InferenceSession(weights/yolov8n.onnx, providers[CPUExecutionProvider]) input_name session.get_inputs()[0].name print(输入名:, input_name, 输出维度:, session.get_outputs()[0].shape) def preprocess(img, size640): h, w img.shape[:2] scale min(size / h, size / w) new_w, new_h int(w * scale), int(h * scale) resized cv2.resize(img, (new_w, new_h)) canvas np.full((size, size, 3), 114, dtypenp.uint8) x_off (size - new_w) // 2 y_off (size - new_h) // 2 canvas[y_off:y_offnew_h, x_off:x_offnew_w] resized blob cv2.cvtColor(canvas, cv2.COLOR_BGR2RGB).transpose(2, 0, 1).astype(np.float32) / 255.0 return np.expand_dims(blob, 0), scale, x_off, y_off def postprocess(preds, conf_thres0.25, iou_thres0.45): preds np.squeeze(preds).T # (8400, 4num_cls) boxes, scores, classes [], [], [] for row in preds: cls_score row[4:].max() cls_id row[4:].argmax() if cls_score conf_thres: cx, cy, w, h row[:4] boxes.append([cx - w/2, cy - h/2, w, h]) scores.append(float(cls_score)) classes.append(int(cls_id)) idx cv2.dnn.NMSBoxes(boxes, scores, conf_thres, iou_thres) return idx, boxes, scores, classes img cv2.imread(test_img/firc_feiji_1043.jpg) blob, scale, x_off, y_off preprocess(img) preds session.run(None, {input_name: blob})[0] idx, boxes, scores, classes postprocess(preds, conf_thres0.4) print(输出形状:, preds.shape, 类别数:, preds.shape[1] - 4, 框数:, len(idx))逻辑说明letterbox保持原图比例后居中填充战斗机这种需要看细节的细粒度目标直接拉伸会让机翼比例变形识别效果打折。YOLOv8输出的是已经解码好的框坐标不需要像YOLOv5那样做grid和anchor解码直接转成(x1, y1, w, h)即可。后处理里用OpenCV自带的NMSBoxesCPU环境下足够快单个批次几十毫秒能处理完。参数说明letterbox填充值114是YOLO系列默认值不用纠结改成0也能跑只是置信度可能有微小波动。输入尺寸640×640要与模型导出时一致不要改成416或320除非你导出前就改过训练尺寸。providers参数显式写CPUExecutionProvider避免某些机器同时存在多个provider时自动选择失败。输入名不一定是images不同工具链导出的onnx命名可能不同先用session.get_inputs()[0].name取值最稳。3.3 用置信度阈值控制细粒度误检参数怎么定战斗机机型之间外形相似度高误检代价比漏检更大界面上标错型号比没检出来更伤演示效果。常见处理是把置信度阈值从0.25提到0.4左右这样视频流里每帧残留的低置信度框会明显变少。具体提多少可以拿weights/results.png里的P曲线做参考如果precision在0.4附近出现明显下降说明大部分候选框置信度集中在0.4以下界面阈值就不宜再往上提。批量评估时可写一个小循环遍历test_img目录统计每张图的平均置信度和平均耗时再反过来和results.png的mAP曲线对比能快速发现“模型部署后表现和训练指标差距大”的问题。两者如果差距超过5个百分点优先检查预处理是否和训练端一致最常见的差异就是letterbox尺寸和归一化方式。3.4 一个翻车实例不看输出维度直接写80类之前帮人排查过一段GUI代码界面能跑但所有检测框的标签都是同一个默认值。最后发现是代码里写死了80个COCO类名而战斗机型模型的输出维度是4num_clsnum_cls只有个位数。标签列表和输出维度对不上后处理阶段把类别ID映射到越界位置得到一个错误的类别。处理思路是先打印preds.shape用shape[1]-4初始化类名列表长度不匹配时直接报错不要静默给默认标签。这也是我建议第一次跑模型先输出shape再继续写业务代码的原因。后面你再把这段脚本接进PyQt5只会改输入来源和显示部分核心推理根本不用动。4. 把推理逻辑接到PyQt5界面布局、线程、输入源切换onnx推理跑通后剩下的工作就是把这段逻辑接进PyQt5界面。这里的难点不是PyQt5控件怎么用而是怎么让推理不阻塞界面、让不同输入源共用同一套检测流程。4.1 主窗口布局QLabel显示、按钮触发、文本回显main.py的界面一般由三部分组成中间显示图像的QLabel控制按钮以及右侧或下方的结果文本框。QLabel负责显示原图和检测框结果QPushButton触发“选择图片、开始检测、打开摄像头”动作QTextBrowser打印每帧推理耗时和标签信息比QLabel更适合打日志因为带滚动能力。from PyQt5.QtWidgets import QMainWindow, QLabel, QPushButton, QTextBrowser class MainWindow(QMainWindow): def __init__(self): super().__init__() self.image_label QLabel(待检测图像) self.detect_btn QPushButton(开始检测) self.result_text QTextBrowser() self.detect_btn.clicked.connect(self.on_detect) def on_detect(self): # 把当前图片路径送入检测线程不在这里做同步推理 pass逻辑说明image_label在检测完成后用QPixmap更新result_text用append方法追加文本天然适合记录多次检测结果。按钮clicked信号接上槽函数但槽函数内部不要直接调session.run原因见下一节。4.2 QThread异步推理不让界面卡死的唯一正确写法GUI项目里最容易翻车的就是把模型推理直接写在按钮的槽函数里。单张图片还好一旦接上摄像头或视频流界面会肉眼可见地卡顿点击按钮后整个窗口变白直到推理完成才恢复。正确做法是把推理放到QThread子线程主线程只负责界面刷新。import cv2 from PyQt5.QtCore import QThread, pyqtSignal class DetectWorker(QThread): finished pyqtSignal(object, object) # 图像, 检测结果 def __init__(self, session, img_path, conf_thres0.4): super().__init__() self.session session self.img_path img_path self.conf_thres conf_thres def run(self): img cv2.imread(self.img_path) blob, scale, x_off, y_off preprocess(img) preds self.session.run(None, {input_name: blob})[0] idx, boxes, scores, classes postprocess(preds, self.conf_thres) self.finished.emit(img, (idx, boxes, scores, classes))逻辑说明run方法里执行读取图片、预处理、推理、后处理整个过程不占用PyQt5主线程。结果通过finished信号传回主线程主线程负责画框和更新QLabel。session在构造函数里传入不要在子线程里重新加载onnx模型否则内存会随线程数量暴涨。参数说明在槽函数中new一个DetectWorker并start后要保存worker引用防止被Python垃圾回收导致线程中途丢失。摄像头场景建议做跳帧控制每两帧推理一次帧率会比逐帧推理稳定不少。cv2图像转成QPixmap显示是另一个容易写错的地方。cv2读出来是BGRQt默认要RGBfrom PyQt5.QtGui import QImage, QPixmap def cv2_to_qpixmap(cv_img): rgb cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB) h, w, ch rgb.shape bytes_per_line ch * w qimg QImage(rgb.data, w, h, bytes_per_line, QImage.Format_RGB888) return QPixmap.fromImage(qimg)这里有一点要注意rgb.data来源于numpy数组如果数组被垃圾回收QPixmap会变成悬空引用。常见做法是让cv_img在函数外部保留引用或者用copy()做一次深拷贝。4.3 输入源切换测试图片、摄像头、视频文件的统一处理思路界面通常要同时支持三种输入单张图片、摄像头、视频文件。常见做法是定义一个统一的process_frame方法把读帧、letterbox、推理、画框、显示做成一条流水线图片和摄像头只是帧来源不同。摄像头切换时有一个细节cv2.VideoCapture(0)打开默认摄像头如果打开失败不会抛异常而是返回一个capture对象第一帧read返回None。所以切换后要先判断frame是否为空再往下送否则onnx推理会拿None去跑。视频文件场景下还要考虑释放切换输入源之前release掉当前VideoCapture并设置一个flag让worker线程退出循环。这里还牵扯到BGR/RGB通道顺序。摄像头读出来的帧是BGR送进模型前要做一次cvtColor转RGB画框时又用回BGR帧最后显示前再转一次RGB。通道顺序错一位检测结果不受影响但画面颜色会异常下文避坑部分会展开。5. 避坑指南跑战斗机型识别系统最常见的五个坑这一章把实施里最容易出问题的五个点展开每条按现象、原因、解决来复盘。这些坑是我自己跑类似项目时踩过的也覆盖了这套包使用者遇到的大部分问题。5.1 启动期排错环境错位和权重路径找不到坑1环境错位导致ModuleNotFoundError现象在终端执行 python main.py报 ModuleNotFoundError: PyQt5 或 ModuleNotFoundError: onnxruntime。原因当前shell里pip命令指向的是base环境而代码要求的是yolov8环境。pip装了但没装进正在使用的环境。解决先 conda activate yolov8再执行 pip install PyQt5 和 pip install onnxruntime。装完执行 python -c import PyQt5, onnxruntime 验证两个包都在当前环境里缺哪个补哪个。坑2工作目录不对导致权重文件找不到现象启动后GUI一闪而过控制台提示FileNotFoundError: weights/yolov8n.onnx。原因main.py用相对路径找权重和图片而当前工作目录不在源码根目录。解决cd到项目根目录再启动。用IDE的话把Working directory设成源码根目录。如果还是找不到直接把weights/yolov8n.onnx的绝对路径写进main.py里的模型加载处这是最直接的兜底方案。5.2 推理期排错坐标还原和中文路径坑3检测框坐标错乱现象检测框出现在画面角落或者所有框堆在同一个点。原因letterbox预处理时记录了缩放scale和填充offset但后处理绘制时直接用了640×640坐标系坐标没有做还原换算。解决绘制前执行 x (x - x_off) / scaley同理宽高直接除以scale。代码里保留preprocess返回的scale、x_off、y_off三个值不要只拿blob进模型。坑4读取图片失败、界面黑屏现象cv2.imread返回NoneGUI里图片区域全黑。原因图片文件路径包含中文。Windows上OpenCV读取非英文字符路径经常失败这是cv2的已知限制。解决用np.fromfile读入字节再imdecode解码img cv2.imdecode(np.fromfile(img_path, dtypenp.uint8), cv2.IMREAD_COLOR)这条代码对中文路径和中文文件名都有效在Windows下做图像工具开发时可以直接当默认读取方式用。5.3 性能与显示期排错GPU没真正启用、颜色畸形坑5有显卡但onnx推理还是慢现象机器带独立显卡检测一张图仍要几百毫秒甚至更久和CPU推理速度没差别。原因onnxruntime默认使用CPUExecutionProvider没有显式开启GPU。装的是CPU版onnxruntime的情况下即使代码写了CUDAExecutionProvider也会报错。解决先卸载CPU版执行 pip install onnxruntime-gpu再在InferenceSession里显式指定ort.InferenceSession(model_path, providers[CUDAExecutionProvider, CPUExecutionProvider])providers列表的顺序代表优先级CUDA不可用时自动回退CPU这样代码在不同机器上都有可用的执行后端。另外有个常混淆的现象检测框正常但画面整体颜色泛蓝或泛红。这个不是模型问题而是cv2的BGR帧直接用QImage显示时没有转成RGB把字节格式当成RGB解析就会发生通道错位。解决办法就是4.2节里的cv2_to_qpixmap先cvtColor再构造QImage。6. 验证模型可用性而不是只看界面评估曲线、离线脚本、部署边界拿到这个包第一件事我会建议先跑一段离线脚本而不是直接开GUI。GUI看着能用不代表换一批图还能用。把test_img目录下所有图片批量跑一遍统计平均置信度再和weights/results.png里的指标对照这是最省事的验收方法。import glob import cv2 import numpy as np import onnxruntime as ort session ort.InferenceSession(weights/yolov8n.onnx, providers[CPUExecutionProvider]) input_name session.get_inputs()[0].name total_conf, total_time, count 0, 0, 0 for path in glob.glob(test_img/*.jpg): img cv2.imdecode(np.fromfile(path, dtypenp.uint8), cv2.IMREAD_COLOR) blob, scale, x_off, y_off preprocess(img) t0 cv2.getTickCount() preds session.run(None, {input_name: blob})[0] total_time (cv2.getTickCount() - t0) / cv2.getTickFrequency() idx, boxes, scores, classes postprocess(preds, conf_thres0.4) if len(idx) 0: total_conf max(scores[i] for i in idx.flatten()) count 1 print(平均置信度:, total_conf / max(count, 1), 平均推理耗时:, total_time / len(glob.glob(test_img/*.jpg)))批量结果的置信度要高但不需要迷信结果数值。更该关心的是它的变化趋势如果09张结果稳定在0.6以上说明模型的区分度在这个数据集上够用如果出现0.2、0.3的离群值换到真实场景时大概率会出漏检。results.png里四张曲线图我一般重点看mAP50和P0.5。P曲线代表“检出来的目标里多少是真正机型对的”R曲线代表“正样本里有多少被检出来”。细粒度识别最怕的是“框到了但类型错了”所以优先级是P高于R。mAP50在0.8以上界面阈值可以用0.25把召回拉满如果只有0.5建议阈值提到0.4甚至0.5只展示高置信度结果宁缺毋滥。部署边界也要说清。当前这个onnx在Windows上用onnxruntime跑得很顺但边缘设备场景不一样比如瑞芯微RK3588这类盒子模型最终要转成RKNN格式才能跑转模型的过程会重新做量化预处理参数也可能跟着变。onnx只是中间形态它给了这套GUI代码和边缘部署之间一条可行的迁移路径但不要指望同一个onnx直接塞进所有硬件都能跑。若要做int8量化也要先确认当前onnx的opset版本和量化工具兼容这个和GUI推理是两套独立工作。从那以后我每次拿到交付的onnx模型都会先做一件事不急着双击界面先跑一遍test_img全量离线推理这个习惯帮我躲过好几次“GUI看着能用换批图就崩”的尴尬。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站