将车牌识别系统细分为三个部分:
1.车牌检测
2.字符分割
3.字符识别
1.车牌检测
为实现更稳定的检测性能,使用yolo来对车牌进行识别。具体的训练方法见darknet用自己的数据进行训练。数据由EasyPR提供的200多张和自己从网上爬的几百张组成,爬虫脚本和数据标注方法见自行准备深度学习训练数据。最终得到的效果不错,下面是一些测试结果:
2.字符分割
利用EasyPR开源的代码来实现字符分割,在源代码下修改如下:
1.src/core/plate_locate.cpp中注释掉int CPlateLocate::plateLocate(Mat src, vector &resultVec, int index)函数中的两行:
int CPlateLocate::plateLocate(Mat src, vector<Mat> &resultVec, int index) {
vector<CPlate> all_result_Plates;
plateColorLocate(src, all_result_Plates, index);
//plateSobelLocate(src, all_result_Plates, index);
//plateMserLocate(src, all_result_Plates, index);
for (size_t i = 0; i < all_result_Plates.size(); i++) {
CPlate plate = all_result_Plates[i];
resultVec.push_back(plate.getPlateMat());
}
return 0;
}
2.将test中全部删除,只保留chars.hpp和main.cpp
chars.hpp:
#ifndef EASYPR_CHARS_HPP
#define EASYPR_CHARS_HPP
namespace easypr {
namespace demo {
int test_chars_segment() {
cv::Mat src = cv::imread("resources/image/try_3.jpg");
std::vector<cv::Mat> resultVec;
CPlateLocate plate;
int result = plate.plateLocate(src, resultVec);
if(result==0)
{
size_t num = resultVec.size();
for(size_t j=0;j<num;j++)
{
std::vector<cv::Mat> resultVec2;
cv::Mat resultMat = resultVec[j];
CCharsSegment plate2;
int result2 = plate2.charsSegment(resultMat, resultVec2);
if(result2==0)
{
size_t num2=resultVec2.size();
for(size_t i=0;i<num2;i++)
{
cv::Mat resultMat2 = resultVec2[i];
cv::imshow("chars_segment", resultMat2);
cv::waitKey(0);
}
cv::destroyWindow("chars_segment");
}
}
}
return result;
}
}
}
#endif // EASYPR_CHARS_HPP
main.cpp:
#include "easypr.h"
#include "chars.hpp"
namespace easypr {
namespace demo {
int testMain() {
assert(test_chars_segment() == 0);
return 0;
}
} // namespace demo
} // namespace easypr
int main(int argc, const char* argv[]) {
easypr::demo::testMain();
return 0;
}
这样处理后,终端在EasyPR根目录下输入
./demo
即可显示车牌分割后的字符
3.字符识别
依然使用darknet来进行字符识别。
不同于检测,这次使用darknet的classfier函数进行训练和识别,并考虑到分割出来的字符很小,使用了很浅的网络。
利用EasyPR提供的字符识别训练集(即resource/train/ann.7z)进行训练。将数据集解压后,每个文件夹中都有着对应的字符数据,对每个文件夹进行重命名处理。每个图像的命名格式采用(图片序号)_(标签).(图片格式)的格式进行命名。
重命名方法可以参考自行准备深度学习训练数据中的第2点。
重命名完成后,在darknet/data文件夹下新建char文件夹,并在char文件夹下新建image文件夹,将重命名完成的图片统一复制到image文件夹中,再使终端进入char路径(即darknet/data/char),输入
find `pwd`/image -name \*.jpg > train.list
会在char文件夹下自动生成train.list文件,其中包含每个图像的绝对路径。
在darknet/cfg文件夹下新建cifar.data(名字cifar可以自定义),并进行如下改写:
classes=65
train = data/char/train.list
labels = data/char/labels.txt
backup = backup/
top=2
class 为识别类别数量,train为train.list路径,label为label文件路径。如果需要训练完后的评估,请参照train.list的方法生成val.list,然后再cifar中增加一行
valid=(your path)
然后在char文件夹下新建labels.txt:
每行一个类别标签。
标签的注意事项
1.不区分大小写
2.不能有包含关系,如ji和jin,不能出现这样的形式,可改为ji1和jin
最后,cfg文件夹下新建 cifar_small.cfg
[net]
batch=128
subdivisions=1
height=28
width=28
channels=3
max_crop=32
min_crop=32
hue=.1
saturation=.75
exposure=.75
learning_rate=0.01
policy=poly
power=4
max_batches = 5000000
momentum=0.9
decay=0.0005
[convolutional]
batch_normalize=1
filters=32
size=3
stride=1
pad=1
activation=leaky
[maxpool]
size=2
stride=2
[convolutional]
batch_normalize=1
filters=64
size=3
stride=1
pad=1
activation=leaky
[maxpool]
size=2
stride=2
[convolutional]
batch_normalize=1
filters=128
size=3
stride=1
pad=1
activation=leaky
[convolutional]
filters=65
size=1
stride=1
pad=1
activation=leaky
[avgpool]
[softmax]
groups=1
[cost]
type=sse
avgpool上面那个convolutional中的filter为自己的类别数。max_batches和学习率之类的自己按照自己的需要来调整。
万事俱备,开始训练:
终端进入darknet根目录,输入
./darknet classifier train cfg/cifar.data cfg/cifar_small.cfg
然后坐等结果吧