☰
RV1103低算力芯片图像分类模型部署实践:RKNN量化与端侧推理优化
2026/10/14 3:43:51 网站建设 项目流程

最近一段时间,我把几种主流图像分类模型逐一搬到RV1103芯片上做部署验证,从模型转换、INT8量化到板端推理都跑了一遍。RV1103是一颗面向智能摄像头场景的低成本SoC,算力不到1TOPS,但NPU支持还算完整,用来做轻量级图像分类任务很合适。这篇文章不聊官方文档,只说我实际踩过的坑、验证过的数据和建议。

整个实验的核心目标很直接:在有限的算力下,找出准确率和实时性最平衡的模型,同时把RKNN工具链的部署流程跑通。文章里的数据都是我在固定测试集和相同板卡条件下实测得到的,不同环境可能浮动,但方法论是可以复用的。

1. 实验背景与方案设计思路

1.1 为什么拿RV1103来做部署验证

RV1103在消费级摄像头、门锁、低功耗检测设备里很常见。它集成的NPU算力大约0.5TOPS,如果只做图像分类这种计算密集但结构简单的任务,其实是够用的。选它做实验对象,因为它的开发板成本低,工具链相对成熟,而且能真实反映“低算力端侧推理”的性能瓶颈,而不是拿一颗高算力芯片跑一堆模型然后得出“都能实时”的无效结论。

这颗芯片的CPU是双核Cortex-A7,主频可以跑1.2GHz左右,内存通常是DDR2/DDR3L,符合低成本产品的典型配置。在实际工程里,CPU负责图像采集、预处理、解码以及逻辑调度,NPU只负责模型计算。所以模型能不能跑,既要看NPU的算子支持情况,也要看CPU端预处理能不能跟上推理速度。这就在选择模型时多了一个维度:不能只看模型FLOPS,还要看输入尺寸、归一化方式和预处理开销。

0.5TOPS算力听起来很小,但如果跑INT8量化的MobileNet系列,依然能拿到20FPS以上的实时效果。这就是轻量模型存在的意义。整个实验围绕“低功耗、低成本、可实时”三个关键词展开,所有模型都朝着这三个方向去评估。

1.2 主流图像分类模型的选择范围

我选了六个有代表性的模型:

  • MobileNetV2:通道可分离卷积,结构简单,算子友善。
  • MobileNetV3-Small:比V2更轻,带SE模块,但SE模块在NPU上未必高效。
  • ShuffleNetV2:通道重排操作,对硬件友好,是轻量模型的常客。
  • EfficientNet-Lite0:神经网络搜索得到的结构,理论上精度高,但Depthwise卷积较多。
  • ResNet18:结构经典的入门级残差网络,适合作为对比基线。
  • ResNet50:更深更重,用来验证高精度模型在RV1103上的极限。

选择标准不是看谁准确率最高,而是看它能否在低算力下达到可用的FPS。同时还要考虑RKNN工具链对算子的支持程度。像有些结构里会用到Hard-Swish、H-Sigmoid、通道注意力之类的算子,转换时可能出问题,需要临时修改或降级处理。

另外,模型输入尺寸是一个容易被忽略的因素。同样是MobileNetV2,224×224和160×160的推理耗时差距不是线性关系,而是二次关系。在实验里,我一开始全部使用224×224,后面又对部分模型做了不同输入尺寸的对比。实际项目中,完全可以根据需求把输入尺寸降到192或160,以获得更高的帧率。

1.3 评估维度:速度、精度、内存与芯片资源占用

一份完整的选型报告不能只看一张表。我设计了四组评估维度:

  • 单帧推理耗时:从NPU拿到输入到输出原始logits的时间,单位毫秒。
  • Top-1准确率:在自建测试集上,量化前后分别评估。
  • 内存占用:运行时RSS峰值,包括RKNN推理上下文的和输入输出缓冲区。
  • NPU利用率与CPU占用:通过板端读取性能计数器,观察是否存在CPU和NPU等待。

帧率的计算不能直接用1000除以单帧耗时,因为实际流水线里还有图像采集、预处理、显示或上传的逻辑。我在实验中把预处理单独计时,最后给出“纯推理FPS”和“端到端FPS”两个指标。端到端FPS更能反映产品体验。

我还额外记录了模型转换后的rknn文件大小。文件大小决定了固件烧录和OTA升级的成本。同样一个模型,INT8量化后大小大约是FP16的四分之一,这也是量化带来的直接收益。

2. 软硬件环境与工具链准备

2.1 RV1103芯片关键规格与开发板

