首页
商城
  • English
  • 简体中文
首页
商城
  • English
  • 简体中文
  • 产品系列

    • FPGA+ARM

      • GM-3568JHF

        • 一、简介

          • GM-3568JHF 简介
        • 二、快速开始

          • 00 前言
          • 01 环境搭建
          • 02 编译说明
          • 03 烧录指南
          • 04 调试工具
          • 05 软件更新
          • 06 查看信息
          • 07 测试命令
          • 08 应用编译
          • 09 源码获取
        • 三、外设与接口

          • 01 USB
          • 02 显示与触摸
          • 03 以太网
          • 04 WIFI
          • 05 蓝牙
          • 06 TF-Card
          • 07 音频
          • 08 串口
          • 09 CAN
          • 10 RTC
        • 四、应用开发

          • 01 UART读写案例
          • 02 按键检测案例
          • 03 LED灯闪烁案例
          • 04 MIPI屏幕检测案例
          • 05 读取 USB 设备信息案例
          • 06 FAN 检测案例
          • 07 FPGA FSPI 通信案例
          • 08 FPGA DMA 读写案例
          • 09 GPS调试案例
          • 10 以太网测试案例
          • 11 RS485读写案例
          • 12 FPGA IIC 读写案例
          • 13 PN532 NFC读卡案例
          • 14 TF卡读写案例
        • 五、QT开发

          • 01 ARM64交叉编译器环境搭建
          • 02 QT 程序加入开机自启服务
        • 六、RKNN_NPU开发

          • 01 RK3568 NPU 概述
          • 02 开发环境搭建
          • 运行官方 YOLOv5 示例
        • 七、FPGA开发

          • ARM与FPGA通讯
          • FPGA开发手册
        • 八、其他

          • 01 根目录文件系统的修改
          • 02 系统自启服务
        • 九、资料下载

          • 资料下载
    • ShimetaPi

      • M4-R1

        • 一、简介

          • M4-R1简介
        • 二、快速上手

          • 01 OpenHarmony概述
          • 02 镜像烧录
          • 03 应用开发快速上手
          • 04 设备开发快速上手
        • 三、应用开发

          • 01 ArkUI

            • 1 ArkTS语言简介
            • 2 UI 组件-Row 容器介绍
            • 3 UI 组件-Column 容器介绍
            • 4 UI 组件-Text 组件
            • 5 UI 组件-Toggle 组件
            • 6 UI 组件-Slider 组件
            • 7 UI 组件-Animation 组件&Transition 组件
          • 02 资料获取

            • 1 OpenHarmony 官方资料
          • 03 开发须知

            • 1 Full-SDK替换教程
            • 2 引入和使用三方库
            • 3 HDC调试
            • 4 命令行恢复出厂模式
            • 5 升级App为system权限
          • 04 构建第一个应用

            • 1 构建第一个ArkTs应用-HelloWorld
          • 05 案例

            • 01 串口调试助手应用案例
            • 02 手写板应用案例
            • 03 数字时钟应用案例
            • 04 WIFI 信息获取应用案例
        • 四、设备开发

          • 1 Ubuntu环境开发

            • 01 环境搭建
            • 02 下载源码
            • 03 编译源码
          • 2 使用DevEco Device Tool 工具

            • 01 工具简介
            • 02 开发环境的搭建
            • 03 导入SDK
            • 04 HUAWEI DevEco Tool 功能介绍
        • 五、内核外设与接口

          • 01 指南
          • 02 设备树介绍
          • 03 NAPI 入门
          • 04 ArkTS入门
          • 05 NAPI开发实战演示
          • 06 GPIO介绍
          • 07 I2C通讯
          • 08 SPI通信
          • 09 PWM 控制
          • 10 串口通讯
          • 11 TF卡
          • 12 屏幕
          • 13 触摸
          • 14 Ethernet(以太网)
          • 15 M.2 硬盘
          • 16 音频
          • 17 WIFI & BT
          • 18 摄像头
        • 六、资料下载

          • 资料下载
      • M5-R1

        • 一、简介

          • M5-R1 开发文档
        • 二、快速上手

          • 镜像烧录
          • 环境搭建
          • 下载源码
        • 三、外设与接口

          • 树莓派接口
          • GPIO 接口
          • I2C 接口
          • SPI通信
          • PWM控制
          • 串口通讯
          • TF Card
          • 屏幕
          • 触摸
          • 音频
          • RTC
          • Ethernet
          • M.2
          • MINI-PCIE
          • Camera
          • WIFI&BT
        • 四、资料下载

          • 资料下载
      • Pico-G1

        • 一、产品概述

          • 01 产品介绍
          • 02 SDK版本基本信息
        • 二、快速入门

          • 01 开发环境搭建
          • 02 镜像编译
          • 03 镜像烧录
          • 04 系统登录
          • 05 网络配置
          • 06 文件传输
          • 07 SDK目录结构
          • 08 部署第一个应用程序
          • 09 部署第一个驱动程序
          • 10 SD卡挂载
        • 三、外设与接口

          • 01 GPIO控制
          • 02 UART串口通信
          • 03 I2C 通信
          • 04 SPI 通信
        • 四、MPP媒体开发

          • 01 MPP媒体处理软件
          • 02 图像处理链路
          • 03 视频输入
          • 04 图像编码
        • 五、NPU与AI

          • 01 NPU驱动与运行库架构
          • 02 .xmm 模型加载
          • 03 SVP视频处理
          • 04 AI降噪 (AI_NR)
        • 六、应用程序示例

          • 01 加解密接口应用
          • 02 ADC 采集应用
          • 03 低功耗应用
          • 04 音频处理应用
          • 05 视频编码应用
          • 06 视频输入应用
          • 07 视频图形子系统应用
          • 08 区域叠加应用
          • 09 智能视频引擎应用
          • 10 UVC 网络摄像头应用
          • 11 综合快速演示应用
          • 12 FPN 校正应用
          • 13 区域运动检测应用
          • 14 MTCNN 人脸检测应用
        • 七、扩展板外设功能例程

          • 00 - Pico 扩展板外设功能例程总览
          • 01 - OLED 显示应用
          • 02 - TFT 显示应用
          • 03 - MPU6050 陀螺仪应用
          • 04 - ADC 采集应用
          • 05 - 无源蜂鸣器应用
          • 06 - MQ 气体传感器应用
          • 07 - GPS 定位应用
          • 08 - SHT20 温湿度应用
          • 09 - 超声波测距应用
          • 10 - SPO2 血氧传感器应用
          • 11 - 电机控制应用
          • 12 - 舵机控制应用
    • 开源鸿蒙

      • SC-3568HA

        • 一、简介

          • SC-3568HA简介
        • 二、快速上手

          • OpenHarmony概述
          • 镜像烧录
          • 开发环境准备
          • Hello World应用以及部署
        • 三、应用开发

          • ArkUI

            • 第一章 ArkTS语言简介
            • 第二章 UI组件介绍和实际应用(上)
            • 第三章 UI组件介绍和实际应用(中)
            • 第四章 UI组件介绍和实际应用(下)
          • 拓展

            • 第一章 入门指引
            • 第二章 三方库的引用和使用
            • 第三章 应用编译以及部署
            • 第四章 命令行恢复出厂设置
            • 第五章 系统调试--HDC调试
            • 第六章 APP 稳定性测试
            • 第七章 应用测试
        • 四、设备开发

          • 第一章 环境搭建
          • 第二章 下载源码
          • 第三章 编译源码
        • 五、内核的外设与接口

          • 树莓派接口
          • GPIO 接口
          • I2C 接口
          • SPI通信
          • PWM控制
          • 串口通讯
          • TF Card
          • 屏幕
          • 触摸
          • 音频
          • RTC
          • Ethernet
          • M.2
          • MINI-PCIE
          • Camera
          • WIFI&BT
          • 树莓派拓展板
        • 六、资料下载

          • 资料下载
      • M-K1HSE

        • 一、简介

          • M-K1HSE 简介
        • 二、快速开始

          • 开发环境搭建
          • 源码获取
          • 编译说明
          • 烧录指南
        • 三、应用开发

          • 00 应用开发环境搭建
          • 01 第一个应用-Hello World
        • 四、外设与接口

          • 01 Audio
          • 02 RS485
          • 03 Display
        • 五、系统定制开发

          • 系统移植
          • 系统定制
          • 驱动开发
          • 系统调试
          • OTA升级
        • 六、资料下载

          • 资料下载
    • HVS视觉融合相机

      • 快速开始

        • 01 SDK 介绍
        • 02 资料下载
        • 03 设备固件烧录
        • 04 第一个 C++ 程序
        • 05 第一个 Python 程序
        • 06 Python 数据分析
        • 07 MultiVision Studio
      • 二次开发

        • 01 编程指引

          • 01 打开相机
          • 02 读取事件
          • 03 录制与回放
          • 04 事件处理(去噪)
          • 05 显示与可视化
          • 06 调参
          • 07 采集 APS 图像
        • 02 Toolkit SDK

          • 01 Hybrid Vision Toolkit
          • 02 快速上手
          • 03 C++ API
          • 04 Python API
        • 03 Algo SDK

          • 01 Hybrid Vision Algo
          • 02 Algo API
          • 03 Windows Algo SDK
        • 04 示例程序总览
        • 05 应用场景
      • 基础知识

        • 01 事件相机基础原理
        • 02 HVS 融合视觉
        • 03 事件可视化
        • 04 数据格式参考
        • 05 术语表
        • 06 偏置与调参
        • 07 视频教程
      • HVS 相机

        • 01 HVS 相机快速上手
        • 02 网络化能力

          • 01 HVS 相机系统架构
          • 2 EVS Network Server
          • 3 EVS Time Sync
          • 4 Web Window
        • 03 HVS 相机兼容性矩阵
        • 04 常见问题解决指南
        • 05 产品

          • CF-NRS1(灵光一号视觉融合相机)
      • HVS 模块

        • 01 MIPI 模块快速上手
        • 02 载板适配

          • RDK X5 载板适配
          • RDK S100 载板适配
          • 树莓派载板适配
          • 地瓜派载板适配
          • shimetapi 板载板适配
        • 03 MIPI 模块兼容性矩阵
        • 04 产品

          • EVS_003 传感器模块
    • AI硬件

      • 1684XB-32T

        • 一、简介

          • AIBOX-1684XB-32简介
        • 二、快速上手

          • 初次使用
          • 网络配置
          • 磁盘使用
          • 内存分配
          • 风扇策略
          • 固件升级
          • 交叉编译
          • 模型量化
        • 三、应用开发

          • 开发简介

            • Sophgo SDK开发
            • SOPHON-DEMO简介
          • 大语言模型

            • 部署Llama3示例
            • Sophon LLM_api_server开发
            • 部署MiniCPM-V-2_6
            • Qwen-2-5-VL图片视频识别DEMO
            • Qwen3-chat-DEMO
            • Qwen3-Qwen Agent-MCP开发
            • Qwen3-langchain-AI Agent
          • 深度学习

            • ResNet(图像分类)
            • LPRNet(车牌识别)
            • SAM(通用图像分割基础模型)
            • YOLOv5(目标检测)
            • OpenPose(人体关键点检测)
            • PP-OCR(光学字符识别)
        • 四、资料下载

          • 资料下载
      • 1684X-416T

        • 简介

          • AIBOX-1684X-416简介
        • Demo简单操作指引

          • shimeta智慧监控demo的简单使用说明
      • RDK-X5

        • 简介

          • RDK-X5 硬件简介
        • 快速开始

          • RDK-X5 快速开始
        • 应用开发

          • AI在线模型开发

            • 实验01-接入火山引擎豆包 AI
            • 实验02-图片分析
            • 实验03-多模态视觉分析定位
            • 实验04-多模态图文比较分析
            • 实验05-多模态文档表格分析
            • 实验06-摄像头运用-AI视觉分析
          • 大语言模型

            • 实验01-语音识别
            • 实验02-语音对话
            • 实验03-多模态图片分析-语音对话
            • 实验04-多模态图片比较-语音对话
            • 实验05-多模态文档分析-语音对话
            • 实验06-多模态视觉运用-语音对话
          • ROS2基础开发

            • 实验01-搭建环境
            • 实验02-工作包的创建及编译
            • 实验03-运行 ROS2 话题通信节点
            • 实验04-ROS2 相机应用
          • 40pin-IO开发

            • 实验01-GPIO 输出(LED闪烁)
            • 实验02-GPIO 输入
            • 实验03-按键控制 LED
            • 实验04-PWM 输出
            • 实验05-串口输出
            • 实验06-IIC 实验
            • 实验07-SPI 实验
          • USB模块开发使用

            • 实验01-USB 语音模块使用
            • 实验02-声源定位模块使用
          • 机器视觉技术实战

            • 实验01-打开 USB 摄像头
            • 实验02-颜色识别检测
            • 实验03-手势识别体验
            • 实验04-YOLOv5物体检测
      • RDK-S100

        • 简介

          • RDK-S100 硬件简介
        • 快速开始

          • RDK-S100 硬件简介
        • 应用开发

          • AI在线模型开发

            • 实验01-接入火山引擎豆包 AI
            • 实验02-图片分析
            • 实验03-多模态视觉分析定位
            • 实验04-多模态图文比较分析
            • 实验05-多模态文档表格分析
            • 实验06-摄像头运用-AI视觉分析
          • 大语言模型

            • 实验01-语音识别
            • 实验02-语音对话
            • 实验03-多模态图片分析-语音对话
            • 实验04-多模态图片比较-语音对话
            • 实验05-多模态文档分析-语音对话
            • 实验06-多模态视觉运用-语音对话
          • ROS2基础开发

            • 实验01-搭建环境
            • 实验02-工作包的创建及编译
            • 实验03-运行 ROS2 话题通信节点
            • 实验04-ROS2 相机应用
          • 40pin-IO开发

            • 实验01-GPIO 输出(LED闪烁)
            • 实验02-GPIO 输入
            • 实验03-按键控制 LED
            • 实验04-PWM 输出
            • 实验05-串口输出
            • 实验06-IIC 实验
            • 实验07-SPI 实验
          • USB模块开发使用

            • 实验01-USB 语音模块使用
            • 实验02-声源定位模块使用
          • 机器视觉技术实战

            • 实验01-打开 USB 摄像头
            • 实验02-图像处理基础
            • 实验03-目标检测
            • 实验04-图像分割
      • RK1828

        • 一、简介

          • M5-182X-A1 AI 边缘盒子 - 产品介绍
          • M5-182X-A1 硬件规格
          • M5-182X-A1 使用与安全
        • 二、快速开始

          • M5-182X-A1 镜像烧录
          • RK182X 硬件安装与验证
          • RK182X 开发环境快速搭建
          • RK182X SDK 总览
          • RK182X 环境搭建详解
          • RK182X 快速开始
          • 原厂 SDK 资料抽取记录
        • 三、开发指南

          • ClawChips 架构与原理
          • SKILL 使用手册
          • RK182X系列LLM推理(RK1828型号)
          • RK182X系列CNN推理(RK1828型号)
          • 模型转换
          • RK182X AI Agent 应用开发指南
          • RK182X 工业异常检测应用
        • 四、SDK 参考

          • RKNN3-SDK 概述

            • RKNN3 SDK 概述
          • RKNN3-Toolkit

            • RKNN3 Toolkit 安装与使用
          • RKLLM

            • RKLLM 设备端 LLM 推理
          • RK182X系列 NPU 概述与架构(RK1828型号)
          • RK182X INT8 量化推理部署
          • RK182X MPP 多媒体处理框架
          • MPP 详解

            • RK182X 视频解码
            • RK182X 视频编码
          • NPU 详解

            • RKNN 模型转换
            • RK182X NPU INT8 量化推理
            • RK182X 多模型并行推理
          • RGA 详解

            • RK182X RGA 2D 图形加速
          • VPU 详解

            • RK182X VPU 编解码
        • 五、硬件参考

          • RK182X系列 硬件架构概述(RK1828型号)
          • RK182X 引脚定义与复用配置
          • RK182X 引脚定义
          • RK182X 电源管理
          • RK182X 时钟与 PLL 配置
          • RK182X 时钟与频率配置
        • 六、实战教程

          • Hello World
          • Hello RK1828 - 第一个程序
          • RTSP 推流
          • RTSP 推流 + AI 分析
          • 视美泰AI龙虾一键部署
          • PaddleOCR-VL 文字识别
          • Qwen3-1.7B LLM 文本对话
          • AI 巡检多视角(Qwen3-VL 包装)
          • YOLOv5 目标检测
        • 七、资料下载

          • 资料下载
        • 八、常见问题

          • 常见问题
    • 核心板

      • C-3568BQ

        • 简介

          • C-3568BQ 简介
      • C-3588LQ

        • 简介

          • C-3588LQ 简介
      • GC-3568JBAF

        • 简介

          • GC-3568JBAF 简介
      • C-K1BA

        • 简介

          • C-K1BA 简介
    • 软件平台

      • ShiMetaPi Workbench

        • 一、产品简介

          • 产品概述
          • 核心架构
          • 功能入口
          • 支持的硬件
          • 版本说明
        • 二、快速入门

          • 安装与登录
          • 连接设备
          • 配置环境
          • 接入 AIHub
          • 首次调用模型
        • 三、用户指南

          • 工作区总览
          • 设备管理
          • 模型广场
          • 一键部署
          • 视觉 - SVP
          • 视觉 - 自定义模型
          • shimeta-py IDE
          • 终端
          • Agent 调试助手
          • 设置与资源
        • 四、常见问题

          • 安装与登录
          • 设备连接
          • 模型与部署
          • 视觉与运行时
          • 设置与其他
      • ShimetaPi软件源

        • 简介

          • ShimetaPi 软件源
        • Pico G1 (GK7206)

          • 快速开始

            • 安装与首次推理
            • shimeta_infer — 图片推理
            • shimeta_camera — 摄像头实时推理
            • SVP 场景检测
            • 文件传输与内置模型参考
            • 常见问题
          • HTTP API & Python SDK

            • HTTP API 参考
      • 模型微调训练平台

        • 一、平台介绍

          • 模型训练平台
        • 二、快速上手

          • 注册与登录
          • 创建第一个模型(30 分钟快速体验)
        • 三、训练指南

          • 数据准备与标注
          • 训练参数配置
          • 启动与监控训练
          • 模型评估与测试
        • 四、模型部署

          • 导出模型
          • 部署到边缘设备

