☰
改进YOLOv8实现矿石煤炭图像分割:从注意力机制到部署全攻略
2026/9/26 2:31:07 网站建设 项目流程

简介:面向目标检测与YOLO改良方向的毕业设计和课程设计人群,这份资源以改进YOLOv8实现矿石煤炭图像分割为核心,完整覆盖从图像预处理、模型设计、训练测试到交互界面和结果评估的全流程。技术方案引入多尺度特征融合提升不同粒度检测效果,利用注意力机制强化矿岩特征识别,并通过超参数调整、数据增强和针对低照度、多分辨率环境的鲁棒性优化来提升泛化能力;代码包含train.py、val.py、predict.py与ui.py,其中ui.py提供简洁图形界面,可上传矿石煤炭图像并直接查看分割结果,支持注释输入以增强交互体验。压缩包共25个文件,含4个Python脚本、19张PNG可视化结果图、1个MD说明与1个DOCX报告文档,整体仅7.55MB,轻量便于快速部署。已有42人学习下载,配套文档不仅详细梳理了改进YOLOv8的技术思路与消融实验,还给出了精确率、召回率、F1分数等评估指标的计算和对比方法,适合希望系统掌握YOLOv8工程化应用和图像分割实战细节的开发者参考。

1. 矿石煤炭图像分割:这份改进YOLOv8资源到底解决了什么

你下载这份资源,多半是冲着毕业设计或者矿山智能化项目来的。把 YOLOv8 从目标检测改成图像分割,专门处理矿石、煤炭这类目标,这个方向本身就有很实际的落地诉求:传送带上的矿石粒度、煤矸石和原煤的边缘提取、块度统计,都需要像素级分割而不是一个框。直接拿官方 YOLOv8-seg 跑,露天矿和井下光照、粉尘、遮挡会让精度崩掉,所以资源里做的“改进”才是核心。它不是换一个更大的模型那么简单,而是针对矿石纹理、煤炭低对比度这类视觉特征去调网络结构。这套思路适合两类人:一是拿它做课题、写论文的学生,二是需要在低算力设备上做分割推理的现场工程师。

2. 从检测到分割:YOLOv8-seg 的选型逻辑与改进点落法

2.1 为什么要用 YOLOv8-seg 而不是检测头

YOLOv8 官方仓库里其实涵盖三类任务:detect、segment、pose。矿石煤炭分割系统用的是 segment,也就是 YOLOv8-seg。它的输出不是四个坐标加一个类别,而是每个目标对应一个二进制 mask。这个 mask 是检测头外加一个上采样分支得到的,检测头负责找出目标框,分割头负责在框内细化出轮廓。

为什么不能只做检测?因为矿石和煤炭在视觉上最大的痛点不是“有没有”,而是“边界在哪”。矿石边缘和背景岩石纹理相近,煤炭在暗光下几乎和阴影连成一片,检测框会框住大量背景,导致统计粒度时误差很大。分割能在像素级别把矿石和背景切开,输出的 mask 可以直接用于计算面积占比、周长估算、粒度分级,这是检测做不到的。

另一个现实原因是对算力的妥协。像 Mask R-CNN 这种经典分割模型,两阶段结构在嵌入式设备上跑不动,而 YOLOv8-seg 是单阶段设计,backbone 用 CSPDarknet 变体,分割分支是轻量的上采样加卷积。常见做法是直接把主干输出的特征图通过 concat 和上采样恢复到输入分辨率,相比 Mask R-CNN 少了一个 ROI Align 阶段,推理速度在 GTX 1660 Ti 这种级别显卡上能达到实时,在 RK3588 这类边缘 NPU 上也能勉强跑起来。这个特性对矿山现场非常重要,因为很多场景没有服务器,只有一台工控机或者边缘盒子。

资源里如果提到“改进 YOLOv8”,最常见的落点就是分割头的结构。头部默认的分割分支其实就是几个卷积加 upsample,对边缘细节不够敏感。我一般会改动的地方有两类:一类是在主干网络的最后一层或者 Neck 部分引入注意力机制,另一类是改分割头的上采样方式。前者影响的是特征提取质量,后者直接影响 mask 的锐利度。后面从第 3.2 节开始,我会说具体怎么改、改完以后效果差异在哪里。

2.2 改进点设计:注意力机制和分割头增强

