SVM训练
简单来说,SVM就是用于区分不同的类型(车牌、非车牌)。SVM的训练数据既有
HOG特征
在训练时,我们将使用HOG来提取一张图片的特征。HOG全称为:Histogram of Oriented Gridients,方向梯度
直方图。是计算机视觉、模式识别领域很常用的一种描述图像局部纹理的特征。
先计算图片某一区域中不同方向上梯度的值,然后进行累积,得到的直方图就可以代表这块区域,也就是作为特 征,可以输入到分类器里面。 简单来说,车牌的边缘与内部文字组成的一组信息(在边缘和角点的梯度值是很大 的,边缘和角点包含了很多物体的形状信息),HOG就是抽取这些信息组成一个直方图。
xx.png--->HOG---> Mat(相当于图片数据矩阵)
正样本对应标签----> 1
负样本对应标签----> -1
每张图片放在一行,每行就是一个样本数据
XXXXXXXXXX
XXXXXXXXXX
XXXXXXXXXX
XXXXXXXXXX
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); } |
现在我们获得了
样本.png
同时与 samples 对应的有一个 labels 标签集合,记录这些特征数据所属于的标签。 我们准备样本时,将正样本标 签置为1,负样本为-1。因此现在 labels 中的数据为
下面我们将 samples 与 lables 包装为OpenCV中的训练数据集:
训练
我们的训练数据 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图像,某个像素点,
转换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