OpenCV(三)车牌识别SVM训练与hsv定位

SVM训练

简单来说,SVM就是用于区分不同的类型(车牌、非车牌)。SVM的训练数据既有特征又有标签,通过训练,让机器 可以自己找到特征和标签之间的联系,在面对只有特征没有标签的数据时,可以判断出标签。属于机器学习中的监 督学习。

HOG特征

在训练时,我们将使用HOG来提取一张图片的特征。HOG全称为:Histogram of Oriented Gridients,方向梯度
直方图。是计算机视觉、模式识别领域很常用的一种描述图像局部纹理的特征。
先计算图片某一区域中不同方向上梯度的值,然后进行累积,得到的直方图就可以代表这块区域,也就是作为特 征,可以输入到分类器里面。 简单来说,车牌的边缘与内部文字组成的一组信息(在边缘和角点的梯度值是很大 的,边缘和角点包含了很多物体的形状信息),HOG就是抽取这些信息组成一个直方图。

样本+标签,数据格式
xx.png--->HOG---> Mat(相当于图片数据矩阵)
正样本对应标签----> 1
负样本对应标签----> -1
每张图片放在一行,每行就是一个样本数据
XXXXXXXXXX
XXXXXXXXXX
XXXXXXXXXX
XXXXXXXXXX

在OpenCV中对图像使用HOG提取特征:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
 //1、灰度化,保存至gray
Mat gray;
cvtColor(plate, gray, COLOR_BGR2GRAY); //2、二值化,保存至shold
Mat shold;
threshold(gray, shold, 0, 255, THRESH_OTSU); //3、提取特征,保存至features
Mat features;
getHogFeatures(svmHog, shold, features);

-----------

//提取特征
void CarPlateRecgnize::getHogFeatures(HOGDescriptor *hog, Mat src, Mat &out) {
    //重新定义大小 缩放 提取特征的时候数据需要为  :CV_32S 有符号的32位数据
    Mat trainImg = Mat(hog->winSize, CV_32S);
    resize(src, trainImg, hog->winSize);
    //计算特征 获得float集合
    vector<float> d;
    hog->compute(trainImg, d, Size(8, 8));

    Mat features(d);
    //特征矩阵
    features.copyTo(out);
    features.release();
    trainImg.release();
}

训练数据

能够提取特征后,接下来就要进行SVM的训练了。opencv中的svm是使用的LIBSVM,LIBSVM是台湾大学林智仁
(Lin Chih-Jen)教授2001年开发的一套支持向量机的库。

深度学习就是调整参数的过程。我们使用的参数都是默认的,肯定不是最好的,但是基本稳定。不同参数效
果不同、训练检测速度不同,同时也影响着模型文件的大小。

我们将使用136x36规格的车牌图片作为正样本,任意136x36的图片作为负样本来进行训练。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct TrainStruct{
    string file;
    int label;
};
---------
    //样本集合
    vector<TrainStruct> svm_data;

----------

    //正样本 标签是:1
    for(auto file : pos_files){
        svm_data.push_back({file,1});
    }
    //负样本 标签为:-1
    vector<string> neg_files;
    getFiles(SVM_NEG, neg_files);
    for(auto file : neg_files){
        svm_data.push_back({file,-1});
    }

现在我们的所有样本都被打上标签记录在 svm_data 中。接下来我们对样本进行特征提取并记录。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Mat samples;
    vector<int> responses;
    for(auto data:svm_data){
        auto image = imread(data.file,IMREAD_GRAYSCALE);
        if (image.empty()) {
            printf("加载样本失败 image: %s.\n", data.file.c_str());
            continue;
        }
        //二值
        threshold(image, image, 0, 255, THRESH_BINARY+THRESH_OTSU);
        Mat feature;
        //获得hog特征
        getSvmHOGFeatures(image, feature);
        //调整为一行
        feature = feature.reshape(1, 1);
        // 图片数据:x x x
        // samples:x x x
        //         x x x
        //保存特征数据
        samples.push_back(feature);
        //记录对应的标签
        responses.push_back(data.label);
    }

现在我们获得了samples与 labels?两个数据。其中 samples 为样本特征数据,存储在矩阵中,每一行就是一个样 本的特征数据。加上我们正负样本各有三个,那么 samples 中记录的数据就为:宽:x,高为:3+3=6。看起来是 这样的:

样本.png

同时与 samples 对应的有一个 labels 标签集合,记录这些特征数据所属于的标签。 我们准备样本时,将正样本标 签置为1,负样本为-1。因此现在 labels 中的数据为 {1,1,1,-1,-1,-1}与 samples 相匹配。
下面我们将 samples 与 lables 包装为OpenCV中的训练数据集: TrainData

训练

我们的训练数据 TrainData 已经准备完成,接下来需要使用OpenCV提供的SVM进行训练。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//训练数据  行
    Ptr <TrainData> trainData = TrainData::create(samples, SampleTypes::ROW_SAMPLE, responses);
    printf("训练数据准备完成,开始训练!\n");
    //其他参数都是默认值 为了把
    //最后一个参数设置true 则会创建更平衡的验证子集
    //也就是如果是2类分类的话能得到更准确的结果
    classifier->trainAuto(trainData, 10, SVM::getDefaultGrid(SVM::C),
                          SVM::getDefaultGrid(SVM::GAMMA), SVM::getDefaultGrid(SVM::P),
                          SVM::getDefaultGrid(SVM::NU), SVM::getDefaultGrid(SVM::COEF),
                          SVM::getDefaultGrid(SVM::DEGREE), true);

    classifier->save( SVM_XML );

    printf("训练完成 ,模型保存: %s  \n", SVM_XML);

HSV 定位

