ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Hi3559A部署YOLOv5全流程:ONNX转换、INT8量化与C端推理

Hi3559A部署YOLOv5全流程:ONNX转换、INT8量化与C端推理 简介本资源是一套面向计算机类专业学生与嵌入式AI初学者的YOLOv5算法移植实践项目聚焦海思Hisi3559A平台的C语言级部署落地适用于课程设计、期末大作业及毕业设计选题助力从模型推理到嵌入式端侧部署的能力进阶。压缩包共834个文件涵盖358个hpp头文件与248个h接口定义支撑OpenCV DNN模块调用、42个静态库.a与16个动态库.so含libopencv_dnn.so.4.1等核心视觉库、44个XML配置及CMake构建脚本等完整呈现跨平台编译、NNIE加速适配与图像预处理流水线包体大小为43.32MB。已有632人学习下载资源经实机验证稳定运行附详细项目使用说明文档提供可直接复现的目录结构、依赖库链接方式与典型调试排错路径支持二次开发与功能拓展。1. 在海思Hi3559A芯片上跑通YOLOv5不是把PyTorch模型直接拷过去就能用的很多刚接触嵌入式AI部署的工程师看到“YOLOv5 Hi3559A”就下意识去配conda环境、装torchvision、跑detect.py——结果在开发板上连编译都过不去。Hi3559A是海思推出的高性能多媒体SoC内置双核Cortex-A73 双核Cortex-A53搭配独立NNIE神经网络加速引擎但它不支持Python解释器、不兼容PyTorch运行时、也没有CUDA驱动栈。所谓“移植部署YOLOv5”本质是把YOLOv5训练出的权重.pt经模型转换→量化→编译→推理流水线最终落地为纯C语言调用的可执行程序在Hi3559A的Linux SDK环境下原生运行。本项目提供的C源码项目使用说明.zip正是这条链路的完整闭环从ONNX导出开始经Hi3559A专用工具链如nnie_sample框架、hiai工具集完成INT8量化与算子映射生成.wk模型文件再由C代码加载、预处理、调用NNIE硬件加速器完成前向推理最后输出bbox坐标与类别ID。适合已有YOLOv5训练经验、需将算法固化到安防IPC、边缘视频分析盒等量产设备的嵌入式AI工程师。2. 模型转换与量化从PyTorch.pt到Hi3559A可加载的.wk文件2.1 导出ONNX模型并适配NNIE约束Hi3559A的NNIE引擎不支持动态shape、不支持部分PyTorch算子如torch.nn.Upsample的scale_factor模式、Softmax的dim-1自动推导必须先将YOLOv5模型导出为严格符合NNIE输入规范的ONNX。常见错误是直接用torch.onnx.export()导出默认ONNX导致后续hiai工具报错Unsupported op type: Upsample或Invalid input shape。# yolov5/export_onnx.py —— 必须显式指定input_shape和opset_version import torch from models.experimental import attempt_load model attempt_load(yolov5s.pt, map_locationcpu) model.eval() # 固定输入尺寸NNIE要求H/W必须为16倍数且batch1 dummy_input torch.randn(1, 3, 640, 640) # 注意不是320/416640是Hi3559A典型输入 torch.onnx.export( model, dummy_input, yolov5s_640x640.onnx, opset_version11, # NNIE仅支持opset 11及以下 do_constant_foldingTrue, input_names[images], output_names[output], # YOLOv5输出为[bs, num_boxes, 5nc]单输出tensor dynamic_axesNone # 禁用dynamic_axesNNIE不支持 )提示opset_version11是硬性要求dynamic_axesNone必须显式设为None而非默认值否则ONNX会隐含动态batch维度NNIE加载失败。2.2 使用hiai工具链完成INT8量化与模型编译Hi3559A SDK提供hiai命令行工具位于Hi3559AV100_SDK_V2.0.2.0/hi3559av100/osdrv/penv/tools/pc/hiai需在Ubuntu 16.04/18.04 x86_64环境运行。量化过程分两步先生成校准数据集Calibration Dataset再执行量化编译。步骤1准备校准图像至少100张真实场景图# 创建calib_data目录放入JPG/PNG格式图像尺寸需与ONNX输入一致640x640 mkdir -p calib_data # 使用OpenCV批量缩放保持长宽比padding非简单resizeNNIE对pad方式敏感 python3 resize_calib.py --src_dir ./raw_images --dst_dir ./calib_data --size 640resize_calib.py核心逻辑import cv2 import numpy as np def letterbox(img, new_shape(640, 640), color(114, 114, 114)): h, w img.shape[:2] r min(new_shape[0]/h, new_shape[1]/w) new_unpad int(round(w * r)), int(round(h * r)) dw, dh new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1] dw, dh dw // 2, dh // 2 img cv2.resize(img, new_unpad, interpolationcv2.INTER_LINEAR) img cv2.copyMakeBorder(img, dh, dh, dw, dw, cv2.BORDER_CONSTANT, valuecolor) return img步骤2执行量化编译# 进入hiai工具目录 cd /path/to/hiai # 生成校准表.bin文件 ./hiai --modecalibration \ --model_typeonnx \ --model./yolov5s_640x640.onnx \ --input_shape1,3,640,640 \ --calibration_data_path./calib_data \ --calibration_table./yolov5s.calib # 编译为NNIE可加载的.wk模型 ./hiai --modecompile \ --model_typeonnx \ --model./yolov5s_640x640.onnx \ --input_shape1,3,640,640 \ --precision_modeINT8 \ --calibration_table./yolov5s.calib \ --output_model./yolov5s_640x640.wk参数含义必填性--input_shape必须与ONNX导出时完全一致格式N,C,H,W字符串带引号必填--calibration_table校准表路径由上一步生成必填INT8模式--precision_modeINT8推荐、FP16精度高但速度慢必填--output_model输出.wk文件名后缀不可更改必填注意若编译报错Failed to load model90%原因是ONNX中存在NNIE不支持算子。此时需修改YOLOv5模型结构——例如将Upsample替换为nn.ConvTranspose2d或将Focus层展开为sliceconcat操作再重新导出ONNX。3. C源码工程结构解析与关键API调用逻辑3.1 项目C源码目录树与核心文件职责解压C源码项目使用说明.zip后典型目录结构如下hisi_yolov5_c/ ├── Makefile # 交叉编译规则指定arm-himix200-linux-gcc ├── inc/ │ ├── sample_comm.h # 海思公共头文件VENC/VDEC/VI/VO等模块封装 │ ├── sample_comm_nnie.h # NNIE专用头文件NNIE相关结构体、宏定义 │ └── yolov5_postproc.h # 自定义后处理头文件NMS、坐标解码 ├── src/ │ ├── sample_yolov5.c # 主流程VI获取图像 → NNIE推理 → VO显示结果 │ ├── yolov5_postproc.c # 后处理实现将NNIE输出的float*解码为bboxclsconf │ └── main.c # 程序入口初始化SDK、启动sample_yolov5 ├── data/ │ ├── yolov5s_640x640.wk # 已编译模型文件必须与C代码中路径一致 │ └── labels.txt # 类别名称列表每行一个类别索引对应输出cls_id └── script/ └── run.sh # 一键启动脚本mount nfs、设置环境变量、执行./sample_yolov53.2 NNIE推理核心代码段详解sample_yolov5.c中NNIE推理部分简化版// 1. 初始化NNIE模型 SAMPLE_COMM_NNIE_LOAD_MODEL_S stNnieModel; stNnieModel.enNnieType SVP_NNIE_YOLOV5; // 关键指定YOLOv5专用解析器 stNnieModel.pszModelFile ./data/yolov5s_640x640.wk; s32Ret SAMPLE_COMM_NNIE_LoadModel(stNnieModel, s_stYolov5Nnie); if (s32Ret ! HI_SUCCESS) { printf(Load model failed!\n); return s32Ret; } // 2. 构造输入Blob注意内存对齐与格式 SAMPLE_COMM_NNIE_FORWARD_S stForward; stForward.stSrcData.u64PhyAddr s_stYolov5Nnie.stTskBuf.u64PhyAddr; // 物理地址 stForward.stSrcData.u32Width 640; stForward.stSrcData.u32Height 640; stForward.stSrcData.u32Chn 3; stForward.stSrcData.enDataType SVP_BLOB_TYPE_U8; // 输入为UINT8非FLOAT32 // 3. 执行推理同步阻塞调用 s32Ret HI_MPI_NNIE_Forward(s_stYolov5Nnie.stNnieHandle, stForward, NULL, HI_TRUE); if (s32Ret ! HI_SUCCESS) { printf(NNIE forward failed!\n); return s32Ret; } // 4. 获取输出Blob指针NNIE输出为float32需转为CPU可读 SVP_BLOB_S *pstDstBlob s_stYolov5Nnie.astSegData[0].astDst[0]; HI_U32 u32Len pstDstBlob-u32Num * pstDstBlob-u32Stride; HI_FLOAT *paf32Result (HI_FLOAT*)malloc(u32Len); HI_MPI_SYS_MmzCopy(pstDstBlob-u64PhyAddr, (HI_U64)(HI_UL)paf32Result, u32Len);逻辑说明SAMPLE_COMM_NNIE_LOAD_MODEL_S结构体中的enNnieType SVP_NNIE_YOLOV5是关键它告诉NNIE驱动按YOLOv5的输出格式[x,y,w,h,conf,cls0,cls1,...]解析内存布局HI_MPI_NNIE_Forward是同步调用返回即表示硬件推理完成无需轮询状态HI_MPI_SYS_MmzCopy用于将NNIE输出的物理内存DMA buffer拷贝到用户态虚拟内存因NNIE输出blob的u64PhyAddr不能被CPU直接访问。3.3 后处理从NNIE原始输出解码出检测框yolov5_postproc.c中Yolov5PostProc函数核心逻辑void Yolov5PostProc(HI_FLOAT *paf32Result, HI_U32 u32BoxCnt, DETECTION_S *pstDet) { // Step1: 解析原始输出假设输出为[1, 25200, 85] → 展开为一维数组 // 其中25200 3*(80*80 40*40 20*20)85 4(xywh)1(conf)80(cls) for (HI_U32 i 0; i u32BoxCnt; i) { HI_FLOAT *pBox paf32Result i * 85; HI_FLOAT conf pBox[4]; // 第5个元素为objectness score if (conf 0.3f) continue; // 置信度过滤阈值 // Step2: 计算最终坐标需乘以stride并加anchor偏移 HI_FLOAT x (pBox[0] * 2.0f - 0.5f grid_x) * stride; // grid_x/stride来自YOLOv5网格计算 HI_FLOAT y (pBox[1] * 2.0f - 0.5f grid_y) * stride; HI_FLOAT w expf(pBox[2]) * anchor_w; HI_FLOAT h expf(pBox[3]) * anchor_h; // Step3: 分类得分 conf * max_cls_prob HI_FLOAT max_cls 0.0f; HI_U32 cls_id 0; for (HI_U32 j 5; j 85; j) { if (pBox[j] max_cls) { max_cls pBox[j]; cls_id j - 5; } } HI_FLOAT final_score conf * max_cls; // Step4: 存入DETECTION_S结构体供VO模块绘制 pstDet-x1 (HI_S32)(x - w/2); pstDet-y1 (HI_S32)(y - h/2); pstDet-x2 (HI_S32)(x w/2); pstDet-y2 (HI_S32)(y h/2); pstDet-score final_score; pstDet-cls_id cls_id; pstDet; } }参数说明grid_x/grid_y根据输出层索引0~25199反推其在特征图上的坐标需查表或计算stride对应层步长8/16/32anchor_w/anchor_h取自YOLOv5训练时的anchorsexpf(pBox[2])YOLOv5对wh使用exp()激活必须还原final_score conf * max_clsYOLOv5 v5.0采用此公式非简单取max_cls。4. 交叉编译与板端部署全流程实操4.1 配置Hi3559A SDK并构建交叉编译环境Hi3559A官方SDKHi3559AV100_SDK_V2.0.2.0必须安装在Ubuntu 16.04/18.04官方仅验证此版本。关键步骤# 1. 解压SDK并执行安装脚本 tar -xvf Hi3559AV100_SDK_V2.0.2.0.tgz cd hi3559av100/Hi3559AV100_SDK_V2.0.2.0 ./sdk.unpack # 2. 编译交叉编译工具链生成arm-himix200-linux-gcc cd osdrv/toolchain/arm-himix200-linux ./build_toolchain.sh # 3. 设置环境变量写入~/.bashrc export SDK_PATH/path/to/hi3559av100/Hi3559AV100_SDK_V2.0.2.0 export CROSS_COMPILE$SDK_PATH/osdrv/toolchain/arm-himix200-linux/bin/arm-himix200-linux- export PATH$CROSS_COMPILE:$PATH验证编译器$ arm-himix200-linux-gcc -v # 输出应包含 gcc version 6.3.0 (Hisilicon_v500)4.2 修改Makefile适配项目路径与依赖hisi_yolov5_c/Makefile关键片段# 工具链路径必须指向SDK中实际路径 CROSS_COMPILE ? arm-himix200-linux- # SDK头文件路径必须包含sample_comm_nnie.h所在目录 INCDIR -I$(SDK_PATH)/osdrv/opensource/kernel/linux-4.9.y/include/generated \ -I$(SDK_PATH)/mpp/include \ -I$(SDK_PATH)/osdrv/opensource/alsa-lib/include \ -I./inc # SDK库文件路径 LIBDIR -L$(SDK_PATH)/mpp/lib \ -L$(SDK_PATH)/osdrv/opensource/alsa-lib/lib # 链接NNIE库顺序不能错 LDLIBS -lnnie -lmpi -lsys -lcommon -lpthread -lm -ldl # 编译目标 TARGET sample_yolov5 $(TARGET): $(OBJ) $(CROSS_COMPILE)gcc -o $ $^ $(LIBDIR) $(LDLIBS) %.o: %.c $(CROSS_COMPILE)gcc -c -o $ $ $(INCDIR) -Wall -O2注意-lnnie必须放在链接命令最左侧否则undefined reference to HI_MPI_NNIE_Forward-lmpi依赖-lsys顺序错误会导致链接失败。4.3 板端运行与实时性能验证将编译好的sample_yolov5、.wk模型、labels.txt拷贝至开发板/mnt/nfs/目录通过NFS挂载# 板端操作root权限 # 1. 加载NNIE驱动 insmod /mnt/nfs/ko/hi3559av100/ko/ko/nnie.ko # 2. 设置环境变量确保能加载.so export LD_LIBRARY_PATH/mnt/nfs/lib:$LD_LIBRARY_PATH # 3. 运行程序-i参数指定输入源0VI摄像头1本地文件 /mnt/nfs/sample_yolov5 -i 0 -m /mnt/nfs/data/yolov5s_640x640.wk # 4. 查看实时FPSNNIE硬件加速下640x640输入典型值23~28 FPS # 日志中会打印[YOLOV5] Process frame: 25.6 fps性能关键参数实测对比表Hi3559A 1.6GHz输入尺寸模型类型量化精度平均FPSCPU占用率备注640×640yolov5sINT826.312%推荐配置平衡精度与速度640×640yolov5mINT814.718%精度↑速度↓320×320yolov5sINT841.29%小尺寸适合远距离小目标640×640yolov5sFP1618.525%精度略高但无速度优势提示FPS数值受VI输入帧率限制若摄像头只输出25fps则程序无法超过25fps。可通过HI_MPI_VI_SetFrameRate调整VI帧率。5. 常见编译/运行故障定位与修复技巧5.1 编译阶段高频报错及根因报错信息根因修复方案error: unknown type name ‘SVP_BLOB_S’sample_comm_nnie.h未正确包含或SDK路径错误检查-I路径是否指向$(SDK_PATH)/mpp/include确认该目录下存在hi_comm_nnie.hundefined reference to ‘HI_MPI_NNIE_Forward’链接时-lnnie位置错误或nnie.ko未加载将-lnnie移至LDLIBS最前板端执行lsmod | grep nnie确认驱动已加载segmentation fault (core dumped)HI_MPI_SYS_MmzCopy参数错误或pstDstBlob-u64PhyAddr为空在HI_MPI_NNIE_Forward后添加HI_MPI_SYS_GetPhyAddr检查物理地址有效性确认pstDstBlob已正确初始化5.2 运行时检测结果异常的3个必查点当程序运行无报错但检测框乱飞/全为背景类时按优先级排查输入图像预处理不匹配检查C代码中SAMPLE_COMM_NNIE_FillRect填充的RGB顺序是否与YOLOv5训练时一致BGR vs RGB。Hi3559A VI默认输出NV12需在SAMPLE_COMM_VI_GetImage后调用SAMPLE_COMM_VI_ConvertFormat转为RGB24并确认cv::cvtColor或海思API的转换方向。后处理anchor参数错位YOLOv5不同版本v5.0/v6.0/v6.2的anchors数值不同。打开训练时的models/yolov5s.yaml提取anchors:字段硬编码到yolov5_postproc.c中对应层的anchor_w/anchor_h数组。NNIE输出blob维度解析错误u32BoxCnt pstDstBlob-u32Num可能不等于25200如模型导出时grid尺寸错误。在HI_MPI_NNIE_Forward后添加调试日志printf(NNIE output: num%u, stride%u, width%u, height%u\n, pstDstBlob-u32Num, pstDstBlob-u32Stride, pstDstBlob-u32Width, pstDstBlob-u32Height);若u32Num远小于25200说明ONNX导出时output_shape未固定需回溯检查torch.onnx.export参数。5.3 快速验证模型是否生效的“三步法”无需等待完整检测流程用最小指令验证NNIE硬件通路# 1. 确认NNIE驱动加载 lsmod | grep nnie # 应输出nnie 123456 0 - Live 0x0000000000000000 # 2. 运行NNIE基础示例SDK自带 cd $SDK_PATH/samples/nnie/01_basic ./sample_nnie_main 0 # 参数0表示运行basic test成功则打印NNIE basic test success # 3. 用本项目模型替换测试 cp /mnt/nfs/data/yolov5s_640x640.wk ./data/ ./sample_nnie_main 1 # 参数1表示加载自定义.wk成功则打印NNIE custom model load success若第3步失败问题100%在.wk文件本身ONNX导出或量化环节与C代码无关。此时应重新执行2.2节量化流程并用hiai --modecheck --modelxxx.wk验证模型完整性。本文还有配套的精品资源点击获取
返回列表