改进 YOLOv8 分割模型,需要先明确一个问题:矿石和煤炭图像分割的主要误差来源是什么。我在实际测试中总结出三点:第一,矿石表面纹理和背景纹理过于相似,导致网络把背景像素误判成目标;第二,煤炭区域光照不足,目标边界和阴影混在一起,分割 mask 边缘会出现锯齿或空洞;第三,小目标矿石在深层特征图上信息丢失,mask 直接被漏掉。

针对这三点,最常见的改进是给模型加注意力机制。YOLOv8 原生的 backbone 里其实已经有一些类似 C2f 的跨阶段连接,但没有显式的通道注意力。很多改进版本会插入 CBAM、SE,或者热度比较高的协调注意力(Coordinate Attention)。协调注意力的价值是它对位置信息更敏感,能同时编码通道和空间位置,这样对边缘定位有帮助。如果你的资源包里恰好带了注意力模块的代码,重点关注它的插入位置:一般插在 backbone 最后一个 stage 之后,或者 Neck 的 PANet 结构之前。

分割头增强这块,原版 YOLOv8-seg 的上采样次数有限,如果我们把输入分辨率 640x640 经过五次下采样,特征是 20x20,要恢复到 160x160 的 mask,需要三次上采样。原版使用最简单的 nearest 上采样加卷积,我一般会替换成可学习的上采样方式,比如转置卷积或者 CARAFE。简单来说,用 nearest 得到的 mask 边缘会有马赛克感,改用可学习上采样以后,mask 边缘的连续性会好很多。如果你的资源里带的是这种改动,通常会在 head 文件的 YOLOv8SegHead 类里看到额外的 ConvTranspose2d 层。这个知识点在后面看代码时特别重要,不然翻半天都不知道改了哪里。

3. 数据集准备:LabelMe 标注与 YOLO 格式转换的完整链路

3.1 矿石和煤炭的标注规范

分割模型的精度上限,由标注决定。说得直白一点,你给网络看什么,它就只能学什么。矿石、煤炭的标注,我不建议直接用矩形框去框,那样做还不如用检测模型。既然走分割,就必须标出轮廓。最常用的工具是 LabelMe,因为 YOLOv8 官方的分割训练格式要求是每个目标的轮廓点坐标,而 LabelMe 导出的 JSON 恰恰是每个多边形顶点坐标,转换起来最直接。

标注矿石和煤炭时要特别注意细节:不要把所有矿石圈成一个整体,而是逐个矿石画多边形;边界模糊的地方宁可多标一点,也不要漏标,因为漏标会把矿石标记为背景,模型之后学出来的 mask 会偏小。煤炭图像在暗光下边界看不清时,建议把图像亮度调高再标,不然标注的轮廓会严重偏离真实边缘,这个我在实际项目里吃过亏。标注完一个类别时,类别名建议直接叫 ore 和 coal,不要用中文,主要是省得之后转格式时字符编码出问题。

标注文件组织推荐这样:

dataset/ ├── images/ │ ├── train/ │ ├── val/ ├── labels/ │ ├── train/ │ ├── val/ └── json/ ├── train/ ├── val/

这个结构是工程上比较标准的分割标注目录。images 放原始图片,labels 放转换后的 YOLO 格式文本,json 放 LabelMe 原始标注文件。有个细节:LabelMe 在保存时会把图片路径写进 JSON 里,如果之后移动了目录地址,路径就失效了。建议在标注前先把图片统一放到一个固定目录,之后转的时候直接按文件名匹配,不要依赖 JSON 里的 imagePath 字段。

3.2 LabelMe 标注转 YOLO 格式:坐标归一化脚本

YOLOv8-seg 训练需要的是这种格式的标签文件:每行一个目标,第一个数字是类别 id,后面跟着一串归一化后的轮廓点坐标。注意是 x y 交替排列,而且是相对于图片宽高的比例值。LabelMe 的标注是像素坐标,需要做换算。下面这个脚本我一般直接放在 dataset 目录下执行。

