天天看点

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

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

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

特征提取与识别

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

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

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

Step3. 最后进行预测

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

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

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

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

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

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

OpenCV自学笔记20. 基于SVM和神经网络的车牌识别(四)
这个过程,完整的程序如下:
#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;
}      
实验结果如下:
OpenCV自学笔记20. 基于SVM和神经网络的车牌识别(四)

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

发现识别并不准确,

7 被识别为 V

5 被识别为 S

D 被识别为 J

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

OCR.xml文件下载地址

链接: ​​​https://pan.baidu.com/s/12iHnXK1mf0Bj_bYC-g2jmg​​ 密码: xv44