ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

YOLOv8在Hi3516CV610上的部署实战:从模型量化到板端推理

YOLOv8在Hi3516CV610上的部署实战:从模型量化到板端推理 1. 从算力焦虑到落地共识Hi3516CV610上的YOLOv8怎么玩先说点实际的。很多做嵌入式视觉的朋友拿到Hi3516CV610的第一反应都是这颗料能不能跑YOLOv8我的答案是可以但和你在x86显卡上那种“跑”完全不是一回事。YOLOv8作为Ultralytics在2023年推出的anchor-free检测框架凭借C2f结构、Decoupled Head和动态标签分配策略迅速成了算法侧的事实标准。而Hi3516CV610这颗海思新一代轻量级IPC SoC主打的是低功耗、低成本、AIoT场景内部集成了一颗算力大约在0.5TOPS到1TOPS量级的NPU——注意这个量级意味着你不能指望去跑原版YOLOv8s甚至YOLOv8m必须走“小模型量化算子适配”这条路。我写这篇东西的初衷是网上关于YOLOv8训练和RK3588部署的资料一大堆但Hi3516CV610相关的实战记录非常零散很多人在模型转换阶段就被卡住了。这文章我会从训练侧、导出侧、转换侧到板端C推理侧完整过一遍把我踩过的坑和验证过的方法都交代清楚。不管你是刚接触海思平台的新手还是已经从海思3516系列老平台迁过来的老手这篇文章的思路都能直接参考。IP摄像头、边缘盒子、工业检测面板这类场景Hi3516CV610的定位就是极致性价比。2. 平台底细与选型逻辑为什么是Hi3516CV6102.1 芯片定位和NPU特性先搞清楚这颗芯片的底细。Hi3516CV610属于海思IPC SoC序列里面的CV系列主打的是“轻量级智能前端”。比起老款的Hi3516DV300或者Hi3516EV200它在AI算力上做了明显增强但整体定位依然是在低功耗、低码流、小体积的摄像头模组市场。这颗芯片的NPU有几个关键特性直接决定了你后续怎么做模型适配算力有限官方标称基本在1TOPS以内Keras/Caffe/TF模型支持有限主力是Caffe和ONNX导出的模型但实际开发中ONNX是最顺的一条路。支持INT8量化这意味着你的模型必须走量化这条路FP16在某些场景下可能支持但INT8才是性能最优解。算子兼容性要求高海思的NNPNeural Network Processor工具链对某些高级算子支持不好比如部分Transformer结构、某些特殊的激活函数在转换时会直接报不支持。所以整个项目的技术选型逻辑就清楚了YOLOv8n作为基座参数最少、计算量最低训练后导出ONNX再通过海思工具链做INT8量化生成WK模型最后在板端用C加载WK模型做推理。2.2 为什么不选YOLOv5或YOLOv7这是很多人的疑问。YOLOv5在海思平台上的资料更多转换也相对成熟为什么不继续用我的判断是YOLOv8相比v5在训练侧的收益很明显——更强的特征提取能力、更简洁的部署流程不需要额外的anchor超参、更好的小目标表现。而转换侧的算子差异其实没有想象中那么大现在工具链已经能较好地处理Decoupled Head的拼接和输出。真正让你觉得“v8不好转”的大概率是输出层后处理部分没有处理好。而且从工程迭代的角度看YOLOv8整个生态更活跃后续如果要做旋转框、关键点、分割任务都在同一个框架体系内不用频繁切换技术栈。这个长远账算下来选择v8是合理的。2.3 项目整体技术栈先把我这次用的整体技术栈列出来后面章节逐个展开模块选型说明训练框架Ultralytics YOLOv88.0.x以上版本注意锁版本训练GPUGTX 1660 Ti / RTX 5060显存有限就老老实实用nano模型模型导出ONNX Opset 11兼容海思工具链量化工具海思RuyiStudio / NNP工具链视SDK版本而定板端推理C/C SDK API加载WK模型NV12输入后处理C手写Decode不依赖OpenCV的DNN模块这套组合的好处是训练侧完全在常规PC上完成不依赖海思的仿真环境转换侧走标准ONNX→WK流程板端只需要一个轻量的C推理引擎。整个链路每一环都能独立调试出了问题上手快。3. 训练侧准备如何训练一个适合NPU的YOLOv8模型3.1 环境配置和版本锁定先给新人一个忠告YOLOv8的环境配置没那么玄乎但版本锁定是个大坑。我自己第一次配的时候就是随便pip install ultralytics结果装到了最新版后来发现部分API变了训练脚本跑不通。建议直接这样操作conda create -n yolov8 python3.9 -y conda activate yolov8 pip install torch2.0.1 torchvision0.15.1 --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics8.0.136 pip install onnx1.14.1 onnxsim0.4.36选8.0.136这个版本是因为它比较稳定且导出的ONNX结构相对简洁后期在NNP工具链里踩坑的概率小。没有必要追新追新往往就是在给工具链适配找麻烦。如果手里是GTX 1660 Ti这种6GB显存的卡建议训练分辨率不要超过640x640batch size控制在8以内。如果是RTX 5060这种新一代卡显存会宽裕很多但依然不建议盲目上大模型。3.2 数据集组织和标注YOLOv8对数据集格式有明确要求规范组织可以省掉后面非常多的麻烦。目录结构如下datasets/ └── your_dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── data.yamldata.yaml里面写好路径、类别数和类别名path: ./datasets/your_dataset train: images/train val: images/val nc: 2 names: [person, dog]这里有两个实操要点第一类别不要贪多。Hi3516CV610这种平台适合2到5个类别的任务类别一多模型的参数量和计算量会明显上升小模型扛不住。第二数据质量比数据量重要。NPU平台对光照、模糊、遮挡的敏感度和GPU平台不完全一样因为INT8量化会损失一部分精度如果训练数据本身就模糊不清量化后基本就没法用了。3.3 训练参数设置freeze和轻量化训练侧最核心的决策就是模型选择。我强烈建议直接用YOLOv8n不要用s或m。很多人觉得n太小、精度不够但实际上在Hi3516CV610上n模型配合足够的训练轮次和有效的数据增强精度可以达到实用水平。训练命令参考如下yolo detect train \ datadatasets/your_dataset/data.yaml \ modelyolov8n.pt \ epochs200 \ imgsz640 \ batch8 \ lr00.01 \ freeze10 \ device0这里重点说下freeze参数。freeze10表示冻结前10层不参与训练这个做法的好处是加快收敛并减少过拟合。但对于从零训练的数据集不建议冻结太多层10层左右是个平衡点。如果你用的是自己的私有数据集且数据量不大freeze10能明显提升训练稳定性。训练过程中你一定会关心损失函数曲线。YOLOv8会默认在runs/train/exp/目录下输出results.png里面包含了box_loss、cls_loss、dfl_loss的曲线。如果发现自己数据集上box_loss下降缓慢优先检查学习率和anchor设置虽然v8是anchor-free但底层还是有类似机制。3.4 训练完如何画损失函数曲线图说起损失函数曲线YOLOv8训练完自带的results.png就够用了。但有些人想自己画更个性化的曲线图或者想把多次实验对比到一张图上这里分享一个我自己常用的脚本思路import pandas as pd import matplotlib.pyplot as plt results pd.read_csv(runs/detect/train/results.csv) plt.figure(figsize(10, 6)) plt.plot(results[ epoch], results[ train/box_loss], labelbox_loss) plt.plot(results[ epoch], results[ train/cls_loss], labelcls_loss) plt.plot(results[ epoch], results[ train/dfl_loss], labeldfl_loss) plt.xlabel(epoch) plt.ylabel(loss) plt.legend() plt.grid(True) plt.show()需要注意的是results.csv里的列名可能带了大量空格读取后先print一下列名再索引。这个脚本后期做实验对比非常有用尤其是判断是否过拟合或者是否需要提前停止。4. 模型导出从PyTorch到ONNX的完整操作4.1 导出命令与Opset选择训练完的模型是PyTorch格式要进入海思工具链第一步得先转成ONNX。Ultralytics已经内置了导出功能命令很简单yolo export modelruns/detect/train/weights/best.pt formatonnx opset11 simplifyTrue但这里有个关键点opset11。海思工具链对高版本opset的支持不够好用opset11是经过很多人验证过的稳妥选择。如果你不指定Ultralytics默认会用比较高的opset转换时容易遇到不支持的算子。simplifyTrue的作用是用onnxsim对模型图做简化去掉一些冗余的节点和恒等映射能减小模型体积、提升转换成功率。我个人建议开启。4.2 导出后必须做的检查验证ONNX输出尺寸导出完成后一定不要急着转换先用下面的方式检查一下ONNX的输出维度。YOLOv8的Detect头是Decoupled Head一个分支输出box预测一个分支输出分类预测。导出为ONNX后默认的输出是一个[num_boxes, num_classes 4]的二维张量比如640x640输入、2个类别时输出shape是[1, 8400, 6]。为什么是8400因为YOLOv8有三个尺度的检测头每个尺度特征图上的anchor点数分别是80x806400、40x401600、20x20400加起来8400个。这个数字后面写后处理代码时要用到。检查ONNX输出的Python脚本import onnx model onnx.load(runs/detect/train/weights/best.onnx) for output in model.graph.output: dims [d.dim_value for d in output.type.tensor_type.shape.dim] print(output.name, dims)正常情况下应该看到类似output0 [1, 8400, 6]的输出。如果不是这个结构检查一下模型版本和导出参数。4.3 裁剪输出层减少后处理压力很多人在这一步会遇到一个优化空间极大的操作把Decoupled Head的输出裁剪成只保留前5个通道4个box坐标 1个类别。这个操作的目的是减小模型输出尺寸因为原始输出有6个通道包含所有类别得分而实际推理时如果类别数很少只保留必要通道能减少板端数据处理量。这个裁剪可以在训练代码里通过改Ultralytics的模型定义实现也可以在ONNX层面做。但我强烈建议在训练侧改改完重新导出别在ONNX图上做手术。具体做法就是在ultralytics/nn/modules/head.py的Detect类的forward方法里把回归和分类分支拼接后按需选择输出的通道。5. 模型转换ONNX到WK的关键步骤5.1 海思工具链的基本认识到了这个阶段你的手里应该有一个best.onnxopset 11、一张板子Hi3516CV610、一套SDK。海思的模型转换工具一般集成在SDK包里面名为NNP工具链或者RuyiStudio不同SDK版本名字有差异。RuyiStudio是Windows下的IDE工具适合新手可视化操作NNP工具链是命令行工具适合集成到自动化脚本里。我的习惯是开发调试用RuyiStudio批量转换用命令行。工具链的核心逻辑是这样把ONNX或者Caffe模型解析成中间表示然后做权重量化和图优化最终生成板端NPU能加载的WK格式文件。这个过程中间涉及三个关键点算子映射、量化校准、内存规划。5.2 量化校准集的准备INT8量化最重要的就是校准集。校准集不能随便找几张图我见过很多人偷懒用训练集的前几十张图做校准结果部署到现场精度崩了。正确的做法是从验证集中随机选100到200张图覆盖所有类别和各种典型场景白天、夜晚、逆光、近景、远景。这些图需要预处理成模型输入格式通常是RGB、640x640、归一化到0到1。校准集在一个文件夹里放好然后工具链会读取这些图片统计每层激活值的分布从而确定量化参数。这里有个容易忽略的坑校准集的预处理必须和训练时保持一致。YOLOv8训练时的预处理包含letterbox等比缩放填充如果你直接用原图缩放分布就不对。我在之前的实践中踩过这个坑量化后的模型在板子上输出全偏到一边排查了半天才发现是校准集预处理和训练预处理不一致。5.3 转换配置文件的编写以命令行工具为例转换配置文件一般长这样[model] model_type ONNX input_model ./best.onnx output_model ./yolov8n.wk input_name images input_shape 1x3x640x640 mean 0.0, 0.0, 0.0 std 1.0, 1.0, 1.0 [quant] quant_type int8 calibration_data ./calibration_bin calibration_data_type bin注意mean和std的写法取决于你的ONNX模型里是否已经包含了归一化操作。如果训练时的预处理里已经做了除以255那么这里mean/std就写成0和1在板端预处理时需要把输入数据转成float再除以255。如果这里配了mean/std工具链会尝试在模型中插入归一化层但有时候会引发奇怪的问题建议保持简单。5.4 常见转换报错与解决办法我在转YOLOv8 onnx到wk时遇到最典型的错误有三个这里直接分享排查方法第一类算子不支持。报类似Unsupported op: xxx的错误。解决办法是改模型结构把不支持的算子替换掉比如把SiLU激活换成ReLU不过会影响精度一般不建议或者在导出ONNX时用opset11让算子尽量走标配。YOLOv8里主要是SiLU和DFL如果DFL算子不支持很多时候需要手动把DFL的后处理部分拆出来放到板端CPU做模型的输出只保留box和cls的原始预测。第二类量化精度严重下降。第一反应是校准集有问题换个更多样化的校准集其次检查是否有数值范围特别大的层可考虑对特定层跳过量化NNP工具链支持per-layer的量化配置把敏感层设置成float或者提高量化位数。第三类模型转换成功但板端输出全零或乱码。大概率是输入数据排布或者预处理和转换时不一致。Hi3516CV610的NPU输入通常是NV12格式灰度或YUV数据如果你在板端把RGB数据直接塞进去出来的结果大概率是乱的。后面我会详细说板端预处理。6. 板端部署Hi3516CV610上的C推理全流程6.1 SDK环境准备和交叉编译转换出yolov8n.wk后真正的板端部署才刚开始。首先你需要把Hi3516CV610的SDK包在Linux服务器上解压然后配置交叉编译环境。我的做法是在服务器上建一个独立的编译目录把SDK里的lib和include软链出来然后写一个CMakeLists.txt编译一个独立的推理可执行文件最后通过NFS或者TF卡拷贝到板子上运行。CMakeLists.txt的关键部分参考cmake_minimum_required(VERSION 3.10) project(yolov8_demo) set(CMAKE_CROSSCOMPILING TRUE) set(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_PROCESSOR arm) set(HI3516_SDK_PATH /path/to/Hi3516CV610_SDK) set(CMAKE_C_COMPILER ${HI3516_SDK_PATH}/arm-none-linux-gnueabi/bin/arm-none-linux-gnueabi-gcc) set(CMAKE_CXX_COMPILER ${HI3516_SDK_PATH}/arm-none-linux-gnueabi/bin/arm-none-linux-gnueabi-g) include_directories(${HI3516_SDK_PATH}/include) link_directories(${HI3516_SDK_PATH}/lib) add_executable(yolov8_demo main.cpp) target_link_libraries(yolov8_demo nnpsdk pthread dl opencv_imgproc # 如果用OpenCV的话 opencv_core )注意NNP SDK的具体库名可能和这里不同根据自己的SDK版本修改。实在找不到头文件时优先看看SDK文档里的sample目录海思sample里一般有非常接近的示例代码。6.2 模型加载和输入预处理板端代码的第一步是加载WK模型并创建NNP推理上下文。基本流程是初始化NNP环境加载模型文件获取模型输入输出信息分配输入输出内存输入部分是最容易出问题的。Hi3516CV610的NNP输入一般支持YUV420SPNV12、RGB等格式。YOLOv8训练时用的是RGB三通道所以你有两个选择选择A在板端先把图像转成RGB再喂给NPU。 选择B把模型的输入改成单通道然后用YUV的Y分量作为灰度输入只适合不需要彩色的场景。实际项目中大部分场景需要彩色信息所以选择A更常见。板端从摄像头拿到的是NV12数据处理链路是NV12 → RGB可用海思提供的硬件加速API → letterbox到640x640 → 转float并除以255 → 喂给NPU。letterbox是重点必须和训练时保持一致。YOLOv8的letterbox做法是按比例缩放图像使长边等于640短边填充灰边114到640。推理时得到的目标框坐标需要按缩放比例映射回原图。下面是一个简单的letterbox参考代码片断cv::Mat letterbox(const cv::Mat src, int target_size, float scale, int pad_x, int pad_y) { int w src.cols; int h src.rows; scale std::min(target_size * 1.0 / w, target_size * 1.0 / h); int new_w round(w * scale); int new_h round(h * scale); cv::Mat resized; cv::resize(src, resized, cv::Size(new_w, new_h), 0, 0, cv::INTER_LINEAR); int pad_w target_size - new_w; int pad_h target_size - new_h; pad_x pad_w / 2; pad_y pad_h / 2; cv::Mat canvas cv::Mat::zeros(target_size, target_size, CV_8UC3); canvas.setTo(cv::Scalar(114, 114, 114)); resized.copyTo(canvas(cv::Rect(pad_x, pad_y, new_w, new_h))); return canvas; }6.3 NPU推理和后处理解码模型推理调用比较简单基本就是SetInput - Run - GetOutput。关键在于拿到输出后如何解码。YOLOv8的输出不是原始的坐标类别而是需要经过DFLDistribution Focal Loss解码才能得到最终的box坐标。常见做法是先在板端用NPU算完模型主体然后DFL的积分部分放到CPU上做。不过我目前验证过的最省事路径是这样在导出ONNX时把DFL相关的解码操作直接写进模型这样板端拿到输出后直接就得到坐标偏移量和类别得分省掉了自己在C里写一堆DFL解码代码的麻烦。如果不想在模型里包含DFLC里的解码也不复杂核心公式如下// 对每个anchor坐标解码 float cx (dfl_decoded_x anchor_x) * stride; float cy (dfl_decoded_y anchor_y) * stride; float w dfl_decoded_w * stride; float h dfl_decoded_h * stride;解码后做阈值过滤去掉置信度低于阈值的框然后做NMS去除重叠框。6.4 性能调优NNP内存复用和多线程思想Hi3516CV610的算力不高好在YOLOv8n的计算量也不大。实测下来640x640输入、单帧推理时间大概在80到150毫秒之间具体看NPU频率和DDR配置也就是能达到7到12FPS。如果你需要更高帧率有两个思路第一降低输入分辨率到416x416推理时间能进一步缩短代价是精度下降小目标变差。第二考虑模型剪枝。YOLOv8n本身已经很小了但还可以在训练阶段把C2f的宽度因子调小比如用0.25倍宽度能显著降低参数量。注意这需要在训练前就改配置而不是训练后去剪枝后者工程复杂度高很多。6.5 C部署中的内存管理和稳定性嵌入式部署最烦人的就是内存管理。NPU推理需要分配物理连续内存建议在初始化阶段一次性分配好输入输出缓冲区不要每帧都申请释放。这样既能避免内存碎片问题也能减少耗时。还有一点海思SDK下的某些API不是线程安全的。如果要做多路并发或者异步推理记得加锁或者串行化否则会有随机崩溃、内存越界这些莫名其妙的问题。我的经验是先跑通单路再考虑多路。7. 常见问题与排查技巧实录7.1 模型转换成功但精度不行一套排查顺序先确认训练侧模型本身的mAP是否可用把best.pt在PC上用验证集跑一下看看原始精度的上限。确认ONNX模型输出和PyTorch模型输出是否一致用相同的输入图分别跑PyTorch和ONNX对比输出结果的差异差异大说明导出环节有问题。确认量化校准集是否和训练分布一致校准集图像数量、分辨率、内容覆盖度逐一排查。换一种量化粒度有的工具链支持per-channel量化相比per-tensor精度会好一些代价是模型稍微变大。7.2 板端推理结果偏移这是常见的坐标系不一致问题。训练侧letterbox做了填充如果解码时没有反算填充的偏移量框的位置就会整体偏移。检查你的解码代码里是否减去了pad_x、pad_y。7.3 时序优化从150ms降到90ms的实践我在另一个项目上实际做过一轮时序优化战绩是从150ms降到90ms方法就是三板斧第一输入图像从1080p先缩放到640x640缩放用硬件去完成别用CPU我这里用的opencv resize其实还有更快的内置硬件接口看SDK版本。第二把DFL解码并入模型减轻CPU负担完成一部分解码在NPU端。第三后处理NMS改用快速排序剪枝策略去掉一些重复计算。这三个手段单独看每项都不复杂但组合起来效果非常显著。7.4 和RK3588部署的差别现在市面是RK3588这类平台的教程非常多用户群体的声量大其实部署原理相差不大都是训练-导出-转换-板端推理的套路。但注意RK3588的NPU算力远强于Hi3516CV610且支持动态shape和更丰富的算子。你会发现同一个YOLOv8模型在RK3588上几乎不用改任何结构而在Hi3516CV610上你得做算子规避、输入分辨率调小、通道裁剪这些操作。这恰恰是轻量级平台部署的乐趣所在——在资源受限的条件下把系统调到最优才是嵌入式算法工程师的核心竞争力。8. 量化这一关过好后面都是坦途最后再分享一个我个人在反复实践后总结的结论YOLOv8在Hi3516CV610上的部署成败80%取决于量化这一关。算子兼容性、精度损失、转换失败最终都能归结到量化策略的选择上。如果你拿到一个全新模型不要急着改结构先按默认参数跑一遍INT8量化看看精度损失有多大如果损失在可接受范围内后面就一路顺风如果损失很大再考虑逐层排查敏感层或者调整校准集分配。另外一个很值得做的经验是在项目启动阶段就把板子上的验证环境搭好每次训练完模型立刻走一遍PC仿真或板端测试不要攒到最后统一转否则批量出问题的时候排查起来会让你怀疑人生。希望这片实操记录能帮你省掉几周弯路。如果你也在做类似的海思平台部署项目欢迎带着具体问题来交流。
返回列表