
前阵子手上接了个边缘设备视觉检测的需求要把目标检测模型从服务器端搬到嵌入式板子上跑。研究了一圈最终选了 YOLOv8 训练、瑞芯微 RK3588 平台配合 RKNN 工具链做推理落地。这条链路走下来踩了不少坑也攒了不少经验。这套流程我会拆成几篇文章来讲第一篇先解决最基础的部分环境搭建、数据集准备和模型训练。只有这一步走稳了后面的 RKNN 转换和板端部署才有得玩。要说清楚这套流程的适用对象手里有或者准备入手 RK3588 系列开发板RK3588S、RK3588J 同理打算在板子上跑 YOLOv8 目标检测但又没接触过 RKNN 工具链的朋友。如果你习惯于在 GPU 服务器上训练从没碰过嵌入式部署这篇文章也很适合做切入点。我默认你的训练机器是 Ubuntu 系统因为后面 RKNN-Toolkit2 在 Linux 下最省心Windows 虽然也能用但是幺蛾子多。1. 为什么是 YOLOv8 RK3588先搞清楚这套组合的定位动手之前我建议先想明白一个事为什么要选 YOLOv8为什么要选 RK3588很多人的思路是别人用这个我也用结果后面转换模型时各种踩坑才开始后悔当初没多花点时间做选型。1.1 RK3588 的算力画像与部署场景RK3588 瑞芯微的旗舰 SoC8 核 CPU4×A76 4×A55集成了 6 TOPS 算力的 NPUINT8 精度。这个算力水平在国产边缘设备里算比较能打的关键是对比 NVIDIA Jetson 系列RK3588 在成本上有明显优势。Jetson Orin Nano 8GB 的板子要两千多二手都要一千七八而 RK3588 的核心板整套下来几百块就能搞定视具体配置这对做量产设备的团队来说是个很大的考虑因素。6 TOPS 的算力能跑什么模型以 YOLOv8 为例实测下来模型精度选 INT8 量化后的 YOLOv8s输入分辨率 640×640在 RK3588 的 NPU 上能跑到 30 FPS 左右换成 YOLOv8n 能到 60 FPS 以上实时性完全够用。这个性能覆盖了工业质检、安防监控、农业巡检、交通流量统计等绝大多数边缘检测场景。1.2 YOLOv8 相比其他版本在端侧部署上的优势YOLOv8 是 Ultralytics 在 2023 年推出的版本相比 YOLOv5 做了几个关键改动Anchor-Free 检测头去掉 anchor 机制减少后处理复杂度、C2f 模块替代 C3增强梯度流、Decoupled Head解耦分类与回归分支。这些改动让模型精度和收敛速度都有提升。那为什么不用更新的 YOLOv9、YOLOv10、YOLOv11说实话我试过几个在 RKNN 工具链上的支持成熟度是关键问题。YOLOv8 的结构相对规整导出 ONNX 时基本不用改代码就能转YOLOv9 用了可逆分支结构YOLOv10 去掉了 NMS 但引入了 one-to-many 头这些在 RKNN 转换时都可能出现算子不支持或需要额外处理的情况。对部署来说稳定能跑比理论精度高重要得多。YOLOv8 在 RKNN 生态里几乎是标准答案参考资料多、踩坑记录全出了问题基本能搜到解决方案。1.3 整套流程的技术链路概览整个部署链路可以概括为四个阶段阶段一在 PCGPU上进行 YOLOv8 模型训练得到 PyTorch 权重.pt阶段二将模型导出为 ONNX 格式做算子检查和优化阶段三使用 RKNN-Toolkit2 将 ONNX 转换为 RKNN 格式同时做 INT8 量化阶段四在 RK3588 板端使用 RKNN RuntimeLite加载模型完成推理本文对应的是阶段一也是整个链条的地基。后续文章会详细展开阶段二到四的操作细节。这里先给一个整体图景让你清楚自己在哪个位置。2. 训练环境搭建动手前的版本匹配少走三天弯路环境搭建这个环节看别人教程总觉得简单自己做的时候才会发现版本兼容性就是个坑。我印象最深的一次踩坑Ubuntu 系统装好了NVIDIA 驱动也装好了结果因为 CUDA 版本和 PyTorch 不匹配import torch 之后 GPU 根本不可用搞了一整天。2.1 硬件与系统选型别小看这个基础先说硬件。训练 YOLOv8 不一定需要多高端的显卡但最好不要用 CPU 硬扛——YOLOv8s 在 CPU 上训练一个 epoch 要几分钟到十几分钟一百个 epoch 就是十几个小时甚至更久。实测下来一张 8GB 显存的显卡比如 RTX 3060Ti、RTX 3070就能很舒服地训练 YOLOv8sbatch size 开到 16 毫无压力如果只有 6GB 显存YOLOv8n 也能跑只是 batch 会小一些。系统方面推荐 Ubuntu 20.04 或 22.04。这里有个很实际的原因瑞芯微官方的 RKNN-Toolkit2 在 Ubuntu 18.04/20.04/22.04 上都有完善的安装支持但 20.04 的案例最多、最稳。后面转换模型时你会发现折腾系统的成本远比安装几个包要高。2.2 CUDA、PyTorch、Python 的版本匹配这是环境搭建的核心也是最容易出问题的地方。PyTorch 官方安装命令里通常会标注对应的 CUDA 版本而 CUDA 版本又受限于 NVIDIA 驱动的版本号。驱动版本和 CUDA 版本的关系是驱动向下兼容高版本的驱动支持低版本的 CUDA。我的建议是直接按这个组合来配组件推荐版本原因操作系统Ubuntu 20.04RKNN-Toolkit2 支持最完善Python3.9兼容 PyTorch 2.x 和 RKNN-Toolkit2CUDA11.8PyTorch 和 TensorRT 支持稳定PyTorch2.0.1与 CUDA 11.8 匹配bug 修复充分显卡驱动≥ 525支持 CUDA 11.8安装驱动有两种路径一是通过 Ubuntu 的软件和更新里的附加驱动选项卡选专有驱动适合新手和日常使用二是从 NVIDIA 官网下 .run 文件安装适合对版本有精确要求的情况。我推荐第一种简单可靠一般不会把系统搞坏。装完驱动后执行nvidia-smi能看到显卡型号和驱动版本就说明驱动 OK。CUDA 装的时候有个容易忽略的细节不要用apt install cuda因为系统源里的版本可能和你 PyTorch 需要的对不上。建议直接从 NVIDIA 官网下载 CUDA Toolkit 11.8 的 runfile只装 Toolkit 组件不装驱动驱动已经装过了也不装 Samples。2.3 conda 虚拟环境创建与验证Python 环境管理这块在原版系统 Python 里直接 pip 装包会把系统环境搞得很乱。用 conda 或 virtualenv 都行我更推荐 Miniconda因为它体积小、创建环境快而且后面的 RKNN-Toolkit2 在 conda 环境里安装最顺。# 安装 Miniconda官网下载对应版本或直接用命令 wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh bash Miniconda3-latest-Linux-x86_64.sh # 创建 YOLOv8 训练环境 conda create -n yolov8 python3.9 -y conda activate yolov8 # 安装 PyTorch注意指定 CUDA 版本用清华源或官方源 pip install torch2.0.1 torchvision0.15.2 --index-url https://download.pytorch.org/whl/cu118 # 安装 ultralyticsYOLOv8 的官方训练库 pip install ultralytics # 验证 GPU 可用性 python -c import torch; print(torch.cuda.is_available()); print(torch.__version__)如果上面最后一步输出True说明你已经在用 GPU 训练了。注意torch.cuda.is_available()返回 False 的原因排查顺序驱动是否正常nvidia-smi→ CUDA 版本是否匹配nvidia-smi 显示的最高 CUDA 版本要 ≥ PyTorch 需要的→ PyTorch 是否是 cu118 版本而不是默认的 CPU 版本。到这里训练环境就算搭完了。但我建议先别急着训练顺手安装一下后面一定会用到的 RKNN-Toolkit2这样后面转换模型时不会手忙脚乱。# 在另一个 conda 环境安装 RKNN-Toolkit2避免污染训练环境 conda create -n rknn python3.9 -y conda activate rknn pip install --user rknn-toolkit22.0.0b0 -i http://pypi.douban.com/simple --trusted-host pypi.douban.com这里有个经验训练环境和 RKNN 转换环境最好分离。因为训练环境可能需要频繁升级PyTorch 版本更新会影响模型训练行为而 RKNN-Toolkit2 对环境依赖比较敏感升级某个包可能导致它直接罢工。分开之后各自维护互不干扰。3. 数据集准备标注格式转换细节决定训练上限数据集的准备是最花时间、也最容易被低估的一个环节。很多初学者第一次接触都是随便找些图片标注跑出来的模型精度一塌糊涂还以为是训练参数的问题其实问题出在数据本身。3.1 数据采集与清洗的规范先说采集。训练数据要尽量贴近实际部署场景光线条件、拍摄角度、目标尺寸范围、背景复杂度这些都要覆盖到。如果部署场景是厂区白天的监控画面你拿网上下载的夜景图片来训练效果一定好不到哪儿去。我见过一个里项目客户提供的视频素材是静止机位拍的训练结果在相似机位下很好用换个角度直接废了——这是数据分布没对齐的典型表现。采集之后是清洗这一步很容易被忽略。主要做三件事剔除严重模糊、过曝、遮挡目标的图片这类图片会让模型学到错误的特征删除重复图片避免训练集和验证集里出现同一张图片否则评估结果虚高检查图片分辨率建议统一到 640×640 或相近尺寸YOLOv8 默认输入就是 640图像太大或太小都会影响训练效率区分训练集和验证集也有讲究。很多人写个脚本把所有图片打乱后按比例随机分成两份这个做法在大部分场景没什么问题但如果数据是从视频里抽帧来的同一条视频前后相邻的帧内容高度相似会导致验证集的指标虚高。正确的做法是按视频或按场景分组划分比如一段视频的帧全部归入训练集另一段视频的帧全部归入验证集这样验证指标才真实反映模型的泛化能力。3.2 labelme 标注实操与格式理解标注工具方面LabelImg 是老牌工具但它是基于 Python 2 的在 Ubuntu 20.04 上安装比较麻烦。labelme 是更现代的选择pip 直接装就能用标注结果保存为 JSON 文件。pip install labelme labelmelabelme 的界面操作虽然简单但有两个容易忽略的设置一是打开图片前先在 Edit 菜单里选择 Create Polygons 而非 Create Rectangles 或 Create Circle因为 YOLOv8 的标注只需要矩形框bounding box二是标注完每个框之后会弹出对话框让你填写类别名一定要提前定义好类别列表避免同一个类别出现两种写法比如 person 和 Person 是两种情况。labelme 产出的 JSON 文件内容大致是{ version: 5.2.1, flags: {}, shapes: [ { label: cat, points: [[132.0, 48.0], [318.0, 320.0]], group_id: null, shape_type: rectangle, flags: {} }, { label: dog, points: [[190.0, 210.0], [400.0, 480.0]], group_id: null, shape_type: rectangle, flags: {} } ], imagePath: cat_dog.jpg, imageData: null }这个格式里points是两个顶点坐标左上角和右下角。这个 JSON 文件不能直接被 YOLOv8 使用YOLO 格式要求的是每个目标一行格式为类别ID x_center y_center width height其中 x_center、y_center、width、height 都是归一化到 0~1 的浮点数相对于图片宽度和高度。3.3 格式转换脚本JSON 到 YOLO txt 的每一步这个转换脚本我通常直接写不依赖第三方库因为逻辑很简单自己写反而最可控。脚本的核心逻辑是读取 labelme JSON把像素坐标换算为归一化坐标。import json import os # 类别映射表注意 ID 从 0 开始 CLASS_MAP {cat: 0, dog: 1} def convert_labelme_json(json_path, txt_path, image_width, image_height): with open(json_path, r, encodingutf-8) as f: data json.load(f) lines [] for shape in data[shapes]: label shape[label] if label not in CLASS_MAP: continue # 矩形框的两个顶点 pt1, pt2 shape[points] x1, y1 pt1 x2, y2 pt2 # 确保 x1x2, y1y2有些工具标注时可能反过来 x_left min(x1, x2) x_right max(x1, x2) y_top min(y1, y2) y_bottom max(y1, y2) # 计算中心点、宽度、高度并归一化 box_width x_right - x_left box_height y_bottom - y_top x_center x_left box_width / 2.0 y_center y_top box_height / 2.0 x_center_n x_center / image_width y_center_n y_center / image_height width_n box_width / image_width height_n box_height / image_height lines.append(f{CLASS_MAP[label]} {x_center_n:.6f} {y_center_n:.6f} {width_n:.6f} {height_n:.6f}) with open(txt_path, w) as f: f.write(\n.join(lines) \n) print(fConverted: {json_path} - {txt_path})关于图片尺寸脚本里的image_width和image_height必须要和实际图片保持一致。我一度因为偷懒直接在脚本里写死尺寸换了一批图片之后转换出的 txt 坐标全部错位训练出来模型对目标框的定位完全乱掉。还有一类坐标要注意labelme 的points用的坐标系原点在图片左上角X 轴向右Y 轴向下。如果你的图片有 EXIF 旋转信息labelme 读取时可能会自动旋转显示但保存的 JSON 坐标仍然是原图坐标系这会导致标注框和图片内容不一致。处理方式是先用脚本把所有图片统一转为无 EXIF 旋转的 JPEG再开始标注。3.4 数据集划分不要忽略时序相关性转换完格式后就到了数据集划分这一步。YOLOv8 需要的数据目录结构如下datasets/ my_data/ images/ train/ img_0001.jpg img_0002.jpg val/ img_0101.jpg img_0102.jpg labels/ train/ img_0001.txt img_0002.txt val/ img_0101.txt img_0102.txt这里有个很容易犯的错误图片集和标注 txt 文件命名必须完全一致才能被 ultralytics 正确匹配包括扩展名。我建议在转换脚本里直接保持原始文件名不要做任何改名操作。如果确实需要重命名务必同时修改对应的 txt 文件名。目录结构准备好之后我们再统计一下各类别的样本数。这一步很关键如果某个类别的标注框数量显著少于其他类别训练时模型会倾向于把该类别预测为其他类别或者干脆预测不到。处理方法有三个一是采集更多该类别图片二是对该类别的图片做离线增强旋转、翻转、亮度变化以扩充数量三是适当提高损失函数中该类别对应的权重但 ultralytics 默认没有暴露这个参数实操中多数靠前两种方式解决。按照我的经验数据准备的完整顺序是采集原始图片 → 清洗整理 → 统一尺寸和格式 → labelme 标注 → 脚本转换为 YOLO 格式 → 检查各类别数量 → 划分数据集。每一步都做完再开始训练后面能省下大量的排查时间。4. 模型训练跑通只是第一步看懂结果才算数环境搭好了数据也备齐了现在进入训练环节。很多人的想法是命令行跑起来就完事了但实际训练过程的监控和结果的解读直接影响后续整个部署流程的成败。4.1 ultralytics 环境与自定义 yaml 配置上面我们已经通过pip install ultralytics装好了训练库。训练自定义数据集需要准备一个描述数据集信息的 YAML 配置文件。在项目目录下创建my_data.yaml# 数据集配置文件 path: /home/user/datasets/my_data # 数据集根目录的绝对路径 train: images/train # 训练集图片目录相对于 path val: images/val # 验证集图片目录 names: 0: cat 1: dog这里的names必须和转换脚本里的CLASS_MAP完全一致ID 从 0 开始递增。如果 YAML 里定义的类别名和标注 txt 里的 ID 对不上训练时模型会静默跳过对不上的标注框导致有效样本数变少训练结果指标很低——这个坑非常隐蔽我当时排查了很久才发现是 YAML 里漏了一个类别。4.2 训练参数设置从 epochs 到 batch sizeultralytics 提供了一组合理的默认参数但实际训练时还是要根据自己的数据和硬件做一些调整。我常用的完整训练命令是这样的yolo detect train \ --model yolov8s.pt \ --data my_data.yaml \ --epochs 100 \ --batch 16 \ --imgsz 640 \ --device 0 \ --workers 8 \ --project runs/train \ --name my_yolov8s_exp1 \ --pretrained参数的含义和选择逻辑我逐个说一下model选择yolov8s.pt还是yolov8n.pt本质是精度和速度的折中。如果你的部署设备是 RK3588我建议直接用yolov8s因为s在量化后精度损失通常还可以接受速度也不会拉胯n虽然更快但精度下降明显。当然如果数据非常简单只有两三类目标n也够用。epochs默认是 100。数据量小的时候 100 轮可能早就收敛了可以看训练曲线提前终止数据量大几千张图的时候 100 轮往往不够可以加到 200 甚至 300。我习惯先用 100 轮跑通再根据 loss 曲线的趋势决定是否加跑。batch设 16 怎么样取决于显存。--device 0指定用第一张 GPU。imgsz设为 640这是 YOLOv8 的默认输入尺寸也是后续 RKNN 量化时推理分辨率改起来最方便的设置——部署阶段模型输入尺寸会严格绑定训练尺寸换尺寸可能精度下降。pretrained使用 COCO 预训练权重做迁移学习。这是个非常重要的加速前提预训练模型已经学会各种底层特征边缘、纹理、颜色只需要微调上层即可收敛速度远快于从零训练。4.3 训练过程监控与常见问题训练启动后终端会实时打印每个 epoch 的 loss 和 metric。这里有几个应该关注的指标box_loss边界框回归损失、cls_loss分类损失、dfl_loss分布焦点损失这三个 loss 应该整体呈下降趋势。如果 loss 在训练后期出现反弹先降后升说明学习率可能太大或者模型过拟合了需要调低学习率或加早停ultralytics 默认有早停机制patience50 会默认开启。如果 loss 卡在一个平台期怎么都降不下去常见原因有两个一是学习率太小二是数据集里有大面积标注错误标签噪声建议抽样检查数据。GPU 显存不足是新手最容易遇到报错OutOfMemoryError或者CUDA out of memory。解决办法按优先级排序调小 batch size从 16 降到 8 或 4调小imgsz从 640 降到 512注意后面部署时也要用对应尺寸开启梯度累积ultralytics 支持--batch 16 --device 0之外加--fraction 0.5使用 50% 显存。训练刚开始的几分钟是最容易慌的时候mAP 先是 0过几个 epoch 才开始涨很多新手以为模型训练失败了。其实这是正常的——模型还没学会任何东西指标自然为零。按我的经验mAP 一般从第 10 个 epoch 左右开始明显上升如果 20 个 epoch 之后还是 0才需要停下来检查问题。4.4 结果评估指标解读训练完成之后ultralytics 会在runs/train/目录下生成一组结果文件包括results.png、confusion_matrix.png、PR_curve.png等。理解这些指标比直接看 mAP 数字重要得多。mAP50和mAP50-95是核心指标。mAP50表示 IoU 阈值为 0.5 时的平均精度容易达到较高的数值mAP50-95是多个 IoU 阈值的平均更能反映定位精度。目标检测模型训练通常 mAP50 会先上去mAP50-95 慢慢跟着涨。如果 mAP50 很高0.95但 mAP50-95 很低0.5以下说明模型定位不够精细通常和目标框标注不够准确有关。混淆矩阵confusion_matrix.png能直观看出模型容易在哪些类别之间搞混。比如猫狗分类如果混淆矩阵显示大量猫被预测为狗说明训练集中这两个类别的特征不够可分或者狗的照片里混入了大量猫的背景。PR_curve.pngPrecision-Recall 曲线反映的是置信度阈值变化时精确率和召回率的权衡。部署时你会选一个置信度阈值比如 0.5 或 0.3。如果想提高检出率减少漏检就要用低一点的阈值比如 0.25代价是误检率会上升如果误检代价高比如报警系统阈值设高一些更稳妥。4.5 导出工作为 RKNN 转换做第一步准备训练完成精度也符合预期之后就要做导出 ONNX 这个动作了。这时你训练好的模型还是 PyTorch 的.pt权重RKNN 工具链不认识它需要先转成 ONNXRKNN-Toolkit2 才能在此基础上转换成 RKNN 格式。yolo export modelruns/train/my_yolov8s_exp1/weights/best.pt formatonnx opset12几个细节注意一下opset建议设为 12RKNN-Toolkit2 对 opset 12 的 ONNX 模型支持最完整。导出完成后检查一下 ONNX 文件里的算子列表确认没有 RKNN 不支持的算子。常用的Conv、Relu、Add、Concat、Resize都没问题如果发现奇怪的自定义算子用onnx.checker可以检查模型结构多半是训练时有特殊模块需要在导出时删掉。部署时只保留模型前向推理需要的部分后处理NMS由 RKNN Runtime 实现或自己在板端写。如果你的模型里用了自定义的预处理比如归一化到特定范围记得在配置文件里记录清楚后面 RKNN 量化时要用到。YOLOv8 默认预处理是(x / 255)归一化到[0, 1]如果没做特殊处理保持默认就好。到这里训练阶段的工作就完成了有了一个训练好的.pt文件又有了一个导出的.onnx文件下一篇文章我会继续讲如何在 PC 上使用 RKNN-Toolkit2 把 ONNX 模型量化和转换成 RKNN 格式然后部署到 RK3588 板端完成推理。回到整个流程我现在越来越觉得边缘部署这件事模型的训练精度固然重要但更关键的是从第一天就带着最终要在 NPU 上跑的意识去选模型、定参数、设计数据流程。数据库采集时想清楚部署场景的光线、角度、目标尺度训练时固定好输入尺寸导出时保证算子和 RKNN 对齐——每一步都为下一步留好接口整条链路才会顺畅。