刚拿到RK3568这块板子的时候,我想大多数人和我一样,觉得把YOLOv8部署到NPU上也就是几条命令的事:模型训练完,导出ONNX,再用官方工具转成RKNN,跑个demo就完事了。真到了实战才发现,从ONNX到RKNN中间隔着一整个"避坑生态"——版本不匹配、量化掉点、算子不支持、预处理错位、后处理踩内存,每一个坑都能卡你好几天。
这篇文章是我在RK3568上完整跑通YOLOv8检测模型(从训练权重到C++上板推理)的实战记录。我会按照"环境准备 → ONNX导出 → RKNN转换量化 → 量化精度抢救 → 板上部署 → 稳定性优化"这条完整链路往下写,重点不是复述官方文档,而是把文档里没写清楚、但十次里有八次会踩中的细节讲透。无论你是做工业视觉、边缘网关,还是毕业设计里要跑目标检测,这篇应该能帮你少走不少弯路。
1. 动手之前:先把RK3568的工具链版本和算力边界搞明白
1.1 版本绑定关系:rknn-toolkit2、板端驱动、librknnrt必须配套
这是整个部署链路上最容易翻车、也最不容易排查的一个点。很多人在PC上装好rknn-toolkit2,转换模型一切正常,结果把.rknn文件丢到板子上加载,直接报错或者推理结果完全不对,折腾半天发现是板端的librknnrt.so版本和PC端转换工具版本对不上。
RK3568这一代NPU工具链的版本绑定关系可以用一句话概括:PC端的rknn-toolkit2版本、板端的librknnrt.so版本、内核里的rknpu驱动版本,三者是强绑定的。你用一个比较新的rknn-toolkit2转换出来的.rknn,放到只装了旧驱动的板子上,轻则warn重则跑不起来;反过来,老工具转出来的模型放在新驱动上,有时候能跑但性能上不去。
我当时的组合是rknn-toolkit2的1.6.0分支(带rknpu2的板端运行库),板子上用配套的librknnrt.so,内核驱动用的是瑞芯微发布的Linux SDK里对应的rknpu驱动。这里给一个相对稳的建议:不要上来就去拉GitHub的master分支,直接用官方release的tag,并且在板端替换运行库后,跑一遍官方自带的yolov5或mobilenet demo确认环境OK,再继续往下做。
安装PC端工具时也有个容易踩的坑:rknn-toolkit2对Python版本有要求,官方文档一般推荐Python 3.8到3.10之间,最好用conda建一个干净的环境。用pip直接装的时候,依赖里带上tensorflow 2.6.0这种大家伙,建议安装结束后立即运行一遍import验证:
pip install rknn-toolkit2==对应版本 python -c "from rknn.api import RKNN; print('OK')"如果import报GLIBC错误,要么是你的系统太老,要么是装到了不支持的Python版本上。我建议优先用纯Linux环境做转换,避免在WSL或虚拟机里遇到USB/文件挂载等额外问题。
1.2 RK3568的NPU算力到底能吃下多大模型
先说清楚RK3568的硬性指标:NPU标称算力0.8 TOPS(INT8),这个数字在2024、2025年的时间点上来看不算高,比它大的RK3588是6 TOPS,比它小的还有RK3566同款NPU。0.8T意味着什么?做个小模型可以,大模型就别硬塞了。
我用几组实测数据给你一个直观认知(640x640输入,INT8量化后):
| 模型 | 尺寸/算力需求 | 单帧NPU推理耗时(实测参考) | 综合体验 |
|---|---|---|---|
| YOLOv8n | 约6.5M参数 | 35~55ms | 勉强可用,配合后处理约50ms级 |
| YOLOv8s | 约22M参数 | 90~130ms | 实时性较差,适合低帧率场景 |
| YOLOv8m | 约52M参数 | 250ms以上 | 基本不建议上RK3568 |
| YOLOv5su | 约7.5M参数 | 40~60ms | 比yolov8n略稳 |
这个耗时数据跟板子的DDR频率、NPU工作频率、温控策略关系很大。同一颗芯片跑在默认频率和锁定最高频率下,帧率差别可能达到30%以上。所以做项目评估时,别只看标称TOPS,要实际跑你最终调度的频率。
如果你手里有YOLOv8s的权重且必须在RK3568上跑,优先考虑蒸馏、剪枝或直接从YOLOv8n开始调参。对于毕业设计或原型验证,YOLOv8n其实是性价比最高的选择。另外提醒一句,RK3568与RK3566的NPU规格一致,但接口、编解码器有差异,如果看到别人说的性能数据,先确认对方是哪块板子、跑在什么频率上,不然容易产生误判。
2. YOLOv8导出ONNX的几个生死细节
2.1 导出命令与参数选择:固定尺寸、opset、simplify
很多人忽略ONNX导出这一步,觉得ultralytics框架里一条API就搞定了,实际上在RKNN转换中,ONNX导出的质量直接决定了后续RKNN转换的成败。
我建议用以下方式导出:
from ultralytics import YOLO model = YOLO("yolov8n.pt") model.export( format="onnx", opset=12, imgsz=640, dynamic=False, simplify=True )这里几个参数都是有意为之的:
dynamic=False:动态shape在RKNN里支持很差,RK3568的NPU对动态尺寸的兼容性有限,一旦开启,后续转换基本都会报错,所以固定输入尺寸是必须的。opset=12:RKNN-Toolkit2对ONNX opset的支持范围一般在11~13之间跑得最稳,opset太高可能导致某些新算子找不到映射。simplify=True:这个会调用onnx-simplifier做图优化,可以把一些冗余算子、无用分支清理掉,对转换成功率影响明显。
导出的ONNX可能在warning里出现大量"Unsupported operator"之类的提示,先不用紧张,很多只是图优化器在尝试做常量折叠。关键要看的是最终转换阶段RKNN工具给出的supported_ops结果。
如果你是为了在RKNN里做更精细的算子融合,也可以不上simplify,但如果是为了快速跑通,simplify能省不少事。实测下来,simplify后的模型体积更小,RKNN转换时间也更短。
2.2 输出节点里藏着的DFL与sigmoid陷阱
YOLOv8的检测头输出和YOLOv5不一样,它没有anchor,输出张量shape是(1, 4+class_num, 8400),同时用了一个DFL(Distribution Focal Loss)分支来解算box坐标。这个大问题在于,YOLOv8的ONNX导出结果中,DFL的解码过程往往是停留在网络端的一堆reshape/split/conv算子,这些算子进了RKNN后可能被拆成大量低效算子,最后推断时间被拉得很高。
我踩过最明显的坑是:ONNX转成RKNN后整体推理从80ms飙到250ms,用工具分析发现DFL部分占了大量NPU周期。解法不是改YOLOv8的结构,而是在导出阶段把检测头输出截断,把坐标解码和置信度sigmoid留到后端C++/Python里手写。
具体操作方法:导出ONNX后,用Netron编译器查看输出节点,找到最后一个卷积输出(不接sigmoid和DFL那个),然后修改模型输出,只保留这个卷积张量。或者在导出前用model.model.model[-1]等自定义方式导出中间层输出。这样可以去掉DFL的一堆地面算子,把解码压力放到CPU上。
这一步看起来增加了一点后处理工作量,但换来的是NPU推理时间大幅下降。对于RK3568这种算力有限的板子,非常值。
2.3 用onnxruntime先做一次导出前验证
在ONNX导出后就直接丢给rknn-toolkit2转,是在给自己埋雷。可能ONNX本身导出时就已经错了,那后面的量化部署全是白费。
强烈建议在转换前,先用onnxruntime在PC上跑一遍这个ONNX,喂一张普通图片,和原始PyTorch模型的输出做对比。不要只看loss或fp32精度,重点看输出的tensor shape是否与预期一致、数值范围是否合理、不同channel的分布有没有异常。
import onnxruntime as ort import numpy as np from ultralytics import YOLO # 用yolo官方模型输出作为baseline model = YOLO("yolov8n.pt") results = model.predict(source="test.jpg", conf=0.25) # 用onnxruntime跑同一张图 sess = ort.InferenceSession("yolov8n.onnx", providers=["CPUExecutionProvider"]) input_name = sess.get_inputs()[0].name # 注意预处理要与训练保持一致 ...我在这一步救回来好几次:有一次导出的模型输出shape变成(1, 84, 8400)和预期一致,但数值经过sigmoid后出现严重偏移,原因是加载权重时走了AMP的half精度孪生权重,换了cast后就好了。总之,把这一步当作准入条件,不要跳过。
3. RKNN转换和量化:校准数据比模型参数更重要
3.1 dataset.txt与校准图片的组织方式
ONNX转RKNN时,量化是最核心的一步,而量化用的校准数据(calibration dataset)的重要性常被低估,甚至很多人随便拿几张图写个txt就上了。
下面是我后来固定下来的做法:准备一个dataset.txt文件,每一行是一张图片的绝对路径,图片数量建议100~300张,最少不要少于50张。这些图片不需要带标注,但是要尽可能贴近真实场景。如果你做的是车间安全帽检测,就全用车间的光照、角度、遮挡情况的图;如果你用网上随便找的风景图来量化,部署出来精度直接掉几个点都是正常的。
图片分辨率不需要等于模型输入尺寸,工具会自己缩放到640x640。但建议校准图片不要统一是全黑或全白,要保持分布多样性。
除了图片数量,还有一个容易忽视的点:dataset.txt里路径不要有空格和中文,RKNN工具用C++读取列表时,遇到特殊字符会有解析问题,这个坑很隐蔽,报错往往只提示"failed to load image"。
/path/to/images/img_0001.jpg /path/to/images/img_0002.jpg ...3.2 RKNN.config中与量化强相关的参数
转换脚本的核心配置段大概是这个样子:
from rknn.api import RKNN rknn = RKNN() rknn.config( mean_values=[[0, 0, 0]], std_values=[[255, 255, 255]], target_platform="rk3568", quantized_dtype="asymmetric_quantized-8", optimization_level=3, )这里每一个参数都值得单独说:
mean_values和std_values:这两个值是用来做输入归一化的。YOLOv8训练时在PyTorch里通常把像素/255归一化到[0,1],所以在RKNN配置里mean=0, std=255可以实现同样的效果。问题是很多人会把训练的mean/std照抄成ImageNet的[0.485,0.456,0.406]那套,但YOLOv8默认训练时并没有用这个,一旦弄错,上板后检测框会全部乱飘。quantized_dtype:老的版本默认是asymmetric_quantized-8,新版本叫w8a8之类的,要看对应工具版本。如果遇到精度掉得很惨,可以尝试fp16跑一遍看看是不是量化本身的问题。optimization_level:默认1或2,一般不用动。这个主要是控制图优化强度,不会影响模型数学结果,但如果设置太高,某些算子会被激进融合,后面混合量化时的可控性变差。
配置完成后执行:
rknn.load_onnx(model="yolov8n.onnx") rknn.build(do_quantization=True, dataset="./dataset.txt") rknn.export_rknn("yolov8n.rknn")build这一步在do_quantization=True时才会做int8量化,如果是False,只是把fp32权重转到RKNN,精度不会掉,但推理速度也不会快。对RK3568来说,不上量化基本谈不上性能。
3.3 构建后的模拟器推理:在PC上先判别生死
build成功后,板子还没拿到手,就可以先用rknn-toolkit2自带的模拟器(simulator)在PC上跑一下推理。这个模拟器在早期版本叫rknn.inference,现在还可以指定target为模拟环境。
这一步的主要目的是在PC上快速判断:RKNN模型输出和ONNX输出是否在合理误差范围内;输入输出通道数是否正确;推理耗时的大致估算。
我当时用模拟器发现了一个大坑:模型的输入是NCHW,但是PC端模拟推理时如果喂入的是NHWC的numpy数组,模型不会报错,因为底层做了隐式转换,但后处理就会出现shape错位。建议在喂数据前,就把数据形态按照1x3x640x640处理好。
如果模拟器推理结果和onnxruntime的差距很大,我建议先不要急着上板。因为板上一旦出了类似问题,排查手段比PC上少得多,先解决到"模拟器输出可接受"这个状态再下一步。
4. 量化翻车的定位与抢救流程
4.1 精度崩坏最常见的三种症状
部署跑到一半发现检测效果拉胯,是最让人抓狂的时刻。我整理一下RK3568上YOLOv8量化后最常见的三种"症状",你可以对照着快速定位。
症状一:所有类的置信度都变成0.0或者极其接近0。这种情况十有八九是预处理配置错了,重点查mean_values、std_values和letterbox是否和模型训练时一致。还有一种可能是模型输入被指定成fp16,而你在config里用了int8,数据在转换时做了不正确的截断。
症状二:能检测到部分目标,但坐标框偏移半格。这种情况多半是letterbox的填充值不对,或者输入图像的长宽比缩放方式与训练时不同。YOLOv8训练时用的letterbox会先把图像等比缩放到640x640并填充灰色(填充值114),如果你在部署端直接用resize到640x640而不保持比例,框偏移是必然的。
症状三:某些类别能检出,另外一些类别大面积漏检。这大概率是校准数据不平衡导致的。比如你的场景里80%都是person,而"helmet"这个类别在校准集里只有两张图,那量化出来的int8 scale对helmet特征非常不友好,漏检率自然飙升。我建议校准集中每个要检测的类别最好都有20张以上样本,并且覆盖2~3种常见的光照和背景。
4.2 用accuracy_analysis定位敏感层
当整个模型的输出精度在量化后掉得很离谱,但预处理和后处理都查不出问题时,就要用工具自带的分析能力定位到具体层。
rknn-toolkit2提供了accuracy_analysis接口,它可以逐层对比fp32模型和量化模型在中间输出上的cosine相似度。虽然跑起来很慢,但定位敏感层非常好用。
rknn.accuracy_analysis(inputs=["test_preprocessed.npy"], output_dir="./accuracy_dir")跑完后去输出目录里看每一层的cosine similarity,如果发现某一层的余弦相似度掉到0.9以下,尤其集中在比较大的卷积层上,那就可以对这层做混合量化处理(保留fp16不量化)。
这个分析不是每次都必须跑,但当你遇到"精度莫名其妙掉了30%"这类问题时,它是排查链条里最强力的证据支持。
4.3 混合量化与预处理对齐的实操
混合量化是RKNN相对实用的一个玩法:把敏感层保留为float16,其余层仍然用int8,从而在精度和性能之间找平衡。
做法上,rknn-toolkit2提供了hybrid_quantization_step1和step2接口,简单理解就是:先跑一次量化得到每层精度报告,然后手动指定某些层的量化类型为fp16,再进行第二轮构建。
我实际用过一次:在YOLOv8n上,把靠近输出头的两个卷积层单独保留fp16,整体NPU推理时间从40ms增加到了48ms,但mAP从0.78回升到0.85,这个取舍对我来说完全值得。也就是说,混合量化不是万能的,但作为"精度不够"的第二方案,比换模型结构成本低得多。
另外有一个关键点容易被忽略:fp32模型、int8量化模型在推理时,输入图像的预处理方式必须完全一致。很多人喜欢在Python侧用PIL做resize,在C++侧用OpenCV做resize,两个库的插值算法不一样,就会导致同样的输入图片在两端产生不同的归一化结果,进而影响检测结果。建议在项目一开始就统一预处理代码,最好C++和Python都用OpenCV,并且固定使用INTER_LINEAR双线性插值。
5. 上板部署链路:C++接口、前后处理与线程模型
5.1 初始化、输入输出tensor的查询与设置
当.rknn模型在模拟器上验证通过,下一步就是移植到板端。RK3568的C++部署其实有两条路:一条是用官方lite例子里的rknn_api,另一条是直接用rknn-zoo里的rknn_yolov8_demo。我建议从后者改起,因为它已经把YOLOv8的前后处理都写好了,但你需要理解其中的接口逻辑,否则出了问题改不动。
核心流程大概是这几步:
#include "rknn_api.h" rknn_context ctx; int ret = rknn_init(&ctx, model_path, 0, 0, NULL); // 查询输入输出信息 rknn_input_output_num io_num; rknn_query(ctx, RKNN_QUERY_IN_OUT_NUM, &io_num, sizeof(io_num)); rknn_tensor_attr input_attrs[io_num.n_input]; for (uint32_t i = 0; i < io_num.n_input; i++) { input_attrs[i].index = i; rknn_query(ctx, RKNN_QUERY_INPUT_ATTR, &input_attrs[i], sizeof(input_attrs[i])); }这里有个重要细节:rknn_query返回的input_attrs里有一个fmt字段,可能是RKNN_TENSOR_NCHW或RKNN_TENSOR_NHWC。YOLOv8的ONNX默认是NCHW,所以转换出来的模型一般也是NCHW,但如果你在config里开了某些优化选项,输入可能会被摆成NHWC。你喂给rknn_inputs_set的数据layout必须和这个fmt一致,否则推理结果不对,而且极难排查。
直接一点说:上板后第一步别急着跑后处理,先写个小程序把输入输出tensor的ndim、dims、fmt打印出来,确认和预期一致,再做后面的逻辑。
5.2 预处理对齐:letterbox和颜色通道一个都不能错
上板后的预处理最常见的错漏有四个。
第一是BGR与RGB顺序。RKNN的NPU在读取输入时,fmt如果是RKNN_TENSOR_NCHW,输入数据本身是你要给出的像素排列,它不会自动帮你做BGR转RGB。而你在PC上做模型训练或onnx验证时,YOLOv8默认用的是BGR(因为ultralytics用OpenCV读图)。所以在C++侧,要么打通读图时转成RGB,要么在预处理阶段用OpenCV的cvtColor转换后输入。如果你两边乱了,直接后果是检测准确率大降,但程序不会报任何错误。
第二是letterbox。YOLOv8在训练时通常会做letterbox缩放,目标是640x640,灰色填充值114。如果部署端不做letterbox而是直接强行拉伸,检测框位置就会系统性偏移。正确操作是先计算缩放比例,将长边缩放到640,短边保持比例后用灰色填充到640。
float scale = std::min(640.0f / img.cols, 640.0f / img.rows); int new_w = round(img.cols * scale); int new_h = round(img.rows * scale); cv::resize(img, resized, cv::Size(new_w, new_h), 0, 0, cv::INTER_LINEAR); int pad_w = 640 - new_w; int pad_h = 640 - new_h; cv::copyMakeBorder(resized, padded, pad_h / 2, pad_h - pad_h / 2, pad_w / 2, pad_w - pad_w / 2, cv::BORDER_CONSTANT, cv::Scalar(114, 114, 114));第三是float32与uint8的传递方式。rknn_api支持直接传uint8的像素数据,NPU会自动做归一化,但前提是你在config里已经设置了mean_values和std_values。如果你传了float32数据进去,就必须自己提前做好归一化,且config里的mean_values/std_values要相应地设置为0和1,否则就是二次归一化,模型精度直接崩。
第四是图像尺寸与模型输入尺寸。板上摄像头取流分辨率不一定是640,可能是1280x720或者1920x1080。每次都要先缩放再加pad,不要偷懒直接假设输入已经是640,否则实际输入尺寸和模型预期不符,rknn_inputs_set会报错或乱跑。
5.3 YOLOv8后处理拆分与NMS实践
YOLOv8的后处理可以分成几段:从模型输出张量切分数据和box回归、置信度解码、阈值过滤、NMS非极大值抑制。
先说模型输出的写入。RKNN的输出不一定是(1, 84, 8400)排列好的,你需要根据rknn_output的size和dims把数据存成浮点数组。假如我们用截断输出,只拿到了最后的卷积输出(1, 64, 8400),那就需要自己在C++里做DFL和sigmoid。这一步虽然有工作量,但比起把DFL放在NPU里跑,CPU端实现其实很快,几百行代码就能搞定。
DFL的解码公式可以这么理解:YOLOv8的预测框坐标是由4个边(l, r, t, b)的分布预测出来的,这个分布被表示为16个bin的概率权重,解码时需要将16个值softmax后与坐标偏移加权求和。如果你不想手动实现,也可以参考官方rknn_model_zoo里的yolov8_postprocess.cpp,但它跟你的模型输出节点是否匹配,一定要先确认。
NMS可以优先选cv::dnn::NMSBoxes,在RK3568上跑得还可以。如果单线程后处理时间超过10ms,可以考虑把bbox过滤阈值从0.25提高到0.45,减少候选框数量,NMS速度也能显著提升。对一般业务场景,你会愿意用0.35左右的阈值来平衡召回率与实时性。
有一点要提醒:RKNN推理得到的结果是排在1x84x8400里的连续内存,如果你的class数改了,比如改为自己训练的1类或2类,输出通道会相应地变成4+num_class。后处理每一处硬编码的84、80、8400都要跟着改,不然直接越界访问,程序动不动就崩溃。
5.4 时间都耗在哪:端到端耗时拆解
板子跑起来以后,你不仅要看"NPU推理要多久",还要看端到端耗时:摄像头取流 + 图像缩放填充 + 数据拷贝 + 模型推理 + 后处理。RK3568上是这几个环节里,往往数据拷贝和预处理比你想象中更耗时间。
我实测过一个典型case:摄像头输入1080p,转成640x640 letterbox大约花了6ms,数据从CPU内存拷贝到NPU输入端又要3~4ms,NPU推理约40ms,后处理8~10ms,一共就是55~60ms,帧率不到20FPS。优化方向就是避免在板上逐像素操作:
- 取流分辨率直接配成1280x720或更小,减少resize耗时。
- 图像resize用
cv::resize即可,但别用太慢的插值算法,RK3568上INTER_LINEAR已经是低延迟和质量的均衡点。 - 数据拷贝时尽量用
rknn_inputs_set传入连续内存,避免中途转成std::vector,减少不必要的数据搬运。 - 后处理用多线程异步:把NPU推理和后处理放到不同线程,NPU在跑下一帧时,CPU同时做上一帧的NMS,端到端帧率能提升不少。
6. 反复重启和运行稳定性方面的避坑记录
6.1 NPU初始化失败与驱动版本错配
板子上最常见的一个问题:程序一启动就报错分配NPU资源失败,或者中途崩掉。排查思路先锁定驱动版本。用dmesg | grep rknpu看一下内核加载的NPU驱动打印,再对比板端的librknnrt.so版本。
如果驱动和运行库不匹配,表现多种多样。有一次我在RK3568上遇到的问题是:rknn_init返回成功,但推理两三次后返回RKNN_ERR_PARAM_INVALID,一开始以为是代码异步处理时机不对,排查半天,最后发现是板端的librknnrt.so比转换用的rknn-toolkit2旧了一个大版本,导致部分算子生成的内部指令不兼容。
遇到这种问题,建议直接在板端跑一遍官方demo,如果官方demo也崩或者报错,那就不是你的代码问题,先解决工具链版本;如果官方demo稳定跑满1000次,那再回来看自己的调用逻辑。
6.2 掉帧、卡死与温度降频
RK3568的NPU在长时间高负载下会有明显发热,达到温度阈值后会降频,推理时间波动可能从45ms跳到90ms。这会导致你看起来像是"随机掉帧"。
有两个方向解决:一是从散热入手,加铝片散热或风扇;二是从软件上控制帧率,不要让NPU连续满负荷跑,比如用信号量或定时器控制处理间隔,为NPU留出降温余量。如果做视频流检测但不追求高帧率,比如每200ms只处理一帧,这种降频现象就基本不会出现。
此外,要注意板载电源稳定性。RK3568开发板如果用质量比较差的USB供电,NPU瞬间功耗上升时电压跌落,可能直接导致NPU挂死甚至系统重启。我遇到过类似问题,换成5V/3A以上的电源后,所有随机卡死都消失了。
6.3 多线程并发调用rknn接口的注意点
在用多线程后处理时,很容易踩一个隐雷:rknn_run和rknn_outputs_get并不是线程安全的,尤其是同时操作同一个rknn_context时,轻则时间错乱,重则直接segmentation fault。
正确的做法是:推理上下文只放在一个线程里,后处理放到另一个线程,通过队列把NPU输出数据传递过去。如果你一定要在多个线程并发推理,请为每个线程建立一个独立的rknn_context和对应的模型拷贝。但这样会同时占用多份NPU内存,RK3568的内存有限,需要额外评估。
如果使用了零拷贝接口(zero-copy,rknn_create_mem),还要注意内存在rknn_outputs_get之后才算真正可用,在此之前对输出buffer的读取会读到未定义数据。这个顺序问题在官方demo里一般没问题,但自己改造异步流程时很容易打乱。
我自己在稳定性测试时,会在程序里加一个计数器,连续推理1000帧后自动归零并重新初始化context,这样即使某一帧因为极端情况出现异常,也能通过重启恢复,避免整个进程卡死。对于产品级部署来说,这是很实用的兜底方案。
跑通整条链路后,再回头看RK3568这个平台,你会发现它并不是算力最强的板子,但在功耗和成本上都比较可控。只要做好模型选型、量化校准和前后端切分,YOLOv8n量产后跑到20FPS以上是完全可以实现的。后面如果再折腾出自己的数据集训练、换一个更小更准的backbone,或者接入摄像头实时流,也都是在这个基础上做增量。希望这篇踩坑记录能帮你把前期那些莫名其妙的坑提前绕过去。