RK3588 这颗芯片在国产边缘设备里的存在感确实很强,8 核 CPU 加上 6 TOPS 的 NPU,跑 YOLO 系列检测模型算是它的“本职工作”。但我发现很多人把 YOLOv8、YOLOv8-seg 搬上这块 NPU 时,总是卡在模型转换、算子支持或者后处理上,要么速度跑不满标称算力,要么分割结果直接没法看。这篇文章就把我实际在 RK3588 NPU 上做过的 YOLOv8 与 YOLOv5 速度对比,以及 YOLOv8-seg 分割模型部署时踩过的坑完整整理出来,包括工具链版本怎么配、量化怎么做、算子回退怎么查、分割头的输出怎么解析,希望能帮你少走几个月弯路。
这篇文章适合正在做边缘端视觉落地的同学,尤其是手里已经拿到 RK3588 开发板、但又不知道从哪开始烧模型的人。读完你会对 NPU 部署的整体流程有一个清晰闭环,也能直接照着里面的代码和排错思路动手跑起来。
1. 部署方案与工具链选择
1.1 认识 RK3588 的 NPU 与 RKNN 工具链
RK3588 集成的 NPU 官方标称 6 TOPS 算力,指的是 INT8 精度下的算力,实际使用中还要看算子利用率和内存带宽。这颗 NPU 不是像 GPU 那样通用可编程的架构,它更接近一堆固定功能计算单元的组合,对卷积、ReLU、池化这类常见算子支持得很好,但对动态 shape、某些上采样方式、sort 类操作就非常不友好。理解这一点,是后面所有排错思路的基础。
官方配套的部署工具链叫 rknn-toolkit2,它负责把 PyTorch、ONNX、TensorFlow 甚至是 Caffe 训练出来的模型转换成 RK3588 NPU 能跑的 .rknn 格式文件。转换过程包含算子映射、权重重排、量化等环节,最终生成的 rknn 文件由板端的 runtime 库(rknn-toolkit-lite2 或 C API)加载执行。
整个部署链路是这样的:训练好的权重 -> 导出为 ONNX -> 用 rknn-toolkit2 在 PC 端转换为 .rknn -> 把 .rknn 拷贝到板子 -> 用 lite2 或 C API 加载推理。这里最关键的一点是:PC 端转换用的 rknn-toolkit2 版本和板端 runtime 版本必须匹配,否则会出现模型加载失败或者算子解析错乱的问题。官方虽然在文档里强调过这一点,但实际项目里很多人就是栽在版本不一致上。
1.2 环境安装与版本匹配实操
PC 端环境建议直接用 Ubuntu 20.04 或 22.04,Python 版本 3.8 到 3.10 之间都可以,我实测 3.8 最稳。安装 rknn-toolkit2 直接用 pip 装就行,但要注意先创建虚拟环境,因为它对 onnx、onnxruntime、numpy 的版本比较敏感,装乱了会波及你其他项目。
依赖安装顺序我一般是这样的:
# 创建虚拟环境 python -m venv rknn_env source rknn_env/bin/activate # 安装基础依赖 pip install numpy==1.24.4 onnx==1.14.0 onnxruntime==1.16.0 opencv-python # 从官方 GitHub 下载 rknn-toolkit2 的 wheel 包并安装 pip install rknn_toolkit2-2.0.0b0-cp38-cp38-linux_x86_64.whl # 验证安装 python -c "from rknn.api import RKNN; print('ok')"这里有个隐藏比较深的坑:onnxruntime 的版本会影响模型解析,高版本 onnxruntime 在某些情况下会做额外的算子融合,导致导出的模型和你预期不一致。我遇到过明明在 PyTorch 里模型输出正常,转成 ONNX 后测试输出也对,但 RKNN 转换时报算子不支持的诡异情况,最后发现是 onnxruntime 版本太新导致的。所以如果你在转换阶段卡住,优先把 onnxruntime 降下来试试。
版本匹配这一块,我现在的做法是:PC 端 rknn-toolkit2 用 2.0.0 及以上版本,板端 lite2 也用同版本,在板子上跑pip show rknn-toolkit-lite2确认版本号。目标平台参数在转换时要显式写成target_platform='rk3588',不要偷懒不写,否则默认可能跑到 rk3566 或 rk3568 的配置上,速度会有明显差异。
1.3 用官方示例工程起步
rnkk_model_zoo 是官方维护的模型仓库,里面已经有 YOLOv5、YOLOv8、YOLOv8-seg 全套转换和部署的示例代码。我的建议是永远不要自己从头写转换脚本,直接在这个仓库基础上改,至少能保证基础环节是对的。你把rknn_model_zoo/examples/yolov8目录里的 python 脚本跑通一遍,基本就理解了数据集准备、量化、推理的标准姿势。
跑通示例后再换成自己的模型,这时你只需要改权重路径、类别数和预处理方式,出问题的概率会小很多。这个路径看起来绕了一圈,实际上是最快的一种方式。
2. YOLOv8 与 YOLOv5 在 RK3588 NPU 上的速度实测
2.1 模型结构差异与理论预估
做速度对比前,先得搞清楚 YOLOv8 和 YOLOv5 到底差在哪。YOLOv5 的 backbone 用的是 C3 模块加早期的 Focus 结构,检测头比较简单,输出层直接给出 box 和 class。YOLOv8 把 C3 换成了 C2f 模块,去掉了 Focus,检测头改用 DFL(Distribution Focal Loss)结构,同时把 anchor 机制改成了 anchor-free。
C2f 模块在结构上比 C3 多了更多分支和拼接操作,计算量会略高一些。DFL 结构在推理时需要对 16 个通道做 softmax 再加权求和,这个操作在 NPU 上不是完全“免费”的,有一部分可能会回退到 CPU。而 YOLOv5 的检测头就是纯粹的卷积加 sigmoid,对 NPU 来说属于最友好的算子组合。
所以在没测之前,我心里大概预期 YOLOv5s 会比 YOLOv8s 快 10% 到 20%,但精度上 YOLOv8 确实通常要更好一些。具体怎么选,完全取决于你的项目是更看重延迟还是更看重精度。
2.2 实测环境与推理方法
我实测用的板子是 RK3588 标准开发板,8G 内存版本,系统是 Ubuntu 22.04,NPU 驱动用的官方 rknpu 固件。模型都是 ultralytics 官方仓库导出的,输入尺寸固定为 640x640,量化精度 INT8,batch size 固定为 1,这也是边缘部署最常用的配置。
推理代码用的是 rknn-toolkit-lite2 的 Python 接口,核心逻辑如下:
from rknnlite.api import RKNNLite rknn = RKNNLite() rknn.load_rknn('yolov8s.rknn') rknn.init_runtime(core_mask=RKNNLite.NPU_CORE_0_1_2) # 预热 img = np.random.randint(0, 255, (640, 640, 3), dtype=np.uint8) for _ in range(5): rknn.inference(inputs=[img]) # 正式计时 import time start = time.perf_counter() for _ in range(50): outputs = rknn.inference(inputs=[img]) end = time.perf_counter() print((end - start) / 50 * 1000, 'ms')这里要提醒两点:一是 NPU 第一次推理会做初始化,速度很慢,必须预热几次再计时。二是core_mask这个参数一定要用NPU_CORE_0_1_2表示同时用三个 NPU 核心,我见过有人默认只跑一个核,速度几乎掉了三倍。
另外,我在转换时打开了 verbose log,目的是看有没有算子回退 CPU。命令是rknn.config(verbose=True),日志里如果出现WARNING: fallback或者某些层显示在 CPU 上计算,那速度绝对会有影响。
2.3 测试结果与对比分析
直接放我实测的数据。注意这里数值受编译版本和板子散热影响会有浮动,但相对关系是稳定的:
| 模型 | 输入尺寸 | 量化精度 | NPU推理耗时 | 整体帧率(含后处理) |
|---|---|---|---|---|
| YOLOv5s | 640x640 | INT8 | 31.5 ms | 22 FPS |
| YOLOv8s | 640x640 | INT8 | 38.2 ms | 19 FPS |
| YOLOv8s | 640x640 | FP16 | 77.6 ms | 10 FPS |
| YOLOv8s-seg | 640x640 | INT8 | 55.4 ms | 12 FPS |
只看 NPU 推理时间,YOLOv8s 比 YOLOv5s 慢了大概 20%,这和理论预估基本吻合。速度差距主要来自两个方面:一是 C2f 模块分支多,NPU 在做 concat 和残差相加时数据搬运开销更大;二是 DFL 头包含一些 NPU 不友好的操作,部分算子被放到了 CPU 上跑。
FP16 相比 INT8 差了将近一半性能,这个结果也很正常,因为 RK3588 的 NPU 对 FP16 的处理效率远不如 INT8。所以除非你的模型精度对 INT8 量化非常敏感,否则推荐直接用 INT8 部署。
如果你在意的是整体帧率,一定要把后处理时间算进去。我上面的整体帧率已经包含了 NMS 和坐标解码的耗时,YOLOv8 的 DFL 解码本身就比 YOLOv5 要复杂,多一次 softmax 和加权求和,CPU 端后处理大约多花 3-4ms。这个差距在低帧率场景下不明显,但如果你要做实时视频流分析,每帧多 3ms 就意味着丢帧概率更高。
2.4 后处理耗时对“整体帧率”的影响
很多人在测速时只盯着 NPU 推理时间,实际落地时发现帧率远低于预期,就是因为后处理跑在 CPU 上。尤其是 YOLOv8,它的后处理流程包括:从输出张量中分离 box 和 cls、对 box 做 DFL 解码、计算每个框的坐标和置信度、过滤低置信度框、跑 NMS。
这些操作对 8400 个候选框来说,纯 Python 循环非常慢。我建议无论用哪种模型,后处理都尽量用 numpy 向量化实现,避免逐框循环。另外,RK3588 是 8 核 CPU,可以开一个线程专门做 NPU 推理,另一个线程做后处理和显示,用双缓冲交替工作,整体吞吐能提高不少。
3. YOLOv8-seg 分割模型部署难点
3.1 分割模型的结构与输出解读
YOLOv8-seg 和检测版最大的区别是多了分割头。整个模型结构包含 backbone、neck、检测头和一个额外的分割头。分割头会输出一个 proto 分支,这个分支生成一组低分辨率的分割原型图,同时检测头的输出里会多出一段掩码系数,最终的 mask 是由 proto 和掩码系数矩阵乘得到的。
具体到输出张量,假设你的类别数是 80,输入 640x640,模型的原始输出是一个 1x116x8400 的张量,其中 4 是 box 坐标,80 是类别概率,32 是 mask 系数。还有一个输出是 1x32x160x160 的 proto 张量。注意,这里的 160x160 是原型图的分辨率,最后需要上采样回原图尺寸。
很多人拿到这个输出是懵的,不知道该拿哪些维度去算。我当时也折腾了好一阵才搞清楚对应关系,所以这里先明确:mask 系数和 box、cls 在同一个张量里,分开切片后,用矩阵乘把 mask 系数和 proto 算出来,上采样,再做 sigmoid,阈值化才能得到最终的二值 mask。原型图本身不是最终 mask,必须先和系数组合。
3.2 第一个大坑:上采样算子不支持
YOLOv8-seg 转 NPU 时最容易踩的坑就是上采样算子。分割头里有一个从低分辨率特征图做双线性插值上采样的操作,这个操作在 ONNX 里通常对应Resize算子或者Upsample算子。RNK 工具链对Resize的支持非常有限,即使支持,也经常会被判定为效率太低而回退到 CPU,导致整体推理速度大幅下降。
我在转换 yolov8s-seg 时第一次就遇到了这个算子回退,日志显示Resize跑在 CPU 上。解决思路有两种:一种是在导出 ONNX 时把上采样方式改成最近邻插值,NPU 对最近邻的支持好很多,但精度会略微下降;另一种是把上采样从模型里拿掉,让模型只输出低分辨率的 proto,上采样放到 CPU 后处理用 OpenCV 的cv2.resize做。我实际采用的是第二种方案,效果最稳定且不损失精度。
具体做法是在导出 ONNX 时,手动把模型最后的 proto 输出的上采样节点删掉,让 proto 直接输出 160x160 的结果。然后在后处理代码里用cv2.resize将 mask 上采样到 640x640 或原图分辨率。NPU 只计算卷积部分,上采样这些“脏活”交给 CPU 做,实测速度提升非常明显。
3.3 第二个大坑:导出与维度匹配问题
分割模型的 ONNX 导出也要格外小心。用 ultralytics 官方接口导出时,默认可能会把一些后处理逻辑也带进去,比如 sigmoid、argmax 这类操作。这些操作有的 NPU 支持得不好,有的甚至不支持。
我建议用以下方式导出干净的 ONNX:
from ultralytics import YOLO model = YOLO('yolov8s-seg.pt') model.export( format='onnx', opset=12, imgsz=640, simplify=True, dynamic=False )这里opset=12很关键,opset 太高某些算子可能会发生变化,RKNN 解析就容易报错。simplify=True会调用 onnxsim 对模型做简化,把一些冗余节点删掉,转换成功率会提高。dynamic=False保证输出是固定 shape,NPU 不支持动态 shape。
导出后先用 onnxruntime 在 PC 端跑一遍,确认输出维度和数值正常再转 rknn。这一步必须做,因为很多时候问题不是出在 RKNN,而是模型导出的 ONNX 本身就有问题。
3.4 第三个大坑:分割后处理流程怎么拆
分割的后处理比检测复杂很多,它不是一个简单的 NMS 就能搞定的。在你的模型拿到原始输出后,后处理流程应该是这样的:
- 从输出中分离 box、cls 和 mask 系数。
- 先跑检测那套流程,做 DFL 解码、置信度过滤、NMS,得到最终的检测框。
- 对每个保留的检测框,从 mask 系数里取出对应的那一行。
- 把 mask 系数和 proto 做矩阵乘,得到一个原始 mask 特征图。
- 对 mask 特征图做 sigmoid 和阈值化,得到二值 mask。
- 用检测框的坐标从原图中裁剪出对应区域,把 mask 上采样到裁剪区域的尺寸。
关键点在于,分割后处理里有两个操作很容易出错:一是在 NMS 之前就要把检测框的置信度算出来,否则后面分不清哪些框该保留;二是掩码系数要和检测框一一对应,排序不能乱。我这里直接用 numpy 做矩阵乘,160x160 的 proto 乘上 32 维系数,速度很快,基本可以忽略不计。
实战建议是:写一个SegPostProcessor类,把检测和分割的后处理分离,先确认检测结果对了,再去对分割结果,不要一上来就调 mask,不然你根本分不清问题出在检测头还是分割头。
3.5 实测速度表现与优化建议
YOLOv8s-seg 在 INT8 量化下,NPU 推理时间大约 55ms,整体帧率含后处理只有 12 FPS 左右。这个速度比检测模型慢了不少,主要原因是分割头增加了额外的卷积和 proto 分支,计算量确实更大。
如果你想进一步提升速度,可以从这几个方向入手:
第一,输出分辨率不要用 640,可以降到 512 或者 416。分割任务本身对分辨率比较敏感,但很多场景下 512 已经够用,速度能提升 30% 以上。第二,把分割头的 proto 分支输出降为 80x80 或更低,这需要在模型结构上做裁剪,省掉 proto 分支最后几个卷积层,代价是 mask 边缘会更粗糙。第三,用多线程把 NPU 推理和 CPU 后处理流水线化,而不是串行执行。
我实测过,把输入从 640 降到 512,NPU 推理时间从 55ms 降到 35ms 左右,降幅非常可观。如果你的项目允许降低一些精度,这个方案是性价比最高的。
4. 板端部署细节与性能优化
4.1 推理代码最小范例
在 RK3588 板端部署,我推荐用 rknn-toolkit-lite2 的 Python 接口,开发效率高,性能也够用。如果是产品化项目再考虑 C API,但初期原型验证用 Python 完全没问题。
一个最小可运行的板端推理脚本大概长这样:
import cv2 import numpy as np from rknnlite.api import RKNNLite rknn = RKNNLite() rknn.load_rknn('yolov8s-seg.rknn') rknn.init_runtime(core_mask=RKNNLite.NPU_CORE_0_1_2) img = cv2.imread('test.jpg') img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img = cv2.resize(img, (640, 640)) img = img.astype(np.uint8)[None, ...] outputs = rknn.inference(inputs=[img]) # 通过 data_format='nhwc' 参数可以省去一次 transpose,推理更快这里有一个细节:rknn.inference默认处理的数据格式是nchw,如果你的输入是 HWC 排列,需要传data_format='nhwc',这样可以省去输入前的一次 transpose 操作,对推理速度有微小但稳定的提升。我在板子上验证过,单帧能快 1-2ms。
4.2 多核 NPU 与内存管理
RK3588 的 NPU 有三个核心,通过core_mask参数可以控制使用哪些核心。默认情况下如果不设置,可能只跑一个核心,速度损失非常大。一定要显式设置为NPU_CORE_0_1_2三个核全开。
内存方面,RKNN 在推理时会在 NPU 和 CPU 之间搬运数据,如果你的图像比较大,内存占用会激增。我遇到过连续推理多个小时之后内存泄漏的问题,追踪下来发现是反复加载模型导致的。正确的用法是:模型加载一次,然后重复调用inference,用完再rknn.release()。
长稳测试时,建议在循环里监控内存占用:
watch -n 1 free -m如果发现内存持续增长,检查是否在循环中重复创建了 RKNNLite 实例,或者有没释放的 numpy 数组。
4.3 与 CPU 后处理的流水线化
真正做实时视频流推理时,不能把 NPU 推理和后处理串在同一个线程里。NPU 推理 55ms,后处理 20ms,串行就是 75ms 一帧,帧率只有 13 FPS。但如果用两个线程,NPU 在做第 N+1 帧推理时,CPU 同时处理后处理第 N 帧的耗时,吞吐量能接近单帧最长耗时的倒数而不是加起来。
实现方式可以用 Python 的threading加双缓冲队列,或者更简单点用concurrent.futures.ThreadPoolExecutor。我这里分享一个简单的生产者消费者思路:
# 推理线程 def infer_worker(): while True: frame = input_queue.get() outputs = rknn.inference(inputs=[frame]) res_queue.put(outputs) # 后处理线程 def post_worker(): while True: outputs = res_queue.get() results = post_process(outputs) show_results(results)用这种方式,整体吞吐按照最耗时的环节计算,而不是所有环节相加,项目实测能提升 20%-30% 的有效帧率。
4.4 输入预处理细节
很多人把模型转换搞定后,卡在输入预处理上。YOLO 系模型在训练时通常会对输入做 letterbox,也就是保持宽高比缩放到 640x640,剩下的位置用灰色填充。如果你直接把图片 resize 到 640x640,会破坏目标的长宽比,导致检测精度下降。
在板端做 letterbox 时用 OpenCV:
def letterbox(im, new_shape=(640, 640), color=(114, 114, 114)): shape = im.shape[:2] r = min(new_shape[0] / shape[0], new_shape[1] / shape[1]) new_unpad = (int(round(shape[1] * r)), int(round(shape[0] * r))) dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1] dw, dh = dw // 2, dh // 2 if shape[::-1] != new_unpad: im = cv2.resize(im, new_unpad, interpolation=cv2.INTER_LINEAR) top, bottom = dh, dh + new_shape[0] - new_unpad[1] left, right = dw, dw + new_shape[1] - new_unpad[0] im = cv2.copyMakeBorder(im, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color) return im这个 letterbox 的填充值 114 是 YOLO 训练时的默认填充值,不要改成 0,否则精度会有细微下降。推理完检测框坐标映射回原图时,记得把 letterbox 的偏移量减回去,不然框的位置会偏。
5. 常见问题与排查技巧实录
5.1 高频问题速查表
| 问题现象 | 可能原因 | 解决办法 |
|---|---|---|
| 转换时报不支持的算子 | ONNX 中包含了 NPU 不友好的算子 | 简化模型、替换上采样方式、把 sigmoid 等操作移到后处理 |
| 转换成功但推理结果全为 0 | 输入数据格式不对 | 确认 nchw/nhwc,确认输入尺寸和 letterbox 对齐 |
| 速度远低于预期 | 算子回退 CPU / 核心没全开 | 打开 verbose 日志查回退算子,设置 core_mask=NPU_CORE_0_1_2 |
| 精度比原始模型低很多 | 量化数据集覆盖率不够 | 收集 100 张以上代表性图片重新量化,检查归一化参数 |
| 模型加载失败 | PC 端工具和板端 runtime 版本不匹配 | 统一到同一个版本号 |
| 长时间运行内存增长 | 循环中重复加载模型未释放 | 模型只加载一次,用完调 rknn.release() |
5.2 转换失败时先别急着怀疑工具链
我见过很多人在 RKNN 转换报错时,第一反应就是工具链有 bug,然后去 GitHub 提 issue。但绝大多数情况下问题出在模型本身。正确的排查顺序是:先用 onnxruntime 在 PC 端加载转换前的 ONNX 模型,输入一张假图片,确认输出能出来且维度正确。这步通过后再转 RKNN,如果还报错,再看具体的算子日志。
还有一个很实用的技巧:把报错的那一层算子截图或者记下来,去 rknn_model_zoo 的 GitHub issues 里搜,大部分已知算子问题都有现成的解决方案。不要自己闷头试,RK3588 部署 YOLO 系列的人太多了,前人踩过的坑大概率你也躲不掉。
5.3 量化精度偏差的处理思路
RK3588 的 NPU 主打 INT8 量化,但量化本身会引入精度损失。如果量化后的模型在你的数据集上表现很差,优先检查两件事:一是量化用的校准图片是否和目标场景一致,二是输入归一化方式是否和训练时一致。
校准图片的选择很关键。我见过有人用 20 张日常照片做校准,结果到了工业检测场景精度崩得一塌糊涂。校准集应该覆盖目标场景下的各种光照、角度、目标大小分布,至少 100 张,多一些更好。另外,如果模型对量化特别敏感,可以考虑混合量化,让某些敏感层保持 FP16,虽然速度会降一些,但精度能保住。
5.4 算子回退日志怎么看
这是一个非常实用的排障技巧。转换模型时打开 verbose 日志后,会看到类似layer xxx not support, fallback to cpu的警告。算子一旦回退 CPU,NPU 推理时还要频繁和 CPU 做数据交互,速度会明显下降。
如果发现某个算子回退,优先考虑在模型层面改掉它。以 YOLOv8-seg 为例,最常见的是Resize算子和Sigmoid算子。Sigmoid其实可以在后处理里用 numpy 实现,转换前把模型末尾的 sigmoid 层裁掉;Resize则按我前面说的方案处理。把这些算子从网络结构里“消灭”掉,转换日志会干净很多,推理速度也会有直观提升。
5.5 避坑清单汇总
结合我的实际经验,列出几个最容易被忽略的细节:
- 板端推理前先确认固件里的 NPU 驱动版本,老固件可能不支持新工具链生成的 rknn 文件。
- 导出 ONNX 时类别数、输入尺寸一定要固定,不要在部署过程中频繁修改,每次改动都要重新量化和验证。
- 用 Python 推理时,不要每次调用 inference 前都对图片做完整预处理缓存,提前把 letterbox 和归一化放到后台线程。
- 如果板子是带散热风扇的型号,长时间跑 NPU 推理前检查一下散热,NPU 温度过高会触发降频,推理速度会瞬时暴跌。
- 不要在 Windows 上跑完整的 RKNN 转换流程,官方对 Linux 支持最完善,WSL2 虽然能用但偶尔会有库冲突,费时费力。
6. 更进一步:分割模型的扩展玩法
YOLOv8-seg 跑通之后,它的输出不仅能拿来做实例分割,还能用来做一些很有意思的下游任务。最典型的例子是人形检测加分割之后的姿态估计或者目标计数,你不再只有一个框,而是有目标在图像中的精细轮廓,可以直接算目标面积、占空比,甚至是触摸检测和物料分拣。
我自己的一个项目里,在 RK3588 上用 YOLOv8-seg 分割传送带上的物料,然后根据掩码面积估算物料大小,效果挺好的。NPU 推理时间 55ms 左右,掩码后处理 10ms,整体能跑到 15 FPS 以上,对产线实时监控来说已经够用了。这一类玩法对于那些想要在边缘设备上做“非标准检测”的应用场景,比如垃圾分拣、农作物病害识别,参考价值很大。
所以如果只把 YOLOv8-seg 当成一个“能出 mask 的检测器”,其实低估了它。分割输出的连续性给了你更多判断维度,这正是纯检测模型给不了的。部署的时候多花点心思把分割头调好,后面做业务逻辑会顺手很多。
7. 写在最后的实操心得
RK3588 的 NPU 部署做久了,你会发现真正的难点从来不是跑通一个模型的 demo,而是把模型、算子、量化和后处理打磨到能在真实场景里稳定运行。YOLOv8 和 YOLOv5 的对比没什么神秘之处,结构决定速度,精度靠量化集和微调来兜底。YOLOv8-seg 的分割部署则更需要耐心,因为要从输出张量里把 mask 和检测信息剥离出来,再自己拼装后处理流程,每一步都要求你理解透模型的结构。
我个人在实际操作中的体会是,遇到算子不支持的时候不要硬刚,换个思路把操作挪出 NPU 反而更高效。RK3588 的 CPU 是 8 核 A76/A55,性能一点都不弱,很多 NPU 不擅长的工作交给 CPU 做,整体系统效率才是最高的。硬件既然给了你两种计算单元,就要学会让它们各司其职。
最后再分享一个小技巧:做板端部署时,一定要在开发板上长期挂着串口日志或者远程日志,记录每次推理的耗时和异常情况。很多问题不是一跑就崩,而是跑久了才暴露出来的。有一份完整的日志,排查起来会轻松很多。