拓冰建站拓冰建站
首页 / 资讯中心 / 正文

PyTorch实战:基于CRNN+CTC的车牌识别全流程解析

1. 为什么把车牌识别当作 PyTorch 实战项目1.1 车牌识别看起来简单实际上卡在哪儿先说一个反直觉的现象车牌识别在工程里看起来特别成熟门口停车场、高速收费口都在用似乎是个“老掉牙”的需求。但当我自己把任务拆开用 PyTorch 从零搭一套识别流程时才发现事情没那么简单。车牌识别不是单一任务它至少包含两件事第一步是“车牌在哪”第二步是“车牌上的字符是什么”。很多初学者会直接把整张图片丢给一个模型指望它输出一行车牌号这种做法在约束环境里能跑通但放到真实场景马上翻车。P10 周这个项目我建议的定位是“字符识别”也就是默认你已经把车牌区域从大图里截出来了专心做第二步把类似“粤B12345”这样的字符序列识别出来。这样项目边界清晰能在有限时间里把 PyTorch 的卷积网络、序列建模、损失函数、解码策略全走一遍。如果你非要把目标检测也包进来那你实际要处理的问题是“目标检测 分类 序列识别”的组合工作量会翻好几倍。这个项目最适合谁两种人。一种是已经跑通 PyTorch 官方教程、会写简单 CNN 分类器的初学者需要找一个“比手写数字识别复杂一点、但又不至于完全无从下手”的进阶项目另一种是做传统图像处理出身的人比如之前用 OpenCV 做模板匹配字符分割遇到倾斜、光照不均、字体差异就想看看深度学习方案到底有多大优势。1.2 车牌识别和通用 OCR 的差异车牌识别LPR经常被人拿来和通用 OCR 对比但两者其实有很多不同。OCR 处理的是印刷体文字尺度相对均匀、字体变化大长文本居多车牌识别的字符数量一般很短像中国车牌是 7 到 8 个字符但字符类别里同时包含数字和汉字还有一些省份简称。车牌字符有几个非常实际的问题字符类别不均衡。汉字“粤”“京”“苏”等出现频率相对固定数字和字母的样本量远大于生僻省份汉字训练时很容易让模型在汉字上犯迷糊。相似字符容易混淆。比如 0 和 O、1 和 I、8 和 B单看一个字符几乎没法区分必须依赖上下文或车牌规则来纠错。字符间距不是完全等宽的。新能源车牌比普通蓝牌多一位而且有渐变分隔符直接用固定切分法做字符分割非常痛苦。正因如此主流的深度学习车牌识别方案分成了两条路线一是先做字符级检测或分割再对每个字符单独分类二是用 CRNN 这种“卷积 序列建模”的结构把整张车牌图像直接映射成一个字符串序列。后者对工程实现更友好也是我在这次 P10 周项目里采用的主要思路。1.3 这次选型的边界用“识别”来回避“检测”我在设计这个项目时刻意做了一个范围裁剪检测部分不在 PyTorch 训练范围内而是用 OpenCV 的轮廓查找、颜色过滤等传统方式来做车牌定位或者干脆用现成的检测模型把结果切出来。这样做的原因很简单——第 10 周的项目应该把核心精力放在“识别”这个环节否则周报里大半时间都被检测调参吃掉了。但“回避检测”不等于不处理图像。实际项目中输入的车牌图像往往不是完美的水平正视图可能是倾斜的、暗光的、模糊的。我在预处理阶段加了透视矫正、双线性插值缩放、灰度化或保留 RGB 通道等操作。后面我会专门讲预处理对训练收敛的影响这一步如果能做好比你在网络结构上花三天时间调参更有效。2. 环境准备是一个劝退重灾区2.1 PyTorch 版本和 CUDA 的搭配PyTorch 环境配置是所有项目里最磨人的步骤没有之一。打开各种教程会发现有 CPU 版、GPU 版、conda 安装、pip 安装很容易让人懵。我个人的建议是能用 conda 就不用 pip原因不是 conda 比 pip 更快而是 conda 在管理 CUDA 相关依赖时不容易把系统搞乱。先说版本搭配。如果电脑有 NVIDIA 显卡你先别急着装 PyTorch先打开终端输入nvidia-smi看右上角的 CUDA Version。这个版本表示你的显卡驱动最多能支持到多少不是说你必须装一样版本的 CUDA 工具包。然后去 PyTorch 官网选择对应的安装命令。举个例子如果你看到显卡驱动支持 CUDA 12.1那就选pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121这套组合一般比较稳。如果看到的是 CUDA 11.8就选cu118对应的命令。常见的坑是Windows 用户直接装了最新版 PyTorch结果报一堆 DLL 加载失败最后发现是显卡驱动太老。我的建议是先把驱动升级到官网最新的稳定版再选一个相对成熟的 PyTorch 版本比如 2.1 或 2.2不要追最新。新版本特性再好对入门项目没有实质影响反而可能因为生态兼容问题耽误时间。2.2 除了 PyTorch 还要装什么车牌识别项目不只依赖 PyTorch我把常用依赖清单列在这里直接复制就能用torch、torchvision核心训练库。opencv-python图像读取、预处理、透视变换。numpy数组操作绕不开。matplotlib画 loss 曲线和查看样本。tensorboard或wandb训练过程可视化建议装一个。lmdb或pillow如果数据量大用 LMDB 加速读取数据少的话 pillow 就够。还要提醒一个细节opencv-python 和 opencv-contrib-python 不能同时装否则会互相覆盖。国内网络下载慢的话可以把 pip 源换成清华源命令是pip install -i https://pypi.tuna.tsinghua.edu.cn/simple 包名能快非常多。2.3 CPU 模式下能不能跑这个项目如果你没显卡或者显卡是多年前的入门级也不要直接放弃。车牌识别用的图像通常被缩放到高 32 或 48 像素宽度 128 到 160 像素数据量并不大。一个简单的 CRNN 模型在 CPU 上训练如果数据集只有几千张跑 30 到 50 个 epoch 也就是几十分钟到两三个小时的事。我用老款笔记本的 CPU 跑过一次完全能接受。但有几个操作在 CPU 上特别慢一是数据增强里的随机仿射变换二是 Batch Size 太大时矩阵计算和损失回传都会拖慢三是验证阶段频繁做 CTC 解码。所以 CPU 训练时数据增强可以简单一点只做亮度扰动、轻微平移和裁剪别上太重的仿射变换。Batch Size 调到 16 或 32不要盲目模仿别人 GPU 环境下的 128。3. 数据集样本从哪来标注怎么统一3.1 开源数据集推荐与取舍车牌识别的公开数据集不算多最常用的几个CCPD中国城市停车场数据集国内车牌样本量大包含多种复杂场景标注信息藏在文件名里需要解析。CRPD中国道路车牌数据集覆盖道路场景倾斜和模糊样本较多适合做泛化测试。自己合成用字体、背景、噪声、透视变换拼接出来的数据虽然真实感差一些但可以精确控制字符组合和分布对起步阶段很有用。CCPD 虽然好用但它的标注格式比较特殊文件名里编码了边界框、四个角点、车牌号等信息。比如类似025-95_113-154383_386473-386473_177454_154383_363402-0_0_22_27_27_33_16-37-15.jpg这种文件名前一段表示车牌号和背景的简单特征后面包含角点坐标和字符数。你需要写一个解析函数把这些字段抽出来。这个工作不复杂但容易漏特别是角点顺序和字符标签的对应关系一旦搞错训练出来的模型基本不能用。3.2 标注格式统一化的小工具无论你从哪个数据集拿数据最后最好统一成我这种格式省得每次训练都要改数据加载器。我习惯的做法是生成一个train.txt每一行左边是图片路径右边是车牌字符串中间用空格隔开data/images/0001.jpg 粤B12345 data/images/0002.jpg 京A88888然后在Dataset类里读取这个文件按行解析。这样做的好处是换数据集时只需要写一次数据清洗脚本把原始标注转成这种格式训练代码完全不用改。字符到索引的映射表也要提前定义好。中国车牌可能出现的字符包括 31 个省份简称汉字、24 个字母I 和 O 一般不用因为容易和数字 1、0 混淆、10 个数字。最终字符表大概是 70 个左右记得加一个blank符用于 CTC 损失。字符表一旦确定就不能中途乱改否则之前训练好的模型权重会全部作废。3.3 数据增广车牌场景真正有用的操作车牌识别任务的数据增广和 ImageNet 分类不太一样。你不能随便做翻转车牌文字左右翻转以后没有任何意义也不能做太夸张的旋转超过 30 度基本就看不出原来的字了。我实验下来最有效的增广操作有这么几个亮度扰动和对比度扰动模拟白天晚上不同光照对鲁棒性提升非常明显。仿射变换里的轻度旋转、缩放、平移模拟摄像头安装角度差异角度控制在正负 10 度左右。高斯噪声和运动模糊模拟雨雪天气、车辆行驶中的拖影。随机裁剪后再缩放模拟车牌在画面中不完整或被遮挡的情况。透视畸变把矩形车牌压成不规则的四边形模拟侧方拍摄。这里有个小技巧先把图像缩放到固定大小再做增广最后再 Resize 到网络输入尺寸。如果顺序反过来先裁剪大图再缩放容易引入不可控的尺度变化。我用的是albumentations库因为它的处理速度比 torchvision 自带的 transform 快不少而且各种空间变换的参数更直观。增广强度也需要控制。我大概是这样设置的增广操作概率参数范围亮度/对比度0.5亮度系数 0.8~1.2旋转0.3角度 ±8 度平移0.3水平 ±10%垂直 ±5%高斯模糊0.2核大小 3 到 5随机裁剪0.2裁剪后缩放回原尺寸透视畸变0.3畸变幅度 0.02~0.05这套配置在我的实验里效果不错。你可以先按这个跑再根据自己数据的实际情况调整。4. 模型实现CRNN CTC 的拆解4.1 为什么用 CRNN 而不是 YOLO 直接读字符看车牌识别很多人第一反应是“用 YOLO 做检测检测完字符再分类”。这个方案确实可行但有一个工程问题你需要先检测出每个字符的位置再做字符分类。字符级检测的训练数据标注成本很高而且相邻字符挨得近小目标检测很容易漏检。CRNN 的思路完全不同。它把整张车牌图看作一个序列卷积层负责提取视觉特征循环层负责建模字符之间的顺序关系最后用 CTC 损失来对齐输入和输出不需要精确知道每个字符的边界。这个思路和语音识别里的“序列到序列”非常像对不定长文本特别友好。车牌本身是定长或近似定长的但字符数有 7 个也有 8 个用 CTC 可以天然规避“先分割再识别”易出错的毛病。4.2 骨干网络用 ResNet18 还是自定义小网络模型结构我建议不要一开始就上重型网络。车牌图像分辨率不高字符细节也不复杂一个轻量级 CNN 骨干就足够了。我试过 ResNet18 和一个自己定义的四层卷积网络最后的准确率差距在 1 个百分点以内但推理速度和显存占用差距明显。如果你后续打算部署到嵌入式设备轻量骨干几乎是必须的。我的推荐结构是这样的输入图像灰度化后缩放到 W x H 128 x 32不过也可以保留 RGB 三通道看你对颜色的利用需求。如果用灰度图参数量更小训练更快。先用几个卷积层提取特征下采样到高度为 1 的特征图比如把 32 的高度逐步降到 1宽度保留在 32 或 16 左右。把特征图按宽度方向展开成序列送入两层双向 LSTM 或 GRU。最后接一个全连接层输出每个时刻在所有字符类别上的概率分布。4.3 PyTorch 实现主干和 CTC 解码核心代码骨架我直接贴一段方便你照着自己的数据改造import torch import torch.nn as nn class LPRNet(nn.Module): def __init__(self, num_classes): super().__init__() # 输入: (batch, 3, 32, 128) 或 (batch, 1, 32, 128) self.conv1 nn.Sequential( nn.Conv2d(3, 64, kernel_size3, stride1, padding1), nn.BatchNorm2d(64), nn.ReLU(inplaceTrue), nn.MaxPool2d(kernel_size2, stride2) # 高度 16, 宽度 64 ) self.conv2 nn.Sequential( nn.Conv2d(64, 128, kernel_size3, stride1, padding1), nn.BatchNorm2d(128), nn.ReLU(inplaceTrue), nn.MaxPool2d(kernel_size2, stride2) # 高度 8, 宽度 32 ) self.conv3 nn.Sequential( nn.Conv2d(128, 256, kernel_size3, stride1, padding1), nn.BatchNorm2d(256), nn.ReLU(inplaceTrue) ) self.conv4 nn.Sequential( nn.Conv2d(256, 256, kernel_size3, stride1, padding1), nn.BatchNorm2d(256), nn.ReLU(inplaceTrue), nn.MaxPool2d(kernel_size(2, 1), stride(2, 1)) # 高度 4, 宽度 32 ) self.conv5 nn.Sequential( nn.Conv2d(256, 512, kernel_size3, stride1, padding1), nn.BatchNorm2d(512), nn.ReLU(inplaceTrue) ) # 高度从 4 压到 1 self.conv6 nn.Sequential( nn.Conv2d(512, 512, kernel_size3, stride1, padding1), nn.BatchNorm2d(512), nn.ReLU(inplaceTrue), nn.MaxPool2d(kernel_size(4, 1), stride(4, 1)) # 高度 1, 宽度 32 ) self.gru nn.GRU(512, 256, batch_firstTrue, bidirectionalTrue, num_layers2) self.fc nn.Linear(512, num_classes) # 双向 GRU 输出维度是 512 def forward(self, x): x self.conv1(x) x self.conv2(x) x self.conv3(x) x self.conv4(x) x self.conv5(x) x self.conv6(x) # 去掉高度维度得到 (batch, 宽度, 512) x x.squeeze(2) # 维度变为 (B, W, C) x x.permute(0, 2, 1) # 调整顺序 x, _ self.gru(x) x self.fc(x) # 输出形状: (batch, width, num_classes) return x这个结构参考了 LPRNet 的思路但我去掉了 NIN 那样复杂的 1x1 卷积保持简单直接。forward返回的形状是(batch, time_step, num_classes)和 PyTorch 内置的CTCLoss期望的格式略有不同训练时需要转置成(time_step, batch, num_classes)logits model(images) # (batch, width, num_classes) log_probs logits.permute(1, 0, 2) # (width, batch, num_classes) loss ctc_loss(log_probs, targets, input_lengths, target_lengths)这里的width就是时间步数也就是 32。你可以把它理解为“从左往右扫描了 32 个位置每个位置预测一个字符的概率分布”。5. 训练调参过程中容易被忽视的细节5.1 损失函数、优化器到底怎么选CTC Loss 是必须的PyTorch 里直接调用torch.nn.CTCLoss即可。它的核心作用是把“每一步输出一个概率分布”的序列和“一个任意长度的目标字符串”对齐。语音识别用 CTC车牌识别又用 CTC原因都是文本长度和特征序列长度不等且不知道每个字符对应的具体位置。CTC 把对齐问题通过动态规划隐式解决了训练时你只需要提供输入序列长度和目标序列长度。优化器我推荐AdamW初始学习率设成1e-3左右。如果你发现训练初期 loss 下降慢可以先调低学习率到3e-4试试不要一上来就换优化器。SGD 配合动量在带 BatchNorm 的网络里也能跑但对超参数敏感入门期容易被学习率折腾得失去信心。AdamW 的权重衰减项设成1e-4正则化效果比 L2 惩罚更稳。5.2 Learning Rate、Batch Size、图像高度的连环影响这是一个特别容易被忽略的组合问题。LSTM/GRU 这类循环网络对输入序列长度很敏感而输入序列长度由特征图的宽度决定。图像宽度越大时间步越多模型表达能力越强但训练时间越长也越容易过拟合。图像高度同样有影响高度太低字符细节丢失相似字符容易混淆高度太高卷积下采样后仍然保留大量无关背景反而干扰序列建模。我实验下来32 x 128是一个性价比很高的输入尺寸。Batch Size 方面CTC Loss 在 batch 维度上做平均batch 太小时梯度噪声大训练不稳定batch 太大时显卡显存吃紧LSTM 的序列长度又让反向传播变得很重。我的经验是 GPU 显存 6G 以上用 644G 左右用 32CPU 训练用 16。如果你发现 loss 曲线像锯齿一样上下跳先把 batch 调大一倍试试。5.3 Loss 不下降时的排查顺序我在训练过程里踩过不少坑总结出一个固定排查顺序遇到 loss 不降先别慌先看数据有没有问题。打印几个 batch 的图片和对应标签确认图像灰度化、归一化是否正常标签和图像是否对得上。标签错一位模型永远学不会。再看 loss 是否从“纯随机”的水平起步。如果一开始 loss 就很低说明字符表可能配错了或者目标都为空。检查input_lengths和target_lengths的维度。CTC Loss 对长度要求极其严格搞错一个 batch 里的长度矩阵loss 计算就会错位。用一个小数据集几百张过拟合测试。如果一个 batch 重复训练 100 步后 loss 能降得很低说明模型和数据加载没问题问题在训练集太大或增广过强如果小数据集都过拟合不了那就是模型或优化器配置的问题。看梯度是否异常。在训练循环里打印梯度的范数torch.nn.utils.clip_grad_norm_(model.parameters(), 10)梯度爆炸基本就是学习率太大或 LSTM 层太深。5.4 模型保存和验证阶段的正确姿势模型保存不能只存参数字典还要把字符表一起存下来。我习惯把整个训练配置打包成一个字典torch.save({ state_dict: model.state_dict(), char_dict: char_dict, input_height: 32, input_width: 128, epoch: epoch, val_acc: best_acc, }, checkpoint.pth)这样推理脚本读取后能完整恢复模型不需要额外手动指定字符表。验证阶段不能只看 loss。CTC Loss 下降不代表每个字符都识别对了必须写一个解码函数把预测结果变成字符串后再算准确率。准确率可以按“整牌正确率”算也可以按“字符正确率”算。对车牌识别来说整牌正确率最严格因为错一个字符在实际业务里就是“识别失败”。如果你的模型整牌正确率一直上不去可以退一步看字符正确率从而判断问题到底出在汉字上还是数字字母上。6. 推理后处理从字符序列变成一块正确车牌6.1 CTC 解码头要注意的重复字符问题训练完成后推理时不是直接取概率最大的类别而是要做一个“去重”操作。CTC 的解码规则是先找到每个时间步上概率最大的字符把连续重复的字符合并再去掉 blank 符号。举个例子模型输出了一段序列粤 粤 B B 1 2 2 2 3 3 4 5 - 5 - 空 空 空解码时先把连续重复的合并比如“粤 粤”合并成“粤”“2 2 2”合并成“2”“3 3”合并成“3”但要注意如果同一个字符中间隔着 blank比如1 空 1那它们不能被合并要保留成两个“1”。def ctc_decode(pred): pred pred.argmax(dim-1) # (batch, width) result [] for batch_idx in range(pred.size(0)): char_indices [] prev None for t in range(pred.size(1)): idx pred[batch_idx, t].item() if idx ! blank_index and idx ! prev: char_indices.append(idx) prev idx plate .join([int2char[idx] for idx in char_indices]) result.append(plate) return result这里最容易出的 bug 就是去重逻辑写错。普通的“相邻去重”在车牌识别里其实不够因为合法的车牌号里确实会出现相同字符相邻的情况比如“京A00001”里有连续的 0TSP 里还要求“00”保留为两个 0。CTC 的合并规则只合并相邻且之间没有 blank 的相同字符这一点实现时一定要仔细。6.2 车牌规则约束帮你纠错解码完毕后还不能直接当成最终结果。车牌有很强的规则约束比如中国大陆普通蓝牌是“省份简称 字母 5 位数字/字母”新能源车牌是“省份简称 字母 6 位数字/字母”。这些规则能拿来纠错。我常用的后处理逻辑是这样的第一位必须是汉字如果不是检查概率最高的几个候选里有没有汉字替换上去。第二位必须是字母如果不是看看是不是 0 和 O、1 和 I 的混淆按车牌规则强制纠正。后面几位只允许数字和大写字母但要注意 I 和 O 在大多数车牌里不出现把它们校正为 1 和 0。总长度校验普通蓝牌 7 位新能源车牌 8 位。如果解码结果长度不对可以结合模型输出的置信度重新选择候选序列。置信度信息可以用log_softmax后取前 k 个候选来做 beam search。对车牌这种短序列beam width 设 4 或 5 就够了一个简单的 beam search 不会比 CTC greedy 慢多少但整牌正确率通常能有 1 到 2 个百分点的提升。6.3 实际场景里提升识别率的几个野路子这里分享几个不正规但实测有效的办法供参考。第一个是投票法。同一个车牌往往能从视频流里抽到多帧比如连续 5 帧都识别到同一块区域最后把 5 次结果做个众数投票或按置信度加权合并。单帧识别率 95% 只能算及格但 5 帧投票后基本能到 99%。这个思路特别适合停车场道闸这种场景摄像头反正对着一个方向多帧信息不用白不用。第二个是模板校正。如果车牌底色已知比如蓝底白字、黄底黑字、绿底黑字新能源可以在预处理时做颜色过滤排除背景干扰。甚至可以直接根据底色在字符识别后校验省份汉字和字母组合的合法性。第三个是边缘锐化。这个看起来有点“传统”但在光照不足的场景里轻微的cv2.filter2D锐化能让卷积网络提取到更清晰的边缘特征。注意锐化系数不要太大否则图像会发白反而丢失字符细节。7. OV7670 和端侧部署的现实问题7.1 OV7670 到底能不能和 PyTorch 联动搜索热度里出现了 OV7670 摄像头模块我猜很多人是单片机出身想拿摄像头采集图像再喂给 PyTorch 做车牌识别。这里必须先泼一盆冷水OV7670 是一个 30 万像素、8 位并口输出的老式摄像头模块通常接 STM32、Arduino 这类 MCU。它采集到的原始数据是 Bayer 格式的 RAW 图需要做色彩插值、格式转换、去噪、白平衡才能变成正常使用的 RGB 图。而且 OV7670 模块通常不带 ISP图像信号处理器直接输出的图像质量比较差。车牌识别需要清晰、不过曝、不偏色的图像如果拿 OV7670 的原始输出去做端侧识别效果大概率不会好。更现实的做法是用 OV7670 做简单的图像采集和预览数据通过串口或 WiFi 传到电脑。在电脑上用 PyTorch 训练车牌识别模型。将训练好的模型转换成 ONNX 或 TensorRT部署在带 GPU 的开发板或边缘设备上。所以 OV7670 本身不能称为“PyTorch 车牌识别”的主力设备。它更适合做入门级的图像采集实验但要真正跑到识别那一步最好换用带 ISP 的 USB 摄像头一块树莓派摄像头或手机摄像头都比它省心。7.2 端侧部署的常规思路如果你真的想部署到嵌入式设备我的建议是先跑通“采集 → 传输 → 推理”的完整闭环再考虑模型压缩。第一步可以考虑把 PyTorch 模型导出为 ONNXmodel.eval() dummy_input torch.randn(1, 3, 32, 128, devicecuda) torch.onnx.export( model, dummy_input, lprnet.onnx, input_names[input], output_names[output], dynamic_axes{input: {0: batch}, output: {0: batch}}, opset_version11 )然后可以换个平台推理比如用 ONNX Runtime 在 ARM Linux 上跑。注意 LSTM 转 ONNX 时有些算子在不同框架中兼容性不一致如果导出报错可以把双向 GRU 换成卷积或 Transformer 的自注意力模块精度损失不大但兼容性好很多。部署时还有一个容易被忽视的问题模型输入尺寸和摄像头输出尺寸不一致时要固定一个统一预处理流程。在 PyTorch 训练时如果用了随机增广推理时就不能再用随机操作必须锁定到完全确定的预处理顺序缩放、归一化、转张量、转通道顺序从 HWC 转 CHW。8. 项目收尾这一步落地后还能怎么延展P10 周做到这儿基础版车牌识别模型已经能跑通了。如果你的目标是做一个更完整的系统接下来有几条路可以根据兴趣选一条继续往下挖。第一条路是把“检测”补上。我已经在开头说了这次刻意绕开了检测但应用端“检测 识别”才是完整方案。推荐你尝试用 SSD 或 YOLO 系列检测车牌区域然后把检测结果裁剪出来送入已有的识别模型。这是工程上最稳妥的衔接方式不需要重新设计网络只要把两个训练好的模型串起来就行。第二条路是做成一个视频流实时识别程序。用 OpenCV 的VideoCapture读取摄像头或视频文件每隔几帧抽一帧做透视变换后送入模型最后把识别结果叠加显示在画面上。这个过程中你会接触到推理延迟优化、画面稳定、多目标车牌追踪等更贴近真实项目的问题。第三条路是往模型轻量化方向走。试一下把 ResNet18 换成 MobileNet 系列或者用参数共享和剪枝减少模型体积。把模型从几十 MB 压到几 MB推理速度能提升很多。有兴趣的还可以用 TensorRT 做 FP16 量化对 GPU 推理加速特别明显。我在完成这一周项目的实际感受是车牌识别最大的门槛不在网络结构而在数据处理和工程细节。一个能跑的分类网络到处都是难的是把“识别出的字符串”变成“一块真实可用的正确车牌”这中间涉及字符表的定义、CTC 解码的边界处理、车牌规则的约束纠错以及和上下游模块的配合。如果你按这条链路完整走一遍PyTorch 的掌握程度会比单纯刷十个分类项目扎实得多。最后再分享一个小技巧训练结束后专门留出一批“难例”图——比如模糊的、倾斜的、曝光异常的、字符粘连严重的单独统计模型在这些样本上的准确率。这个做法比只看整体准确率更能暴露出系统在实际环境里的短板也是往工程化方向走时最有价值的一步。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门