模型转换详解

4.1 模型转换基本概念

什么是模型转换

模型转换的定义

模型转换是将训练好的深度学习模型从一种格式转换为另一种格式的过程。在RK3568 NPU开发中,主要是将常见的深度学习框架模型(如PyTorch、TensorFlow、ONNX等)转换为RKNN格式,以便在Rockchip NPU上高效运行。

转换的必要性

原始模型 (PyTorch/TensorFlow/ONNX)
    ↓
模型优化 (图优化、算子融合)
    ↓
量化处理 (FP32 → INT8/INT16)
    ↓
硬件适配 (NPU指令集映射)
    ↓
RKNN模型 (可在RK3568 NPU运行)

转换的主要目的:

  • 硬件适配: 将通用模型适配到特定的NPU硬件
  • 性能优化: 通过图优化和算子融合提升推理速度
  • 内存优化: 减少模型大小和运行时内存占用
  • 量化加速: 将FP32模型量化为INT8,提升推理速度

支持的模型格式

输入格式支持

框架格式支持版本备注
ONNX.onnx1.6-1.12推荐格式,兼容性最好
TensorFlow.pb1.x, 2.x需要frozen graph
TensorFlow Lite.tflite2.x轻量化模型
Caffe.prototxt + .caffemodel1.0经典框架
DarkNet.cfg + .weights-YOLO系列模型

推荐转换路径

PyTorch → ONNX → RKNN (推荐)
TensorFlow → ONNX → RKNN (推荐)
TensorFlow → TensorFlow Lite → RKNN
Caffe → RKNN (直接转换)

量化技术详解

量化类型对比

量化类型精度速度模型大小适用场景
FP32最高慢大精度要求极高
FP16高中等中等平衡精度和性能
INT8中等快小大多数应用场景
混合精度高快小关键层保持高精度

量化策略

# 对称量化 vs 非对称量化
symmetric_quantization = {
    "range": "[-127, 127]",
    "zero_point": 0,
    "advantages": "计算简单,硬件友好",
    "disadvantages": "可能浪费量化范围"
}

asymmetric_quantization = {
    "range": "[0, 255] 或 [-128, 127]",
    "zero_point": "非零",
    "advantages": "充分利用量化范围",
    "disadvantages": "计算复杂度稍高"
}

转换流程概述

完整转换流程

graph TD
    A[原始模型] --> B[模型验证]
    B --> C[预处理配置]
    C --> D[量化数据准备]
    D --> E[模型转换]
    E --> F[精度验证]
    F --> G[性能测试]
    G --> H[模型优化]
    H --> I[最终部署]

关键步骤说明

  1. 模型验证: 确保原始模型可正常推理
  2. 预处理配置: 设置输入数据的预处理参数
  3. 量化数据准备: 准备代表性数据集用于量化校准
  4. 模型转换: 执行实际的转换过程
  5. 精度验证: 对比转换前后的精度差异
  6. 性能测试: 测试转换后模型的推理性能
  7. 模型优化: 根据测试结果进行进一步优化

4.2 准备待转换的模型

获取预训练模型

从官方源获取ONNX模型

#!/usr/bin/env python3
# download_models.py

import torch
import torchvision.models as models
import requests
import os

def download_classification_models():
    """下载分类模型"""
    models_info = {
        'resnet18': 'https://download.pytorch.org/models/resnet18-5c106cde.pth',
        'resnet50': 'https://download.pytorch.org/models/resnet50-19c8e357.pth',
        'mobilenet_v2': 'https://download.pytorch.org/models/mobilenet_v2-b0353104.pth',
        'efficientnet_b0': 'https://download.pytorch.org/models/efficientnet_b0_rwightman-3dd342df.pth'
    }
    
    os.makedirs('models/classification', exist_ok=True)
    
    for model_name, url in models_info.items():
        print(f"下载 {model_name}...")
        
        # 加载预训练模型
        if model_name == 'resnet18':
            model = models.resnet18(pretrained=True)
        elif model_name == 'resnet50':
            model = models.resnet50(pretrained=True)
        elif model_name == 'mobilenet_v2':
            model = models.mobilenet_v2(pretrained=True)
        
        model.eval()
        
        # 导出为ONNX
        dummy_input = torch.randn(1, 3, 224, 224)
        onnx_path = f'models/classification/{model_name}.onnx'
        
        torch.onnx.export(
            model,
            dummy_input,
            onnx_path,
            export_params=True,
            opset_version=11,
            do_constant_folding=True,
            input_names=['input'],
            output_names=['output'],
            dynamic_axes={
                'input': {0: 'batch_size'},
                'output': {0: 'batch_size'}
            }
        )
        
        print(f"ONNX模型保存到: {onnx_path}")

def download_yolo_models():
    """下载YOLO模型"""
    import ultralytics
    
    os.makedirs('models/detection', exist_ok=True)
    
    # YOLOv5模型
    yolo_models = ['yolov5s', 'yolov5m', 'yolov5l']
    
    for model_name in yolo_models:
        print(f"下载 {model_name}...")
        
        # 加载模型
        model = torch.hub.load('ultralytics/yolov5', model_name, pretrained=True)
        model.eval()
        
        # 导出ONNX
        dummy_input = torch.randn(1, 3, 640, 640)
        onnx_path = f'models/detection/{model_name}.onnx'
        
        torch.onnx.export(
            model,
            dummy_input,
            onnx_path,
            export_params=True,
            opset_version=11,
            do_constant_folding=True,
            input_names=['images'],
            output_names=['output'],
            dynamic_axes={
                'images': {0: 'batch_size'},
                'output': {0: 'batch_size'}
            }
        )
        
        print(f"ONNX模型保存到: {onnx_path}")

if __name__ == "__main__":
    download_classification_models()
    download_yolo_models()

从Hugging Face获取模型

#!/usr/bin/env python3
# download_huggingface_models.py

from transformers import AutoModel, AutoTokenizer
import torch
import os

def download_transformer_models():
    """下载Transformer模型"""
    models_info = {
        'bert-base-uncased': 'bert-base-uncased',
        'distilbert-base-uncased': 'distilbert-base-uncased',
        'roberta-base': 'roberta-base'
    }
    
    os.makedirs('models/nlp', exist_ok=True)
    
    for model_name, model_id in models_info.items():
        print(f"下载 {model_name}...")
        
        # 下载模型和tokenizer
        model = AutoModel.from_pretrained(model_id)
        tokenizer = AutoTokenizer.from_pretrained(model_id)
        
        # 保存模型
        model_dir = f'models/nlp/{model_name}'
        model.save_pretrained(model_dir)
        tokenizer.save_pretrained(model_dir)
        
        # 导出ONNX (示例)
        model.eval()
        dummy_input = torch.randint(0, 1000, (1, 128))  # 序列长度128
        
        onnx_path = f'{model_dir}/{model_name}.onnx'
        torch.onnx.export(
            model,
            dummy_input,
            onnx_path,
            export_params=True,
            opset_version=11,
            input_names=['input_ids'],
            output_names=['last_hidden_state'],
            dynamic_axes={
                'input_ids': {0: 'batch_size', 1: 'sequence'},
                'last_hidden_state': {0: 'batch_size', 1: 'sequence'}
            }
        )
        
        print(f"模型保存到: {model_dir}")

if __name__ == "__main__":
    download_transformer_models()

模型验证和预处理

