您的位置:首页 > 理论基础 > 计算机网络

OpenCV自学笔记20. 基于SVM和神经网络的车牌识别(四)

2017-07-19 19:58 721 查看

基于SVM和神经网络的车牌识别(四)

本系列文章参考自《深入理解OpenCV实用计算机视觉项目解析》仅作学习用途

特征提取与识别

上一篇中,我们得到了车牌的每一个数字,在本篇将对利用神经网络识别出每一个数字

Step1. 首先提取特征,特征主要包含几个部分:水平方向和竖直方向的累积直方图、以及低分辨率的图像样本

Step2. 提取特征后,我们将创建分类器,并开始训练

Step3. 最后进行预测

我们的样本数据来自一个xml文件:

这个xml 文件包含多组训练数据,其中:

TrainingDataF5 表示 5*5 的低分辨率图像

TrainingDataF10 表示 10*10 的低分辨率图像

TrainingDataF15 表示 15*15 的低分辨率图像

TrainingDataF20 表示 20*20 的低分辨率图像



这个过程,完整的程序如下:

#include <iostream>
#include "opencv2/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include <ml.hpp>

using namespace std;
using namespace cv;
using namespace ml;

const int HORIZONTAL = 1; // 水平方向,在创建累积直方图时,需要用到
const int VERTICAL = 0; // 竖直方向,在创建累积直方图时,需要用到
const char strCharacters[] = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'B', 'C', 'D', 'F', 'G', 'H', 'J', 'K', 'L', 'M',
};
const int numCharacters = 30; // 一共30个字符
const int charSize = 20;

/* 累积直方图 */
Mat ProjectHistogram(Mat img, int t) {

// 如果是竖直方向,t = 0,sz = img.cols
// 总之,选取长宽的最大值,以便创建完全包含数字图像的矩阵
int sz = (t) ? img.rows : img.cols;
Mat mhist = Mat::zeros(1, sz, CV_32F); // mhist是一个1 * sz的矩阵

// 按行/列 统计非零像素值的个数,并保存在mhist中
for (int j = 0; j < sz; j++) {
Mat data = (t) ? img.row(j) : img.col(j);
mhist.at<float>(j) = countNonZero(data);
}

double min, max;
minMaxLoc(mhist, &min, &max); // 找到矩阵中的最大值,以便归一化

if (max > 0) {
// 矩阵的每一个元素都除以最大值,这正是归一化操作
mhist.convertTo(mhist, -1, 1.0f/max, 0);
return mhist;
}
}

/*
创建特征矩阵
水平方向累积直方图 + 竖直方向累积直方图 + 低分辨率图像
*/
Mat features(Mat in, int sizeData) {

// 分别在水平方向和垂直方向上 创建累积直方图
Mat vhist = ProjectHistogram(in, VERTICAL);
Mat hhist = ProjectHistogram(in, HORIZONTAL);

// 低分辨率图像
// 低分辨率图像中的每一个像素都将被保存在特征矩阵中
Mat lowData;
resize(in, lowData, Size(sizeData, sizeData));

// 特征矩阵的列数
int numCols = vhist.cols + hhist.cols + lowData.cols * lowData.cols;
Mat out = Mat::zeros(1, numCols, CV_32F); // 创建特征矩阵

// 向特征矩阵赋值
int j = 0;
// 首先把水平方向累积直方图的值,存到特征矩阵中
for (int i = 0; i < vhist.cols; i++) {
out.at<float>(j) = vhist.at<float>(i);
j++;
}
// 然后把竖直方向累积直方图的值,存到特征矩阵中
for (int i = 0; i < hhist.cols; i++) {
out.at<float>(j) = hhist.at<float>(i);
j++;
}
// 最后把低分辨率图像的像素值,存到特征矩阵中
for (int x = 0; x < lowData.cols; x++) {
for (int y = 0; y < lowData.rows; y++){
out.at<float>(j) = (float)lowData.at<unsigned char>(x, y);
j++;
}
}
return out;
}

