前言
前段时间学习完OpenCV的基础部分后,做了简单的二维码识别与条形码识别,最近做了一下这个车牌识别系统,本来是OpenCv还没有学到机器学习的位置,但是结合之前参加数学建模的时候对神经网络的了解,做了这个车牌字符的训练与识别。
目录:
目录
1.5 将筛选后的矩形在原图上标出,并在灰度图中抠出该矩形位置
处理步骤
- 图像预处理与车牌定位
- 字符分割
- 神经网络训练与识别
1 图像预处理与车牌定位
输入图像为:
1.1 灰度处理-滤波-索贝尔求导-二值化
车牌为横向放置,观察车牌可发现车牌类似与条形码,竖向特征较多,所以用Sobel进行X方向求导,并进行自适应二值化
1.2 开操作-去除小的对象
1.3 闭操作-填充小的孔,使车牌特征连在一起
1.4 findContours与轮廓筛选
运用findContours函数寻找轮廓,并做出最小矩形,通过对矩形的筛选,选出我们想要的车牌的位置;
小型汽车车牌尺寸为440mm×140mm,宽高比为3.143;
故:筛选条件:rect1.width/ rect1.height >= 3 && rect1.width / rect1.height <=4
1.5 将筛选后的矩形在原图上标出,并在灰度图中抠出该矩形位置
2 字符分割
2.1预处理
对上一步的结果result进行滤波和二值化
2.2 分割
前段时间刚刚学了分水岭算法,所以起初是准备用分水岭算法实现的结果如下
效果很不理想:
- 汉字 “沪” 用分水岭分割智能分割到marks标记的那一部分
- K不知道什么原因,溢出到了背景色上
那就换个思路:
观察二值化后的图发现,没有字符的部分是全黑色的,即不存在数值大于0的点
统计每一列数值大于0的点的个数,如下:
00000000061610011823198644613110000019292925977810131918181510400000013100000293030217667911151920201590000008111311111110111113161519181370000071925231613129910131626262011000004162327191512101111111423272013000000112126221511119910142127221600005500
可以发现,当连续0的个数大于4个时,0序列两侧,即为我们想要的分割线。
分割图:
将分割后的图片输出:
3 字符识别
3.1 神经网络训练
特征向量的定义:将图片分为8*4个方块,计算每一个方块内灰度值的综合除以整张图片灰度值的总和,再按顺序排列成长度为32的行向量,即为特征向量。
这里我只选择了对数字和字母进行训练,每个字符有100个样本,所以输入的训练样本数列为 Mat inputs(34*100, 32, CV_32FC1)
训练输出矩阵为 Mat response(3400, 34, CV_32FC1);
设置规则:若训练图片为1,则response(x,1)=1,该行其余为0
若训练图片为5,则response(x,5)=1,该行其余为0。
设置神经网络过程如下:
/**************神经网络设置与训练*******/
cout << "开始训练......." << endl;
Mat layerSizes = (Mat_<int>(1, 3) << 32, 32, 34); //设置神经网络参数输入层为32,中间层32,输出层34
Ptr<ANN_MLP> bp = ANN_MLP::create(); //创建空模型
bp->setLayerSizes(layerSizes); //输入神经元结构
bp->setTrainMethod(ml::ANN_MLP::BACKPROP, 0.1, 0.1); //训练方法为反向传播方法
bp->setActivationFunction(ml::ANN_MLP::SIGMOID_SYM); //指定激活函数
//bp->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER, 5000, 0.01)); //终止条件
bp->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER | TermCriteria::EPS, 5000, 0.0001));
bool trained=bp->train(inputs, ROW_SAMPLE, response);
cout << "训练结束!" << endl << endl;
bp->save("bp_trained.xml"); //保存训练好的神经网络
3.2 神经网络预测识别字符
读取训练好的神经网络参数,对输入图片归一化为特征向量后,bp->predict进行预测。
这里我设置了一个函数,输入字符图片,输出字符
char disting(Mat inputA)
{
Ptr<ANN_MLP> bp = ANN_MLP::load("C:/Users/LBJ/Desktop/神经网络训练/OpenCVTest/bp_trained.xml");
vector<double> sample;
Mat output;
division(inputA, sample);
convers(sample);
bp->predict(sample, output);
double min, max;
Point minLoc, maxLoc;
minMaxLoc(output, &min, &max, &minLoc, &maxLoc, Mat());
//cout << endl << maxLoc << endl;
char re;
if (maxLoc.x<=9)
{
re = '0' + maxLoc.x;
}
else if (maxLoc.x <= 17 && maxLoc.x > 9)
{
re = '0' + maxLoc.x;
}
else if (maxLoc.x > 17 && maxLoc.x<=22)
{
re = '0' + maxLoc.x + 8;
}
else if (maxLoc.x > 22)
{
re = '0' + maxLoc.x + 9;
}
return re;
}
分别输入 分割好的字符图片,输出结果如下:
识别正确。
源码分享
神经网路训练源码:
#include<opencv2/opencv.hpp>
#include<opencv2/core/mat.hpp>
#include<iostream>
#include<vector>
#include<opencv2/ml/ml.hpp>
using namespace std;
using namespace cv;
using namespace ml;
int sumMat(Mat inputA); //求和函数
void division(Mat intput,vector<double>& output); //归一化为8*4的向量
void convers(vector<double>& output); //归一化为和为1向量
string adress="C:/Users/LBJ/Desktop/神经网络训练/字符训练图片";
vector<string> Adr_id(3400); //图片地址向量
vector<vector<double>> divi(3400); //图片特征向量,每个图片有 4*8=32个特征;
int main()
{
for (size_t i = 0; i < 34*100; i=i+100)
{
for (size_t j = 0; j < 100; j++)
{
Adr_id[i + j] = adress + "/" + to_string(i / 100) + "/z" + to_string(i + j + 1) + ".jpg";;
cout << Adr_id[i + j] << endl;
Mat src = imread(Adr_id[i + j]);
if (!src.data)
{
cout << "The iamge is empty" << endl;
return -1;
}
//imshow("src", src);
cvtColor(src, src, CV_RGB2GRAY);
division(src, divi[i+j]);
convers(divi[i + j]);
}
}
Mat inputs(3400, 32, CV_32FC1); //设置训练样本 ,将divi存在input里边
for (size_t i = 0; i < 3400; i++)
{
float* data = inputs.ptr<float>(i);
for (size_t j = 0; j < 32; j++)
data[j] = divi[i][j];
}
Mat response(3400, 34, CV_32FC1); //设置输出矩阵
for (size_t i = 0; i < 3400; i++)
{
float* data = response.ptr<float>(i);
for (size_t j = 0; j < 34; j++)
{
if(j==i/100)
data[j] = 1;
else
{
data[j] = 0;
}
}
}
cout << endl << endl << response;
cout << "开始训练。。。。。" << endl;
/**************神经网络设置与训练*******/
Mat layerSizes = (Mat_<int>(1, 3) << 32, 32, 34); //设置神经网络参数输入层为32,中间层32,输出层34
Ptr<ANN_MLP> bp = ANN_MLP::create(); //创建空模型
bp->setLayerSizes(layerSizes); //输入神经元结构
bp->setTrainMethod(ml::ANN_MLP::BACKPROP, 0.1, 0.1); //训练方法为反向传播方法
bp->setActivationFunction(ml::ANN_MLP::SIGMOID_SYM); //指定激活函数
//bp->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER, 5000, 0.01)); //终止条件
bp->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER | TermCriteria::EPS, 10000, 0.0001));
bool trained=bp->train(inputs, ROW_SAMPLE, response);
cout << "训练结束!" << endl << endl;
bp->save("bp_trained.xml"); //保存训练好的神经网络
/**************神经网络预测*******/
Mat src = imread("C:/Users/LBJ/Desktop/神经网络训练/char3.jpg");
cvtColor(src, src, CV_BGR2GRAY);
vector<double> sample;
division(src, sample);
convers(sample);
Mat result(Size(1, 34), CV_32FC1);
bp->predict(sample, result);
cout << endl << result << endl;
double min, max;
Point minLoc, maxLoc;
minMaxLoc(result, &min, &max, &minLoc, &maxLoc, Mat());
cout << maxLoc;
getchar();
return 0;
}
int sumMat(Mat inputA)
{
int sum=0;
for (size_t i = 0; i < inputA.cols; i++)
{
for (size_t j = 0; j < inputA.rows; j++)
{
sum += inputA.at<uchar>(i, j);
}
}
return sum;
}
void division(Mat intput, vector<double>& output)
{
for (size_t i = 0; i < intput.rows; i=i+4)
{
for (size_t j = 0; j < intput.cols; j=j+4)
{
output.push_back(sumMat(intput(Rect(j, i, 4, 4))));
}
}
}
void convers(vector<double>& output)
{
double sum = 0;
for (size_t k = 0; k < output.size(); k++)
{
sum += output[k];
}
for (size_t k = 0; k < output.size(); k++)
{
output[k] = output[k] / sum;
}
}
车牌预处理--字符分割与识别源码:
#include<opencv2/opencv.hpp>
#include<opencv2/core/mat.hpp>
#include<iostream>
#include<vector>
#include<opencv2/ml/ml.hpp>
using namespace std;
using namespace cv;
using namespace ml;
int sumMat(Mat inputA);
void division(Mat intput, vector<double>& output); //归一化为8*4的向量
void convers(vector<double>& output); //归一化为和为1向量
char disting(Mat inputA); //图片识别,输入图片,输出识别的字符
Mat src, dst;
int main()
{
src = imread("C:/Users/LBJ/Desktop/车牌识别/1 - 副本.jpg");
if (!src.data)
{
cout << "The iamge is empty" << endl;
return -1;
}
imshow("input_imge", src);
/****************灰度处理,提取边缘,二值化********/
Mat src_gray,Gblur;
cvtColor(src, src_gray, CV_BGR2GRAY);
GaussianBlur(src_gray, Gblur, Size(3, 3), 0, 0);
Mat canny;
Sobel(Gblur, canny, CV_32F, 1, 0, 3);
convertScaleAbs(canny, canny);
medianBlur(canny, canny,5);
threshold(canny, canny, 40, 255, THRESH_BINARY | THRESH_OTSU);
imshow("canny", canny);
Mat open;
Mat structure = getStructuringElement(MORPH_RECT, Size(5, 5), Point(-1, -1));
morphologyEx(canny, open, CV_MOP_OPEN, structure, Point(-1, -1), 1);
imshow("open", open);
Mat close;
structure = getStructuringElement(MORPH_RECT, Size(13, 9 ), Point(-1, -1));
morphologyEx(open, close, CV_MOP_CLOSE, structure, Point(-1, -1), 1);
imshow("close", close);
//
/*************提取轮廓************/
vector<vector<Point>> contours;
vector<Vec4i> hierachy;
findContours(close, contours, hierachy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));
//
///**************筛选出长宽比为3.14左右,矩形度高的轮廓*************/
Rect rect1,rect;
dst = Mat::zeros(src.size(), CV_8UC3);
RNG rng(12345);
for (size_t i = 0; i < contours.size(); i++)
{
rect1 = boundingRect(contours[i]);
if ( rect1.width/ rect1.height >= 3 && rect1.width / rect1.height <=4)
{
rect = rect1;
}
Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
drawContours(dst, contours, i, color, 2, 8, hierachy, 0, Point(0, 0));
}
// imshow("dst", dst);
Mat result;
result = src_gray(Rect(rect));
rectangle(src, rect, Scalar(0, 255, 0), 2);
imshow("output_img", src);
imshow("Result", result);
/*************车牌分割**************/
medianBlur(result, result, 3);
threshold(result, result, 180, 255, THRESH_BINARY );
imshow("thresh", result);
//cout << "rows" << endl;
vector<int> row_count(result.rows);
for (size_t i = 0; i < result.rows; i++)
{
row_count[i] = 0;
for (size_t j = 0; j < result.cols; j++)
{
if (result.at<uchar>(i, j) > 0)
{
row_count[i]++;
}
}
//cout << row_count[i];
}
cout << endl;
int high, low;
for (size_t i = 0; i < result.rows; i++)
{
if (row_count[i] != 0)
{
high = i; break;
}
}
for (size_t i = result.rows -1 ; i >0; i--)
{
if (row_count[i] != 0)
{
low = i; break;
}
}
result = result.rowRange(high, low);
vector<int> count(result.cols),limit;
for (size_t i = 0; i < result.cols; i++)
{
count[i] = 0;
for (size_t j = 0; j < result.rows; j++)
{
if (result.at<uchar>(j,i)>0)
{
count[i]++;
}
}
cout << count[i];
}
cout << endl;
int k;
for (size_t i = 0; i < result.cols; i++)
{
if (count[i]==0)
{
k = i; //储存一下i的起始值
int zeros=0;
while (count[i] == 0)
{
i++;
zeros++;
if (i>= result.cols)
{
break;
}
}
//cout << zeros<<", ";
if (zeros >= 4)
{
limit.push_back(k);
limit.push_back(i);
}
}
}
Mat char1, char2, char3, char4, char5, char6, char7;
char1 = result.colRange(limit[1]-2, limit[2]+2);
char2 = result.colRange(limit[3]-2, limit[4]+2);
char3 = result.colRange(limit[7]-2, limit[8]+2);
char4 = result.colRange(limit[9]-2, limit[10]+2);
char5 = result.colRange(limit[11]-2, limit[12]+2);
char6 = result.colRange(limit[13]-2, limit[14]+2);
char7 = result.colRange(limit[15]-2, limit[16]+2);
resize(char1, char1, Size(16, 32));
resize(char2, char2, Size(16, 32));
resize(char3, char3, Size(16, 32));
resize(char4, char4, Size(16, 32));
resize(char5, char5, Size(16, 32));
resize(char6, char6, Size(16, 32));
resize(char7, char7, Size(16, 32));
imshow("char1", char1);
imshow("char2", char2);
imshow("char3", char3);
imshow("char4", char4);
imshow("char5", char5);
imshow("char6", char6);
imshow("char7", char7);
/**************字符识别****************///读取训练好的神经网络参数
cout << disting(char2) << " "
<< disting(char3) << " "
<< disting(char4) << " "
<< disting(char5) << " "
<< disting(char6) << " "
<< disting(char7) << " ";
waitKey(0);
return 0;
}
int sumMat(Mat inputA)
{
int sum = 0;
for (size_t i = 0; i < inputA.cols; i++)
{
for (size_t j = 0; j < inputA.rows; j++)
{
sum += inputA.at<uchar>(i, j);
}
}
return sum;
}
void division(Mat intput, vector<double>& output)
{
for (size_t i = 0; i < intput.rows; i = i + 4)
{
for (size_t j = 0; j < intput.cols; j = j + 4)
{
output.push_back(sumMat(intput(Rect(j, i, 4, 4))));
}
}
}
void convers(vector<double>& output)
{
double sum = 0;
for (size_t k = 0; k < output.size(); k++)
{
sum += output[k];
}
for (size_t k = 0; k < output.size(); k++)
{
output[k] = output[k] / sum;
}
}
char disting(Mat inputA)
{
Ptr<ANN_MLP> bp = ANN_MLP::load("C:/Users/LBJ/Desktop/神经网络训练/OpenCVTest/bp_trained.xml");
vector<double> sample;
Mat output;
division(inputA, sample);
convers(sample);
bp->predict(sample, output);
double min, max;
Point minLoc, maxLoc;
minMaxLoc(output, &min, &max, &minLoc, &maxLoc, Mat());
//cout << endl << maxLoc << endl;
char re;
if (maxLoc.x<=9)
{
re = '0' + maxLoc.x;
}
else if (maxLoc.x <= 17 && maxLoc.x > 9)
{
re = '0' + maxLoc.x;
}
else if (maxLoc.x > 17 && maxLoc.x<=22)
{
re = '0' + maxLoc.x + 8;
}
else if (maxLoc.x > 22)
{
re = '0' + maxLoc.x + 9;
}
return re;
}