模型完整性检查

#!/usr/bin/env python3
# model_validation.py

import onnx
import onnxruntime as ort
import numpy as np
import cv2

def validate_onnx_model(model_path):
    """验证ONNX模型的完整性"""
    try:
        # 加载模型
        model = onnx.load(model_path)
        
        # 检查模型
        onnx.checker.check_model(model)
        print(f"✓ 模型 {model_path} 验证通过")
        
        # 打印模型信息
        print(f"模型版本: {model.ir_version}")
        print(f"生产者: {model.producer_name}")
        print(f"操作集版本: {[opset.version for opset in model.opset_import]}")
        
        # 打印输入输出信息
        print("\n输入信息:")
        for input_tensor in model.graph.input:
            print(f"  名称: {input_tensor.name}")
            print(f"  形状: {[dim.dim_value for dim in input_tensor.type.tensor_type.shape.dim]}")
            print(f"  类型: {input_tensor.type.tensor_type.elem_type}")
        
        print("\n输出信息:")
        for output_tensor in model.graph.output:
            print(f"  名称: {output_tensor.name}")
            print(f"  形状: {[dim.dim_value for dim in output_tensor.type.tensor_type.shape.dim]}")
            print(f"  类型: {output_tensor.type.tensor_type.elem_type}")
        
        return True
        
    except Exception as e:
        print(f"✗ 模型验证失败: {e}")
        return False

def test_onnx_inference(model_path, input_shape):
    """测试ONNX模型推理"""
    try:
        # 创建推理会话
        session = ort.InferenceSession(model_path)
        
        # 获取输入输出名称
        input_name = session.get_inputs()[0].name
        output_name = session.get_outputs()[0].name
        
        # 创建随机输入
        dummy_input = np.random.randn(*input_shape).astype(np.float32)
        
        # 执行推理
        result = session.run([output_name], {input_name: dummy_input})
        
        print(f"✓ 推理测试成功")
        print(f"输入形状: {dummy_input.shape}")
        print(f"输出形状: {result[0].shape}")
        
        return True
        
    except Exception as e:
        print(f"✗ 推理测试失败: {e}")
        return False

def analyze_model_complexity(model_path):
    """分析模型复杂度"""
    model = onnx.load(model_path)
    
    # 统计节点类型
    node_types = {}
    for node in model.graph.node:
        op_type = node.op_type
        node_types[op_type] = node_types.get(op_type, 0) + 1
    
    print(f"\n模型复杂度分析:")
    print(f"总节点数: {len(model.graph.node)}")
    print(f"节点类型分布:")
    for op_type, count in sorted(node_types.items()):
        print(f"  {op_type}: {count}")
    
    # 估算参数量
    total_params = 0
    for initializer in model.graph.initializer:
        param_size = 1
        for dim in initializer.dims:
            param_size *= dim
        total_params += param_size
    
    print(f"估算参数量: {total_params:,}")
    print(f"估算模型大小: {total_params * 4 / 1024 / 1024:.2f} MB (FP32)")

if __name__ == "__main__":
    # 测试示例
    model_path = "models/classification/resnet18.onnx"
    
    if validate_onnx_model(model_path):
        test_onnx_inference(model_path, (1, 3, 224, 224))
        analyze_model_complexity(model_path)

准备量化数据集

创建量化校准数据集

#!/usr/bin/env python3
# prepare_calibration_dataset.py

import os
import cv2
import numpy as np
import random
from pathlib import Path

class CalibrationDataset:
    """量化校准数据集"""
    
    def __init__(self, data_dir, input_size=(224, 224), num_samples=100):
        self.data_dir = Path(data_dir)
        self.input_size = input_size
        self.num_samples = num_samples
        self.image_paths = self._collect_images()
        
    def _collect_images(self):
        """收集图片路径"""
        extensions = ['.jpg', '.jpeg', '.png', '.bmp']
        image_paths = []
        
        for ext in extensions:
            image_paths.extend(self.data_dir.glob(f"**/*{ext}"))
            image_paths.extend(self.data_dir.glob(f"**/*{ext.upper()}"))
        
        # 随机采样
        if len(image_paths) > self.num_samples:
            image_paths = random.sample(image_paths, self.num_samples)
        
        print(f"收集到 {len(image_paths)} 张校准图片")
        return image_paths
    
    def preprocess_image(self, image_path):
        """图像预处理"""
        # 读取图像
        image = cv2.imread(str(image_path))
        if image is None:
            return None
        
        # 转换颜色空间
        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
        
        # Resize
        image = cv2.resize(image, self.input_size)
        
        # 归一化
        image = image.astype(np.float32) / 255.0
        
        # ImageNet标准化
        mean = np.array([0.485, 0.456, 0.406])
        std = np.array([0.229, 0.224, 0.225])
        image = (image - mean) / std
        
        # 转换为NCHW格式
        image = np.transpose(image, (2, 0, 1))
        
        return image
    
    def generate_calibration_data(self, output_file):
        """生成校准数据"""
        calibration_data = []
        
        print("生成校准数据...")
        for i, image_path in enumerate(self.image_paths):
            processed_image = self.preprocess_image(image_path)
            if processed_image is not None:
                calibration_data.append(processed_image)
            
            if (i + 1) % 20 == 0:
                print(f"处理进度: {i + 1}/{len(self.image_paths)}")
        
        # 转换为numpy数组
        calibration_data = np.array(calibration_data)
        
        # 保存
        np.save(output_file, calibration_data)
        print(f"校准数据保存到: {output_file}")
        print(f"数据形状: {calibration_data.shape}")
        
        return calibration_data

def download_imagenet_samples():
    """下载ImageNet样本数据"""
    import urllib.request
    
    # ImageNet验证集样本URL (示例)
    sample_urls = [
        "https://github.com/pytorch/hub/raw/master/images/dog.jpg",
        "https://github.com/pytorch/hub/raw/master/images/deeplab1.png",
        # 添加更多样本URL
    ]
    
    os.makedirs("calibration_data/imagenet_samples", exist_ok=True)
    
    for i, url in enumerate(sample_urls):
        try:
            filename = f"calibration_data/imagenet_samples/sample_{i:03d}.jpg"
            urllib.request.urlretrieve(url, filename)
            print(f"下载: {filename}")
        except Exception as e:
            print(f"下载失败 {url}: {e}")

def create_synthetic_dataset(output_dir, num_samples=100, input_size=(224, 224)):
    """创建合成数据集 (用于测试)"""
    os.makedirs(output_dir, exist_ok=True)
    
    print(f"创建合成数据集: {num_samples} 张图片")
    
    for i in range(num_samples):
        # 生成随机图像
        image = np.random.randint(0, 256, (input_size[1], input_size[0], 3), dtype=np.uint8)
        
        # 添加一些结构
        cv2.rectangle(image, (50, 50), (150, 150), (255, 0, 0), -1)
        cv2.circle(image, (100, 100), 30, (0, 255, 0), -1)
        
        # 保存图像
        filename = f"{output_dir}/synthetic_{i:03d}.jpg"
        cv2.imwrite(filename, image)
    
    print(f"合成数据集创建完成: {output_dir}")

if __name__ == "__main__":
    # 创建校准数据集
    
    # 方法1: 使用现有图片目录
    if os.path.exists("path/to/your/images"):
        dataset = CalibrationDataset("path/to/your/images")
        dataset.generate_calibration_data("calibration_data.npy")
    
    # 方法2: 下载样本数据
    download_imagenet_samples()
    
    # 方法3: 创建合成数据集
    create_synthetic_dataset("calibration_data/synthetic", num_samples=50)
    
    # 使用合成数据集
    dataset = CalibrationDataset("calibration_data/synthetic")
    dataset.generate_calibration_data("calibration_data_synthetic.npy")

4.3 RKNN-Toolkit2 转换 API 讲解

核心API介绍

RKNN类基本用法

#!/usr/bin/env python3
# rknn_api_tutorial.py

from rknn.api import RKNN
import numpy as np

