opencv 视觉项目学习笔记(二): 基于 svm 和 knn 车牌识别
车牌识别的属于常见的 模式识别 ,其基本流程为下面三个步骤:
1) 分割: 检测并检测图像中感兴趣区域;
2)特征提取: 对字符图像集中的每个部分进行提取;
3)分类: 判断图像快是不是车牌或者 每个车牌字符的分类。
车牌识别分为两个步骤, 车牌检测, 车牌识别, 都属于模式识别。
基本结构如下:
一、车牌检测
1、车牌局部化(分割车牌区域),根据尺寸等基本信息去除非车牌图像;
2、判断车牌是否存在 (训练支持向量机 -svm, 判断车牌是否存在)。
二、车牌识别
1、字符局部化(分割字符),根据尺寸等信息剔除不合格图像
2、字符识别 ( knn 分类)
1.1 车牌局部化、并剔除不合格区域
vector<Plate> DetectRegions::segment(Mat input) {
vector<Plate> output;
//转为灰度图,并去噪
Mat img_gray;
cvtColor(input, img_gray, CV_BGR2GRAY);
blur(img_gray, img_gray, Size(, ));
//找垂直边
Mat img_sobel;
Sobel(img_gray, img_sobel, CV_8U, , , , , , BORDER_DEFAULT);
// 阈值化过滤像素
Mat img_threshold;
threshold(img_sobel, img_threshold, , , CV_THRESH_OTSU + CV_THRESH_BINARY);
// 开运算
Mat element = getStructuringElement(MORPH_RECT, Size(, ));
morphologyEx(img_threshold, img_threshold, CV_MOP_CLOSE, element);
//查找轮廓
vector<vector<Point>> contours;
findContours(img_threshold, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);
vector<vector<Point>>::iterator itc = contours.begin();
vector<RotatedRect> rects;
// 去除面积以及宽高比不合适区域
while (itc != contours.end())
{
// create bounding rect of object
RotatedRect mr = minAreaRect(Mat(*itc));
if (!verifySizes(mr))
{
itc = contours.erase(itc);
}
else
{
++itc;
rects.push_back(mr);
}
}
// 绘出获取区域
cv::Mat result;
input.copyTo(result);
cv::drawContours(result, contours, -, cv::Scalar(, , ), );
for (int i = ; i < rects.size(); i++) {
//For better rect cropping for each posible box
//Make floodfill algorithm because the plate has white background
//And then we can retrieve more clearly the contour box
circle(result, rects[i].center, , Scalar(, , ), -);
//get the min size between width and height
float minSize = (rects[i].size.width < rects[i].size.height) ? rects[i].size.width : rects[i].size.height;
minSize = minSize - minSize * 0.5;
//initialize rand and get 5 points around center for floodfill algorithm
srand(time(NULL));
//Initialize floodfill parameters and variables
Mat mask;
mask.create(input.rows + , input.cols + , CV_8UC1);
mask = Scalar::all();
int loDiff = ;
int upDiff = ;
int connectivity = ;
int newMaskVal = ;
int NumSeeds = ;
Rect ccomp;
int flags = connectivity + (newMaskVal << ) + CV_FLOODFILL_FIXED_RANGE + CV_FLOODFILL_MASK_ONLY;
for (int j = ; j < NumSeeds; j++) {
Point seed;
seed.x = rects[i].center.x + rand() % (int)minSize - (minSize / );
seed.y = rects[i].center.y + rand() % (int)minSize - (minSize / );
circle(result, seed, , Scalar(, , ), -);
int area = floodFill(input, mask, seed, Scalar(, , ), &ccomp, Scalar(loDiff, loDiff, loDiff), Scalar(upDiff, upDiff, upDiff), flags);
}
if (showSteps)
imshow("MASK", mask);
//cvWaitKey(0);
//Check new floodfill mask match for a correct patch.
//Get all points detected for get Minimal rotated Rect
vector<Point> pointsInterest;
Mat_<uchar>::iterator itMask = mask.begin<uchar>();
Mat_<uchar>::iterator end = mask.end<uchar>();
for (; itMask != end; ++itMask)
if (*itMask == )
pointsInterest.push_back(itMask.pos());
RotatedRect minRect = minAreaRect(pointsInterest);
if (verifySizes(minRect)) {
// rotated rectangle drawing
Point2f rect_points[];
minRect.points(rect_points);
for (int j = ; j < ; j++)
line(result, rect_points[j], rect_points[(j + ) % ], Scalar(, , ), , );
// 获取旋转矩阵
float r = (float)minRect.size.width / (float)minRect.size.height;
float angle = minRect.angle;
if (r < )
angle = + angle;
Mat rotmat = getRotationMatrix2D(minRect.center, angle, );
// 获取映射图像
Mat img_rotated;
warpAffine(input, img_rotated, rotmat, input.size(), CV_INTER_CUBIC);
// Crop image
Size rect_size = minRect.size;
if (r < )
swap(rect_size.width, rect_size.height);
Mat img_crop;
getRectSubPix(img_rotated, rect_size, minRect.center, img_crop);
Mat resultResized;
resultResized.create(, , CV_8UC3);
resize(img_crop, resultResized, resultResized.size(), , , INTER_CUBIC);
// 直方图
Mat grayResult;
cvtColor(resultResized, grayResult, CV_BGR2GRAY);
blur(grayResult, grayResult, Size(, ));
grayResult = histeq(grayResult);
output.push_back(Plate(grayResult, minRect.boundingRect()));
}
}
return output;
}
1.2 判断车牌是否存在
1.2.1 训练 svm
svm 会创建一个或多个超平面, 这些超级平面能判断数据属于那个类。
训练数据: 所有训练数据存储再一个 N x M 的矩阵中, 其中 N 为样本数, M 为特征数(每个样本是该训练矩阵中的一行)。这些数据 所有数据存在 xml 文件中,
标签数据: 每个样本的类别信息存储在另一个 N x 1 的矩阵中, 每行为一个样本标签。
训练数据存放在本地 svm.xml 文件中。
// TrainSvm.cpp 文件
#include <iostream>
#include <opencv2/opencv.hpp> #include "Preprocess.h" using namespace std;
using namespace cv;
using namespace cv::ml; int main(int argc, char** argv)
{
FileStorage fs;
fs.open("SVM.xml", FileStorage::READ);
Mat SVM_TrainingData;
Mat SVM_Classes;
fs["TrainingData"] >> SVM_TrainingData;
fs["classes"] >> SVM_Classes;
// Set SVM storage
Ptr<ml::SVM> model = ml::SVM::create();
model->setType(SVM::C_SVC);
model->setKernel(SVM::LINEAR); // 核函数
// 训练数据
Ptr<TrainData> tData = TrainData::create(SVM_TrainingData, ROW_SAMPLE, SVM_Classes);
// 训练分类器
model->train(tData);
model->save("model.xml"); // TODO: 测试
return ;
// Preprocess.cpp
#include <string>
#include <vector>
#include <fstream>
#include <algorithm> #include "Preprocess.h" using namespace cv; void Preprocess::getAllFiles(string path, vector<string> &files, string fileType)
{
long hFile = ;
struct _finddata_t fileInfo;
string p;
if ((hFile = _findfirst(p.assign(path).append("\\*" + fileType).c_str(), &fileInfo)) != -)
{
do
{
files.push_back(p.assign(path).append("\\").append(fileInfo.name));
} while (_findnext(hFile, &fileInfo) == );
_findclose(hFile); // 关闭句柄
} } void Preprocess::extract_img_data(string path_plates, string path_noPlates)
{
cout << "OpenCV Training SVM Automatic Number Plate Recognition\n"; int imgWidth = ;
int imgHeight = ;
int numPlates = ;
int numNoPlates = ;
Mat classes;
Mat trainingData; Mat trainingImages;
vector<int> trainingLabels; for (int i = ; i < numPlates; i++)
{
stringstream ss(stringstream::in | stringstream::out);
ss << path_plates << i << ".jpg";
Mat img = imread(ss.str(), );
resize(img, img, Size(imgWidth, imgWidth));
img = img.reshape(, );
trainingImages.push_back(img);
trainingLabels.push_back();
} for (int i = ; i < numNoPlates; i++)
{
stringstream ss;
ss << path_noPlates << i << ".jpg";
Mat img = imread(ss.str(), );
img = img.reshape(, );
trainingImages.push_back(img);
trainingLabels.push_back();
} Mat(trainingImages).copyTo(trainingData);
trainingData.convertTo(trainingData, CV_32FC1);
Mat(trainingLabels).copyTo(classes); FileStorage fs("SVM.xml", FileStorage::WRITE);
fs << "TrainingData" << trainingData;
fs << "classess" << classes;
fs.release();
}
1.2.2 利用 svm 判断车牌是否存在
// load model
Ptr<ml::SVM> model = SVM::load("model.xml"); // For each possible plate, classify with svm if it's plate
vector<Plate> plates;
for (int i = ; i < posible_regions.size(); i++)
{
Mat img = posible_regions[i].plateImg;
Mat p = img.reshape(, );
p.convertTo(p, CV_32FC1);
int reponse = (int)model->predict(p);
if (reponse)
{
plates.push_back(posible_regions[i]);
//bool res = imwrite("test.jpg", img);
}
}
以上,已经找了存在车牌的区域,并保存到一个 vector 中。
下面使用 k 邻近算法, 来识别车牌图像中的车牌字符。
2.1 字符分割
分割字符,并剔除不合格图像
vector<CharSegment> OCR::segment(Plate plate) {
Mat input = plate.plateImg;
vector<CharSegment> output;
//使字符为白色,背景为黑色
Mat img_threshold;
threshold(input, img_threshold, , , CV_THRESH_BINARY_INV);
Mat img_contours;
img_threshold.copyTo(img_contours);
// 找到所有物体
vector< vector< Point> > contours;
findContours(img_contours,
contours, // a vector of contours
CV_RETR_EXTERNAL, // retrieve the external contours
CV_CHAIN_APPROX_NONE); // all pixels of each contours
// Draw blue contours on a white image
cv::Mat result;
img_threshold.copyTo(result);
cvtColor(result, result, CV_GRAY2RGB);
cv::drawContours(result, contours,
-, // draw all contours
cv::Scalar(, , ), // in blue
); // with a thickness of 1
//Remove patch that are no inside limits of aspect ratio and area.
vector<vector<Point> >::iterator itc = contours.begin();
while (itc != contours.end()) {
//Create bounding rect of object
Rect mr = boundingRect(Mat(*itc));
rectangle(result, mr, Scalar(, , ));
//提取合格图像区域
Mat auxRoi(img_threshold, mr);
if (verifySizes(auxRoi)) {
auxRoi = preprocessChar(auxRoi);
output.push_back(CharSegment(auxRoi, mr));
rectangle(result, mr, Scalar(, , ));
}
++itc;
}
return output;
}
Mat OCR::preprocessChar(Mat in) {
//Remap image
int h = in.rows;
int w = in.cols;
Mat transformMat = Mat::eye(, , CV_32F);
int m = max(w, h);
transformMat.at<float>(, ) = m / - w / ;
transformMat.at<float>(, ) = m / - h / ;
// 仿射变换,将图像投射到尺寸更大的图像上(使用偏移)
Mat warpImage(m, m, in.type());
warpAffine(in, warpImage, transformMat, warpImage.size(), INTER_LINEAR, BORDER_CONSTANT, Scalar());
Mat out;
resize(warpImage, out, Size(charSize, charSize));
return out;
}
2.2 字符识别
2.2.1 训练 knn
使用 opencv 自带的 digits.png 文件, 可以训练训练识别识别数字的 knn 。
#include <iostream>
#include <opencv2/opencv.hpp> using namespace cv;
using namespace std;
using namespace cv::ml; const int numFilesChars[] = { , , , , , , , , , , , , , , , , , , , , , , , , , , , , , }; int main()
{ std::cout << "OpenCV Training OCR Automatic Number Plate Recognition\n"; string path = "D:/Program Files (x86)/opencv_3.4.3/opencv/sources/samples/data/digits.png";
Mat img = imread(path);
Mat gray;
cvtColor(img, gray, CV_BGR2GRAY);
int b = ;
int m = gray.rows / b; // 将原图裁剪为 20 * 20 的小图块
int n = gray.cols / b; // 将原图裁剪为 20 * 20 的小图块 Mat data, labels; // 特征矩阵 // 按照列来读取数据, 每 5 个数据为一个类
for (int i = ; i < n; i++)
{
int offsetCol = i * b; // 列上的偏移量
for (int j = ; j < m; j++)
{
int offsetRow = j * b; // 行上的偏移量
Mat tmp;
gray(Range(offsetRow, offsetRow + b), Range(offsetCol, offsetCol + b)).copyTo(tmp);
data.push_back(tmp.reshape(, )); // 序列化后放入特征矩阵
labels.push_back((int)j / ); // 对应的标注
}
}
data.convertTo(data, CV_32F);
int samplesNum = data.rows;
int trainNum = ;
Mat trainData, trainLabels;
trainData = data(Range(, trainNum), Range::all()); // 前 3000 个为训练数据
trainLabels = labels(Range(, trainNum), Range::all()); // 使用k 邻近算法那(knn, k-nearest_neighbor) 算法
int K = ;
Ptr<cv::ml::TrainData> tData = cv::ml::TrainData::create(trainData, ROW_SAMPLE, trainLabels);
Ptr<KNearest> model = KNearest::create(); model->setDefaultK(K); // 设定查找时返回数量为 5
// 设置分类器为分类 或回归
// 分类问题:输出离散型变量(如 -1,1, 100), 为定性输出(如预测明天是下雨、天晴还是多云)
// 回归问题: 回归问题的输出为连续型变量,为定量输出(如明天温度为多少度)
model->setIsClassifier(true);
model->train(tData); // 预测分类
double train_hr = , test_hr = ;
Mat response;
// compute prediction error on train and test data
for (int i = ; i < samplesNum; i++)
{
Mat smaple = data.row(i);
float r = model->predict(smaple); // 对所有进行预测
// 预测结果与原结果对比,相等为 1, 不等为 0
r = std::abs(r - labels.at<int>(i)) <= FLT_EPSILON ? .f : .f; if (i < trainNum)
{
train_hr += r; // 累计正确数
}
else
{
test_hr += r;
}
} test_hr /= samplesNum - trainNum;
train_hr = trainNum > ? train_hr / trainNum : .;
cout << "train accuracy : " << train_hr * . << "\n";
cout << "test accuracy : " << test_hr * . << "\n"; // 保存 ocr 模型
string model_path = "ocr.xml";
model->save(model_path);
// 载入模型
// Ptr<KNearest> knn = KNearest::load<KNearest>(model_path); waitKey();
return ;
}
2.2.2 使用 knn 识别字符
// Mat target_img 为目标图像矩阵
model->save(model_path);
// 载入模型
Ptr<KNearest> knn = KNearest::load<KNearest>(model_path);
float it_type = knn->predict(target_img)
以上就是车牌识别的核心代码了。
全部流程的代码我放到下面这个群里面了,欢迎来交流下载。
广州 OpenCV 学校交流群: 892083812
参考:
深入理解 OpenCV
https://www.cnblogs.com/denny402/p/5032839.html
opencv 视觉项目学习笔记(二): 基于 svm 和 knn 车牌识别的更多相关文章
- STM32学习笔记(二) 基于STM32-GPIO的流水灯实现
学会了如何新建一个工程模板,下面就要开始动手实践了.像c/c++中经典的入门代码"hello world"一样,流水灯作为最简单的硬件设备在单片机领域也是入门首推.如果你已经有了一 ...
- OpenCV for Python 学习笔记 二
今天主要看了OpenCV中的事件以及回调函数,这么说可能不准确,主要是下面这两个函数(OpenCV中还有很多这些函数,可以在 http://docs.opencv.org/trunk/modules/ ...
- amazeui学习笔记二(进阶开发1)--项目结构structure
amazeui学习笔记二(进阶开发1)--项目结构structure 一.总结 1.项目结构:是说的amazeui在github上面的项目结构,二次开发amazeui用 二.项目结构structure ...
- 语义分割:基于openCV和深度学习(二)
语义分割:基于openCV和深度学习(二) Semantic segmentation in images with OpenCV 开始吧-打开segment.py归档并插入以下代码: Semanti ...
- OpenCV之Python学习笔记
OpenCV之Python学习笔记 直都在用Python+OpenCV做一些算法的原型.本来想留下发布一些文章的,可是整理一下就有点无奈了,都是写零散不成系统的小片段.现在看 到一本国外的新书< ...
- amazeui学习笔记二(进阶开发5)--Web 组件开发规范Rules
amazeui学习笔记二(进阶开发5)--Web 组件开发规范Rules 一.总结 1.见名知意:见那些class名字知意,见函数名知意,见文件名知意 例如(HISTORY.md Web 组件更新历史 ...
- ZooKeeper学习笔记二:API基本使用
Grey ZooKeeper学习笔记二:API基本使用 准备工作 搭建一个zk集群,参考ZooKeeper学习笔记一:集群搭建. 确保项目可以访问集群的每个节点 新建一个基于jdk1.8的maven项 ...
- java之jvm学习笔记二(类装载器的体系结构)
java的class只在需要的时候才内转载入内存,并由java虚拟机的执行引擎来执行,而执行引擎从总的来说主要的执行方式分为四种, 第一种,一次性解释代码,也就是当字节码转载到内存后,每次需要都会重新 ...
- 学习笔记(二)--->《Java 8编程官方参考教程(第9版).pdf》:第七章到九章学习笔记
注:本文声明事项. 本博文整理者:刘军 本博文出自于: <Java8 编程官方参考教程>一书 声明:1:转载请标注出处.本文不得作为商业活动.若有违本之,则本人不负法律责任.违法者自负一切 ...
随机推荐
- CDQZ集训DAY9 日记
彻彻底底的爆炸了…… 考试上来第一题看完30分暴力后就不知道怎么打了,然后看第二题,一开始脑残以为是网络流,后来发现是树状结构后觉得是那个经典的n^2的树上背包DP,然而脑子又一次犯笨,竟然,竟然去枚 ...
- python通过TimedRotatingFileHandler按时间切割日志
通过TimedRotatingFileHandler按时间切割日志 线上跑了一个定时脚本,每天生成的日志文件都写在了一个文件中.但是日志信息不可能输出到单一的一个文件中. 原因有二:1.日志文件越来越 ...
- Q&A-20180128
Orleans与Akka对比,为什么选用Orleans? 答: Akka对参与开发的人员要求更高一些,普遍是专家级别,Orleans框架进一步抽象了一层,结合C#语言特性,能普遍降低开发难度. 下面是 ...
- 预学第三天:Ge常用t快捷键,码云,Git使用
目录 Get常用快捷键 码云及Git的使用 Get常用快捷键 git init #创建一个本地的仓库 **gie add test.txt #指定文件添加 ***git add . #当前文件夹下所有 ...
- IIS应用程序池标识(程序池账户)ApplicationPoolIdentify
IIS中应用程序池的运行账户(标识)有以下4个选项 LocalService 本地服务 LocalSystem 本地系统 NetWorkService 网络服务 ApplicationPoolIden ...
- [译].Net中的内存
原文链接:https://jonskeet.uk/csharp/memory.html 人们在理解值类型和引用类型之间的差异时因为“值类型在栈上分配,引用类型在堆上分配”这句话造成了很多混乱.这完全是 ...
- springboot - 登录+静态资源访问+国际化
1.项目目录结构 2.pom.xml <?xml version="1.0" encoding="UTF-8"?> <project xmln ...
- 【TensorFlow 3】mnist数据集:与Keras对比
在TF1.8之后Keras被当作为一个内置API:tf.keras. 并且之前的下载语句会报错. mnist = input_data.read_data_sets('MNIST_data',one_ ...
- web设计_8_数据表格内容样式分离
1.页面需要用到table的时候,样式重置CSS要设置: table{ border-collapse: collapse; border-spacing:; } 2. HTML结构 <tabl ...
- python中if __name__ == '__main__' :main(()
例如: if __name__ == '__main__': main() 如果运行的是主函数的话,执行下一句main() 如果作为模块被其他文件导入使用的话,我们就不执行后面的main()什么的. ...