import os import cv2 import json import glob def labelme_to_yolo_seg(json_path, out_label_path, img_dir, classes): with open(json_path, 'r', encoding='utf-8') as f: data = json.load(f) img_name = os.path.basename(data['imagePath']) img_path = os.path.join(img_dir, img_name) img = cv2.imread(img_path) if img is None: print(f'[WARN] image not found: {img_path}') return h, w = img.shape[:2] lines = [] for shape in data['shapes']: label = shape['label'].strip() if label not in classes: # 跳过未在类别配置里出现的标注 print(f'[INFO] skip unknown label: {label}') continue class_id = classes.index(label) points = shape['points'] if len(points) < 3: # 少于3个点无法构成多边形,丢弃 print(f'[WARN] polygon too small: {label}') continue norm_points = [] for pt in points: x_norm = pt[0] / w y_norm = pt[1] / h norm_points.append(f'{x_norm:.6f}') norm_points.append(f'{y_norm:.6f}') line = str(class_id) + ' ' + ' '.join(norm_points) lines.append(line) if lines: with open(out_label_path, 'w', encoding='utf-8') as f: f.write('\n'.join(lines)) print(f'[OK] {out_label_path}') else: print(f'[WARN] no valid labels: {json_path}') classes = ['ore', 'coal'] json_dir = 'json/train' label_out_dir = 'labels/train' img_dir = 'images/train' os.makedirs(label_out_dir, exist_ok=True) for json_file in glob.glob(os.path.join(json_dir, '*.json')): base_name = os.path.basename(json_file).replace('.json', '.txt') out_label = os.path.join(label_out_dir, base_name) labelme_to_yolo_seg(json_file, out_label, img_dir, classes)

代码我加了几个保护分支,不是多余的。第一是图片路径找不到时直接跳过,避免之后训练时报 FileNotFoundError;第二是少于三个点的多边形直接删掉,因为 YOLO 格式要求多边形至少三个点;第三是类别名不在配置里的直接跳过,防止标了无关类别导致训练崩溃。转换完成后建议抽样检查几个 txt 文件,坐标值最大不应该超过 1.0,如果出现大于 1.0 的坐标,一定是图片尺寸读取有误或者标注坐标超出图像边界,要回头查 JSON 里 points 的数值范围。

如果你的资源包自带的是 COCO 格式数据,转换思路是一样的,只不过 COCO 的 annotation 里存的是 mask 的 RLE 编码或者 polygon 坐标,需要从 COCO JSON 里解析出来再归一化。流程上多一步:读 annotations 数组、按 image_id 关联图片、解析 segmentation 字段。这一步最容易踩的坑是 COCO 的 segmentation 可能是 list 也可能 RLE,必须用 pycocotools 去统一解析,用哪个版本的 pycocotools 都会影响效率,建议直接用官方库不要手写解析。

3.3 数据集划分与类别配置文件

数据划分建议用脚本一次性完成,不要手动拖文件。手动操作最大的问题是容易漏掉对应的标签文件,比如 image 移过去了,label 没同步。下面这段代码不仅划分数据,还会校验图片和标签是否一一对应。

import os import random import shutil img_src = 'raw_images' label_src = 'raw_labels' train_img_dir = 'images/train' val_img_dir = 'images/val' train_label_dir = 'labels/train' val_label_dir = 'labels/val' random.seed(42) imgs = os.listdir(img_src) imgs = [i for i in imgs if i.endswith('.jpg') or i.endswith('.png')] random.shuffle(imgs) val_num = max(1, int(len(imgs) * 0.2)) val_imgs = imgs[:val_num] train_imgs = imgs[val_num:] for d in [train_img_dir, val_img_dir, train_label_dir, val_label_dir]: os.makedirs(d, exist_ok=True) for img in train_imgs: base = os.path.splitext(img)[0] label_path = os.path.join(label_src, base + '.txt') if not os.path.exists(label_path): print(f'[WARN] missing label for {img}, skipped') continue shutil.copy(os.path.join(img_src, img), train_img_dir) shutil.copy(label_path, train_label_dir) for img in val_imgs: base = os.path.splitext(img)[0] label_path = os.path.join(label_src, base + '.txt') if not os.path.exists(label_path): print(f'[WARN] missing label for {img}, skipped') continue shutil.copy(os.path.join(img_src, img), val_img_dir) shutil.copy(label_path, val_label_dir) print(f'train: {len(train_imgs)}, val: {len(val_imgs)}')

这段代码的核心价值在于它会自动校验标签存在性。实际项目里最普遍的问题就是 label 缺失或者命名后缀不一致。图片是 .jpg,标签却生成成了 .png.txt,一旦没对应上,训练时 ultralytics 会直接跳过这张图片,数据量就白白少了。所以划分之后最好打印一下实际数量,对比原始总量,如果小于总量说明有数据被跳过,回去检查命名。