class RKNNConverter:
    """RKNN转换器封装类"""
    
    def __init__(self, verbose=True):
        self.rknn = RKNN(verbose=verbose)
        self.model_loaded = False
        self.model_built = False
    
    def configure_model(self, target_platform='rk3568', **kwargs):
        """配置模型转换参数"""
        config_params = {
            'target_platform': target_platform,
            'quantized_dtype': kwargs.get('quantized_dtype', 'asymmetric_quantized-u8'),
            'optimization_level': kwargs.get('optimization_level', 3),
            'output_optimize': kwargs.get('output_optimize', 1),
            'compress_weight': kwargs.get('compress_weight', False),
            'single_core_mode': kwargs.get('single_core_mode', False),
            'model_pruning': kwargs.get('model_pruning', False)
        }
        
        print("配置转换参数:")
        for key, value in config_params.items():
            print(f"  {key}: {value}")
        
        ret = self.rknn.config(**config_params)
        if ret != 0:
            raise Exception("模型配置失败!")
        
        return ret
    
    def load_model(self, model_path, model_type='onnx'):
        """加载模型"""
        print(f"加载 {model_type.upper()} 模型: {model_path}")
        
        if model_type.lower() == 'onnx':
            ret = self.rknn.load_onnx(model=model_path)
        elif model_type.lower() == 'tensorflow':
            ret = self.rknn.load_tensorflow(
                tf_pb=model_path,
                inputs=['input'],
                outputs=['output'],
                input_size_list=[[1, 224, 224, 3]]
            )
        elif model_type.lower() == 'tflite':
            ret = self.rknn.load_tflite(model=model_path)
        elif model_type.lower() == 'caffe':
            ret = self.rknn.load_caffe(
                model=model_path + '.prototxt',
                blobs=model_path + '.caffemodel'
            )
        else:
            raise ValueError(f"不支持的模型类型: {model_type}")
        
        if ret != 0:
            raise Exception(f"{model_type.upper()} 模型加载失败!")
        
        self.model_loaded = True
        print("模型加载成功!")
        return ret
    
    def build_model(self, do_quantization=True, dataset=None):
        """构建模型"""
        if not self.model_loaded:
            raise Exception("请先加载模型!")
        
        print("开始构建模型...")
        
        build_params = {'do_quantization': do_quantization}
        
        if do_quantization and dataset is not None:
            print("使用自定义数据集进行量化...")
            build_params['dataset'] = dataset
        
        ret = self.rknn.build(**build_params)
        if ret != 0:
            raise Exception("模型构建失败!")
        
        self.model_built = True
        print("模型构建成功!")
        return ret
    
    def export_model(self, export_path):
        """导出RKNN模型"""
        if not self.model_built:
            raise Exception("请先构建模型!")
        
        print(f"导出模型到: {export_path}")
        ret = self.rknn.export_rknn(export_path)
        if ret != 0:
            raise Exception("模型导出失败!")
        
        print("模型导出成功!")
        return ret
    
    def init_runtime(self, target='rk3568'):
        """初始化运行时"""
        print(f"初始化运行时 (目标: {target})...")
        ret = self.rknn.init_runtime(target=target)
        if ret != 0:
            raise Exception("运行时初始化失败!")
        
        print("运行时初始化成功!")
        return ret
    
    def inference(self, inputs):
        """执行推理"""
        return self.rknn.inference(inputs=inputs)
    
    def release(self):
        """释放资源"""
        if self.rknn:
            self.rknn.release()
            print("资源释放完成")

# 使用示例
def basic_conversion_example():
    """基本转换示例"""
    converter = RKNNConverter(verbose=True)
    
    try:
        # 1. 配置参数
        converter.configure_model(
            target_platform='rk3568',
            quantized_dtype='asymmetric_quantized-u8',
            optimization_level=3
        )
        
        # 2. 加载模型
        converter.load_model('models/classification/resnet18.onnx', 'onnx')
        
        # 3. 构建模型
        converter.build_model(do_quantization=True)
        
        # 4. 导出模型
        converter.export_model('resnet18_rk3568.rknn')
        
        # 5. 测试推理 (可选)
        converter.init_runtime()
        dummy_input = np.random.randn(1, 3, 224, 224).astype(np.float32)
        outputs = converter.inference([dummy_input])
        print(f"推理输出形状: {outputs[0].shape}")
        
    finally:
        converter.release()

if __name__ == "__main__":
    basic_conversion_example()

高级配置选项

量化配置详解

#!/usr/bin/env python3
# advanced_quantization.py

from rknn.api import RKNN
import numpy as np

def configure_quantization_options():
    """配置量化选项"""
    
    # 量化数据类型选项
    quantization_types = {
        'asymmetric_quantized-u8': {
            'description': '非对称8位无符号整数量化',
            'range': '[0, 255]',
            'precision': '中等',
            'speed': '快',
            'recommended': True
        },
        'asymmetric_quantized-i8': {
            'description': '非对称8位有符号整数量化',
            'range': '[-128, 127]',
            'precision': '中等',
            'speed': '快',
            'recommended': False
        },
        'symmetric_quantized-u8': {
            'description': '对称8位无符号整数量化',
            'range': '[0, 255]',
            'precision': '中等',
            'speed': '快',
            'recommended': False
        },
        'dynamic_fixed_point-i8': {
            'description': '动态定点8位量化',
            'range': '[-128, 127]',
            'precision': '高',
            'speed': '中等',
            'recommended': False
        },
        'dynamic_fixed_point-i16': {
            'description': '动态定点16位量化',
            'range': '[-32768, 32767]',
            'precision': '很高',
            'speed': '慢',
            'recommended': False
        }
    }
    
    print("支持的量化类型:")
    for qtype, info in quantization_types.items():
        print(f"\n{qtype}:")
        for key, value in info.items():
            print(f"  {key}: {value}")
    
    return quantization_types

def advanced_quantization_config():
    """高级量化配置示例"""
    rknn = RKNN(verbose=True)
    
    # 高级配置选项
    advanced_config = {
        'target_platform': 'rk3568',
        'quantized_dtype': 'asymmetric_quantized-u8',
        'optimization_level': 3,  # 0-3, 3为最高优化级别
        'output_optimize': 1,     # 输出优化
        'compress_weight': True,  # 权重压缩
        'single_core_mode': False, # 单核模式
        'model_pruning': False,   # 模型剪枝
        'quantized_algorithm': 'normal',  # 量化算法
        'quantized_method': 'channel',    # 量化方法
        'float_dtype': 'float16'  # 浮点数据类型
    }
    
    print("高级配置参数:")
    for key, value in advanced_config.items():
        print(f"  {key}: {value}")
    
    ret = rknn.config(**advanced_config)
    rknn.release()
    
    return ret

def mixed_precision_quantization():
    """混合精度量化示例"""
    rknn = RKNN(verbose=True)
    
    # 混合精度配置
    # 某些层保持高精度,其他层使用低精度
    mixed_precision_config = {
        'target_platform': 'rk3568',
        'quantized_dtype': 'asymmetric_quantized-u8',
        'optimization_level': 3,
        # 指定特定层的量化类型
        'quantize_input_node': False,  # 输入节点不量化
        'quantize_output_node': False, # 输出节点不量化
    }
    
    ret = rknn.config(**mixed_precision_config)
    rknn.release()
    
    return ret

if __name__ == "__main__":
    configure_quantization_options()
    advanced_quantization_config()
    mixed_precision_quantization()

优化级别详解

#!/usr/bin/env python3
# optimization_levels.py

def explain_optimization_levels():
    """解释优化级别"""
    
    optimization_levels = {
        0: {
            'name': '无优化',
            'description': '保持原始模型结构,不进行任何优化',
            'speed': '慢',
            'accuracy': '最高',
            'model_size': '大',
            'use_case': '调试和精度对比'
        },
        1: {
            'name': '基础优化',
            'description': '基本的图优化,如常量折叠',
            'speed': '中等',
            'accuracy': '高',
            'model_size': '中等',
            'use_case': '平衡精度和性能'
        },
        2: {
            'name': '标准优化',
            'description': '包含算子融合和内存优化',
            'speed': '快',
            'accuracy': '中等',
            'model_size': '小',
            'use_case': '大多数应用场景'
        },
        3: {
            'name': '激进优化',
            'description': '最大程度的优化,可能影响精度',
            'speed': '最快',
            'accuracy': '中等偏低',
            'model_size': '最小',
            'use_case': '性能要求极高的场景'
        }
    }
    
    print("RKNN优化级别详解:")
    for level, info in optimization_levels.items():
        print(f"\n级别 {level} - {info['name']}:")
        for key, value in info.items():
            if key != 'name':
                print(f"  {key}: {value}")
    
    return optimization_levels

