\(\quad\) 在车牌识别之前先利用HOG特征提取和支持向量机SVM线性分类器对汉字模型和数字字母进行训练,将训练完成后的xml文件导入到车牌识别程序中,对车牌进行识别。核心思想:获取一张图片后会将图片特征写入到容器中,紧接着会将标签写入另一个容器中,这样就保证了特征和标签是一一对应的关系。先从二值车牌的文件夹中读取图片训练数据集,依次循环遍历每个文件夹中的图片,对图像进行灰度变换并二值化,并将每个图片统一尺寸为16×32的大小,便于训练。循环读取每张图片,依次放在input_images内,进行HOG特征提取,并将提取结果序列化向量依次存入。
\(\quad\) 创建SVM模型:创建分类器并设置参数,C_SVC用于分类,C_SVR用于回归,LINEAR线性核函数。SIGMOID为高斯核函数,核函数中的参数degree,针对多项式核函数,核函数中的参数gamma,针对多项式/RBF/SIGMOID核函数,核函数中的coef0参数,针对多项式/SIGMOID核函数,SVM最优问题参数,设置C-SVC,EPS_SVR、NU_SVR、NU_SVC, ONE_CLASS 、NU_SVR的参数,并且设置EPS_SVR 中损失函数p的值,结束条件为训练1000次或者误差小于0.01结束,训练数据和标签的结合进行训练分类器,将训练好的模型进行保存,最后用二值车牌的具体图片进行测试验证,汉字模型和数字字母的训练算法都是遵循上述思路。
\(\quad\) 训练好之后将xml文件导入到车牌识别的程序中进行车牌识别,车牌识别的具体算法步骤为:先进行车牌图片的读取,本实验为了方便图片的读取,设置了一个滑动组件,用户可以拖动滑动组件进行车牌图像的读取。读取完图像之后,重新调整图像大小为900×900,将RGB图像转为HSV图像,进行二值化处理,并自定义矩形元素进行开、闭运算。因为大部分车牌都是蓝色车牌,因此我在实验中设置了h_lower = 100;s_lower = 52;v_lower = 150;h_upper = 120;s_upper = 255;v_upper = 255; 之后设置亮度阈值为160,并循环进行减10衰减,直到找到车牌或者亮度v_lower过低,对查找到的区域进行轮廓检测,利用minAreaRect找出最小外界矩形,即为符合车牌形状的矩形,标记下来,再根据车牌长宽特征进行筛选。
\(\quad\) 由于图像的拍摄角度等因素的干扰,可能使得图片不是标准正方向而出现倾斜,变形,因此我对图像进行透视变换矫正变形图像,调整顺序:左上-右上-右下-左下,最终获得ROI图像,利用边缘检测和模型匹配筛出,最有可能是车牌的图,先将ROI图像灰度化,进行Canny边缘检测,再进行阈值分割,自定义矩形元素对齐进行闭运算,利用findcontours寻找轮廓,找外接矩形,对车牌ROI上下分割,利用投影法进行x方向的投影切除字母上下多余的部分,通过车牌在y方向的映射分离分割每个字母。
\(\quad\) 之后进行七个字符的分割,首先用findcontours进行轮廓检测获取轮廓在图像中的矩形坐标,根据轮廓坐标使用方框标记出来,将存放7个字符的矩形图片,依次利用xml模型文件排列进行汉字和数字字母的识别,并在源图中用putText显示车牌号码,并在终端返回识别结果及所用时间。下图分别为二值车牌的汉字及数字字母的模板图.
#include <stdio.h>
#include <time.h>
#include <opencv2/opencv.hpp>
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/ml/ml.hpp>
#include <io.h> //查找文件相关函数
#include<opencv2/imgproc/types_c.h>
/*选择是否重新训练*/
#define TRAIN true
using namespace std;
using namespace cv;
using namespace ml;
/*全局变量*/
void GetAllFiles(string path, vector<string>& files);
ostringstream oss;
int num = -1;
Mat src;
Mat yangben_gray;
Mat yangben_thresh;
int main()
{
//核心思路://获取一张图片后会将图片特征写入到容器中,
//紧接着会将标签写入另一个容器中,这样就保证了特征
// 和标签是一一对应的关系。
////===============================读取训练数据===============================////
#if TRAIN
//训练数据,每一行一个训练图片
Mat trainingData;
//训练样本标签
Mat labels;
//最终的训练样本标签
Mat clas;
//最终的训练数据
Mat traindata;
/*文件夹下的文件夹名*/
vector<string>files_name;
string files_path = "../../二值车牌/汉字/";
GetAllFiles(files_path, files_name);
/********************获得汉字文件夹下的所有文件夹的名称***********************/
for (int num = 0; num < files_name.size(); num++)//依次提取0到9文件夹中的图片 减去汉字这个文件夹 加上中间少的两个字母
{
oss << "../../二值车牌/汉字/";
int label = num; //省份标签0~28
oss << files_name[num] << "/*.png";//图片名字后缀,oss可以结合数字与字符串
string pattern = oss.str();//oss.str()输出oss字符串,并且赋给pattern
oss.str("");//每次循环后把oss字符串清空
vector<String> input_images_name;
glob(pattern, input_images_name, false);
//为false时,仅仅遍历指定文件夹内符合模式的文件,当为true时,会同时遍历指定文件夹的子文件夹
//此时input_images_name存放符合条件的图片地址
//文件夹下总共有几个图片
int all_num = input_images_name.size();
for (int i = 0; i < all_num; i++)//依次循环遍历每个文件夹中的图片
{
/********HOG特征提取*****/
/*读取图像:
大小:size(32,64)
*/
Mat src = imread(input_images_name[i]);
Mat gray;
cvtColor(src, gray,CV_BGR2GRAY);
HOGDescriptor detector(Size(16,32),Size(16,16),Size(8,8),Size(8,8),9); //向量大小:((36-8)/4+1)*((64-8)/4+1)*2*2*9=4320;
/*临时存放每张图临时生成的一维向量*/
vector<float>descriptors;
detector.compute(gray,descriptors,Size(0,0),Size(0,0));
trainingData.push_back(static_cast<Mat>(descriptors).reshape(1,1));//序列化后的图片依次存入 放在下一个row
labels.push_back(label);//把每个图片对应的标签依次存入 放在下一个row
}
}
//图片数据和标签转变下
trainingData.copyTo(traindata);//复制
traindata.convertTo(traindata, CV_32FC1);//更改图片数据的类型,必要,不然会出错
labels.copyTo(clas);//复制
////===============================创建SVM模型===============================////
// 创建分类器并设置参数
Ptr<SVM> SVM_params = SVM::create();
SVM_params->setType(SVM::C_SVC);//C_SVC用于分类,C_SVR用于回归
SVM_params->setKernel(SVM::LINEAR); //LINEAR线性核函数。SIGMOID为高斯核函数
SVM_params->setDegree(0);//核函数中的参数degree,针对多项式核函数;
SVM_params->setGamma(1);//核函数中的参数gamma,针对多项式/RBF/SIGMOID核函数;
SVM_params->setCoef0(0);//核函数中的参数,针对多项式/SIGMOID核函数;
SVM_params->setC(1);//SVM最优问题参数,设置C-SVC,EPS_SVR和NU_SVR的参数;
SVM_params->setNu(0);//SVM最优问题参数,设置NU_SVC, ONE_CLASS 和NU_SVR的参数;
SVM_params->setP(0);//SVM最优问题参数,设置EPS_SVR 中损失函数p的值.
//结束条件,即训练1000次或者误差小于0.01结束
SVM_params->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER + TermCriteria::EPS, 1000, 0.01));
//训练数据和标签的结合
Ptr<TrainData> tData = TrainData::create(traindata, ROW_SAMPLE, clas);
// 训练分类器
SVM_params->train(tData);//训练
//保存模型
SVM_params->save("HOG字svm.xml");
cout << "训练好了!!!" << endl;
#else
Ptr<SVM> SVM_params = SVM::load("HOG字svm.xml");
#endif
////===============================预测部分===============================////
Mat src = imread("../../二值车牌/汉字/guizhou/21.png");
Mat Gray;
cvtColor(src, Gray, CV_BGR2GRAY);
imshow("原图像", src);
Mat input;
HOGDescriptor testDetector(Size(16, 32), Size(16, 16), Size(8, 8), Size(8, 8), 9);
vector<float>testDescriptor;
testDetector.compute(Gray,testDescriptor,Size(0,0),Size(0,0));
input.push_back(static_cast<Mat>(testDescriptor).reshape(1,1));
float r = SVM_params->predict(input); //对所有行进行预测
cout<<r<<endl;
waitKey(0);
return 0;
}
/*获得文件夹下的文件夹名称*/
void GetAllFiles(string path, vector<string>& files)
{
long long hFile = 0;
//文件信息
struct _finddata_t fileinfo;//用来存储文件信息的结构体
string p;
if ((hFile = _findfirst(p.assign(path).append("/*").c_str(), &fileinfo)) != -1) //第一次查找
{
do
{
if ((fileinfo.attrib & _A_SUBDIR)) //如果查找到的是文件夹
{
if (strcmp(fileinfo.name, ".") != 0 && strcmp(fileinfo.name, "..") != 0) //进入文件夹查找
{
files.push_back(p.assign(fileinfo.name));
}
}
else //如果查找到的不是是文件夹
{
//files.push_back(p.assign(fileinfo.name)); //将文件路径保存,也可以只保存文件名: p.assign(path).append("\\").append(fileinfo.name)
}
} while (_findnext(hFile, &fileinfo) == 0);
_findclose(hFile); //结束查找
}
}
此时生成一个xml的模型
#include <stdio.h>
#include <time.h>
#include <opencv2/opencv.hpp>
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/ml/ml.hpp>
#include <io.h> //查找文件相关函数
#include<opencv2/imgproc/types_c.h>
using namespace std;
using namespace cv;
using namespace ml;
#define PATH "../../二值车牌/"
#define TRIAN 1 //0不用训练
/*全局变量*/
void GetAllFiles(string path, vector<string>& files);
ostringstream oss;
int num = -1;
Mat dealimage;
Mat src;
Mat yangben_gray;
Mat yangben_thresh;
int main()
{
//核心思路://获取一张图片后会将图片特征写入到容器中,
//紧接着会将标签写入另一个容器中,这样就保证了特征
// 和标签是一一对应的关系。
////===============================读取训练数据===============================////
//训练数据,每一行一个训练图片
Mat trainingData;
//训练样本标签
Mat labels;
//最终的训练样本标签
Mat clas;
//最终的训练数据
Mat traindata;
/*文件夹下的文件夹名*/
vector<string>files_name;
string files_path = PATH;
GetAllFiles(files_path, files_name);
//////////////////////从指定文件夹下提取图片//////////////////
#if TRIAN
for (int num = 0; num < (files_name.size()-1+2); num++)//依次提取0到9文件夹中的图片 减去汉字这个文件夹 加上中间少的两个字母
{
oss << files_path;
int label = num;
if (num >= 10) {
label = num + 65 - 10; //以字母对应的十进制数字作为标签
char Num = num + 65 - 10;
if (Num == ‘I‘ || Num == ‘O‘) { //车牌没有这两个字母
oss.str("");
continue;
}
oss << Num << "/*.png";//图片名字后缀,oss可以结合数字与字符串
}
else {
oss << num << "/*.png";//图片名字后缀,oss可以结合数字与字符串
}
string pattern = oss.str();//oss.str()输出oss字符串,并且赋给pattern
oss.str("");//每次循环后把oss字符串清空
vector<String> input_images_name;
glob(pattern, input_images_name, false);
//为false时,仅仅遍历指定文件夹内符合模式的文件,当为true时,会同时遍历指定文件夹的子文件夹
//此时input_images_name存放符合条件的图片地址
//文件下总共有几个图片
int all_num = input_images_name.size();
for (int i = 0; i < all_num; i++)//依次循环遍历每个文件夹中的图片
{
cvtColor(imread(input_images_name[i]), yangben_gray, COLOR_BGR2GRAY);//灰度变换
threshold(yangben_gray, yangben_thresh, 0, 255, THRESH_OTSU);//二值化
resize(yangben_thresh, yangben_thresh, Size(16, 32), 0, 0);
//循环读取每张图片并且依次放在vector<Mat> input_images内
dealimage = yangben_thresh;
//特征提取的方式有很多,比如LBP,HOG等等
//我们利用reshape()函数完成特征提取,
//eshape(1, 1)的结果就是原图像对应的矩阵将被拉伸成一个一行的向量,作为特征向量。
/*HOG特征提取*/
HOGDescriptor detector(Size(16, 32), Size(16, 16), Size(8, 8), Size(8, 8), 9); //向量大小:((16-16)/8+1)*((32-16)/8+1)*2*2*9=108;
/*临时存放每张图临时生成的一维向量*/
vector<float>descriptors;
detector.compute(dealimage, descriptors, Size(0, 0), Size(0, 0));
trainingData.push_back(static_cast<Mat>(descriptors).reshape(1, 1));//序列化后的图片依次存入 放在下一个row
labels.push_back(label);
}
}
//图片数据和标签转变下
trainingData.copyTo(traindata);//复制
//traindata.convertTo(traindata, CV_32FC1);//更改图片数据的类型,必要,不然会出错
labels.copyTo(clas);//复制
////===============================创建SVM模型===============================////
// 创建分类器并设置参数
Ptr<SVM> SVM_params = SVM::create();
SVM_params->setType(SVM::C_SVC);//C_SVC用于分类,C_SVR用于回归
SVM_params->setKernel(SVM::LINEAR); //LINEAR线性核函数。SIGMOID为高斯核函数
SVM_params->setDegree(0);//核函数中的参数degree,针对多项式核函数;
SVM_params->setGamma(1);//核函数中的参数gamma,针对多项式/RBF/SIGMOID核函数;
SVM_params->setCoef0(0);//核函数中的参数,针对多项式/SIGMOID核函数;
SVM_params->setC(1);//SVM最优问题参数,设置C-SVC,EPS_SVR和NU_SVR的参数;
SVM_params->setNu(0);//SVM最优问题参数,设置NU_SVC, ONE_CLASS 和NU_SVR的参数;
SVM_params->setP(0);//SVM最优问题参数,设置EPS_SVR 中损失函数p的值.
//结束条件,即训练1000次或者误差小于0.01结束
SVM_params->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER + TermCriteria::EPS, 10000, 0.001));
//训练数据和标签的结合
Ptr<TrainData> tData = TrainData::create(traindata, ROW_SAMPLE, clas);
// 训练分类器
SVM_params->train(tData);//训练
//保存模型
SVM_params->save("HOG数字字母svm.xml");
cout << "训练好了!!!" << endl;
#else
////===============================预测部分===============================////
Ptr<SVM> SVM_params = SVM::load("HOG数字字母svm.xml");
#endif
Mat src = imread("../../二值车牌/7/50.png");
Mat gray;
cvtColor(src, gray, COLOR_BGR2GRAY);
threshold(gray, gray, 0, 255, CV_THRESH_OTSU);
resize(gray, gray, Size(16, 32), 0, 0);
imshow("原图像", gray);
Mat input;
HOGDescriptor Detector(Size(16, 32), Size(16, 16), Size(8, 8), Size(8, 8), 9); //向量大小:((16-16)/8+1)*((32-16)/8+1)*2*2*9=108;
/*临时存放每张图临时生成的一维向量*/
vector<float>Descriptors;
Detector.compute(gray, Descriptors, Size(0, 0), Size(0, 0));
input.push_back(static_cast<Mat>(Descriptors).reshape(1, 1));//序列化后的图片依次存入 放在下一个row
//input.convertTo(input, CV_32FC1);//更改图片数据的类型,必要,不然会出错
float r = SVM_params->predict(input); //对所有行进行预测
if (r>9) {
cout << (char)r << endl;
}
else {
cout << r << endl;
}
waitKey(0);
return 0;
}
/*获得文件夹下的文件夹名称*/
void GetAllFiles(string path, vector<string>& files)
{
long long hFile = 0;
//文件信息
struct _finddata_t fileinfo;//用来存储文件信息的结构体
string p;
if ((hFile = _findfirst(p.assign(path).append("/*").c_str(), &fileinfo)) != -1) //第一次查找
{
do
{
if ((fileinfo.attrib & _A_SUBDIR)) //如果查找到的是文件夹
{
if (strcmp(fileinfo.name, ".") != 0 && strcmp(fileinfo.name, "..") != 0) //进入文件夹查找
{
files.push_back(p.assign(fileinfo.name));
}
}
else //如果查找到的不是是文件夹
{
//files.push_back(p.assign(fileinfo.name)); //将文件路径保存,也可以只保存文件名: p.assign(path).append("\\").append(fileinfo.name)
}
} while (_findnext(hFile, &fileinfo) == 0);
_findclose(hFile); //结束查找
}
}
此时生成一个xml的模型
训练完成后的两个xml文件放在程序文件夹中
#pragma once
#ifndef PUTTEXT_H_
#define PUTTEXT_H_
#include <windows.h>
#include <string>
#include <opencv2/opencv.hpp>
using namespace cv;
void GetStringSize(HDC hDC, const char* str, int* w, int* h);
void putTextZH(Mat& dst, const char* str, Point org, Scalar color, int fontSize,
const char* fn = "Arial", bool italic = false, bool underline = false);
#endif // PUTTEXT_H_
#include "putText.h"
#pragma comment(lib,"ws2_32.lib")
void GetStringSize(HDC hDC, const char* str, int* w, int* h)
{
SIZE size;
GetTextExtentPoint32A(hDC, str, strlen(str), &size);
if (w != 0) *w = size.cx;
if (h != 0) *h = size.cy;
}
void putTextZH(Mat& dst, const char* str, Point org, Scalar color, int fontSize, const char* fn, bool italic, bool underline)
{
CV_Assert(dst.data != 0 && (dst.channels() == 1 || dst.channels() == 3));
int x, y, r, b;
if (org.x > dst.cols || org.y > dst.rows) return;
x = org.x < 0 ? -org.x : 0;
y = org.y < 0 ? -org.y : 0;
LOGFONTA lf;
lf.lfHeight = -fontSize;
lf.lfWidth = 0;
lf.lfEscapement = 0;
lf.lfOrientation = 0;
lf.lfWeight = 5;
lf.lfItalic = italic; //斜体
lf.lfUnderline = underline; //下划线
lf.lfStrikeOut = 0;
lf.lfCharSet = DEFAULT_CHARSET;
lf.lfOutPrecision = 0;
lf.lfClipPrecision = 0;
lf.lfQuality = PROOF_QUALITY;
lf.lfPitchAndFamily = 0;
strcpy_s(lf.lfFaceName, fn);
HFONT hf = CreateFontIndirectA(&lf);
HDC hDC = CreateCompatibleDC(0);
HFONT hOldFont = (HFONT)SelectObject(hDC, hf);
int strBaseW = 0, strBaseH = 0;
int singleRow = 0;
char buf[1 << 12];
strcpy_s(buf, str);
char* bufT[1 << 12]; // 这个用于分隔字符串后剩余的字符,可能会超出。
//处理多行
{
int nnh = 0;
int cw, ch;
const char* ln = strtok_s(buf, "\n", bufT);
while (ln != 0)
{
GetStringSize(hDC, ln, &cw, &ch);
strBaseW = max(strBaseW, cw);
strBaseH = max(strBaseH, ch);
ln = strtok_s(0, "\n", bufT);
nnh++;
}
singleRow = strBaseH;
strBaseH *= nnh;
}
if (org.x + strBaseW < 0 || org.y + strBaseH < 0)
{
SelectObject(hDC, hOldFont);
DeleteObject(hf);
DeleteObject(hDC);
return;
}
r = org.x + strBaseW > dst.cols ? dst.cols - org.x - 1 : strBaseW - 1;
b = org.y + strBaseH > dst.rows ? dst.rows - org.y - 1 : strBaseH - 1;
org.x = org.x < 0 ? 0 : org.x;
org.y = org.y < 0 ? 0 : org.y;
BITMAPINFO bmp = { 0 };
BITMAPINFOHEADER& bih = bmp.bmiHeader;
int strDrawLineStep = strBaseW * 3 % 4 == 0 ? strBaseW * 3 : (strBaseW * 3 + 4 - ((strBaseW * 3) % 4));
bih.biSize = sizeof(BITMAPINFOHEADER);
bih.biWidth = strBaseW;
bih.biHeight = strBaseH;
bih.biPlanes = 1;
bih.biBitCount = 24;
bih.biCompression = BI_RGB;
bih.biSizeImage = strBaseH * strDrawLineStep;
bih.biClrUsed = 0;
bih.biClrImportant = 0;
void* pDibData = 0;
HBITMAP hBmp = CreateDIBSection(hDC, &bmp, DIB_RGB_COLORS, &pDibData, 0, 0);
CV_Assert(pDibData != 0);
HBITMAP hOldBmp = (HBITMAP)SelectObject(hDC, hBmp);
//color.val[2], color.val[1], color.val[0]
SetTextColor(hDC, RGB(255, 255, 255));
SetBkColor(hDC, 0);
//SetStretchBltMode(hDC, COLORONCOLOR);
strcpy_s(buf, str);
const char* ln = strtok_s(buf, "\n", bufT);
int outTextY = 0;
while (ln != 0)
{
TextOutA(hDC, 0, outTextY, ln, strlen(ln));
outTextY += singleRow;
ln = strtok_s(0, "\n", bufT);
}
uchar* dstData = (uchar*)dst.data;
int dstStep = dst.step / sizeof(dstData[0]);
unsigned char* pImg = (unsigned char*)dst.data + org.x * dst.channels() + org.y * dstStep;
unsigned char* pStr = (unsigned char*)pDibData + x * 3;
for (int tty = y; tty <= b; ++tty)
{
unsigned char* subImg = pImg + (tty - y) * dstStep;
unsigned char* subStr = pStr + (strBaseH - tty - 1) * strDrawLineStep;
for (int ttx = x; ttx <= r; ++ttx)
{
for (int n = 0; n < dst.channels(); ++n) {
double vtxt = subStr[n] / 255.0;
int cvv = vtxt * color.val[n] + (1 - vtxt) * subImg[n];
subImg[n] = cvv > 255 ? 255 : (cvv < 0 ? 0 : cvv);
}
subStr += 3;
subImg += dst.channels();
}
}
SelectObject(hDC, hOldBmp);
SelectObject(hDC, hOldFont);
DeleteObject(hf);
DeleteObject(hBmp);
DeleteDC(hDC);
}
/*
程序思路:
1.颜色定位:通过改变亮度,从而得到不同亮度阈值下的颜色区域矩形框;
2.矩形框处理:先进行初步的筛选,再通过透视透视变换得到颜色矩形的ROI图像
3.对ROI图像逐一处理:通过白色像素在y方向的投影,由于字母都在同一水平高度,字母所投影到y方向的值就比较大
再根据阈值进行上下切割;
4.再对处理后的ROI对7个字符的提取:进行边缘检测,再外接矩形,如果外接矩形的数目少于7个,则一定不是车牌的ROI,
舍弃处理下一个ROI。大于则按照7字符的特点进行提取
5.对7个字符进行匹配:HOG+SVM,先用HOG进行特征提取,再加载训练好的模型进行匹配。
注:文件putText.h和putText.cpp仅仅是为了能在图片中能够显示汉字而加上去的
*/
#include<opencv2/opencv.hpp>
#include<sstream> //文件操作
#include<iostream>
#include<fstream>
#include<cmath>
#include "putText.h"
#include <time.h>
#include<opencv2/imgproc/types_c.h>
using namespace std;
using namespace cv;
using namespace ml;
/**************全局变量声明****************/
void on_bgrThresh(int, void*);//回调函数
int index = 0; //将在第几张图片
int indexMax; //catImage文件夹下的jpg文件张数
vector<Mat>src; //加载进来的图像集
/*蓝色颜色值变量*/
int h_lower = 100;
int s_lower = 52;
int v_lower = 150;
int h_upper = 120;
int s_upper = 255;
int v_upper = 255;
/*源图*/
Mat srcImg; //源图
/*读取图片*/
void ReadImage(vector<Mat>& im);
/*将七组字母返回到前七个*/
void selectRect(vector<Rect>rt, vector<Rect>& numRect);
/*输入标签,返回省*/
string Province_Show(int label);
int main()
{
ReadImage(src);
indexMax = src.size() - 1;
namedWindow("图片序号");
resizeWindow("图片序号", Size(600, 100));
createTrackbar("index", "图片序号", &index, indexMax, on_bgrThresh);
on_bgrThresh(0, 0);
waitKey(0);
return 0;
}
/*读取图像*/
void ReadImage(vector<Mat>& im) {
vector<String>path_name;
string path = "catImage/*";
glob(path, path_name, false);
for (int i = 0; i < path_name.size(); i++) {
im.push_back(imread(path_name[i]));
}
}
void on_bgrThresh(int, void*) {
/*计算识别一张图片的时间*/
clock_t start, finish;
double totaltime;
start = clock();
int catch_success = 0;//判别车牌识别是否成功 1成功 0失败
srcImg = src[index].clone(); //得到要处理的源图
if (srcImg.empty()) {
cout << "srcImg is empty...." << endl;
}
if (srcImg.rows > 900 || srcImg.cols > 900) { //重新调整图像的大小
int wide = max(srcImg.rows, srcImg.cols);
resize(srcImg, srcImg, Size(), (double)900 / wide, (double)900 / wide);
}
v_lower = 160; //设置亮度阈值,并循环衰减,直到找到车牌或者亮度v_lower过低
while (v_lower >= 30 && catch_success == 0) {
v_lower -= 10;
/*转灰度值*/
/***************1.hsv颜色定位***************/
Mat hsv_srcImg;
Mat bin_HsvImg;
/*转HSV图像*/
cvtColor(srcImg, hsv_srcImg, CV_BGR2HSV);
/*将符合hsv颜色区域的边为白色,其他边黑色*/
inRange(hsv_srcImg, Scalar(h_lower, s_lower, v_lower), Scalar(h_upper, s_upper, v_upper), bin_HsvImg);
//inRange可实现二值化功能,可以同时针对多通道进行操作
/*开运算-闭运算*/
Mat kernel1 = getStructuringElement(MORPH_RECT, Size(5, 5));//自定义矩形元素
Mat kernel2 = getStructuringElement(MORPH_RECT, Size(5, 5));
morphologyEx(bin_HsvImg, bin_HsvImg, MORPH_CLOSE, kernel1);
morphologyEx(bin_HsvImg, bin_HsvImg, MORPH_OPEN, kernel2);
/*轮廓检测*/
vector<vector<Point>>ColorContours;
findContours(bin_HsvImg, ColorContours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
/*最小外接矩形--符合车牌形状的矩形*/
vector<RotatedRect>ColorRect;
vector<int>carLabel; /*用来标签车牌矩形*/
for (int i = 0; i < ColorContours.size(); i++) {
RotatedRect rt;
rt = minAreaRect(ColorContours[i]);
double wide, hiegh;
wide = max(rt.size.width, rt.size.height);
hiegh = min(rt.size.width, rt.size.height);
/*符合车牌特征的筛选*/
if (wide * hiegh > 300 && wide * hiegh < srcImg.rows * srcImg.cols / 4 && wide / hiegh > 2 && wide / hiegh < 6) {
ColorRect.push_back(rt);
}
}
#ifdef SHOW
/*画矩形*/
/*寻找最外轮廓*/
Mat showImg;
showImg = srcImg.clone();
for (int i = 0; i < ColorRect.size(); i++) {
Point2f rect_point[4];
ColorRect[i].points(rect_point);
for (int j = 0; j < 4; j++) {
line(showImg, rect_point[j], rect_point[(j + 1) % 4], Scalar(0, 255, 0), 2);
}
}
imshow("颜色轮廓", bin_HsvImg);
imshow("源图", showImg);
waitKey(0);
#endif // SHOW
/*透视变换*/
vector<Mat>ColorROI(ColorRect.size());//通过矩形大小初步筛选的图
for (int i = 0; i < ColorRect.size(); i++) {
Point2f src_vertices[4]; //存放4个有顺序的点
Point2f midpts; //调整点的中间变量
ColorRect[i].points(src_vertices);
/*调整顺序*/
/*顺序---左上-右上-右下-左下*/
for (int k = 0; k < 4; k++) { //先按y值从上到下排列
for (int l = k; l < 4; l++) {
if (src_vertices[k].y >= src_vertices[l].y) {
midpts = src_vertices[k];
src_vertices[k] = src_vertices[l];
src_vertices[l] = midpts;
}
}
}
/*判断最上面那两个点是不是最长边*/
if (pow(abs(src_vertices[0].x - src_vertices[1].x), 2) < pow(abs(src_vertices[0].x - src_vertices[2].x), 2)) {
midpts = src_vertices[1];
src_vertices[1] = src_vertices[2];
src_vertices[2] = midpts;
}
if (src_vertices[0].x > src_vertices[1].x) {
midpts = src_vertices[1];
src_vertices[1] = src_vertices[0];
src_vertices[0] = midpts;
}
if (src_vertices[2].x < src_vertices[3].x) {
midpts = src_vertices[2];
src_vertices[2] = src_vertices[3];
src_vertices[3] = midpts;
}
Point2f dst_vertices[4];
double carWidth = max(ColorRect[i].size.width, ColorRect[i].size.height);
double carHeigh = min(ColorRect[i].size.width, ColorRect[i].size.height);
dst_vertices[0] = Point(0, 0);
dst_vertices[1] = Point(carWidth, 0);
dst_vertices[2] = Point(carWidth, carHeigh);
dst_vertices[3] = Point(0, carHeigh);
Mat H = getPerspectiveTransform(src_vertices, dst_vertices);
ColorROI[i] = Mat(Size(carWidth, carHeigh), CV_8UC3);
warpPerspective(srcImg, ColorROI[i], H, ColorROI[i].size(), 1); //是直接在源图上得到ROI
}
vector<Mat>new_colorROI; //通过边缘检测和模型匹配筛出,最有可能是车牌的图
/*寻找颜色矩形里面最有可能的车牌*/
for (int i = 0; i < ColorROI.size(); i++) {
Mat gray;
cvtColor(ColorROI[i], gray, CV_BGR2GRAY);
/****************基于边缘匹配************************/
Mat edegImg;
Canny(gray, edegImg, 100, 150, 3);
threshold(edegImg, edegImg, 0, 255, THRESH_BINARY | THRESH_OTSU);
/*闭操作*/
Mat element = getStructuringElement(MORPH_RECT, Size(ColorROI[i].cols / 6, 1));
morphologyEx(edegImg, edegImg, MORPH_CLOSE, element, Point(-1, -1));
/*寻找轮廓,找外接矩形*/
vector<vector<Point>>edgeContours;
findContours(edegImg, edgeContours, RETR_EXTERNAL, CHAIN_APPROX_NONE);
for (int j = 0; j < edgeContours.size(); j++) {
Rect rt;
rt = boundingRect(edgeContours[j]);
if (rt.width > ColorROI[i].cols * 0.8 && rt.height > ColorROI[i].rows * 0.8) {
new_colorROI.push_back(ColorROI[i]);
carLabel.push_back(i);
break;
}
}
}
if (new_colorROI.size() == 0) {
continue;
}
/***********************对车牌ROI上下切割****************/
for (int num = 0; num < new_colorROI.size(); num++) {
/*统一大小*/
int ratio = 6000 * new_colorROI[num].cols / 126; //用来筛选字母上下限的值 //4000
Mat gray_carImg, bin_carImg; //Size(126,40)
cvtColor(new_colorROI[num], gray_carImg, CV_BGR2GRAY);
threshold(gray_carImg, bin_carImg, 0, 255, THRESH_OTSU);
/********************进行x方向的投影切除字母上下多余的部分*****************************/
Mat RowSum;
reduce(bin_carImg, RowSum, 1, CV_REDUCE_SUM, CV_32SC1);
Point cutNumY; //Y方向的切割点
int sign = 0;
int Lenth = 0;
for (int j = 0; j < RowSum.rows; j++) {
if (RowSum.ptr<int>(0)[0] > ratio && sign == 0) {
while (RowSum.ptr<int>(Lenth)[0] >= ratio) {
Lenth++;
if (Lenth == RowSum.rows - 1) {
break;
}
}
if (Lenth > RowSum.rows * 0.6) { //以字母开始
cutNumY = Point(0, Lenth);
break;
}
else {
sign = 1;//开始分割字
j = Lenth;
Lenth = 0;
}
}
else {
sign = 1;
}
if (RowSum.ptr<int>(j)[0] > ratio && sign == 1) {
Lenth++;
if (j == RowSum.rows - 1) {
if (Lenth > RowSum.rows * 0.6) {
cutNumY = Point(j - Lenth, j);
}
else {
cutNumY = Point(RowSum.rows * 0.15, RowSum.rows * 0.85);
}
}
}
else if (RowSum.ptr<int>(j)[0] <= ratio && sign == 1) {
if (Lenth > RowSum.rows * 0.6) {
cutNumY = Point(j - Lenth, j);
break;
}
else if (j == RowSum.rows - 1) {
cutNumY = Point(RowSum.rows * 0.15, RowSum.rows * 0.85);
}
else {
Lenth = 0;
}
}
}
/*通过车牌在y方向的映射分离分割每个字母*/
Mat Newbin_carImg; //字母上下被切割后的图像
Newbin_carImg = bin_carImg(Rect(Point(0, cutNumY.x), Point(bin_carImg.cols, cutNumY.y)));
#ifdef SHOW
imshow("上下切割后", Newbin_carImg);
waitKey(0);
#endif // SHOW
/**********************七个字符分割*******************/
/*轮廓检测进行分割*/
Mat midbin_carImg = Mat::zeros(Size(Newbin_carImg.cols + 4, Newbin_carImg.rows + 4), CV_8UC1); //宽增加4个像素点
for (int row = 2; row < midbin_carImg.rows - 2; row++) {
for (int col = 2; col < midbin_carImg.cols - 2; col++) {
midbin_carImg.ptr<uchar>(row)[col] = Newbin_carImg.ptr<uchar>(row - 2)[col - 2];
}
}
vector<vector<Point>>Num_contours;
findContours(midbin_carImg, Num_contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
/*最小外接矩形*/
vector<Rect>num_rect;
for (int k = 0; k < Num_contours.size(); k++) {
num_rect.push_back(boundingRect(Num_contours[k]));
}
#ifdef SHOW
Mat img;
img = midbin_carImg.clone();
for (int i = 0; i < num_rect.size(); i++) {
rectangle(img, num_rect[i], Scalar(255), -1);
}
imshow("字母矩形", img);
waitKey(0);
#endif // SHOW
/*矩形框的处理*/
vector<Rect>real_numRect;//存放七个字母的矩形
if (num_rect.size() < 7) {
continue;
}
selectRect(num_rect, real_numRect);
if (real_numRect.size() >= 7) {
cout << "7个字母提取成功..." << endl;
}
else {
continue;
}
/*防止第七个越界*/
if (real_numRect[6].x + real_numRect[6].width > midbin_carImg.cols) {
real_numRect[6].width = real_numRect[6].x + real_numRect[6].width - midbin_carImg.cols;
}
/*车牌从左到右字符图像*/
Mat NumImg[7];
for (int i = 0; i < 7; i++) {
NumImg[i] = midbin_carImg(Rect(real_numRect[i].x, real_numRect[i].y, real_numRect[i].width, real_numRect[i].height));
}
#ifdef SHOW
for (int i = 0; i < 7; i++) {
imshow("字母", NumImg[i]);
waitKey(0);
}
#endif // SHOW
ostringstream carnumber;
string charater;
/*汉字*/
Ptr<SVM> SVM_paramsH = SVM::load("HOG字svm.xml");
Mat Input;
Mat bin_charater;
resize(NumImg[0], bin_charater, Size(16, 32), 0, 0);
HOGDescriptor testDetector(Size(16, 32), Size(16, 16), Size(8, 8), Size(8, 8), 9);
vector<float>testDescriptor;
testDetector.compute(bin_charater, testDescriptor, Size(0, 0), Size(0, 0));
Input.push_back(static_cast<Mat>(testDescriptor).reshape(1, 1));
int r = SVM_paramsH->predict(Input); //对所有行进行预测
charater = Province_Show(r);
cout << "识别结果:" << Province_Show(r) << endl;
/*数字字母识别*/
Ptr<SVM> SVM_paramsZ = SVM::load("HOG数字字母svm.xml");
for (int i = 1; i < 7; i++) {
Mat bin_num = Mat::zeros(Size(16, 32), CV_8UC1);
Mat midBin_num;
resize(NumImg[i], bin_num, Size(16, 32), 0, 0);
Mat input;
HOGDescriptor Detector(Size(16, 32), Size(16, 16), Size(8, 8), Size(8, 8), 9);
vector<float>Descriptors;
Detector.compute(bin_num, Descriptors, Size(0, 0), Size(0, 0));
input.push_back(static_cast<Mat>(Descriptors).reshape(1, 1));//序列化后的图片依次存入 放在下一个row
float r = SVM_paramsZ->predict(input); //对所有行进行预测
/*对0和D再进行区分*/
if (r == 0 || r == ‘D‘) {
if (bin_num.ptr<uchar>(0)[0] == 255 || bin_num.ptr<uchar>(31)[0] == 255)
r = ‘D‘;
else
r = 0;
}
if (r > 9) {
cout << "识别结果" << (char)r << endl;
carnumber << (char)r;
}
else {
cout << "识别结果" << r << endl;
carnumber << r;
}
}
/*在源图中显示车牌号码*/
charater = charater + carnumber.str();
putTextZH(srcImg,
&charater[0],
Point(ColorRect[carLabel[num]].boundingRect().x, abs(ColorRect[carLabel[num]].boundingRect().y - 30)),
Scalar(0, 0, 255), 30, "宋体");
Point2f pts[4];
ColorRect[carLabel[num]].points(pts);
for (int j = 0; j < 4; j++) {
line(srcImg, pts[j], pts[(j + 1) % 4], Scalar(0, 255, 0), 2);
}
catch_success = 1; //成功提取
}
}
finish = clock();
totaltime = (double)(finish - start) / CLOCKS_PER_SEC;
cout << "\n此程序的运行时间为" << totaltime << "秒!" << endl;
namedWindow("result", WINDOW_NORMAL);
imshow("result", srcImg);
}
/*输入标签,返回省*/
string Province_Show(int label) {
string pv = "";
switch (label)
{
case 0:pv = "皖";
break;
case 1:pv = "京";
break;
case 2:pv = "渝";
break;
case 3:pv = "闽";
break;
case 4:pv = "甘";
break;
case 5:pv = "粤";
break;
case 6:pv = "桂";
break;
case 7:pv = "贵";
break;
case 8:pv = "琼";
break;
case 9:pv = "翼";
break;
case 10:pv = "黑";
break;
case 11:pv = "豫";
break;
case 12:pv = "鄂";
break;
case 13:pv = "湘";
break;
case 14:pv = "苏";
break;
case 15:pv = "赣";
break;
case 16:pv = "吉";
break;
case 17:pv = "辽";
break;
case 18:pv = "蒙";
break;
case 19:pv = "宁";
break;
case 20:pv = "青";
break;
case 21:pv = "鲁";
break;
case 22:pv = "晋";
break;
case 23:pv = "陕";
break;
case 24:pv = "川";
break;
case 25:pv = "津";
break;
case 26:pv = "新";
break;
case 27:pv = "云";
break;
case 28:pv = "浙";
break;
default:
break;
}
return pv;
}
/*将七组字母返回到前七个*/
void selectRect(vector<Rect>rt, vector<Rect>& numRect) {
numRect.resize(2);
Rect mid;
/*按面积排顺序*/
int averArea;
int averWidth;//平均宽度
int averHieght;//平均高度
for (int i = 0; i < rt.size(); i++) {
for (int j = i; j < rt.size(); j++) {
if (rt[i].area() <= rt[j].area()) {
mid = rt[i];
rt[i] = rt[j];
rt[j] = mid;
}
}
}
averHieght = (rt[0].height + rt[1].height) / 2;
averWidth = (rt[0].width + rt[1].width) / 2;
averArea = (rt[0].area() + rt[1].area()) / 2;
/*计算更准确的平均值*/
int new_averHieght = 0;
int new_averWidth = 0;
int new_averArea = 0;
int add = 0; //个数
for (int q = 0; q < rt.size(); q++) {
if (rt[q].area() > 0.4 * averArea && rt[q].height > averHieght * 0.7) {
new_averHieght += rt[q].height;
new_averWidth += rt[q].width;
new_averArea += rt[q].area();
add++;
}
}//add不可能是0
new_averHieght /= add;
new_averWidth /= add;
new_averArea /= add;
/*按x坐标排顺序*/
for (int i = 0; i < rt.size(); i++) {
for (int j = i; j < rt.size(); j++) {
if (rt[i].x >= rt[j].x) {
mid = rt[i];
rt[i] = rt[j];
rt[j] = mid;
}
}
}
/*判断矩形的位置*/
int sum_one = 0; //用来计算一的个数
Rect ForSrect; //第一或第二个字母 如果第二个字母是1就很有可能出错
int taltol = 0; //ForSrect前面的面积总和
int minX = rt[0].x; //左边不为0的最小值
for (int h = 0; h < rt.size() - 4; h++) {
if (rt[h].x <= 2 + new_averWidth / 20) { //将矩形框边缘的白色像素去掉
minX = rt[h + 1].x;
continue;
}
taltol += rt[h].area();
/*判断是否是川*/
if (rt[h].area() < new_averArea * 0.5 && rt[h].height > new_averHieght * 0.60) {
sum_one++;
}
if (rt[h].area() > new_averArea * 0.8) { //找到大的字母停止for循环
ForSrect = rt[h];
taltol -= ForSrect.area();
if (taltol > new_averArea * 0.4 || sum_one >= 3) {
/*第二个*/
numRect[1] = ForSrect;
/*求第一个*/
/*水平方向*/
int maxX = rt[h - 1].x + rt[h - 1].width;
/*竖直方向*/
int minY = 1000;
int maxY = 0;
for (int w = 0; w < h; w++) {
if (minY >= rt[w].y)
minY = rt[w].y;
if (maxY <= rt[w].y + rt[w].height)
maxY = rt[w].y + rt[w].height;
}
numRect[0] = Rect(Point(minX, minY), Point(maxX, maxY));
}
else {
/*第一个*/
numRect[0] = ForSrect;
/*求第二个*/
h++;
while (rt[h].area() < new_averArea * 0.7 && h < (rt.size() - 2)) { //若是以h!= rt.size()-2条件退出循环,分出的字符定时少于7个
h++;
}
numRect[1] = rt[h];
}
h++;
for (int d = h; d < rt.size(); d++) {
if (rt[d].height > new_averHieght * 0.6) { //有一定的高度
if (rt[d].width < new_averWidth * 0.5) {
rt[d].x = rt[d].x - (new_averWidth - rt[d].width) / 2;
rt[d].width = new_averWidth;
}
numRect.push_back(rt[d]);
}
}
break;
}
}
}
选取25张车牌图像进行测试
原文:https://www.cnblogs.com/jgg54335/p/14711349.html