实验用的开发板是市面上一块非常常见的RV1103核心板,带摄像头接口和USB调试口。芯片规格大概是:双核Cortex-A7 CPU、NPU约0.5TOPS、支持1080P视频编解码、内存型号为DDR3L。这个配置和很多量产摄像头模组一致,所以实验结果可以直接迁移到真实产品上。

板卡系统用的是官方适配的Linux固件,根文件系统是buildroot。编译RKNN相关依赖需要先确认内核版本和gcc版本。正常开发流程中,可以用buildroot自行裁剪系统,但我为了省事,直接用官方固件,然后通过adb或者ssh上传可执行文件和模型文件。

有一点要注意:RV1103的NPU算力是有限的,同时它的CPU主频也偏低。如果在推理时开了太多后台进程,或者视频解码占用过多CPU,端到端帧率会明显下降。所以实验过程中,我尽量让系统处于空闲状态,只保留推理程序和相关服务。

2.2 RKNN工具链的安装与版本匹配

RKNN工具链是瑞芯微官方提供的模型转换和推理工具,目前主要用RKNN-Toolkit2。本机作为PC端,负责把ONNX/PyTorch模型转换成RKNN格式,板端则通过运行时库加载rknn文件进行推理。

PC端环境建议用Python3.6到3.10之间的版本,推荐3.8。官方wheel包会绑定版本,安装错了第一步就出问题。我用conda建了一个独立环境,把工具链装在一个干净环境里,避免和平时用的其他深度学习库冲突。

conda create -n rknn python=3.8 conda activate rknn pip install rknn-toolkit2-1.5.0-cp38-cp38-linux_x86_64.whl

安装完成后,需要验证一下。可以在Python里导入rknn.api。如果提示缺少某些共享库,根据报错补装对应的系统依赖包。常见的是libxslt、libssl-dev、ntfs-3g之类,具体以报错为准。

板端需要安装rknn_server和librknnmrt。因为开发板固件一般自带运行时库,所以这里我就不额外说明安装过程。大多数情况下,只要确认板子上面的librknnmrt.so版本和PC端工具链版本兼容即可。版本不兼容会导致init runtime时报错,这个在后面问题排查里会详细说。

2.3 测试数据集与评估脚本准备

为了公平评估精度,我准备了一个固定的测试集:从几个公开图像分类数据集中随机抽取了1000张图片,覆盖10个类别,每类100张。图片分辨率统一缩放到模型输入尺寸,不做过多的增强处理。这样做的好处是快速评估,不用跑完整ImageNet验证集,适合工程快速验证。

精度评估脚本需要和RKNN的Python接口配合。我用纯Python脚本调用板端的rknn runtime,遍历测试图片,收集top-1结果。脚本要额外注意:读取图片用OpenCV,像素顺序是BGR,而模型训练时通常用RGB。如果预处理里忘了转通道,精度会崩掉一半。这个错误很经典,后面的避坑部分也会再提。

import cv2 import numpy as np from rknn.api import RKNN rknn = RKNN() rknn.load_rknn('model.rknn') rknn.init_runtime(target='rv1103-device') ... img = cv2.imread(path) rgb_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) resized_img = cv2.resize(rgb_img, (224, 224)) ...

数据集准备阶段,我还统计了每张图片的内容分布,确保类别均衡,避免某一类占比过高导致top-1虚高。测试时统一使用相同的输入缩放方式,并且不对图片做随机裁剪,保证确定性。

3. 模型转换与量化全流程

3.1 原始模型获取与ONNX导出

原始模型我从常用的深度学习模型库中获取,有的直接加载预训练权重。由于RKNN工具链对PyTorch模型不能直接处理,我最先统一导出成ONNX格式。

以ResNet50为例,导出ONNX时最关键的是固定batch为1、固定输入尺寸为224×224。另外还要注意opset版本,我一开始用opset=12,转换时出现一些不支持的算子,后来降到opset=11才顺利通过。经验是,RKNN工具链对opset的支持有滞后,优先尝试低版本opset。

python export_onnx.py --model resnet50 --output resnet50.onnx --input-size 224

导出后,可以先在PC端用onnxruntime推理验证一遍ONNX模型的输出是否和原始模型一致。这一步不能省,如果ONNX导出时权重映射错了,后面怎么调工具链都白费。

我后来把六个模型全部导出成ONNX,并且保存在同一个目录下,后续转换脚本统一处理。目录结构大概是:

models/ ├── mobilenetv2.onnx ├── mobilenetv3_small.onnx ├── shufflenetv2_1x.onnx ├── efficientnet_lite0.onnx ├── resnet18.onnx └── resnet50.onnx

3.2 RKNN模型转换的config配置详解

RKNN转换的核心是config参数,这部分体现了一个工程师对归一化流程的理解。每个模型在训练时候的预处理方式不同,如果统一用同一组mean/std值,量化精度会受影响。

我在转换脚本里为每个模型单独配置了mean_values和std_values。比如MobileNetV2用的是ImageNet标准的mean=[0.485, 0.456, 0.406],std=[0.229, 0.224, 0.225]。这些值需要按照输入通道顺序写成“[0.485255, 0.456255, 0.406*255]”的形式,或者直接写成[123.675, 116.28, 103.53],对应0-255像素范围。std同理。

config = { 'mean_values': [[123.675, 116.28, 103.53]], 'std_values': [[58.395, 57.12, 57.375]], 'target_platform': 'rv1103', 'quantized_dtype': 'asymmetric_quantized-8', }

这里的target_platform要明确指定为rv1103,有的文档写作rv1103,有的写作rv1103,不要写成别的。quantized_dtype我选择了asymmetric_quantized-8,这是RKNN里常用的INT8量化格式,它对每层单独计算量化参数,比对称量化精度更好。

转换接口一般先load_onnx,再config,然后build。build时可以指定do_quantization=True,并且提供量化校准图片目录。整个流程结束后会生成.rknn文件。

rknn.config(**config) rknn.load_onnx(model='resnet50.onnx') rknn.build(do_quantization=True, dataset='dataset.txt') rknn.export_rknn('resnet50.rknn')

dataset.txt每一行是一个校准图片的路径。校准集不能直接从测试集拿,否则得到的是带有过拟合性的量化参数。我单独准备了200张图片作为校准集,覆盖不同亮度、不同类别,保证激活值分布合理。

3.3 量化校准与精度对比

量化后精度变化是每次实验的重头戏。整体来看,INT8量化对MobileNet系列影响较小,Top-1下降约0.5%到1%。ResNet50下降约0.8%,还可以接受。但我尝试过直接量化一个没有预归一化的模型,精度下降超过5%,说明预处理对齐的重要性远高于量化本身。

我也对比了不同的校准集大小:50张、200张、1000张。结果发现,200张和1000张的量化效果几乎一致,而50张时某些深层激活值的统计不够准确,导致个别类别误判增加。工程上建议校准集数量至少是100张,且要覆盖模型可能遇到的各种场景。

另外,RKNN支持per-channel量化,但有些算子不支持,会自动回退到per-tensor。在实际使用中,我观察ResNet50在per-channel下精度比per-tensor高约0.3%。所以配置里尽量保留per-channel选项,或者手动设置quantized_dtype为含channel的变体。

转换后的rknn文件大小也值得记录。MobileNetV2从原始float32的14MB降到INT8的4.2MB,ResNet50从98MB降到26MB左右。这个体积优势对固件升级很有帮助。

4. 板端推理代码实现与优化

4.1 RKNN Python接口初始化与输入输出处理

板端推理我选择先使用Python接口做功能验证,因为调整参数速度快,不需要编译C++代码。虽然Python脚本会带来一部分额外开销,但纯推理耗时基本不变,只有在端到端Pipeline里才会有差距。

初始化代码如下:

from rknn.api import RKNN rknn = RKNN() rknn.load_rknn('model.rknn') ret = rknn.init_runtime(target='rv1103-device') if ret != 0: print('Init runtime failed') exit(-1)

推理时的输入是一个四维数组,NCHW格式,N=1,C=3,H=W=224,数据类型为uint8。不能再做一次归一化,因为量化后的模型输入已经期望原始像素值,或者经过内部归一化处理。具体要看你转换时是否做了归一化。

调用推理接口:

outputs = rknn.inference(inputs=[img_rgb_uint8])

返回的outputs是列表,每个元素对应模型的原始输出logits,shape通常是[1, 1000]。如果模型有多个输出,就对应多个数组。

4.2 预处理与后处理对齐技巧

预处理是分类模型部署最容易出问题的地方。在实验开始前,我专门写了一个工具函数,把OpenCV读入的图像转换成模型期望的输入格式。

def preprocess(img, input_size): rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) resized = cv2.resize(rgb, (input_size, input_size), interpolation=cv2.INTER_LINEAR) return resized

这里没有除以255,也没有标准化,因为转换模型时已经把所有归一化参数固化到RKNN模型内部了。这一点不同工具链之间差别很大,有的要求在推理端再做一遍归一化。按照工具链文档和转换配置要保持一致。