def benchmark_optimization_levels(model_path):
    """测试不同优化级别的效果"""
    from rknn.api import RKNN
    import time
    import os
    
    results = {}
    
    for opt_level in range(4):
        print(f"\n测试优化级别 {opt_level}...")
        
        rknn = RKNN(verbose=False)
        
        try:
            # 配置
            rknn.config(
                target_platform='rk3568',
                quantized_dtype='asymmetric_quantized-u8',
                optimization_level=opt_level
            )
            
            # 加载和构建
            start_time = time.time()
            rknn.load_onnx(model=model_path)
            rknn.build(do_quantization=True)
            build_time = time.time() - start_time
            
            # 导出
            output_path = f'model_opt_{opt_level}.rknn'
            rknn.export_rknn(output_path)
            
            # 获取文件大小
            model_size = os.path.getsize(output_path) / 1024 / 1024  # MB
            
            # 测试推理速度
            rknn.init_runtime()
            dummy_input = np.random.randn(1, 3, 224, 224).astype(np.float32)
            
            # 预热
            for _ in range(10):
                rknn.inference(inputs=[dummy_input])
            
            # 测试
            inference_times = []
            for _ in range(100):
                start_time = time.time()
                rknn.inference(inputs=[dummy_input])
                inference_times.append(time.time() - start_time)
            
            avg_inference_time = np.mean(inference_times)
            
            results[opt_level] = {
                'build_time': build_time,
                'model_size_mb': model_size,
                'avg_inference_time_ms': avg_inference_time * 1000,
                'fps': 1.0 / avg_inference_time
            }
            
            print(f"  构建时间: {build_time:.2f}s")
            print(f"  模型大小: {model_size:.2f}MB")
            print(f"  推理时间: {avg_inference_time*1000:.2f}ms")
            print(f"  FPS: {1.0/avg_inference_time:.2f}")
            
            # 清理
            os.remove(output_path)
            
        except Exception as e:
            print(f"  优化级别 {opt_level} 测试失败: {e}")
            results[opt_level] = None
        
        finally:
            rknn.release()
    
    return results

if __name__ == "__main__":
    explain_optimization_levels()
    
    # 如果有模型文件,可以测试不同优化级别
    model_path = "models/classification/resnet18.onnx"
    if os.path.exists(model_path):
        results = benchmark_optimization_levels(model_path)
        print("\n优化级别对比结果:")
        for level, result in results.items():
            if result:
                print(f"级别 {level}: {result}")

4.4 转换实战:ONNX 模型

ResNet分类模型转换

完整的ResNet转换流程

#!/usr/bin/env python3
# resnet_conversion.py

import os
import cv2
import numpy as np
import time
from rknn.api import RKNN

class ResNetConverter:
    """ResNet模型转换器"""
    
    def __init__(self, model_path, output_path):
        self.model_path = model_path
        self.output_path = output_path
        self.rknn = RKNN(verbose=True)
        
    def prepare_calibration_data(self, num_samples=50):
        """准备校准数据"""
        print("准备校准数据...")
        
        # 创建合成数据 (实际使用中应该用真实数据)
        calibration_data = []
        
        for i in range(num_samples):
            # 生成随机图像
            image = np.random.randint(0, 256, (224, 224, 3), dtype=np.uint8)
            
            # 预处理
            image = image.astype(np.float32) / 255.0
            
            # ImageNet标准化
            mean = np.array([0.485, 0.456, 0.406])
            std = np.array([0.229, 0.224, 0.225])
            image = (image - mean) / std
            
            # 转换为NCHW格式
            image = np.transpose(image, (2, 0, 1))
            calibration_data.append(image)
        
        calibration_data = np.array(calibration_data)
        print(f"校准数据形状: {calibration_data.shape}")
        
        return calibration_data
    
    def convert_model(self, use_custom_dataset=True):
        """转换模型"""
        try:
            # 1. 配置转换参数
            print("配置转换参数...")
            ret = self.rknn.config(
                target_platform='rk3568',
                quantized_dtype='asymmetric_quantized-u8',
                optimization_level=3,
                output_optimize=1,
                compress_weight=True
            )
            if ret != 0:
                raise Exception("配置失败!")
            
            # 2. 加载ONNX模型
            print(f"加载ONNX模型: {self.model_path}")
            ret = self.rknn.load_onnx(model=self.model_path)
            if ret != 0:
                raise Exception("模型加载失败!")
            
            # 3. 构建模型
            print("构建模型...")
            if use_custom_dataset:
                calibration_data = self.prepare_calibration_data()
                ret = self.rknn.build(do_quantization=True, dataset=calibration_data)
            else:
                ret = self.rknn.build(do_quantization=True)
            
            if ret != 0:
                raise Exception("模型构建失败!")
            
            # 4. 导出RKNN模型
            print(f"导出RKNN模型: {self.output_path}")
            ret = self.rknn.export_rknn(self.output_path)
            if ret != 0:
                raise Exception("模型导出失败!")
            
            print("✓ 模型转换成功!")
            return True
            
        except Exception as e:
            print(f"✗ 模型转换失败: {e}")
            return False
        
        finally:
            self.rknn.release()
    
    def test_converted_model(self):
        """测试转换后的模型"""
        print("测试转换后的模型...")
        
        rknn = RKNN(verbose=False)
        
        try:
            # 加载RKNN模型
            ret = rknn.load_rknn(self.output_path)
            if ret != 0:
                raise Exception("RKNN模型加载失败!")
            
            # 初始化运行时
            ret = rknn.init_runtime()
            if ret != 0:
                raise Exception("运行时初始化失败!")
            
            # 准备测试数据
            test_image = np.random.randn(1, 3, 224, 224).astype(np.float32)
            
            # 推理测试
            print("执行推理测试...")
            start_time = time.time()
            outputs = rknn.inference(inputs=[test_image])
            inference_time = time.time() - start_time
            
            print(f"✓ 推理成功!")
            print(f"输入形状: {test_image.shape}")
            print(f"输出形状: {outputs[0].shape}")
            print(f"推理时间: {inference_time*1000:.2f}ms")
            print(f"FPS: {1/inference_time:.2f}")
            
            # 分析输出
            output = outputs[0][0]  # 移除batch维度
            top5_indices = np.argsort(output)[-5:][::-1]
            
            print("Top-5 预测结果:")
            for i, idx in enumerate(top5_indices):
                print(f"  {i+1}. 类别 {idx}: {output[idx]:.4f}")
            
            return True
            
        except Exception as e:
            print(f"✗ 模型测试失败: {e}")
            return False
        
        finally:
            rknn.release()

def convert_resnet_models():
    """转换多个ResNet模型"""
    models = {
        'resnet18': 'models/classification/resnet18.onnx',
        'resnet50': 'models/classification/resnet50.onnx',
        'mobilenet_v2': 'models/classification/mobilenet_v2.onnx'
    }
    
    for model_name, model_path in models.items():
        if not os.path.exists(model_path):
            print(f"跳过不存在的模型: {model_path}")
            continue
        
        print(f"\n{'='*50}")
        print(f"转换模型: {model_name}")
        print(f"{'='*50}")
        
        output_path = f"converted_models/{model_name}_rk3568.rknn"
        os.makedirs("converted_models", exist_ok=True)
        
        converter = ResNetConverter(model_path, output_path)
        
        # 转换模型
        if converter.convert_model():
            # 测试模型
            converter.test_converted_model()
        
        print(f"模型 {model_name} 处理完成")

if __name__ == "__main__":
    convert_resnet_models()

YOLOv5目标检测模型转换

YOLOv5转换专用脚本

#!/usr/bin/env python3
# yolov5_conversion.py

import os
import cv2
import numpy as np
import time
from rknn.api import RKNN