类别文件的配置在 ultralytics 里就是一个 yaml 文件。我习惯放在项目的 dataset.yaml 里:

path: ./dataset train: images/train val: images/val names: 0: ore 1: coal

这里有个比较容易犯的错:path 字段如果写绝对路径,换一台机器就必须改;写相对路径的话,yaml 文件所在位置和 dataset 目录的相对关系要对。names 必须从 0 开始,顺序和刚才转换脚本里的 classes 保持一致,不然类别就错位了。比如说 classes 里 ore 是 0,coal 是 1,那 yaml 里这就是固定死的,不能按字母序调换。

4. 训练与调参:损失函数曲线、超参组合与改进模块的接入位置

4.1 训练命令与关键参数说明

切换进你的虚拟环境,训练命令的常见写法是下面这段。如果你的机器是 CPU 版本,环境搭建的时候把 torch 装成 CPU 版就能跑,只是速度慢一些;有 GTX 1660 Ti 这个级别的显卡,显存 6GB,训练一批 640x640 的图,batch size 开到 8 问题不大。下面以 bash 命令为例:

yolo segment train \ model=yolov8s-seg.pt \ data=dataset.yaml \ epochs=100 \ imgsz=640 \ batch=8 \ device=0 \ patience=20 \ project=run/segment \ name=ore_coal_exp

命令里的 model 指定预训练权重。不一定要用 s 版本,如果显存只有 4GB,建议用 yolov8n-seg.pt 起步;如果精度不满意再升级到 s。data 指向刚才写的 dataset.yaml。batch 和显存成反比,显存不够时会报 CUDA out of memory,先用 4 测试。device=0 是 GPU,填 cpu 则跑 CPU。patience 是早停参数,20 表示验证集指标连续 20 轮不提升就停,这是防止后期过拟合的机制。

训练过程中,epochs 不是越大越好。矿石煤炭数据量如果只有一千张,100 轮足够,再多就会把背景纹理也学进去。还有一个参数是 cache,如果你想加速读取,可以加 cache=True,它会先把图像加载进显存或者内存。如果显存不够,建议加 cache=ram,用内存缓存图像,同样能提升速度但占用内存。我一般不动 cache,除非训练时磁盘 IO 成了瓶颈。

4.2 损失函数曲线与实际效果解读

训练完成后,项目目录下会生成 results.csv 和各张曲线图。先看 box_loss、seg_loss、cls_loss 三条训练曲线。最简单有效的判断标准:训练 loss 应该平滑下降,如果出现断崖式下降再回升,多半是学习率设置过高或者数据标注有问题。验证集的 loss 如果先降后升,是过拟合的典型信号,说明网络开始背训练集了,这个时候调大数据增强参数比调模型结构更有效。

分割任务重点关注 val_seg_loss。如果 val_seg_loss 降不下去,原因通常集中在三个地方:标注边界不精细、类别样本不平衡、上采样分支细节恢复能力不够。第一个原因需要人工抽检标签,第二个原因计算一下两个类别的目标数量占比,如果 coal 太少,用权重采样或者增加该类别的数据增强;第三个原因就需要用到我第 2.2 节说的改进上采样方式。

mAP 指标的判断要结合应用场景。一般分割任务的 mAP50 能到 0.85 以上在矿山现场已经算可用状态,mAP50-95 反而不是重点,因为矿石这种目标在像素级交并比上很难达到非常高的水平,边缘稍有偏差就把 IoU 拉下来了。所以评估时先看 mAP50,再看 mask 的视觉效果。

4.3 改进模块的接入:注意力与分割头修改实操

如果你的资源带有改动过的模型文件,或者你想自己动手改进,核心操作是改 ultralytics 的 nn 模块。以协调注意力为例,插入位置一般在 C2f 模块的输出后。假设你的协调注意力模块写在 new_modules.py 里:

import torch import torch.nn as nn class CoordAtt(nn.Module): def __init__(self, inp, oup, reduction=32): super().__init__() self.pool_h = nn.AdaptiveAvgPool2d((None, 1)) self.pool_w = nn.AdaptiveAvgPool2d((1, None)) mip = max(8, inp // reduction) self.conv1 = nn.Conv2d(inp, mip, kernel_size=1, stride=1, padding=0) self.bn1 = nn.BatchNorm2d(mip) self.act = nn.ReLU() self.conv_h = nn.Conv2d(mip, oup, kernel_size=1, stride=1, padding=0) self.conv_w = nn.Conv2d(mip, oup, kernel_size=1, stride=1, padding=0) def forward(self, x): identity = x n, c, h, w = x.size() x_h = self.pool_h(x) x_w = self.pool_w(x).permute(0, 1, 3, 2) y = torch.cat([x_h, x_w], dim=2) y = self.conv1(y) y = self.bn1(y) y = self.act(y) x_h, x_w = torch.split(y, [h, w], dim=2) x_w = x_w.permute(0, 1, 3, 2) a_h = self.conv_h(x_h).sigmoid() a_w = self.conv_w(x_w).sigmoid() return identity * a_w * a_h

这是标准 Coordinate Attention 的 PyTorch 实现。它首先对特征图分别在高度和宽度方向做全局池化,这样能把沿着一个方向的空间信息压缩成通道描述符,另一个方向保持空间位置,最终通过 sigmoid 生成注意力权重,作用到原始特征上。对矿石煤炭这种细长边缘目标有不错的增益。如果你决定使用,核心是注意 pool_h 的 output size,H 方向上保留高度维度,W 方向上保留宽度维度,不能简单用 AdaptiveAvgPool2d 输出 (1,1),那样就把位置信息全部丢掉了。

接进 YOLOv8 的方式是改写 model yaml 文件,把主干最后一个 C2f 换成带注意力的变体,或者在需要的位置插入这个模块。最省事的做法是在 yaml 里加一层自定义结构,注册到 ultralytics 的 nn.tasks 里。这里要注意注册名字和 yaml 里的 type 字段必须一致,不然会报 module not found。

5. YOLOv8 矿石煤炭训练避坑:六个高频翻车点与排查记录

5.1 标签序号错位引发训练异常

训练跑起来之后,前几个 epoch 的 loss 异常大,比如到 3.0 以上,而且降不下来。检查训练样本,发现很多标签的第一列数字和图片内容不一致,比如图片里全是煤,标签类别却是 0(ore)。这个问题的本质原因是 classes 列表顺序和 yaml names 顺序不一致。我遇到过转换脚本里 classes 是 ['ore', 'coal'],yaml 里图省事写成 numbers: {0: coal, 1: ore},结果类别全部交换。解决的方法很基础但有效:随机抽五张训练图片,把 txt 标签里的第一个数字和图片内容人工对应一遍,确认无误再放进去训练。

5.2 显存不足导致训练中断

batch=8、imgsz=640 的情况下,6GB 显存一到 20 个 epoch 就报 CUDA out of memory。这个现象其实不是代码问题,是显存峰值出现在前向传播和损失计算阶段。我一般的解决路径很简单,先看 batch 能不能降到 4,不行就 imgsz 降到 512。如果降到 512 精度又不够,就用梯度累积。ultralytics 里没有直接提供累积参数,常见做法是在训练循环外面包一层手动累积,或者直接换更小的模型。对于分割任务,n 模型在 512 分辨率下还是能保住基本轮廓的。提示:训练分割模型的过程中,显存的占用比检测模型高不少,因为 mask 的标签是一个高维张量,显存预算按检测模型的 1.5 倍估算比较稳。

5.3 mask 边缘粗糙像锯齿

训练结束,验证集输出图里 mask 的边界从远处看是轮廓,放大后锯齿非常明显。这个通常有两种情况:一是训练分辨率太低,模型根本没学过细边缘生成;二是分割头上采样方式不对,nearest 上采样产生的块状效应。解决方法是训练时用 imgsz=640 起步,不要为了迁就显存一直用 416。如果硬件不允许,就考虑改上采样层结构,把 nearest 换成双线性或转置卷积。很多资源里的“改进”其实就落在这里,改了之后边缘会明显顺滑。

5.4 模型预测时漏检小矿石

验证集 mAP50 很高,但在实际图片里小矿石经常只出来半边 mask 或者直接漏掉。根本原因有两个:一是训练时小目标数量少,网络没学到;二是网络步幅过大,10x10 的矿石在深层特征图上已经退化到不足一个像素。我一般在数据层面先做处理,把包含小目标较多的图片截取成 patch 作为额外样本,或者使用 mosaic 增强时调小缩放范围。如果你的数据里小目标占比超过百分之二十,这一步基本是必须做的。另一方面可以在检测头增加一个浅层 P2 输出层,但成本高,推理速度会慢。先试数据增强,不行再动结构。

5.5 val 集 mAP 波动很大

每次验证,mAP 在 0.7 到 0.9 之间跳,不稳定。这种现象在数据总量低于 800 张时很常见。验证集太小,随机采样偏差会放大。我一般加大验证集比例到 30%,或者直接采用 K 折交叉验证,用五折的平均值作为最终指标。虽然麻烦,但写论文时这个数据比单次结果抗质疑。如果你的项目时间紧,最简单的折中方案是固定随机种子,保证实验之间可比性,然后报告中写多次实验的平均值。

5.6 背景像素被分割成目标

煤炭图像里阴影区域被误判成煤,导致 mask 面积虚大。核心原因是训练数据里煤炭样本和阴影的亮度特征过于接近。我处理这个问题的有效手段是加数据增强,比如随机调整亮度对比度、添加高斯噪声、模拟阴影遮挡。还有一招是给 loss 加权重,让背景区域的惩罚更高,但实际操作需要改损失函数代码。推荐先做增强,改动小,效果也直观。我在自己的项目里试过把曝光度调低 20% 做增强,阴影误检立刻降低,代价是平坦煤炭区域偶尔漏检,需要权衡。

6. 验证到部署验证:导出 ONNX 并跑通 CPU 推理

训练完的模型还在 PyTorch 权重文件里,直接拿去部署不现实。我习惯做两步验证:第一步导出 ONNX,在 CPU 上用 onnxruntime 跑一遍,确认模型结构和算子兼容;第二步再用导出的模型去预测几张没见过的现场图,观察 mask 效果。这个流程能一次性发现很多在 PyTorch 环境里发现不了的问题。

yolo export model=run/segment/ore_coal_exp/weights/best.pt format=onnx opset=12 dynamic=False imgsz=640

导出命令里 format=onnx 是目标格式,opset=12 控制算子集的兼容性,imgsz=640 必须和训练分辨率一致,dynamic=False 表示输入尺寸固定,这样在 CPU 推理时更稳定。如果导出时报某个算子不支持,最常见的是 torch 版本和 onnx 版本不匹配,先升级 onnx 和 onnxruntime。导出完成后会在 best.pt 同目录下生成 best.onnx。

接着用 onnxruntime 在纯 CPU 环境推理:

import onnxruntime as ort import cv2 import numpy as np sess = ort.InferenceSession('best.onnx', providers=['CPUExecutionProvider']) input_name = sess.get_inputs()[0].name img = cv2.imread('test.jpg') img = cv2.resize(img, (640, 640)) img = img[:, :, ::-1].transpose(2, 0, 1) # BGR to RGB, HWC to CHW img = np.ascontiguousarray(img) img = img.astype(np.float32) / 255.0 img = np.expand_dims(img, axis=0) outputs = sess.run(None, {input_name: img}) # outputs 里第一个是检测框和类别,后面的是 mask 系数 # 具体索引取决于导出的输出顺序,一般最后一个对应原型 mask print(outputs[0].shape, outputs[1].shape)

这段代码的价值在于验证 ONNX 模型的输出维度。输出张量包含检测框和 mask 系数,需要和原型 mask 做矩阵乘才能还原出最终 mask,这个后处理在 ultralytics 源码里叫 postprocess。这里我用的是从 YOLOv8 导出 ONNX 的标准流程,实际资源里的后处理脚本可能已经写好了,如果发现 mask 输出和你预期的不一致,先确认输出名称,用 sess.get_outputs() 打印出来对照。CPU 推理 640x640 单张图在普通笔记本上大概几百毫秒,如果现场要求实时,只能上 GPU 或者 NPU。

这个环节最值得说的经验是:不要直接拿 best.pt 去现场部署,先跑一遍 ONNX 导出。我接手过不少项目,PyTorch 环境下跑得好好的模型,转成 ONNX 之后因为某个算子不支持,数字全变了。从那以后我每次跑完训练,都强制自己导出 ONNX 并检查三个输出的尺寸和数值范围,确认没问题才敢说是“能部署”。这一步做完,你再把模型接到 RK3588 或工控机上的推理脚本里,基本就顺滑了。希望这份资源和这篇拆解能帮你在矿石煤炭分割项目上少走一段弯路。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询