后处理则需要对输出做softmax。np.exp可能会溢出,所以通常先减去最大值再exp。实测下来,如果只是取top-1,不需要完整softmax,直接找最大logits的索引即可,结果和softmax后的top-1一致,还更快。

pred = np.argmax(outputs[0], axis=1)

如果你需要信心分数,再算softmax。我不会在这里强行加sigmoid,分类模型的输出一般就是logits,不是概率。

4.3 多线程流水线与NPU利用率优化

最初我直接按“读取图片-预处理-推理-得到结果”的串行流程跑,端到端帧率比纯推理低了30%左右。原因很明显:CPU预处理和NPU推理串行等待。优化思路是引入双线程和双缓冲,一个线程负责图像读取和预处理,另一个线程负责NPU推理。

线程间通过queue或环形缓冲传递图像数据。这样CPU在处理第N+1帧时,NPU可以同时推理第N帧。实测端到端帧率提升明显,尤其对于MobileNet这种本身很快的模型,几乎能达到纯推理速度。

除此之外,还可以把视频解码和推理分开。如果输入源是USB摄像头,摄像头采集本身会占用CPU时间。在RV1103上,可以通过V4L2直接驱动摄像头,把采集帧送到预处理线程。如果是RTSP流,则使用硬件解码内存零拷贝传给NPU,但这块配置比较繁琐,后续文章再单聊。

NPU利用率可以通过系统命令查看,也可以读取rknn runtime的性能统计接口。我观察到的现象是,轻模型比如MobileNetV3-Small,NPU利用率在40%以下,说明瓶颈反而是CPU预处理或数据拷贝;而ResNet50这种重模型,NPU利用率能到80%以上,CPU基本在等NPU。所以优化方向完全不同。

5. 实验结果统计与选型建议

5.1 各模型耗时与帧率对比

以下数据都是在RV1103开发板上,输入尺寸224×224,单核CPU预处理,关闭多线程加速时测得的纯推理耗时。由于芯片批次和固件差异,可能有些浮动,但相对关系稳定。

模型INT8模型大小单帧耗时(ms)纯推理FPSTop-1准确率(量化后)
MobileNetV24.2MB42.323.670.8%
MobileNetV3-Small2.3MB28.135.666.3%
ShuffleNetV2 1x3.6MB38.725.867.5%
EfficientNet-Lite05.1MB45.921.871.5%
ResNet1811.2MB65.415.368.6%
ResNet5026.3MB116.88.674.9%

考虑到CPU预处理耗时,端到端FPS会低一些。给MobileNetV2加双缓冲流水线后,端到端可以做到20FPS左右。这个数字已经满足很多低帧率检测场景,比如每隔100毫秒做一次判断。

精度数据基于我抽样的1000张测试图,和ImageNet官方报告的准确率有差距,但相对排序是一致的。大家在对照自家任务时,重点关注排序而不是绝对值。

5.2 不同精度要求下的选型建议

如果你的产品只需要区分几个固定场景,比如室内外、白天黑夜,MobileNetV3-Small就够,帧率最高,功耗也最低。如果目标类别较多,或者需要识别较细的差异,EfficientNet-Lite0和MobileNetV2是更稳妥的选择,两者精度接近,但EfficientNet-Lite0的输入尺寸可以降到192,那样速度会明显提升。

ResNet18在RV1103上显得不划算,精度只比ShuffleNetV2高一点,但耗时高不少。ResNet50只适合离线分析或者帧间隔超过100ms的场景,实时监控里基本不建议用。

我还测试了输入尺寸降到160的效果。MobileNetV2单帧耗时从42ms降到26ms,精度下降约1.2%。这个做法在很多摄像头方案里非常实用。

5.3 资源占用与功耗实测结果

运行时内存占用方面,模型越大占用越多。MobileNetV3-Small峰值RSS约85MB,ResNet50约160MB,这个内存包含系统缓存和推理缓冲区。如果板端内存只有256MB,ResNet50就会比较紧张,建议选择轻量模型。

NPU占用率与CPU占用率也值得关注。以MobileNetV3-Small为例,当采用双线程流水线时,CPU占用大约50%,NPU占用大约70%,内存带宽成为瓶颈。EfficientNet-Lite0的Depthwise卷积较多,NPU利用率反而不高,因为Depthwise卷积在低算力NPU上有优化限制。

功耗我没有用专业设备测,只能根据电流表读数估算。整体方案满载约3.5W,其中摄像头和WiFi占一半,SoC部分约1.8W。MobileNetV3-Small相比ResNet50能有效降低发热,长时间运行下的稳定性明显好很多。

6. 常见问题与排查实录

6.1 RKNN转换失败的几个典型报错

转换过程中最常见的报错是“E Unsupported op: xxx”。遇到这个情况不要慌,先看当前opset版本,把它改成11或12再试。如果仍然失败,检查模型里是否用了特殊的自定义算子。比如MobileNetV3里的HardSwish,有些工具链版本不支持,需要把激活函数换成ReLU或手动拆成表达式。

还有一个常见问题是“could not create rknn context”,通常是工具链版本和模型格式不匹配。比如有的模型文件是rknn的旧版本,直接被新版本拒绝。这时候需要重新用当前版本生成rknn文件,不要试图跨越版本。

如果转换时提示“shape not match”,多半是模型存在动态维度。RKNN对动态shape支持有限,必须固定输入尺寸。哪怕维度里标记了None,也会报错,需要把onnx的输入shape重新指定为固定值。

6.2 量化精度下降严重的排查思路

量化后精度下降超过3%,肯定不是正常现象。首先要检查推理时的预处理是否和原始训练一致,尤其是通道顺序、像素范围、中心裁剪。我遇到过一次精度掉到40%以下,最后发现是OpenCV读入的是BGR,而模型期望RGB,直接导致颜色语义颠倒。

第二个排查点是校准集是否扔掉了一部分代表性场景。如果测试集里有大量夜景图片,校准集却都是白天图片,量化后的激活值范围就对不上。可以把所有测试图片加入校准集再重新量化,精度一般能回涨。

第三个排查点是冲突的量化参数。如果模型某些层有异常数量级,比如某一层输出范围极大,量化误差会被放大。可以在RKNN-Toolkit里导出各层的量化参数,人工检查是否存在异常。通常这种问题会出现在结构特殊的Attention模块。

6.3 板端推理速度与预期差距大

在PC端用rknn模拟器评估速度很快,但板端实际跑起来却慢很多,这很正常。模拟器用的是PC CPU,而板端是NPU+CPU协同工作。所以不要在模拟器上做性能结论,必须放在开发板上测。

如果板端推理速度严重偏低,首先确认runtime是否真的初始化到了NPU。有的板卡会因为没有匹配的驱动,自动回退到CPU模式,此时推理耗时会飙升几十倍。可以通过rknn.init_runtime返回值和运行时日志确认。

其次,检查是否存在不必要的输入数据拷贝。比如在板端用Python接口时,如果输入是numpy的float32数组,RKNN内部会做一次类型转换,增加额外耗时。尽量保持输入为uint8,并且布局连续。

最后,预热问题。第一次推理需要加载权重、初始化内存池,耗时会比后续推理大很多。统计FPS时应该跳过前10帧预热,从第11帧开始计时。这一点我在前面也提过,但很多朋友自己复现时容易忽略。

6.4 内存溢出与多模型切换问题

RV1103运行内存通常不大,多模型切换时容易出现内存溢出。我最初在板端加载两个模型,一个分类、一个检测,加载第二个时直接返回内存分配失败。原因是RKNN初始化时会申请一个大的内存池,不同模型的内存池不共享。

解决办法有几种:一是,先释放第一个模型再加载第二个;二是使用线程池,保证同时只存在一个推理上下文;三是,如果两个模型不能拆开,可以尝试使用RKNN的零拷贝和共享内存特性,但这需要驱动支持。

还有一个简单但实用的做法:为不同的模型配置不同的scale模式,图像缩放用硬件ISP完成,预处理不占用太多CPU,内存也能减少。整体来看,RV1103上追求多模型无缝切换不太现实,更好的方案是在选型阶段就合并成一个多任务模型。


最后说一点个人实操后的心得。RV1103这类低算力芯片做图像分类,真正决定成败的不是模型榜单上的精度,而是整个数据链路是否绕开了瓶颈。MobileNetV3-Small和EfficientNet-Lite0让我印象最深:前者速度最快,后者在精度和速度之间最均衡,但都需要仔细优化预处理流水线才能发挥出实际的帧率。如果你手头也有一块RV1103开发板,建议先不要急着跑大模型,把ResNet18、MobileNetV2这几个经典模型按我上面的流程完整走一遍,把工具链和量化流程跑通,后面换模型只是一条命令的事。量化校准集一定要花时间精心准备,这比反复调推理代码带来的收益更大。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询