
去年年底从抽屉里翻出吃灰的嘉立创泰山派RK3566开发板当时想做一个真正的边缘AI摄像头本地识别物体、不依赖云端、能实时显示分类结果。折腾了两周从画扩展板到模型转换再到板端部署把RKNN这套工具链彻底走了一遍。这篇博文就把整个从零到一的过程完整拆开包括方案选型、嘉立创EDA画PCB、MobileNetV3模型微调、rknn-toolkit2转换、板端推理性能优化以及我在实际踩坑中总结的排查方法。想用RK3566这类带NPU的国产板子跑自己的模型、又不想走弯路的朋友这份实战记录应该能帮你省下不少时间。1. 项目定位与整体方案的几个关键取舍1.1 这套方案到底解决什么问题很多人一提到智能摄像头就直接往云服务上靠人脸、车牌全都传到服务器识别。但我这个项目从一开始就定了个死规矩所有识别必须在板端本地完成。原因很简单摄像头画面属于敏感数据能不出本地就尽量不出另外工业现场或居家环境下网络抖动会导致识别延迟不可控本地NPU推理则稳定得多。泰山派RK3566自带NPU正好就是做这件事的理想平台。我最终实现的效果是摄像头实时采集画面板载NPU运行MobileNetV3分类模型把识别结果标签、置信度直接叠加到画面显示整个过程延迟在肉眼可接受的范围内完全离线。这个方案适合谁来参考如果你手里有泰山派、奕斯伟或者任何RK3566/RK3568系列的板子想跑通训练自己的图像分类模型 → 转换成RKNN → 板端实时推理这条完整链路这篇文章应该能带你绕开我当初踩过的所有坑。1.2 为什么是RK3566 MobileNetV3而不是别家先说硬件。RK3566是瑞芯微推出的四核Cortex-A55处理器主频最高1.8GHz集成一颗NEON协处理器和NPU加速单元官方标称NPU算力在1 TOPS级别跑轻量级分类网络绰绰有余。更重要的是这颗芯片周边生态相当完整MIPI-CSI摄像头接口、MIPI-DSI/LVDS显示接口、千兆以太网、USB 3.0一个嵌入式视觉终端要用到的接口基本全齐了。嘉立创泰山派作为基于RK3566的国产开发板最大的优势是硬件资料完全开源原理图、PCB、底板的嘉立创EDA工程全部可以直接下载这就给后面自己画扩展板提供了极大便利。再说模型。MobileNetV3是Google在2019年提出的轻量级CNN专门为移动端和嵌入式场景设计。我选它不是因为最新而是因为它在精度和计算量之间的平衡确实适合NPU落地。MobileNetV3-Small的FLOPs只有约6000万在1 TOPS的NPU上跑224x224输入只需要十几毫秒到二十几毫秒。就算后期要换成目标检测模型YOLOX的骨干网络也经常用MobileNetV3做特征提取所以先把这个分类链路跑通等于给后续检测任务铺路。1.3 整个系统的技术链路长什么样整个系统的数据流是这样的摄像头模组通过MIPI-CSI接口把RAW图像传给RK3566的ISPISP输出YUV图像OpenCV读取后做预处理resize到模型输入尺寸、BGR转RGB、归一化然后交给NPU执行RKNN模型推理CPU端对输出向量做softmax和argmax拿到类别标签后叠加到画面显示最终通过HDMI或屏幕输出给人看。这条链路里有三个关键环节决定了项目成败模型能不能成功转换成RKNN格式并在NPU上跑起来预处理和训练时是否一致这直接影响识别准确率帧率瓶颈在CPU还是NPU这决定最终体验流畅与否后面所有工作实际上都是围绕这三个点展开的。2. 硬件准备与嘉立创EDA扩展板设计实战2.1 泰山派开发板和摄像头选型泰山派主板本身集成了不少东西板载的接口包括千兆网口、两个USB口、一个MIPI-DSI显示接口、一个MIPI-CSI摄像头接口、3.5mm耳机口和Debug串口。用官方SD卡镜像启动系统之后默认桌面环境跑在HDMI输出上整体使用体验和一台小电脑差不多。摄像头选择上我强烈建议直接买MIPI接口的模组不要图省事用USB摄像头。原因有两个一是MIPI摄像头数据走ISP硬件处理CPU不需要参与逐帧拷贝帧率更高二是因为泰山派底板的MIPI-CSI FPC座是有方向性的用官方配套的模组如GC2093、OV5640可以直接插上去不用改线序。我用的是一颗支持1080P输出的500万像素MIPI摄像头模组排线接口和泰山派底板上的40pin FPC座匹配。拿到手之后先用排线连上确认系统能出图再继续下一步。这一步千万别跳后面画扩展板、写DTS都要以摄像头在当前系统能正常工作为前提。2.2 用嘉立创EDA画扩展板原理图怎么做为什么非要自己画一块扩展板因为泰山派底板的接口虽然全但都是排针、FPC座这种通用形式实际项目里你需要把摄像头接口固定位置、引出一路串口、加几个GPIO控制的LED指示灯、甚至板载一个DHT11温湿度传感器这些直接用杜邦线飞线会很乱而且MIPI信号走飞线到后面调试容易出问题。我的做法是画一块HAT扩展板通过底板上的2.54mm排针和泰山派连接。扩展板上的功能模块包括MIPI-CSI摄像头FPC转接座把泰山派信号按线序引出来一路UART调试串口用CH340N做USB转串口两个LED指示灯GPIO控制用于显示工作状态DHT11温湿度传感器挂在GPIO上补充环境信息原理图用嘉立创EDA专业版画流程非常顺。新建工程后在元件库搜索CH340NUSB转串口芯片DHT11温湿度传感器0402/0603电阻电容无源晶振12MHzCH340N用泰山派底板的排针封装直接从嘉立创开放平台下载泰山派底板工程把对应的连接器封装复制过来能省掉自己量封装的时间。原理图连线有几处要注意CH340N的VCC接5VV3引脚接0.1uF电容到GNDTXD接泰山派的RXD、RXD接泰山派的TXD注意交叉连接DHT11的DATA引脚上拉一个10k电阻到VCC不然读数据会一直失败LED指示灯串330欧电阻再接地电流控制在10mA左右选0603封装即可。2.3 布线时的几个细节电源线宽、MIPI差分、接地策略原理图画完之后直接转到PCB布局分区很重要。MIPI相关的走线要放在板子边缘让FPC座尽量靠近泰山派底板对应接口的位置减少走线长度电源部分单独放一块区域DHT11和LED放另一边和模拟量、数字量分开。走线宽度的经验值直接给结论5V电源输入线至少要1.2mm约48milGND线同样不能细。如果扩展板上还有其他设备总电流到1.5A建议直接铺铜而不是单纯走线。USB的D/D-信号线走0.2mm左右保持等长UART的TX/RX和GPIO控制线走0.2mm就行对阻抗不敏感。MIPI信号是差分对走线要求严一点。每组差分线的线宽建议8mil间距5mil组内等长误差控制在5mil以内。过孔尽量少打能不走底层就不走底层。如果是六层板或四层板MIPI差分对最好走参考平面连续的层避免跨分割。热词里提到嘉立创阻抗计算神器和50欧姆怎么画实际上MIPI用的是100欧姆差分阻抗USB高速信号才要求90欧姆HDMI是100欧姆。嘉立创EDA里自带的阻抗计算工具在工具→阻抗计算里按叠层参数算然后把差分线宽和间距填到设计规则里就行。接地策略上我这次踩了个小坑DHT11读出来的湿度一直在跳怎么排查都找不到原因最后发现是数字地平面和DHT11的模拟信号参考地混在一起。解决办法就是把传感器区域的GND独立出来通过0欧电阻在电源入口处单点汇合到主GND也就是所谓的星型接地。改完再读数据就稳定了。2.4 投板与打样免费打板全流程画完PCB后检查DRC重点看有没有未连接的引脚、丝印有没有压到焊盘然后在嘉立创下单下单。现在嘉立创对新人每月有免费打样名额规则一般是1至4层板、10cm x 10cm以内板厚默认1.6mm颜色可选绿色完全够这个扩展板用。打样流程很简单导出Gerber文件 → 在下单助手里上传 → 确认层数和尺寸 → 提交审核 → 等板子。我这次大概四天就收到了。顺手说一句嘉立创EDA专业版可以直接把工程在线同步到嘉立创下单不用手动导Gerber这对新手极其友好。3. 系统环境搭建与烧录3.1 镜像选择与烧录泰山派系统镜像有Ubuntu桌面版、Buildroot精简版等多个选择。做摄像头实时推理我推荐用Ubuntu桌面版因为后面要装OpenCV、Python、rknn-toolkit2-lite桌面版自带apt源和很多依赖库省去大量交叉编译的时间Buildroot虽然资源占用更少但装Python包会痛苦很多。镜像用balenaEtcher直接烧到一张16G以上的TF卡上。烧完插卡上电默认账号直接进桌面。如果手里有HDMI屏接上就能看到界面没有屏幕的话用串口登录也是一样的效果。3.2 RKNN工具链安装PC端和板端的差异这里有个特别容易绕晕的地方RKNN工具链分成两套。PC端x86_64 Linux装的是rknn-toolkit2负责把ONNX/PyTorch模型转换、量化成RKNN格式文件。这个包依赖很多深度学习库不能装在板子上。板端ARM64用的是rknn-toolkit2-lite或者直接调用librknnrt.so的C API负责加载RKNN模型并执行NPU推理。所以正确的流程是在电脑上装rknn-toolkit2做模型转换把生成的小体积.rknn文件拷到板子上再在板端装rknn-toolkit2-lite或者用C API做推理。PC端安装建议用conda单独建一个Python 3.8环境官方wheel包对Python版本有严格限制直接在系统Python里装大概率会把环境搞乱。板端则更简单官方Ubuntu镜像里一般已经预装了librknnrt.so如果没有从rknn-toolkit2-lite包里把对应ARM64的库拷到/usr/lib目录下即可。3.3 让系统识别摄像头DTS配置周知经常看到rk3566 点lvds、rk3566 aiot dts这类问题其实摄像头和屏幕一样能不能用完全取决于内核设备树里有没有把对应的节点打开。瑞芯微的内核里对很多常见sensor都写了驱动泰山派SDK也不例外关键是dts文件里要选对型号。踩坑实录我第一次上电后打开/dev/video0提示cannot open device排查了好久最后发现系统默认设备树里摄像头节点是disabled需要重新编译设备树把gc2093节点设为okay同时确认sensor的复位脚、电源脚和实际原理图上的GPIO一致否则上电时序不对摄像头根本无法初始化。如果不想编译内核还有一个替代思路先用USB摄像头替代MIPI摄像头验证整个推理链路等软件流程跑通之后再来解决MIPI硬件和DTS的问题。我就是先用USB摄像头把模型跑通再切回MIPI两种方式共用了同一套上层代码。4. MobileNetV3模型训练与RKNN转换4.1 MobileNetV3的核心结构回顾MobileNetV3是MobileNet系列的第三代核心结构分三块倒残差模块bottleneck、SE注意力机制和h-swish激活函数。倒残差结构是先升维、再深度可分离卷积、再降维升维是为了让深度可分离卷积在高维空间提取到更丰富的特征降维则是控制输出通道数整条路径的计算量和普通卷积相比大幅度降低。这也是MobileNet系列能在边缘设备上跑得动的核心原因。SE注意力模块是MobileNetV3的点睛之笔对每个通道的特征图做一个全局平均池化再通过两个全连接层算出每个通道的权重相当于给网络划重点让模型自动关注对当前任务最有利的通道。这种即插即用的模块后来也被大量使用在目标检测和分割模型里。h-swish激活函数则是把swish函数用ReLU和分段线性函数近似出来因为一步步计算exp在嵌入设备上开销大h-swish只需要几次乘加和ReLU硬件实现极其友好。4.2 准备数据集与迁移学习微调我的分类任务一开始只做三类识别人、电脑、水杯每类收集了100张左右图片从不同角度、不同光照条件下拍。数量虽然不算多但分类任务比较简单迁移学习完全够用。训练代码用PyTorch核心思路是加载ImageNet预训练的mobilenet_v3_small替换最后一层分类头的输出维度。具体代码如下import torch import torch.nn as nn from torchvision import models, transforms from torch.utils.data import DataLoader, Dataset model models.mobilenet_v3_small(weightsmodels.MobileNet_V3_Small_Weights.IMAGENET1K_V1) num_classes 3 model.classifier[3] nn.Linear(model.classifier[3].in_features, num_classes) # 数据增强与归一化 transform transforms.Compose([ transforms.Resize((256, 256)), transforms.RandomCrop(224), transforms.RandomHorizontalFlip(), transforms.ColorJitter(brightness0.2, contrast0.2), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) # 先冻结backbone只训练分类头再全模型微调 for param in model.features.parameters(): param.requires_grad False criterion nn.CrossEntropyLoss() optimizer torch.optim.Adam(filter(lambda p: p.requires_grad, model.parameters()), lr0.001)微调时先只训练分类头几个epoch之后把backbone解冻用较小的学习率0.0001全模型微调。我大概训练了30个epoch在验证集上准确率就能到95%以上。4.3 ONNX导出与RKNN转换训练好后先导出ONNX再转RKNN。这一步卡过很多人分享几个关键点。导出ONNX的坑RKNN工具链对ONNX算子支持有限opset_version建议用12不要用最新的17、18否则转换时会遇到不支持的算子报错。另外模型输入尺寸必须是静态的推理时固定为1x3x224x224导出时用dummy_input固定shape。model.eval() dummy_input torch.randn(1, 3, 224, 224) torch.onnx.export( model, dummy_input, mobilenetv3_small.onnx, input_names[input], output_names[output], opset_version12, do_constant_foldingTrue )然后用rknn-toolkit2转换from rknn.api import RKNN rknn RKNN() rknn.config( mean_values[[123.675, 116.28, 103.53]], std_values[[58.395, 57.12, 57.375]], target_platformrk3566 ) rknn.load_onnx(modelmobilenetv3_small.onnx) rknn.build(do_quantizationTrue, datasetdataset.txt) rknn.export_rknn(mobilenetv3_small.rknn)dataset.txt内容是若干张校准图片的路径这些图片不是训练集而是覆盖实际场景的代表性图片一般20到100张就够。量化时工具会统计这些图片在每一层的激活值分布然后确定INT8的缩放系数。4.4 量化是精度与速度的平衡点RK3566的NPU对INT8量化支持最好所以do_quantizationTrue几乎是必须的。但量化会带来精度损失处理不当的话识别率会肉眼可见地下降。我在实际测试中把量化前后的模型都跑了一遍结论是校准数据集的选择直接影响量化后精度。如果校准图片和实际应用场景偏差太大比如校准图全是室内白墙环境实际使用是户外光照量化后的模型会严重掉点。另外RGB和BGR的通道顺序、mean/std的值是否一致这属于性价比最高的检查项。rknn.config里mean_values和std_values用的是训练时的归一化参数ImageNet的均值和标准差如果训练时用的是别的归一化方式这里必须改成一样的否则即使模型转换成功识别结果也是一团糟。5. 板端部署与推理实现5.1 RKNN Python API快速打通流程模型转换完成后把.rknn文件拷到泰山派板子上接下来就是板端推理。我用的是rknn-toolkit2-lite的Python API简单直接适合先把流程跑通。from rknnlite.api import RKNNLite rknn RKNNLite() rknn.load_rknn(mobilenetv3_small.rknn) rknn.init_runtime(core_maskRKNNLite.NPU_CORE_AUTO)init_runtime的core_mask参数指定NPU核心模式。RK3566的NPU有多个核心NPU_CORE_AUTO会自动选择负载较少的核心NPU_CORE_0则主动绑定第一个核。实测差异不大但IO密集场景用AUTO更省心。推理调用简单到让人怀疑outputs rknn.inference(inputs[img])inputs是一个列表每个元素对应模型的一个输入。正常转出来的量化模型输入是uint8类型shape是NHWC即(1, 224, 224, 3)但也有可能输出NCHW这个在转换日志里能看到如果不确定建议用netron打开原始ONNX确认输入布局。5.2 摄像头取流与图像预处理细节摄像头取流我直接用OpenCV它底层走V4L2。MIPI摄像头在系统里同样注册为/dev/video*设备所以代码和USB摄像头没区别。import cv2 import numpy as np cap cv2.VideoCapture(0) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) def preprocess(frame): img cv2.resize(frame, (224, 224)) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img np.expand_dims(img, axis0).astype(np.uint8) return img while True: ret, frame cap.read() if not ret: continue img preprocess(frame) outputs rknn.inference(inputs[img]) # 后处理图像预处理要做到和训练一致resize到224x224、BGR转RGB因为训练时用的是RGB图、转成uint8。如果模型是需要归一化的浮点模型那就还要除以255但如果是量化模型且量化时设了mean/std输入已经是经过内部处理的uint8不需要手动减均值除方差。5.3 推理结果后处理与显示TensorFlow/PyTorch模型输出的softmax往往在损失函数内部计算所以导出ONNX时输出的只是一个logits向量需要自己在代码里加softmax。def softmax(x): e_x np.exp(x - np.max(x)) return e_x / e_x.sum(axis-1) probs softmax(outputs[0][0]) top_idx np.argsort(probs)[::-1][:3] labels [person, computer, cup] for i in top_idx: print(f{labels[i]}: {probs[i]:.3f})显示部分直接把标签画在帧上label labels[top_idx[0]] cv2.putText(frame, f{label}: {probs[top_idx[0]]:.2f}, (20, 40), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) cv2.imshow(AI Camera, frame)到这里一个基本能用的智能摄像头就完成了。如果想远程看画面把frame通过HTTP推流到自己的Web服务或者用Flask起一个简单的RTSP代理都属于锦上添花。6. 性能优化与常见问题排查实录6.1 实测帧率与瓶颈分析先给一组我的实测数据模型输入尺寸NPU推理耗时整链路FPSMobileNetV3-Small224x224约15ms25-30MobileNetV3-Large224x224约60ms12-15第一版代码整链路FPS只有13左右完全跑不满NPU。逐个环节打点后发现瓶颈根本不在NPU推理而在图像预处理和显示。OpenCV的resize和BGR转RGB都很吃CPU再加上Qt窗口绘制CPU核心被打满。优化方案很直接采集分辨率降到720P再resize到224x224避免从1080P大图做缩放把显示分辨率降低或者干脆去掉imshow只打印推理结果速度立刻上来用多线程采集线程负责读帧和预处理推理线程负责NPU推理和显示两个线程之间用双缓冲队列传递最新帧避免采集阻塞推理实测优化后FPS从13提升到27左右如果再把预处理用Cython或OpenCV的UMat优化还能往上走。6.2 高频踩坑记录直接按问题整理下面这张表把我遇到的和群友经常问的高频问题整理成了速查表照着排查能省很多时间现象可能原因解决方法RKNN转换报算子不支持ONNX版本太新或模型含特殊算子降opset到12检查是否有自定义算子板端加载RKNN段错误RKNN runtime版本与转换版本不一致统一rknn-toolkit2和板端librknnrt.so版本摄像头打不开 / 画面全黑DTS未使能sensor节点排线松动检查dts中sensor节点重新插拔FPC排线识别结果严重不对预处理与训练不一致核对mean/std、RGB/BGR、resize尺寸量化后精度大幅下降校准数据集不具代表性增加实际场景图片作为校准集板子无法启动 / 变砖eMMC镜像损坏进入MaskRom模式重新烧录串口工具无法连接驱动没装或芯片型号不对确认CH340驱动串口波特率1500000NPU推理速度慢未用NPU或模型被CPU执行检查init_runtime状态确认NPU驱动正常6.3 RK3566救砖与开发板恢复热词里rk3566救砖出现频率很高确实有几次我在重新编译内核、刷错镜像后把板子搞到没法启动。RK3566系列设计上是有完备恢复机制的关键在于进入MaskRom模式。具体操作流程用USB线把开发板的OTG口连接到电脑Windows上先安装瑞芯微的DriverAssistant驱动。然后找到板子上的MaskRom按键通常在背面一个很不起眼的小按钮按住该键不放再给板子上电这时电脑端会识别出一个新的USB设备说明已经进入MaskRom模式。接着打开RKDevTool在高级功能里烧录Loader或者直接烧录完整镜像就能把板子从砖头状态救回来。需要提醒的是MaskRom模式下手滑烧错Loader可能会把板子弄到更彻底的砖态所以烧录前务必核对镜像和芯片型号是否匹配。另外能用SD卡启动的开发板永远是救砖保底方案——道理很简单只要把SD卡做成启动盘板子就多了一条独立于eMMC的启动通道随便怎么折腾eMMC都不会彻底完蛋。6.4 后续扩展从分类到检测从单板到终端MobileNetV3这步跑通之后整个RKNN工具链你已经熟悉了接下来可以顺着两个方向拓展。第一个方向把分类模型换成目标检测模型。热词里出现的yolo26转rknn、dinov3转rknn其实都是同一个链路关键差异在于检测模型的后处理更复杂需要在RKNN输出基础上做NMS或者DIOU-NMS。rknn_model_zoo官方仓库里有YOLO系列和PP-YOLO的完整转换示例直接移植比自己从零写快得多。第二个方向把摄像头做成完整终端。泰山派支持LVDS和MIPI-DSI屏幕可以在DTS里按官方说明把屏幕点亮再把识别结果直接显示在屏上不依赖HDMI。再配合扩展板上的DHT11传感器和网络接口一台本地方感知终端就成型了。最后再分享一个我在实际项目里坚持的小习惯每次换数据集、换预处理方式或者改模型结构都重新跑一遍转换 板端推理 帧率测试三个环节并把耗时、精度记录在表格里。RKNN这种工具链版本迭代很快今天能成功转换的算子过两个月换了新版本可能就报错今天随手写的预处理代码换个模型可能就是识别率崩盘的元凶。把这些东西记录清楚后面排障会轻松非常多。希望这份实战记录能帮你少走几步弯路把RK3566的NPU真正用起来。