从LPRNet到CRNN:我在RK3588上部署车牌识别的模型选型踩坑记
边缘计算设备上的深度学习模型部署从来不是一条平坦的道路。当我在RK3588平台上实现车牌识别系统时,原以为选择成熟的LPRNet就能轻松完成任务,却没想到在模型转换环节遭遇了意想不到的障碍。这段经历让我深刻认识到,在边缘设备上部署模型时,理论精度和实际运行效果之间往往存在巨大鸿沟。
1. 为什么LPRNet在RK3588上水土不服
最初选择LPRNet(License Plate Recognition Network)并非偶然。这个专为车牌识别设计的轻量级网络,在论文和开源社区中都表现出色。我的PyTorch实现用5万张图片训练后,测试集准确率达到了92%,完全满足项目需求。但当我开始将其部署到RK3588芯片时,问题接踵而至。
1.1 MaxPool3D的转换陷阱
第一个拦路虎出现在PyTorch到ONNX的转换环节。LPRNet中使用了MaxPool3D操作,而torch.onnx.export()对此的支持并不完善。为了解决这个问题,我设计了一个变通方案——将单个MaxPool3D拆解为三个MaxPool2D的组合:
class maxpool_3d(nn.Module): def __init__(self, kernel_size, stride): super(maxpool_3d, self).__init__() assert(len(kernel_size)==3 and len(stride)==3) kernel_size2d1 = kernel_size[-2:] stride2d1 = stride[-2:] kernel_size2d2 = (kernel_size[0],kernel_size[0]) stride2d2 = (kernel_size[0], stride[0]) self.maxpool1 = nn.MaxPool2d(kernel_size=kernel_size2d1, stride=stride2d1) self.maxpool2 = nn.MaxPool2d(kernel_size=kernel_size2d2, stride=stride2d2) def forward(self,x): x = self.maxpool1(x) x = x.transpose(1,3) x = self.maxpool2(x) x = x.transpose(1,3) return x这个修改确实让模型成功转换为了ONNX格式,但埋下了更严重的隐患。
1.2 精度断崖式下跌的噩梦
转换后的模型在ONNXRuntime上测试时,准确率从92%骤降至80%。更糟的是,当进一步转换为RKNN格式后,性能继续下滑到75%。我尝试了各种方法挽救:
- 调整输入数据的预处理流程
- 修改量化参数和校准集
- 尝试不同的opset版本
但无论如何调整,精度损失都无法挽回。最终不得不承认:这种网络结构修改虽然解决了格式转换问题,却破坏了模型原有的特征提取能力。
2. CRNN:意外收获的解决方案
当LPRNet的路走不通时,我开始寻找替代方案。CRNN(Convolutional Recurrent Neural Network)虽然最初是为OCR设计的,但其结合CNN和RNN的结构同样适合车牌识别任务。
2.1 结构优势对比
| 特性 | LPRNet | CRNN |
|---|---|---|
| 主体结构 | 纯CNN | CNN+BiLSTM |
| 参数量 | 约2.3M | 约8.4M |
| 序列处理能力 | 隐式通过空间池化 | 显式通过循环层 |
| RKNN支持度 | 部分操作不支持 | 完整支持 |
虽然CRNN的参数量更大,但在RK3588上实际运行时,得益于NPU对标准卷积和LSTM操作的良好支持,推理速度反而比修改后的LPRNet更快。
2.2 无缝的模型转换体验
与LPRNet的挣扎形成鲜明对比,CRNN的转换过程异常顺利:
# CRNN转换配置示例 rknn.config(mean_values=[[0, 0, 0]], std_values=[[1, 1, 1]], target_platform='rk3588') ret = rknn.load_onnx(model='crnn.onnx') ret = rknn.build(do_quantization=False) # 首轮尝试不量化关键优势在于:
- 所有操作都得到ONNX和RKNN的原生支持
- PyTorch、ONNX和RKNN三个版本的输出完全一致
- 无需对网络结构做任何妥协性修改
3. RK3588部署的实战细节
模型选型只是成功的一半,在边缘设备上实现高效推理还需要注意许多细节。
3.1 输入处理的微妙之处
RKNN Toolkit Lite的inference接口与PC端有些许不同,特别是数据格式的处理:
# PC端RKNN Toolkit的处理方式 img = img.transpose(2, 0, 1) # HWC转CHW outputs = rknn.inference(inputs=[img], data_format='nchw')[0] # 板端RKNN Lite的处理方式 outputs = rknn_lite.inference(inputs=[img])[0] # 只支持NHWC格式这意味着在板端部署时,需要调整预处理流程,去除不必要的转置操作,直接保持NHWC格式输入。
3.2 内存与计算核心的优化
RK3588的NPU有多个计算核心,合理分配资源能显著提升性能:
# 自动分配NPU核心 rknn_lite.init_runtime(core_mask=RKNNLite.NPU_CORE_AUTO) # 或者手动指定核心(0-3) rknn_lite.init_runtime(core_mask=RKNNLite.NPU_CORE_0_1)实际测试发现,对于CRNN这样的中等规模模型,使用两个核心能在延迟和吞吐量之间取得最佳平衡。
4. 从失败中学到的经验
这次技术选型的曲折经历让我总结了几个关键教训:
边缘部署的兼容性优先于论文指标
一个在标准测试集上表现优异的模型,如果无法高效部署到目标硬件,其价值就大打折扣。转换过程中的精度验证必不可少
每完成一步格式转换(PyTorch→ONNX→RKNN),都要严格验证输出的一致性,尽早发现问题。保持备选方案的灵活性
当主选方案遇到难以克服的障碍时,及时转向备选方案可能比死磕更高效。硬件特性决定最终性能
RK3588对CNN和RNN的良好支持,使得CRNN这样"较重"的模型反而能发挥更好效果。
在项目后期,我还尝试了其他几种车牌识别模型,最终确认CRNN确实是RK3588平台上综合表现最佳的选择。这段经历让我明白,边缘AI部署不仅需要深度学习知识,更需要深入理解硬件特性和工具链限制。