class YOLOv5Converter:
    """YOLOv5模型转换器"""
    
    def __init__(self, model_path, output_path, input_size=(640, 640)):
        self.model_path = model_path
        self.output_path = output_path
        self.input_size = input_size
        self.rknn = RKNN(verbose=True)
    
    def prepare_yolo_calibration_data(self, num_samples=100):
        """准备YOLO专用校准数据"""
        print("准备YOLO校准数据...")
        
        calibration_data = []
        
        # 创建多样化的测试图像
        for i in range(num_samples):
            if i % 20 == 0:
                print(f"生成校准数据: {i+1}/{num_samples}")
            
            # 生成不同类型的图像
            if i % 4 == 0:
                # 随机噪声图像
                image = np.random.randint(0, 256, (*self.input_size, 3), dtype=np.uint8)
            elif i % 4 == 1:
                # 几何图形图像
                image = np.zeros((*self.input_size, 3), dtype=np.uint8)
                cv2.rectangle(image, (100, 100), (300, 300), (255, 0, 0), -1)
                cv2.circle(image, (400, 400), 80, (0, 255, 0), -1)
            elif i % 4 == 2:
                # 渐变图像
                image = np.zeros((*self.input_size, 3), dtype=np.uint8)
                for y in range(self.input_size[1]):
                    image[y, :, :] = int(255 * y / self.input_size[1])
            else:
                # 棋盘图像
                image = np.zeros((*self.input_size, 3), dtype=np.uint8)
                square_size = 40
                for y in range(0, self.input_size[1], square_size):
                    for x in range(0, self.input_size[0], square_size):
                        if (x // square_size + y // square_size) % 2 == 0:
                            image[y:y+square_size, x:x+square_size] = 255
            
            # YOLO预处理
            processed_image = self.preprocess_yolo_image(image)
            calibration_data.append(processed_image)
        
        calibration_data = np.array(calibration_data)
        print(f"YOLO校准数据形状: {calibration_data.shape}")
        
        return calibration_data
    
    def preprocess_yolo_image(self, image):
        """YOLO图像预处理"""
        # 确保输入是正确的尺寸
        if image.shape[:2] != self.input_size:
            image = cv2.resize(image, self.input_size)
        
        # 转换为RGB
        if len(image.shape) == 3:
            image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
        
        # 归一化到[0,1]
        image = image.astype(np.float32) / 255.0
        
        # 转换为NCHW格式
        image = np.transpose(image, (2, 0, 1))
        
        return image
    
    def convert_yolo_model(self):
        """转换YOLO模型"""
        try:
            # 1. 配置转换参数 (YOLO专用配置)
            print("配置YOLO转换参数...")
            ret = self.rknn.config(
                target_platform='rk3568',
                quantized_dtype='asymmetric_quantized-u8',
                optimization_level=3,
                output_optimize=1,
                compress_weight=True,
                # YOLO特定配置
                quantized_algorithm='normal',
                quantized_method='channel'
            )
            if ret != 0:
                raise Exception("配置失败!")
            
            # 2. 加载ONNX模型
            print(f"加载YOLOv5 ONNX模型: {self.model_path}")
            ret = self.rknn.load_onnx(model=self.model_path)
            if ret != 0:
                raise Exception("模型加载失败!")
            
            # 3. 构建模型
            print("构建YOLOv5模型...")
            calibration_data = self.prepare_yolo_calibration_data()
            ret = self.rknn.build(do_quantization=True, dataset=calibration_data)
            if ret != 0:
                raise Exception("模型构建失败!")
            
            # 4. 导出RKNN模型
            print(f"导出YOLOv5 RKNN模型: {self.output_path}")
            ret = self.rknn.export_rknn(self.output_path)
            if ret != 0:
                raise Exception("模型导出失败!")
            
            print("✓ YOLOv5模型转换成功!")
            return True
            
        except Exception as e:
            print(f"✗ YOLOv5模型转换失败: {e}")
            return False
        
        finally:
            self.rknn.release()
    
    def test_yolo_model(self):
        """测试转换后的YOLO模型"""
        print("测试转换后的YOLOv5模型...")
        
        rknn = RKNN(verbose=False)
        
        try:
            # 加载RKNN模型
            ret = rknn.load_rknn(self.output_path)
            if ret != 0:
                raise Exception("RKNN模型加载失败!")
            
            # 初始化运行时
            ret = rknn.init_runtime()
            if ret != 0:
                raise Exception("运行时初始化失败!")
            
            # 准备测试数据
            test_image = np.random.randint(0, 256, (*self.input_size, 3), dtype=np.uint8)
            processed_image = self.preprocess_yolo_image(test_image)
            test_input = np.expand_dims(processed_image, axis=0)
            
            # 推理测试
            print("执行YOLOv5推理测试...")
            start_time = time.time()
            outputs = rknn.inference(inputs=[test_input])
            inference_time = time.time() - start_time
            
            print(f"✓ YOLOv5推理成功!")
            print(f"输入形状: {test_input.shape}")
            print(f"输出数量: {len(outputs)}")
            for i, output in enumerate(outputs):
                print(f"输出 {i} 形状: {output.shape}")
            print(f"推理时间: {inference_time*1000:.2f}ms")
            print(f"FPS: {1/inference_time:.2f}")
            
            # 分析YOLO输出
            self.analyze_yolo_output(outputs[0])
            
            return True
            
        except Exception as e:
            print(f"✗ YOLOv5模型测试失败: {e}")
            return False
        
        finally:
            rknn.release()
    
    def analyze_yolo_output(self, output):
        """分析YOLO输出"""
        print("\nYOLO输出分析:")
        
        # YOLOv5输出格式: [batch, num_detections, 85]
        # 85 = 4(bbox) + 1(confidence) + 80(classes)
        
        batch_size, num_detections, features = output.shape
        print(f"批次大小: {batch_size}")
        print(f"检测数量: {num_detections}")
        print(f"特征维度: {features}")
        
        # 分析第一个批次的检测结果
        detections = output[0]  # [num_detections, 85]
        
        # 提取置信度
        confidences = detections[:, 4]
        max_conf = np.max(confidences)
        min_conf = np.min(confidences)
        mean_conf = np.mean(confidences)
        
        print(f"置信度统计:")
        print(f"  最大值: {max_conf:.4f}")
        print(f"  最小值: {min_conf:.4f}")
        print(f"  平均值: {mean_conf:.4f}")
        
        # 统计高置信度检测
        high_conf_count = np.sum(confidences > 0.5)
        print(f"高置信度检测 (>0.5): {high_conf_count}")

def convert_yolo_models():
    """转换多个YOLO模型"""
    yolo_models = {
        'yolov5s': {
            'path': 'models/detection/yolov5s.onnx',
            'input_size': (640, 640)
        },
        'yolov5m': {
            'path': 'models/detection/yolov5m.onnx',
            'input_size': (640, 640)
        },
        'yolov5l': {
            'path': 'models/detection/yolov5l.onnx',
            'input_size': (640, 640)
        }
    }
    
    os.makedirs("converted_models", exist_ok=True)
    
    for model_name, model_info in yolo_models.items():
        model_path = model_info['path']
        input_size = model_info['input_size']
        
        if not os.path.exists(model_path):
            print(f"跳过不存在的模型: {model_path}")
            continue
        
        print(f"\n{'='*60}")
        print(f"转换YOLOv5模型: {model_name}")
        print(f"{'='*60}")
        
        output_path = f"converted_models/{model_name}_rk3568.rknn"
        
        converter = YOLOv5Converter(model_path, output_path, input_size)
        
        # 转换模型
        if converter.convert_yolo_model():
            # 测试模型
            converter.test_yolo_model()
        
        print(f"YOLOv5模型 {model_name} 处理完成")

if __name__ == "__main__":
    convert_yolo_models()

精度验证和对比

转换前后精度对比

#!/usr/bin/env python3
# accuracy_validation.py

import os
import cv2
import numpy as np
import onnxruntime as ort
from rknn.api import RKNN
import matplotlib.pyplot as plt

class AccuracyValidator:
    """精度验证器"""
    
    def __init__(self, onnx_path, rknn_path):
        self.onnx_path = onnx_path
        self.rknn_path = rknn_path
        self.onnx_session = None
        self.rknn = None
    
    def load_models(self):
        """加载模型"""
        # 加载ONNX模型
        self.onnx_session = ort.InferenceSession(self.onnx_path)
        print(f"✓ ONNX模型加载成功: {self.onnx_path}")
        
        # 加载RKNN模型
        self.rknn = RKNN(verbose=False)
        ret = self.rknn.load_rknn(self.rknn_path)
        if ret != 0:
            raise Exception("RKNN模型加载失败!")
        
        ret = self.rknn.init_runtime()
        if ret != 0:
            raise Exception("RKNN运行时初始化失败!")
        
        print(f"✓ RKNN模型加载成功: {self.rknn_path}")
    
    def prepare_test_data(self, num_samples=50):
        """准备测试数据"""
        test_data = []
        
        for i in range(num_samples):
            # 生成随机测试图像
            image = np.random.randint(0, 256, (224, 224, 3), dtype=np.uint8)
            
            # 预处理
            processed = self.preprocess_image(image)
            test_data.append(processed)
        
        return np.array(test_data)
    
    def preprocess_image(self, image):
        """图像预处理"""
        # 转换为RGB
        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
        
        # 归一化
        image = image.astype(np.float32) / 255.0
        
        # ImageNet标准化
        mean = np.array([0.485, 0.456, 0.406])
        std = np.array([0.229, 0.224, 0.225])
        image = (image - mean) / std
        
        # 转换为NCHW格式
        image = np.transpose(image, (2, 0, 1))
        
        return image
    
    def compare_outputs(self, test_data):
        """比较输出结果"""
        onnx_outputs = []
        rknn_outputs = []
        
        print("执行精度对比测试...")
        
        for i, data in enumerate(test_data):
            if (i + 1) % 10 == 0:
                print(f"处理进度: {i+1}/{len(test_data)}")
            
            # ONNX推理
            input_name = self.onnx_session.get_inputs()[0].name
            onnx_result = self.onnx_session.run(None, {input_name: np.expand_dims(data, 0)})
            onnx_outputs.append(onnx_result[0][0])  # 移除batch维度
            
            # RKNN推理
            rknn_result = self.rknn.inference(inputs=[np.expand_dims(data, 0)])
            rknn_outputs.append(rknn_result[0][0])  # 移除batch维度
        
        onnx_outputs = np.array(onnx_outputs)
        rknn_outputs = np.array(rknn_outputs)
        
        return onnx_outputs, rknn_outputs
    
    def calculate_metrics(self, onnx_outputs, rknn_outputs):
        """计算精度指标"""
        # 均方误差 (MSE)
        mse = np.mean((onnx_outputs - rknn_outputs) ** 2)
        
        # 平均绝对误差 (MAE)
        mae = np.mean(np.abs(onnx_outputs - rknn_outputs))
        
        # 余弦相似度
        def cosine_similarity(a, b):
            return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
        
        cos_similarities = []
        for onnx_out, rknn_out in zip(onnx_outputs, rknn_outputs):
            cos_sim = cosine_similarity(onnx_out.flatten(), rknn_out.flatten())
            cos_similarities.append(cos_sim)
        
        avg_cos_similarity = np.mean(cos_similarities)
        
        # Top-1准确率对比
        onnx_top1 = np.argmax(onnx_outputs, axis=1)
        rknn_top1 = np.argmax(rknn_outputs, axis=1)
        top1_accuracy = np.mean(onnx_top1 == rknn_top1)
        
        # Top-5准确率对比
        def top5_accuracy(onnx_out, rknn_out):
            onnx_top5 = np.argsort(onnx_out, axis=1)[:, -5:]
            rknn_top5 = np.argsort(rknn_out, axis=1)[:, -5:]
            
            matches = 0
            for i in range(len(onnx_out)):
                if np.any(np.isin(onnx_top5[i], rknn_top5[i])):
                    matches += 1
            
            return matches / len(onnx_out)
        
        top5_acc = top5_accuracy(onnx_outputs, rknn_outputs)
        
        metrics = {
            'mse': mse,
            'mae': mae,
            'cosine_similarity': avg_cos_similarity,
            'top1_accuracy': top1_accuracy,
            'top5_accuracy': top5_acc
        }
        
        return metrics
    
    def visualize_comparison(self, onnx_outputs, rknn_outputs, save_path='accuracy_comparison.png'):
        """可视化对比结果"""
        fig, axes = plt.subplots(2, 2, figsize=(12, 10))
        
        # 输出分布对比
        axes[0, 0].hist(onnx_outputs.flatten(), bins=50, alpha=0.7, label='ONNX', density=True)
        axes[0, 0].hist(rknn_outputs.flatten(), bins=50, alpha=0.7, label='RKNN', density=True)
        axes[0, 0].set_title('输出分布对比')
        axes[0, 0].legend()
        
        # 散点图对比
        sample_indices = np.random.choice(len(onnx_outputs.flatten()), 1000, replace=False)
        onnx_sample = onnx_outputs.flatten()[sample_indices]
        rknn_sample = rknn_outputs.flatten()[sample_indices]
        
        axes[0, 1].scatter(onnx_sample, rknn_sample, alpha=0.5)
        axes[0, 1].plot([onnx_sample.min(), onnx_sample.max()], 
                       [onnx_sample.min(), onnx_sample.max()], 'r--')
        axes[0, 1].set_xlabel('ONNX输出')
        axes[0, 1].set_ylabel('RKNN输出')
        axes[0, 1].set_title('输出相关性')
        
        # 误差分布
        errors = np.abs(onnx_outputs - rknn_outputs)
        axes[1, 0].hist(errors.flatten(), bins=50)
        axes[1, 0].set_title('绝对误差分布')
        axes[1, 0].set_xlabel('绝对误差')
        
        # Top-1预测对比
        onnx_top1 = np.argmax(onnx_outputs, axis=1)
        rknn_top1 = np.argmax(rknn_outputs, axis=1)
        
        axes[1, 1].scatter(onnx_top1, rknn_top1, alpha=0.6)
        axes[1, 1].plot([0, max(onnx_top1.max(), rknn_top1.max())], 
                       [0, max(onnx_top1.max(), rknn_top1.max())], 'r--')
        axes[1, 1].set_xlabel('ONNX Top-1预测')
        axes[1, 1].set_ylabel('RKNN Top-1预测')
        axes[1, 1].set_title('Top-1预测对比')
        
        plt.tight_layout()
        plt.savefig(save_path, dpi=300, bbox_inches='tight')
        plt.close()
        
        print(f"对比图表保存到: {save_path}")
    
    def run_validation(self):
        """运行完整的精度验证"""
        print("开始精度验证...")
        
        # 加载模型
        self.load_models()
        
        # 准备测试数据
        test_data = self.prepare_test_data(num_samples=100)
        
        # 比较输出
        onnx_outputs, rknn_outputs = self.compare_outputs(test_data)
        
        # 计算指标
        metrics = self.calculate_metrics(onnx_outputs, rknn_outputs)
        
        # 打印结果
        print("\n精度验证结果:")
        print(f"均方误差 (MSE): {metrics['mse']:.6f}")
        print(f"平均绝对误差 (MAE): {metrics['mae']:.6f}")
        print(f"余弦相似度: {metrics['cosine_similarity']:.6f}")
        print(f"Top-1准确率: {metrics['top1_accuracy']:.4f}")
        print(f"Top-5准确率: {metrics['top5_accuracy']:.4f}")
        
        # 可视化
        self.visualize_comparison(onnx_outputs, rknn_outputs)
        
        # 释放资源
        if self.rknn:
            self.rknn.release()
        
        return metrics

def batch_accuracy_validation():
    """批量精度验证"""
    model_pairs = [
        {
            'name': 'ResNet18',
            'onnx': 'models/classification/resnet18.onnx',
            'rknn': 'converted_models/resnet18_rk3568.rknn'
        },
        {
            'name': 'MobileNetV2',
            'onnx': 'models/classification/mobilenet_v2.onnx',
            'rknn': 'converted_models/mobilenet_v2_rk3568.rknn'
        }
    ]
    
    results = {}
    
    for model_info in model_pairs:
        model_name = model_info['name']
        onnx_path = model_info['onnx']
        rknn_path = model_info['rknn']
        
        if not (os.path.exists(onnx_path) and os.path.exists(rknn_path)):
            print(f"跳过 {model_name}: 模型文件不存在")
            continue
        
        print(f"\n{'='*50}")
        print(f"验证模型: {model_name}")
        print(f"{'='*50}")
        
        validator = AccuracyValidator(onnx_path, rknn_path)
        metrics = validator.run_validation()
        results[model_name] = metrics
    
    # 汇总结果
    print(f"\n{'='*60}")
    print("精度验证汇总")
    print(f"{'='*60}")
    
    for model_name, metrics in results.items():
        print(f"\n{model_name}:")
        for metric_name, value in metrics.items():
            print(f"  {metric_name}: {value:.6f}")

if __name__ == "__main__":
    batch_accuracy_validation()

常见问题和解决方案

转换问题排查

# 1. 检查ONNX模型
python3 -c "
import onnx
model = onnx.load('model.onnx')
onnx.checker.check_model(model)
print('ONNX模型验证通过')
"

# 2. 检查RKNN-Toolkit2版本
pip show rknn-toolkit2

# 3. 检查支持的算子
python3 -c "
from rknn.api import RKNN
rknn = RKNN()
print('支持的算子:', rknn.list_ops())
"

# 4. 内存使用监控
free -h
top -p $(pgrep python3)

性能优化建议

# 1. 量化数据集优化
# 使用真实数据而非随机数据
# 确保数据分布代表性

# 2. 模型结构优化
# 减少动态形状
# 合并小算子
# 使用硬件友好的算子

# 3. 转换参数调优
optimization_levels = [0, 1, 2, 3]  # 测试不同优化级别
quantization_types = [
    'asymmetric_quantized-u8',
    'dynamic_fixed_point-i8',
    'dynamic_fixed_point-i16'
]

# 4. 后处理优化
# 在NPU上执行尽可能多的计算
# 减少数据传输

总结

通过本章的学习,您已经掌握了:

  1. 模型转换基本概念: 理解了为什么需要模型转换以及转换的基本流程
  2. 模型准备: 学会了如何获取、验证和准备待转换的模型
  3. RKNN-Toolkit2 API: 掌握了核心转换API的使用方法和高级配置选项
  4. 实战转换: 完成了ResNet分类模型和YOLOv5检测模型的转换实战
  5. 精度验证: 学会了如何验证转换后模型的精度和性能

下一章将介绍如何在板端运行这些转换后的自定义模型,包括Python和C++ API的使用方法。

在 GitHub 上编辑此页