/*
训练和识别
注:为了测试方便,我把训练和识别写到一个函数里了
正常情况下,应该单独封装为函数
*/
int classificationANN(Mat TrainingData, Mat classes, int nlayers, Mat f){

// step1. 生成训练数据
Mat trainClasses;
trainClasses.create(TrainingData.rows, numCharacters, CV_32FC1);
for (int i = 0; i < trainClasses.rows; i++) {
for (int j = 0; j < trainClasses.cols; j++) {
if (j == classes.at<int>(i))
trainClasses.at<float>(i, j) = 1;
else
trainClasses.at<float>(i, j) = 0;
}
}
Ptr<TrainData> trainingData = TrainData::create(TrainingData, ROW_SAMPLE, trainClasses);

// step2. 创建分类器
Mat layers(1, 3, CV_32SC1);
layers.at<int>(0) = TrainingData.cols;
layers.at<int>(1) = nlayers;
layers.at<int>(2) = numCharacters;

Ptr<ANN_MLP> ann = ANN_MLP::create();
ann->setLayerSizes(layers); // 设置层数
ann->setActivationFunction(ANN_MLP::SIGMOID_SYM, 1, 1); // 设置激励函数

// step3. 训练
ann->train(trainingData);

// step4. 预测
// 处理输入的特征Mat f
Mat src;
src.create(45, 77, CV_32FC1);
resize(f, src, src.size(), 0, 0, INTER_CUBIC);
src.convertTo(src, CV_32FC1);
src = src.reshape(1, 1);

Mat output(1, numCharacters, CV_32FC1);
ann->predict(f, output); // 开始预测

Point maxLoc;
double maxVal;
// output为一个向量,向量的每一个元素反映了输入样本属于每个类别的概率
minMaxLoc(output, 0, &maxVal, 0, &maxLoc); // 找到最大概率

// 返回字符在strCharacters[]数组中的索引
return maxLoc.x;
}

int main() {
// Step0. 预处理数字图像
Mat src = imread("7.jpg", 0); // 读取灰度图

int h = src.rows;
int w = src.cols;
Mat transformMat = Mat::eye(2, 3, CV_32F); // 创建对角阵
int m = max(w, h);
transformMat.at<float>(0, 2) = m / 2 - w / 2;
transformMat.at<float>(1, 2) = m / 2 - h / 2;

// 仿射变换
Mat warpImage(m, m, src.type());
warpAffine(src, warpImage, transformMat, warpImage.size(), INTER_LINEAR, BORDER_CONSTANT, Scalar(0));

Mat out;
resize(warpImage, out, Size(charSize, charSize)); // 重新调整大小
imshow("out", out);

// Step1. 读取训练数据OCR.xml
FileStorage fs;
fs.open("OCR.xml", FileStorage::READ);
Mat TrainingData;
Mat Classes;
fs["TrainingDataF5"] >> TrainingData;
fs["classes"] >> Classes;

// Step2. 创建特征矩阵
Mat f = features(out, 5);

// Step3. 训练 + 测试(写到一个函数里了)
int index = classificationANN(TrainingData, Classes, 10, f);
cout << strCharacters[index] << endl;

waitKey();
return 0;
}


实验结果如下:



把上一节切割得到的数字,都进行一遍实验,结果如下

发现识别并不准确,

7 被识别为 V

5 被识别为 S

D 被识别为 J



参考

累积直方图:http://blog.csdn.net/combfish/article/details/45056239

系列文章

OpenCV自学笔记17. 基于SVM和神经网络的车牌识别(一)

OpenCV自学笔记18. 基于SVM和神经网络的车牌识别(二)

OpenCV自学笔记19. 基于SVM和神经网络的车牌识别(三)

OpenCV自学笔记20. 基于SVM和神经网络的车牌识别(四)

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: