项目三车牌检测+识别项目三LPRNet车牌识别网络原理和核心源码解读

Posted 满船清梦压星河HK

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了项目三车牌检测+识别项目三LPRNet车牌识别网络原理和核心源码解读相关的知识,希望对你有一定的参考价值。

目录

前言

马上要找工作了,想总结下自己做过的几个小项目。

之前已经总结过了我做的第一个项目:xxx病虫害检测项目,github源码地址:HuKai97/FFSSD-ResNet。CSDN讲解地址:

  1. 【项目一、xxx病虫害检测项目】1、SSD原理和源码分析
  2. 【项目一、xxx病虫害检测项目】2、网络结构尝试改进:Resnet50、SE、CBAM、Feature Fusion
  3. 【项目一、xxx病虫害检测项目】3、损失函数尝试:Focal loss

第二个项目:蜂巢检测项目,github源码地址:https://github.com/HuKai97/YOLOv5-ShuffleNetv2。CSDN讲解地址:

【项目二、蜂巢检测项目】一、串讲各类经典的卷积网络:InceptionV1-V4、ResNetV1-V2、MobileNetV1-V3、ShuffleNetV1-V2、ResNeXt、Xception
【项目二、蜂巢检测项目】二、模型改进:YOLOv5s-ShuffleNetV2

如果对YOLOv5不熟悉的同学可以先看看我写的YOLOv5源码讲解CSDN:【YOLOV5-5.x 源码讲解】整体项目文件导航,注释版YOLOv5源码我也开源在了Github上:HuKai97/yolov5-5.x-annotations,欢迎大家star!

之前一直在学习OCR相关的东西,就想着能不能做一个车牌识别的项目出来,刚好车牌检测也好做,直接用v5就可以了。我的打算是做一个轻量级的车牌识别项目,检测网络用的是YOLOv5s,识别网络有的是LPRNet。

这一节主要介绍下怎么训练LPRNet车牌识别模型。

车牌识别项目所有讲解:

  1. 【项目三、车牌检测+识别项目】一、CCPD车牌数据集转为YOLOv5格式和LPRNet格式
  2. 【项目三、车牌检测+识别项目】二、使用YOLOV5进行车牌检测
  3. 【项目三、车牌检测+识别项目】三、LPRNet车牌识别网络原理和核心源码解读
  4. 【项目三、车牌检测+识别项目】四、使用LPRNet进行车牌识别

代码已全部上传GitHub:https://github.com/HuKai97/YOLOv5-LPRNet-Licence-Recognition

这篇博客写的我好难受,写好了,忘保存了,又重新写一遍…

一、LPRNet网络介绍

LPRNet是一个非常经典的车牌识别算法,论文是Intel于2018年发表的: LPRNet: License Plate Recognition via Deep Neural Networks,整个网络结构设计高度轻量化,只用1.5M,瞄准的就是在嵌入式设备中使用,但是识别率却毫不逊色,Intel已经在自己的嵌入式设备中应用了。

网络特点/优点:

  1. 不需要对字符进行预分割,是一个端到端的轻量化字符识别模型,速度快,精度还不错;这里主要是因为仿照squeezenet和inception的思想设计了一个轻量化的卷积模块。
  2. 仿照的还是经典的CRNN+CTC的思路,不过LPRNet首次将RNN删除了,整个网络只有CNN+CTC Loss。但是也不是说不要上下文信息,只是舍弃了BiLSTM那样的RNN提取上下文,而是在backbone的末尾使用了一个13x1的卷积模块提取序列方向(w)的上下文信息。而且在backbone外还额外使用一个全连接层进行全局上下文特征提取,提取之后再和backbone进行concat特征融合,再输入head。
  3. 损失使用的CTC Loss、推理应用了贪心算法,搜索取每个位置上类概率的最大值。

二、LPRNet网络结构

2.1、STN定位网络

论文中设计了一个图像预处理网络,将车牌图像进行变换(如偏移、旋转车牌图片),得到比较正的最佳车牌图片再输入CNN中。这部分有点像仿射变换(图像处理方法),不同的是这里是用了一个CNN小网络进行转换,原论文使用的是LocNet网络自动学习最佳的转换参数。LocNet模型结构如下:

不过这里为了简便,我用YOLOv5s检测到车牌区域后,我是直接裁剪下来输入车牌识别网络的CNN当中,并没有做这一步的图像矫正模块,感兴趣可以去github搜搜看,应该是能找到的。

2.2、Backbone

作者原话:

LPRNet的backbone模块是自行设计的一个轻量化的backbone,其中自己设计的一个Small basic block其实就是一个瓶颈型的结构(有点像去掉shortcut分枝的bottleneck),下面来好好说说。

Small basic block结构如下(思路:squeezenet/bottleneck+inception):


以第一个Small basic block结构为例子,先经过第一个1x1卷积进行降维,再用一个3x1和一个1x3卷积进行特征提取,最后再用1个1x1卷积进行升维,这不就是一个squeezenet中的fire module嘛,先使用1x1卷积对输入特征进行squueze,缩小channel,降低参数,再提取特征,最后再用expand对增大输出特征channel(这个和resnet中的bottleneck也很像)。而中间的3x1和1x3是用的inceptionv2的结论:不对称卷积可以代替对称矩阵,3x1+1x3产生和3x3一样的效果,参数量还更少了,而且还多了一个ReLU激活函数,增加了非线性。


代码也比较简单,就照着顺序搭建就是了:

class small_basic_block(nn.Module):
    def __init__(self, ch_in, ch_out):
        super(small_basic_block, self).__init__()
        self.block = nn.Sequential(
            nn.Conv2d(ch_in, ch_out // 4, kernel_size=1),
            nn.ReLU(),
            nn.Conv2d(ch_out // 4, ch_out // 4, kernel_size=(3, 1), padding=(1, 0)),
            nn.ReLU(),
            nn.Conv2d(ch_out // 4, ch_out // 4, kernel_size=(1, 3), padding=(0, 1)),
            nn.ReLU(),
            nn.Conv2d(ch_out // 4, ch_out, kernel_size=1),
        )
    def forward(self, x):
        return self.block(x)

不过我有个疑问:为什么backbone都使用了bn,这个模块反而不适应bn呢?有知道的小伙伴可以在评论区讨论下?

backbone的整体架构:


仔细数下发现只用11个,怪不得这么轻便,代码依葫芦画瓢就是了:

# __init__
self.backbone = nn.Sequential(
            nn.Conv2d(in_channels=3, out_channels=64, kernel_size=3, stride=1),    # 0  [bs,3,24,94] -> [bs,64,22,92]
            nn.BatchNorm2d(num_features=64),                                       # 1  -> [bs,64,22,92]
            nn.ReLU(),                                                             # 2  -> [bs,64,22,92]
            nn.MaxPool3d(kernel_size=(1, 3, 3), stride=(1, 1, 1)),                 # 3  -> [bs,64,20,90]
            small_basic_block(ch_in=64, ch_out=128),                               # 4  -> [bs,128,20,90]
            nn.BatchNorm2d(num_features=128),                                      # 5  -> [bs,128,20,90]
            nn.ReLU(),                                                             # 6  -> [bs,128,20,90]
            nn.MaxPool3d(kernel_size=(1, 3, 3), stride=(2, 1, 2)),                 # 7  -> [bs,64,18,44]
            small_basic_block(ch_in=64, ch_out=256),                               # 8  -> [bs,256,18,44]
            nn.BatchNorm2d(num_features=256),                                      # 9  -> [bs,256,18,44]
            nn.ReLU(),                                                             # 10 -> [bs,256,18,44]
            small_basic_block(ch_in=256, ch_out=256),                              # 11 -> [bs,256,18,44]
            nn.BatchNorm2d(num_features=256),                                      # 12 -> [bs,256,18,44]
            nn.ReLU(),                                                             # 13 -> [bs,256,18,44]
            nn.MaxPool3d(kernel_size=(1, 3, 3), stride=(4, 1, 2)),                 # 14 -> [bs,64,16,21]
            nn.Dropout(dropout_rate),  # 0.5 dropout rate                          # 15 -> [bs,64,16,21]
            nn.Conv2d(in_channels=64, out_channels=256, kernel_size=(1, 4), stride=1),   # 16 -> [bs,256,16,18]
            nn.BatchNorm2d(num_features=256),                                            # 17 -> [bs,256,16,18]
            nn.ReLU(),                                                                   # 18 -> [bs,256,16,18]
            nn.Dropout(dropout_rate),  # 0.5 dropout rate                                  19 -> [bs,256,16,18]
            nn.Conv2d(in_channels=256, out_channels=class_num, kernel_size=(13, 1), stride=1),  # class_num=68  20  -> [bs,68,4,18]
            nn.BatchNorm2d(num_features=class_num),                                             # 21 -> [bs,68,4,18]
            nn.ReLU(),                                                                          # 22 -> [bs,68,4,18]
        )
# __forward__
	   for i, layer in enumerate(self.backbone.children()):
	        x = layer(x)

结构很简单,唯一需要注意的是:最后一层conv用的是一个13x1的卷积代替了原先的BiLSTM来结合序列方向(W方向)的上下文信息。

疑问:为什么backbone要设置两个dropout层,据我所知现在用这个已经很少了,可能那时候才2018年,用的比较多吧。现在基本都是被BN代替了,BN+Dropout联合使用的话可能发生方差偏移问题,具体原因可以自己去搜搜一下,这里不展开细说了。

2.3、全局上下文嵌入

这部分论文是使用一个全连接层提取上下文信息的,只融合了两层,而这里是使用的avg pool层,而且还在4个尺度上进行了融合:

        global_context = list()
        # keep_features: [bs,64,22,92]  [bs,128,20,90] [bs,256,18,44] [bs,68,4,18]
        for i, f in enumerate(keep_features):
            if i in [0, 1]:
                # [bs,64,22,92] -> [bs,64,4,18]
                # [bs,128,20,90] -> [bs,128,4,18]
                f = nn.AvgPool2d(kernel_size=5, stride=5)(f)
            if i in [2]:
                # [bs,256,18,44] -> [bs,256,4,18]
                f = nn.AvgPool2d(kernel_size=(4, 10), stride=(4, 2))(f)
            f_pow = torch.pow(f, 2)
            f_mean = torch.mean(f_pow)
            f = torch.div(f, f_mean)
            global_context.append(f)

        x = torch.cat(global_context, 1)

有人对这个结构提出了质疑(非官方版),发现验证时是有问题的,具体的话大家可以看下这里,这位作者也按照原论文的结构进行了一个复现,解决了上面的这个问题:LPRnet pytorch 实现 (参考官方版本)

由于我跑这个模型的时候并没有发现这个问题,所以,我用的还是原版的有问题的代码,有条件的可以试试这个博文的上这段代码,个人人为更合理一点,也和原论文结构一致,效果好像还更好,大家可以试试。

另外,如果用了我的代码跑的话,验证时bs尽量不要太小,否则会和训练结果相差很大。

2.4、head

head部分很简单,就是一个1x1卷积,控制下输出的shape:

# __init__
self.container = nn.Sequential(
            nn.Conv2d(in_channels=448+self.class_num, out_channels=self.class_num, kernel_size=(1, 1), stride=(1, 1)),
            # nn.BatchNorm2d(num_features=self.class_num),
            # nn.ReLU(),
            # nn.Conv2d(in_channels=self.class_num, out_channels=self.lpr_max_len+1, kernel_size=3, stride=2),
            # nn.ReLU(),
        )
# __forward__
x = self.container(x)  # -> [bs, 68, 4, 18]   head头
logits = torch.mean(x, dim=2)  # -> [bs, 68, 18]  # 68 字符类别数   18字符序列长度
return logits

三、CTC Loss

这部分在我的另一篇博文上有讲:【OCR入门】二、文本识别(CRNN+CTC)

这里简单的回顾以下

解决问题/目的:处理不定长序列对齐问题,即得到的预测序列长度和真实gt的序列是不一样长的,导致无法使用交叉熵等一些经典的loss函数。

解决办法/CTC Loss:采用动态规划的思想
现在已知模型对序列中每个位置对所有类别的概率,即得到网络输出【68,18】,这里舍去bs维度更好理解,其中68代表序列中每个位置字符为相应类别的概率,车牌字符共有68类,18代表字符序列长度;还知道这个序列对于的gt字符串,很明显这两者是不等长的。那么我们可以用动态规划算法,计算出在当前这个预测结果下,最终能得到这个gt字符串的总概率。因为肯定有很多条路可以走到最终的gt标签。这个总概率越大,说明这个模型越好。(刷题刷的多的很快可以反应过来,其中这就是一个动态规划的网格类问题)。得到这个总概率,再带入交叉熵损失函数,就得到了最终的CTC Loss

代码其实是掉包的,一行就解决了:

ctc_loss = nn.CTCLoss(blank=len(CHARS)-1, reduction='mean')  # reduction: 'none' | 'mean' | 'sum'
...
# 网络输出 [bs,68,18]
# log_probs: 预测结果 [18, bs, 68]  其中18为序列长度  68为字典数
# labels: [93]  
# input_lengths:  tuple   example: 000=18  001=18...   每个序列长度 
# target_lengths: tuple   example: 000=7   001=8 ...   每个gt长度
loss = ctc_loss(log_probs, labels, input_lengths=input_lengths, target_lengths=target_lengths)

四、后处理-解码

网络预测最终输出:[bs, 68, 18],其中68是字典中字符的个数也就是每个位置的分类数,18是序列的长度,对每张图片进行后处理,先用argmax找到序列中每个位置的最大概率对应的类别(贪婪搜索),得到一个长度为18的序列,再对这个序列进行去除空白(’-‘字符,表示序列的当前位置没有字符),去重重复(序列的相邻两个位置字符不能重复),得到一个最终的预测序列。

代码很简单:

def Greedy_Decode_Eval(Net, datasets, args):
    # TestNet = Net.eval()
    epoch_size = len(datasets) // args.test_batch_size
    batch_iterator = iter(DataLoader(datasets, args.test_batch_size, shuffle=True, num_workers=args.num_workers, collate_fn=collate_fn))

    Tp = 0
    Tn_1 = 0
    Tn_2 = 0
    t1 = time.time()
    for i in range(epoch_size):
        # load train data
        images, labels, lengths = next(batch_iterator)
        start = 0
        targets = []
        for length in lengths:
            label = labels[start:start+length]
            targets.append(label)
            start += length
        targets = np.array([el.numpy() for el in targets])
        imgs = images.numpy().copy()

        if args.cuda:
            images = Variable(images.cuda())
        else:
            images = Variable(images)

        # forward
        # images: [bs, 3, 24, 94]
        # prebs:  [bs, 68, 18]
        prebs = Net(images)
        # greedy decode
        prebs = prebs.cpu().detach().numpy()
        preb_labels = list()
        for i in range(prebs.shape[0]):
            preb = prebs[i, :, :]  # 对每张图片 [68, 18]
            preb_label = list()
            for j in range(preb.shape[1]):  # 18  返回序列中每个位置最大的概率对应的字符idx  其中'-'是67
                preb_label.append(np.argmax(preb[:, j], axis=0))
            no_repeat_blank_label = list()
            pre_c = preb_label[0]
            if pre_c != len(CHARS) - 1:  # 记录重复字符
                no_repeat_blank_label.append(pre_c)
            for c in preb_label:  # 去除重复字符和空白字符'-'
                if (pre_c == c) or (c == len(CHARS) - 1):
                    if c == len(CHARS) - 1:
                        pre_c = c
                    continue
                no_repeat_blank_label.append(c)
                pre_c = c
            preb_labels.append(no_repeat_blank_label)  # 得到最终的无重复字符和无空白字符的序列
        for i, label in enumerate(preb_labels):  # 统计准确率
            # show image and its predict label
            if args.show:
                show(imgs[i], label, targets[i])
            if len(label) != len(targets[i]):
                Tn_1 += 1  # 错误+1
                continue
            if (np.asarray(targets[i]) == np.asarray(label)).all():
                Tp += 1  # 完全正确+1
            else:
                Tn_2 += 1
    Acc = Tp * 1.0 / (Tp + Tn_1 + Tn_2)
    print("[Info] Test Accuracy:  [:::]".format(Acc, Tp, Tn_1, Tn_2, (Tp+Tn_1+Tn_2)))
    t2 = time.time()
    print("[Info] Test Speed: s 1/]".format((t2 - t1) / len(datasets), len(datasets)))

五、其他注意的点

  1. 网络输入只能是 bsx3x24x94 ,对应网络输出是 bsx68x18,其中68是字典中字符总个数,18个预测序列长度;

六、总结

  1. 整体架构是CNN+CTC Loss,虽然去除了RNN,但是为了获得全局信息,在backbone会添加一个13x1的大卷积提取一个在序列维度h的全局信息(其实还是局部,不过卷积核接近feature map了);而且在cnn和head之间还添加了一个全连接层,用以提取全局信息。
  2. 网络之所以非常前量化,主要是因为backbone设计了一种超轻量的模块(Small basic block),灵感来源于SqueezeNet和Inception,先用1x1卷积进行降维,再接一个3x1和1x3卷积代替原先的3x3卷积进行特征提取,在效果差不多的情况下,不但可以降低模型参数量还能增加非线性表达。最后再接一个1x1卷积进行升维。
  3. 损失使用的CTC Loss、推理应用了贪心算法,搜索取每个位置上类概率的最大值。

Reference

CSDN: linux-mobaxterm-yolov5训练数据集ccpd–无数踩雷后

Github: https://github.com/ultralytics/yolov5

Github: https://github.com/sirius-ai/LPRNet_Pytorch

Gitee: https://gitee.com/reason1251326862/plate_classification

Github:https://github.com/kiloGrand/License-Plate-Recognition

知乎:LPRNet论文详解

GitHub车牌检测识别项目调研


GitHub车牌检测识别项目调研_EasyOCR

汽车车牌检测和识别实践指南,提供了算法方案和测试效果。

  • ​一,EasyOCR​
  • ​​1.1,仓库介绍​​
  • ​​1.2,使用记录​​
  • ​二,HyperLPR​
  • ​​2.1,HyperLPR 概述​​
  • ​​2.3,使用记录​​
  • ​​2.3,使用建议​​
  • ​三,simple-car-plate-recognition-2​
  • ​​3.1,仓库介绍​​
  • ​​3.2,使用记录​​
  • ​​3.3,使用建议​​
  • ​四,车牌检测-License-Plate-Detector​
  • ​​4.1,仓库介绍​​
  • ​​4.2,建议​​
  • ​五,MMOCR​
  • ​​5.1,仓库介绍​​
  • ​​5.2,使用记录​​
  • ​​5.3,使用建议​​
  • ​六,推荐 YOLOv5-LPRNet-Licence-Recognition​
  • ​​6.1,仓库介绍​​
  • ​​6.2,使用记录​​

一,EasyOCR

1.1,仓库介绍

​EasyOCR​​ 是一个用于从图像中提取文本的 python 库, 它是一种通用的 OCR,既可以读取自然场景文本,也可以读取文档中的密集文本。目前支持 80 多种语言和所有流行的书写脚本,包括:拉丁文、中文、阿拉伯文、梵文、西里尔文等。

EasyOCR 仓库 截止到 ​​2022-11-8​​​日,​​star​​​ 数为 ​​16.2k​​,其文件目录和作者给出的一些示例效果如下。

├── custom_model.md
├── Dockerfile
├── easyocr
├── easyocr.egg-info
├── examples
├── LICENSE
├── MANIFEST.in
├── README.md
├── releasenotes.md
├── requirements.txt
├── scripts
├── setup.cfg
├── setup.py
├── trainer
└── unit_test

GitHub车牌检测识别项目调研_EasyOCR_02

1.2,使用记录

1,安装较为麻烦

在自行安装了 ​​cuda​​​ 库和 ​​pytorch​​​ 的基础上,可通过 ​​pip install easyocr​​​ 命令安装 ​​easyocr​​​ 库,但是注意卸载掉之前安装的 ​​opencv-python​​ 库(如果有)。

2,代码自动下载模型速度很慢

下载的仓库里面默认是不提供任何模型的,因此第一次运行快速推理脚本会自动下载对应的 ​​ocr​​​ 模型,但是!如果网络不稳定,其下载速度非常慢,试了 ​​n​​ 次,基本不可能下载成功。

所以一般必须通过 ​​Model hub​​ 页面借助浏览器手动点击下载对应中英文 ​​ocr​​​ 识别模型,然后手动把模型文件移动到 ​​~/.EasyOCR/model​​ 文件夹下。

​EasyOCR​​​ 仓库主要是通过 ​​download_and_unzip​​​ 接口下载对应模型文件的,其也是通过调用 ​​urllib​​​ 模块提供的 ​​urlretrieve()​​ 函数来实现文件的下载,其定义如下:

def download_and_unzip(url, filename, model_storage_directory, verbose=True):
zip_path = os.path.join(model_storage_directory, temp.zip)
reporthook = printProgressBar(prefix=Progress:, suffix=Complete, length=50) if verbose else None
# url 下载链接,zip_path 文件保存的本地路径, reporthook 利用这个回调函数来显示当前的下载进度
urlretrieve(url, zip_path, reporthook=reporthook)
with ZipFile(zip_path, r) as zipObj:
zipObj.extract(filename, model_storage_directory) # 解压到指定目录
os.remove(zip_path) # 移除下载的压缩包文件

3,车牌场景识别准确率非常低

经过我的大量测试,其在中国车牌场景下识别率几乎为 ​​0​​​,我猜测是因为作者提供的训练模型所用的训练数据没有车牌场景的,而 ​​ocr​​​ 效果又非常依赖场景数据,所以导致汽车车牌识别率几乎为 ​​0​​ ,具体实践效果如下。

GitHub车牌检测识别项目调研_EasyOCR_03

二,HyperLPR

2.1,HyperLPR 概述

​HyperLPR 框架​​​是 github 作者 ​​szad670401​​ 开源的基于深度学习高性能中文车牌识别框架,支持多平台,提供了 Window、Linux、Android、IOS、ROS 平台的支持。 Python 依赖于 Keras (>2.0.0) 和 Theano(>0.9) or Tensorflow(>1.1.x) 机器学习库。项目的 C++ 实现和 Python 实现无任何关联,均为单独实现

作者提供的测试用例效果如下:

GitHub车牌检测识别项目调研_HyperLPR_04

2.3,使用记录

仓库 README 文件描述说 HyperLPR 框架对 python 包支持一键安装: ​​pip install hyperlpr​​​ 。但是经过我实际测试发现,​​pip install hyperlpr​​​ 命令只能成功安装 ​​hyperlpr​​ 库.

1,快速上手的 py 代码运行会出错:

GitHub车牌检测识别项目调研_HyperLPR_05

2,我把 ​​demo​​​ 代码移动到 ​​hyperlpr_py3​​​ 目录下运行,不再报上图的错误,但是又报了 ​​opencv​​ 函数版本的问题。

hyperlpr) root@crowd-max:/framework/HyperLPR/hyperlpr_py3# python test.py 
(1, 3, 150, 400)
40 22 335 123
Traceback (most recent call last):
File "test.py", line 7, in <module>
print(HyperLPR_plate_recognition(image))
File "/opt/miniconda3/envs/hyperlpr/lib/python3.8/site-packages/hyperlpr/__init__.py", line 8, in HyperLPR_plate_recognition
return PR.plate_recognition(Input_BGR,minSize,charSelectionDeskew)
File "/opt/miniconda3/envs/hyperlpr/lib/python3.8/site-packages/hyperlpr/hyperlpr.py", line 311, in plate_recognition
cropped_finetuned = self.finetune(cropped)
File "/opt/miniconda3/envs/hyperlpr/lib/python3.8/site-packages/hyperlpr/hyperlpr.py", line 263, in finetune
g = self.to_refine(image_, pts)
File "/opt/miniconda3/envs/hyperlpr/lib/python3.8/site-packages/hyperlpr/hyperlpr.py", line 231, in to_refine
mat_ = cv2.estimateRigidTransform(org_pts, target_pts, True)
AttributeError: module cv2 has no attribute estimateRigidTransform

3,​​ubuntu16.04+python3.8+cuda11.0​​​ 环境下,​​pip install -r requirements.txt​​ 命令安装依赖包依然会出错。

GitHub车牌检测识别项目调研_HyperLPR_06

2.3,使用建议

个人建议直接使用 C++ 版本,截止到 2022-11-8 日为止,纯 Python 版本还是有各种问题。

三,simple-car-plate-recognition-2

3.1,仓库介绍

​simple-car-plate-recognition-2仓库 ​​​ 简称:简易车牌字符识别 ​​2-Inception/CTC​​ 。

作者使用的字符识别模型是参考 ​​HyperLPR​​​ 里面的一个叫 ​​SegmenationFree-Inception​​​ 的模型结构,并改用 ​​pytorch​​ 框架实现,然后训练模型,最后测试用整张车牌图片进行字符识别。

作者所用的车牌训练集,是利用 ​​generateCarPlate​​ 这个车牌生成工具生成的。

3.2,使用记录

直接用车牌做识别,实际测试下来,不管用作者给的模型,还是自己训练的模型,效果都很差

GitHub车牌检测识别项目调研_HyperLPR_07

3.3,使用建议

虽然代码简单,模型结构容易看懂,但是不建议使用,效果不稳定和太差。

四,车牌检测-License-Plate-Detector

4.1,仓库介绍

​License-Plate-Detector 仓库​​​ 作者利用 Yolov5 模型进行了车牌检测,训练集使用 ​​CCPD​​ 数据集,测试效果如下:

GitHub车牌检测识别项目调研_EasyOCR_08

4.2,建议

不建议使用,代码写的不够整洁,使用不够方便,使用 yolov5** 用作车牌检测的模型**的方法还是可以参考下。

五,MMOCR

5.1,仓库介绍

​mmocr​​​ 是商汤 + ​​openmmlab​​ 实验室开发的 OCR 框架。​​MMOCR​​​ 是基于 ​​PyTorch​​​ 和 ​​mmdetection​​​ 的开源工具箱,专注于文本检测,文本识别以及相应的下游任务,如关键信息提取。 它是 ​​OpenMMLab​​ 项目的一部分。

主分支目前支持 PyTorch 1.6 以上的版本。mmocr 库的安装,可参考我之前的文章-​​ubuntu16.04安装mmdetection库​​。

5.2,使用记录

1,官方提供中文字符识别模型只有一个,其使用步骤如下:

  1. 创建 ​​mmocr/data/chineseocr/labels​​ 目录;
  2. 为了模型推理成功,下载中文字典,并放置到 ​​labels​​ 目录;
wget -c https://download.openmmlab.com/mmocr/textrecog/sar/dict_printed_chinese_english_digits.txt
mv dict_printed_chinese_english_digits.txt mmocr/data/chineseocr/labels
  1. 运行推理脚本。
python mmocr/utils/ocr.py --det DB_r18 --recog SAR_CN demo/car1.jpeg --output=./

车牌识别效果不好,测试结果如下:

GitHub车牌检测识别项目调研_EasyOCR_09

GitHub车牌检测识别项目调研_EasyOCR_10

GitHub车牌检测识别项目调研_MMOCR_11

2,官方提供的测试用例的推理效果如下:

GitHub车牌检测识别项目调研_EasyOCR_12

5.3,使用建议

官方提供的不管是中文还是英文文本识别模型,在车牌场景下识别效果都不好,不推荐在车牌识别场景下使用,更适合通用场景。

六,推荐 YOLOv5-LPRNet-Licence-Recognition

6.1,仓库介绍

​YOLOv5-LPRNet-Licence-Recognition​​​ 项目是使用 ​​YOLOv5s​​​ 和 ​​LPRNet​​​ 对中国车牌进行检测和识别,车牌数据集是使用 ​​CCPD​​。

车牌字符识别的准确率如下:

​model​

数据集

​epochs​

​acc​

​size​

LPRNet

val

100

94.33%

1.7M

LPRNet

test

100

94.30%

1.7M

总体模型速度:(​​YOLOv5 + LPRNet​​​)速度:​​47.6 FPS​​(970 GPU)。

6.2,使用记录

作者提供的模型实际测试下来效果还不错,部分示例如下:

GitHub车牌检测识别项目调研_HyperLPR_13

GitHub车牌检测识别项目调研_HyperLPR_14

GitHub车牌检测识别项目调研_EasyOCR_15



以上是关于项目三车牌检测+识别项目三LPRNet车牌识别网络原理和核心源码解读的主要内容,如果未能解决你的问题,请参考以下文章

智能驾驶 车牌检测和识别《CRNN和LPRNet实现车牌识别(含车牌识别数据集和训练代码)》

GitHub车牌检测识别项目调研

车牌识别系统原理与代码「YOLO+MLP」

一套基于java的开源车牌识别算法

Paddle入门实战系列:车牌检测与识别

OpenCV(项目)车牌识别4 -- 总结篇