赞
踩
甲方一拍脑门,让我去实现车牌识别,还是远距离监控视角的,真开心。
数据?呵~ 不会有人期待甲方提供数据吧??
先逛逛某宝,一万张车辆图片,0.4元/张。
甲方:阿巴阿巴…
嗯,那没事了。
再逛逛全球同性交友网站,感谢CCPD数据集~ 数量挺多的,补充了新能源绿色车牌,标注也很详细,除了车牌矩形框坐标,居然还有车牌4个角点坐标。我看了一下,矩形框标的有点随意了,于是我把角点坐标的最小包络矩形作为新的标注框,随手一个YOLOv5,这车牌检测的任务不就完成了嘛~ 四舍五入,车牌识别项目收工!
由于数据搜集的地域原因,其中一大半车牌的开头都是皖A,这…不得训练出来个人工智障,见个车牌就说是皖A?哎,只能含泪筛选出1W张用作车牌识别训练。
这数据量肯定不够呀,只能造假了…参考车牌生成代码,调调参数,解决个别字符的小bug,最终效果这样婶儿滴:
生成10+W张假车牌,再去全网捡捡零碎数据,东拼西凑了20+W张。凑合过吧,要啥自行车。
接下来进入正题,车牌切出来了,咋识别?
用传统方法,字符分割再识别单个字符?emmm,算了叭,传统是不可能传统的(我也不会!!)。
最典型的字符识别模型是CRNN+CTC,CTC Loss是为了解决字符对齐问题。因为车牌字符数目是固定的,这就大大简化了问题,其实没必要使用CTC Loss,直接用CNN、RNN和全连接,根据心情搭配一下都能训练。
之前我学习的时候,也玩过一个Seq2Seq的车牌识别模型(在这里!),但如果不加注意力机制,效果不咋样,我试了一下,直接暴力增大LSTM隐藏层的size,也能训练出来,只是模型有100+M。
咳…你才捞呢!
还是决定自己搭个网络玩儿,参考了CRNN的原网络和GitHub上的一些实现,先整一套尝尝,噔噔蹬蹬~
import torch.nn as nn class Crnn(nn.Module): def __init__(self): super(Crnn, self).__init__() self.cnn = nn.Sequential( # (N,3,32,100) nn.Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=1), # (N,64,32,100) nn.ReLU(), nn.MaxPool2d((2, 2), 2), # (N,3,16,50) nn.Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=1), # (N,128,16,50) nn.ReLU(), nn.MaxPool2d((2, 2), 2), # (N,128,8,25) nn.Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=1), # (N,256,8,25) nn.ReLU(), nn.Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=1), # (N,256,8,25) nn.ReLU(), nn.MaxPool2d((2, 1), stride=(2, 1)), # (N,256,4,25) nn.Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=1), # (N,512,4,25) nn.BatchNorm2d(512), nn.ReLU(), nn.Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=1), # (N,512,4,25) nn.BatchNorm2d(512), nn.ReLU(), nn.AdaptiveMaxPool2d([1, 25]) ) self.lstm = nn.LSTM(input_size=512, hidden_size=140, batch_first=True, bidirectional=True, num_layers=2) def forward(self, x): cnn_out = self.cnn(x).reshape(-1, 512, 25).transpose(2, 1) # (N,25,512) lstm_out, (h_n, c_n) = self.lstm(cnn_out, None) lstm_out = lstm_out[:, -1, :] # (N, 140) out = lstm_out.reshape(-1, 8, 35) return out
32个中文字符和34个数字字母(不含I和O)由列表定义:
c1 = ['皖', '沪', '津', '渝', '冀', '晋', '蒙', '辽', '吉', '黑', '苏', '浙', '京', '闽', '赣', '鲁',
'豫', '鄂', '湘', '粤', '桂', '琼', '川', '贵', '云', '西', '陕', '甘', '青', '宁', '新', '藏']
c2 = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S',
'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '']
拆成两个列表是节约输出的维度,减少参数量。
输入车牌尺寸是 100 ∗ 32 100*32 100∗32,因为CNN最后我用了个自适应平均池化,所以图片尺寸不对也不会报错。网络输出shape为 ( N , 8 , 35 ) (N, 8,35) (N,8,35),普通车牌是7位的,新能源绿色车牌是8位的,所以需要增加一个空字符串,如果是7位车牌,最后一个字符就为空。
将标签做one-hot,损失函数直接用MSELoss。看起来有点捞,训练效果却出奇的好,10轮验证精度就到99%。模型大小26M,推理速度还行。拿实际的监控视频测试了一下,效果也还能看。
但用MSELoss做分类问题,多少有点别扭,而且输出的值无法表示概率。思考了一下怎么改用CrossEntropyLoss,直接把标签的one-hot去掉是会报错的,因为我们的输出是 ( N , 8 , 35 ) (N, 8,35) (N,8,35),相当于是8个字符的分类结果,CrossEntropyLoss算的是一个分类结果,于是我把输出 ( N , 8 , 35 ) (N, 8,35) (N,8,35)reshape成 ( N ∗ 8 , 35 ) (N*8,35) (N∗8,35),把标签 ( N , 8 ) (N, 8) (N,8)拉平成 ( N ∗ 8 ) (N*8) (N∗8),这样就可以计算辣~
蓝鹅,效果很不好,精度到60+%就上不去了…
经过一番深思(才不是瞎猜的 哼!),我觉得循环网络的输出直接跟交叉熵损失不合适,一般的分类问题最后一层都是全连接输出的,于是把lstm层改成了全连接:
self.fc = nn.Linear(512, 280)
这样就可以训练辣,模型还更小了点,24M~
最后尝试将LSTM替换成GRU,再次减少参数。以及将pytorch自带的交叉熵损失改成Focal Loss,以增加对困难样本(主要是第一个中文字符)的训练力度。
之前领导为了跟甲方展示(吹niubility),按量买了百度的车牌识别。
卖的死贵,效果就这?就这??
咱的效果:
哼哼哼~ 走咯~ 卖算法去咯~
我不是在黑百度,对不起!!
百度的技术是很厉害的,只是人家针对的是近距离的车牌。实际上,现在车牌识别使用最多的应该是停车场的道闸系统,都是近距离的,这种监控视角的,确实有难度,精度还达不到要求,我这个模型只是最简单的一种实现。
不足:
在实际使用中,如果每一帧都识别的话,不仅费时,而且显示上会闪烁,就很鬼畜。所以我结合DeepSort跟踪算法,跟踪每一个车牌目标,设计规则,如在连续数帧都识别为同一车牌号后,就稳定显示,不再识别。
数据集还在筛选和补充,模型也还在改进,就不上传辣~
真有需要的同学私撩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。