三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

从LPRNet到CRNN:我在RK3588上部署车牌识别的模型选型踩坑实录

从LPRNet到CRNN:我在RK3588上部署车牌识别的模型选型踩坑实录

从LPRNet到CRNN:我在RK3588上部署车牌识别的模型选型踩坑实录

边缘计算设备上的AI模型部署从来不是简单的"训练-转换-运行"流水线。当我在瑞芯微RK3588平台上尝试部署车牌识别系统时,原以为选择业界常用的LPRNet就能轻松完成任务,却意外陷入长达三周的模型转换泥潭。本文将完整还原从LPRNet到CRNN的完整技术决策路径,包含量化失败时的排查思路、两种模型在NPU上的性能对比,以及最终让识别准确率从75%提升到92%的关键转折点。

1. 初始方案:为什么选择LPRNet?

车牌识别作为计算机视觉的经典应用,主流方案通常分为检测+识别两阶段。在模型选型时,LPRNet因其轻量级特性成为我的首选——这个专为车牌识别优化的CNN网络,在x86平台上的表现堪称完美:

  • 参数量仅0.48M,理论计算量1.1GFLOPs
  • 单张1080P图像推理时间<15ms(Intel i7-11800H)
  • 自研数据集测试准确率92.3%
# LPRNet典型结构(PyTorch实现) class LPRNet(nn.Module): def __init__(self, class_num): super().__init__() self.backbone = nn.Sequential( nn.Conv2d(3, 64, 3, stride=1), nn.BatchNorm2d(64), nn.MaxPool3d((3,3,3), stride=(1,2,2)), # 后续会看到这个操作的致命性 nn.ReLU() ) self.lstm = nn.LSTM(64, 128, bidirectional=True)

但现实很快给了我一记重击:当尝试将这个模型部署到RK3588的NPU时,量化后的准确率暴跌至75%。更诡异的是,同样的量化策略在YOLOv5检测模型上却工作正常。

2. 问题定位:NPU量化中的"隐形杀手"

通过对比实验,逐步锁定问题根源:

测试场景准确率问题现象
原始PyTorch模型92.3%-
ONNX模型(未量化)80.1%输出数值偏差
RKNN模型(INT8量化)75.4%字符级错误率激增
移除MaxPool3d后的变体85.7%仍低于原始精度

关键发现出现在MaxPool3d操作的转换过程。RKNN Toolkit对3D池化的支持存在隐性限制:

  1. ONNX导出时需将MaxPool3d拆解为多个MaxPool2d(如原始代码所示)
  2. 量化过程中,维度变换操作(transpose)导致数值精度损失累积
  3. NPU硬件对非连续内存访问的效率低下

技术细节:RK3588的NPU采用平铺式内存架构,对高维张量的stride参数极为敏感。当遇到transpose等维度变换操作时,会触发低效的内存重排计算。

3. 模型替代方案:CRNN的逆袭

在尝试了三种优化方案均告失败后,决定改用CRNN结构。这个结合CNN+RNN的混合架构带来了意外惊喜:

性能对比(RK3588 @1.6GHz)

指标LPRNet量化版CRNN非量化版CRNN量化版
推理时延(ms)23.428.718.2
内存占用(MB)6.29.83.1
准确率(%)75.491.289.5

CRNN的优势在于:

  • 纯2D卷积结构,避开3D操作转换陷阱
  • LSTM层对量化误差的容忍度更高
  • 动态输入尺寸适应性强
# CRNN关键部署代码(RKNN Lite版) rknn_lite = RKNNLite() ret = rknn_lite.load_rknn('crnn.rknn') rknn_lite.init_runtime(core_mask=RKNNLite.NPU_CORE_0) # 注意NPU的NHWC内存布局 img = cv2.resize(img, (100, 32)) # 保持宽高比 img = np.expand_dims(img, axis=0) # 直接NHWC布局 outputs = rknn_lite.inference(inputs=[img])

4. 实战经验:RK3588部署的五个黄金法则

经过这次项目,总结出边缘设备模型部署的核心原则:

  1. 结构优先原则
    选择网络时,优先考虑:

    • 避免3D/5D等高维操作
    • 减少transpose/reshape等维度变换
    • 控制分支结构复杂度
  2. 量化友好设计

    • 在训练时加入Quantization-Aware Training
    • 使用对称量化激活函数(如ReLU6)
    • 避免数值敏感操作(如除法)
  3. 内存布局优化
    RK3588的NPU对NHWC布局有硬件加速:

    # 推荐做法(省去transpose) img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img = np.expand_dims(img, axis=0) # 直接NHWC
  4. 多核负载均衡
    通过core_mask参数分配计算任务:

    # 同时使用两个NPU核心 rknn_lite.init_runtime(core_mask=RKNNLite.NPU_CORE_0_1)
  5. 精度监控机制
    部署后建立持续验证管道:

    • 每1000次推理做一次float16精度比对
    • 设置动态阈值触发模型热更新

5. 技术选型的深层思考

这次技术路线调整带来的启示远超预期。在项目复盘时,我意识到几个关键点:

  • 模型轻量化≠部署友好:LPRNet虽然参数少,但结构特性导致NPU利用率低下
  • 硬件知识决定上限:不了解NPU的内存架构特点,就像用C++思维写Python代码
  • 量化是个系统工程:从训练策略到部署配置需要端到端协同设计

最终方案采用CRNN+非量化的组合,在保证91%准确率的同时,推理速度达到25FPS(1080P输入)。这个案例再次证明:边缘计算场景下,没有最好的模型,只有最合适的架构组合。

← 返回列表