我们已经使用了边缘定位,边缘定位是通过图像的各种处理,将车牌区域连接成一块整体从而进行轮廓查 找得到的候选车牌集合,但是边缘定位在某些情况下可能把背景也混合成一块整体。这样得到的车牌并不是最合适 的。所以我们再加入颜色定位同时进行筛选。以蓝色车牌举例,如果以BGR值来确定颜色,就算B为255,但是一 旦混合了G与R得到的结果可能并不是蓝色。以BGR来进行颜色的分辨并不容易。

而HSV(也可称为HSB)是根据颜色的直观特性创建的一种颜色空间,也称六角锥体模型。这个模型中颜色的参数分 别是:

  • 色度(Hue):用角度度量,取值范围为0°~360°,从红色开始按逆时针方向计算,红色为0°,绿色为120°, 蓝色为240°;
  • 饱和度(Saturation): 取值范围为0-1。降低S,饱和度越低,颜色越白;
  • 透明度 (Value/brightness ): 取值范围为0-1。降低V,亮度月底,颜色越黑 。

hsv.png

经过查询发现,H为:200°-280°范围,S为0.37-1之间,V也是0.37-1之间。这个范围可能并不是非常精准,但是足 够了。
而OpenCV中H被定义为:0-180的范围(缩小一半)。而S与V都为0-255的范围(乘以255)。
因此最终认为 使用OpenCV获得HSV图像,某个像素点,H在100-140S与V都在95-255之间则该像素点为蓝色。那么使用颜色 定位的流程为:

转换HSV流程.png

转换HSV

1
2
 Mat hsv;
cvtColor(src, hsv, COLOR_BGR2HSV);

颜色匹配

循环处理每个像素点,根据蓝色HSV取值范围,如果像素点为蓝色,则将其HSV数据置为:0,0,255(只保留亮 度);若像素点非蓝色,则置为0,0,0(纯黑色)。这样只有蓝色的区域被保留下来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
    //3通道
    int chanles = hsv.channels();
    //高
    int h = hsv.rows;
    //宽数据长度
    int w = hsv.cols * chanles;
    //判断数据是否为一行存储的
    //头一行的末尾在内存里和下一行的开头是相连的
    if (hsv.isContinuous()) {
        w *= h;
        h = 1;
    }
    for (size_t i = 0; i < h; i++)
    {
        //第i行的数据 一行 hsv的数据 uchar = java byte
        uchar* p = hsv.ptr<uchar>(i);
        //处理3个数据 所以+=3
        for (size_t j = 0; j < w; j+=3)
        {
            int h = int(p[j]);
            int s = int(p[j+1]);
            int v = int(p[j + 2]);
            //是否为蓝色像素点的标记
            bool blue = false;
            // h:蓝色就可以了
            //当V和S都达到最高值,也就是1时(opencv*255,0-255),颜色是最纯正的。
            // 降低S,颜色越发趋向于变白。降低V,颜色趋向于变黑,当V为0时,颜色变为黑色。
            if (h >= 100 && h <= 140 && s >= 95 && s <= 255 && v >= 95 && v <= 255) {
                blue = true;
            }
            //把蓝色像素 凸显出来 ,其他的区域全变成黑色
            if (blue) {
                p[j] = 0;
                p[j+1] = 0;
                p[j + 2] = 255;
            }
            else {
                //变成黑色
                p[j] = 0;
                p[j + 1] = 0;
                p[j + 2] = 0;
            }
        }
    }

此时,显示出来的图像为:

HSV结果.png

之所以会呈现红色,是因为 imshow 会将输出的Mat视为BGR数据进行显示。那么在我们的处理中蓝色被处理为: 0,0,255。如果看为BGR,则为R=255,所以为红色。

分离HSV

1
2
 vector<Mat> hsv_split;
split(hsv, hsv_split);

对上一步的结果进行分离,得到了三个元素(H、S、V)的向量集合 vector 。在这个集合中,我们直接取出第2个元 素,即为S饱和度数据矩阵。此矩阵Mat即为二值化的图像。

二值化.png

接下来的操作和边缘定位一致,对二值图像进行闭操作、查找轮廓、初步筛选最后旋转角度。即可获得候选车牌集 合。

后续处理

OpenCV(三)应用车牌识别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
    //闭操作
    Mat close;
    Mat element = getStructuringElement(MORPH_RECT, Size(17, 3));
    morphologyEx(shold, close, MORPH_CLOSE, element);
    imshow("close",close);
    waitKey();

    //6、查找轮廓
    //获得初步筛选车牌轮廓================================================================
    //轮廓检测
    vector< vector<Point> > contours;
    //查找轮廓 提取最外层的轮廓  将结果变成点序列放入 集合
    findContours(close, contours, RETR_EXTERNAL, CHAIN_APPROX_NONE);
    //遍历
    vector<RotatedRect> vec_color_roi;
    for (vector<Point> point : contours) {
        RotatedRect rotatedRect = minAreaRect(point);
        //rectangle(src, rotatedRect.boundingRect(), Scalar(255, 0, 255));
        //进行初步的筛选 把完全不符合的轮廓给排除掉 ( 比如:1x1,5x1000 )
        if (verifySizes(rotatedRect)) {
            vec_color_roi.push_back(rotatedRect);
        }
    }

    tortuosity(src, vec_color_roi, dst);

补充知识

机器学习算法-------> 核函数

交错数据

1
2
例子:
花生,石头,瓜子散落在桌子上,用里拍桌子,物体起落有高有低,就容易在空间维度划分区域

二维线性不可分------> 三维空间

SVM 向量机

上面的参数可以模拟运行

在线PS工具

跳变-----》黑色,白色变化(干扰物比较小)用来区分干扰,因为我们的数据是按照行来的 如GB888888