OpenCV C++案例实战十《车牌号识别》
本文将使用OpenCV C++ 进行车牌号识别。

原图如图所示。本案例的需求是进行车牌号码识别。所以,首先我们得定位车牌所在的位置,然后将车牌切割出来。接下来我们就来看看是如何实现。
首先经过一些常规的图像预处理,我们可以提取出图像的大致轮廓。然后根据轮廓的特征进一步确定我们所需要查找的轮廓。在这里,不同的图像需要根据本身图像特征设定预处理算法。所以,本案例的一个缺点就是不具有鲁棒性,只针对特定需求。
Mat gray;
cvtColor(src, gray, COLOR_BGR2GRAY);
Mat thresh;
threshold(gray, thresh, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);
//使用形态学开操作去除一些小轮廓
Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
Mat open;
morphologyEx(thresh, open, MORPH_OPEN, kernel);

如图为经过二值化后的图像,接下来我们就可以使用findContours寻找我们需要的轮廓。根据图像的轮廓特征就可以定位到车牌所在位置,然后将其从原图中切割出来,以便后续的识别工作。在这里,我定义了一个License结构体,用于存储ROI图像,以及其相对于原图所在位置。这样在后续的绘制工作中,我们就可以定位到ROI所在位置。
//自定义车牌结构体
struct License
{
Mat mat; //ROI图片
Rect rect; //ROI所在矩形
};
//使用 RETR_EXTERNAL 找到最外轮廓
vector<vector<Point>>contours;
findContours(open, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
vector<vector<Point>>conPoly(contours.size());
for (int i = 0; i < contours.size(); i++)
{
double area = contourArea(contours[i]);
double peri = arcLength(contours[i], true);
//根据面积筛选出可能属于车牌区域的轮廓
if (area > 1000)
{
//使用多边形近似,进一步确定车牌区域轮廓
approxPolyDP(contours[i], conPoly[i], 0.02*peri, true);
if (conPoly[i].size() == 4)
{
//计算矩形区域宽高比
Rect box = boundingRect(contours[i]);
double ratio = double(box.width) / double(box.height);
if (ratio > 2 && ratio < 4)
{
//截取ROI区域
Rect rect = boundingRect(contours[i]);
License_ROI = { src(rect),rect };
}
}
}
}

如图为从汽车上定位到的车牌,并将其切割出来以便下面的识别工作。
//获取车牌所在ROI区域--车牌定位
bool Get_License_ROI(Mat src, License &License_ROI)
{
Mat gray;
cvtColor(src, gray, COLOR_BGR2GRAY);
Mat thresh;
threshold(gray, thresh, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);
//使用形态学开操作去除一些小轮廓
Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
Mat open;
morphologyEx(thresh, open, MORPH_OPEN, kernel);
//使用 RETR_EXTERNAL 找到最外轮廓
vector<vector<Point>>contours;
findContours(open, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
vector<vector<Point>>conPoly(contours.size());
for (int i = 0; i < contours.size(); i++)
{
double area = contourArea(contours[i]);
double peri = arcLength(contours[i], true);
//根据面积筛选出可能属于车牌区域的轮廓
if (area > 1000)
{
//使用多边形近似,进一步确定车牌区域轮廓
approxPolyDP(contours[i], conPoly[i], 0.02*peri, true);
if (conPoly[i].size() == 4)
{
//计算矩形区域宽高比
Rect box = boundingRect(contours[i]);
double ratio = double(box.width) / double(box.height);
if (ratio > 2 && ratio < 4)
{
//截取ROI区域
Rect rect = boundingRect(contours[i]);
License_ROI = { src(rect),rect };
}
}
}
}
if (License_ROI.mat.empty())
{
return false;
}
return true;
}
通过刚才的车牌定位,我们已经将车牌从原图中切割出来了。接下来,我们还需要将车牌上的字符一一切割出来,以便进行后续的识别工作。同理,我们也需要对车牌做同样的预处理操作。
Mat gray;
cvtColor(License_ROI.mat, gray, COLOR_BGR2GRAY);
Mat thresh;
threshold(gray, thresh, 0, 255, THRESH_BINARY | THRESH_OTSU);
Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
Mat close;
morphologyEx(thresh, close, MORPH_CLOSE, kernel);
经过灰度、阈值、形态学操作后的图像如下图所示。

接下来我们进行轮廓提取就可以提取出车牌上的每一个字符了。
vector<vector<Point>>contours;
findContours(close, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
for (int i = 0; i < contours.size(); i++)
{
double area = contourArea(contours[i]);
//由于我们筛选出来的轮廓是无序的,故后续我们需要将字符重新排序
if (area > 200)
{
Rect rect = boundingRect(contours[i]);
//计算外接矩形宽高比
double ratio = double(rect.height) / double(rect.width);
if (ratio > 1)
{
Mat roi = License_ROI.mat(rect);
resize(roi, roi, Size(50, 100), 1, 1, INTER_LINEAR);
Character_ROI.push_back({ roi ,rect });
}
}
}
如图为切割出来的字符。不过这里有一个小问题就是,我们切割出来的字符并不是按车牌号码那样顺序排列。所以,在这里我们还得对其重新进行排序,使其按车牌顺序排列。
//冒泡排序
for (int i = 0; i < Character_ROI.size()-1; i++)
{
for (int j = 0; j < Character_ROI.size() - 1 - i; j++)
{
if (Character_ROI[j].rect.x > Character_ROI[j + 1].rect.x)
{
License temp = Character_ROI[j];
Character_ROI[j] = Character_ROI[j + 1];
Character_ROI[j + 1] = temp;
}
}
}

//获取车牌每一个字符ROI区域
bool Get_Character_ROI(License &License_ROI, vector<License>&Character_ROI)
{
Mat gray;
cvtColor(License_ROI.mat, gray, COLOR_BGR2GRAY);
Mat thresh;
threshold(gray, thresh, 0, 255, THRESH_BINARY | THRESH_OTSU);
Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
Mat close;
morphologyEx(thresh, close, MORPH_CLOSE, kernel);
vector<vector<Point>>contours;
findContours(close, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
for (int i = 0; i < contours.size(); i++)
{
double area = contourArea(contours[i]);
//由于我们筛选出来的轮廓是无序的,故后续我们需要将字符重新排序
if (area > 200)
{
Rect rect = boundingRect(contours[i]);
//计算外接矩形宽高比
double ratio = double(rect.height) / double(rect.width);
if (ratio > 1)
{
Mat roi = License_ROI.mat(rect);
resize(roi, roi, Size(50, 100), 1, 1, INTER_LINEAR);
Character_ROI.push_back({ roi ,rect });
}
}
}
//将筛选出来的字符轮廓 按照其左上角点坐标从左到右依次顺序排列
//冒泡排序
for (int i = 0; i < Character_ROI.size()-1; i++)
{
for (int j = 0; j < Character_ROI.size() - 1 - i; j++)
{
if (Character_ROI[j].rect.x > Character_ROI[j + 1].rect.x)
{
License temp = Character_ROI[j];
Character_ROI[j] = Character_ROI[j + 1];
Character_ROI[j + 1] = temp;
}
}
}
if (Character_ROI.size() != 7)
{
return false;
}
return true;
}

如图所示,为模板图像以及对应的label。我们需要读取文件,进行匹配。在这里我使用UTF8ToGB函数实现读取txt文件,目的是为了在控制台显示中文时,不会出现乱码情况。
//读取文件 图片
bool Read_Data(string filename,vector<Mat>&dataset)
{
vector<String>imagePathList;
glob(filename, imagePathList);
if (imagePathList.empty())return false;
for (int i = 0; i < imagePathList.size(); i++)
{
Mat image = imread(imagePathList[i]);
resize(image, image, Size(50, 100), 1, 1, INTER_LINEAR);
dataset.push_back(image);
}
return true;
}
//读取文件 标签
bool Read_Data(string filename, vector<string>&data_name)
{
fstream fin;
fin.open(filename, ios::in);
if (!fin.is_open())
{
cout << "can not open the file!" << endl;
return false;
}
string s;
while (std::getline(fin, s))
{
string str = UTF8ToGB(s.c_str()).c_str();
data_name.push_back(str);
}
fin.close();
return true;
}
在这里,我的思路是:使用一个for循环,将我们切割出来的字符与现有的模板进行匹配。而这个匹配算法是求两张图像的像素差,以此来判断图像的相似程度。具体是使用OpenCV absdiff函数计算两张图像的像素差.。


如图为使用absdiff得到的效果图。接下来,我们只需要计算图像中灰度值为0的像素点个数就可以了。像素点个数最少的那个label即为我们的匹配结果。当然,此方法肯定是会存在误识别的情况的。进行字符匹配的方法还有:模板匹配,基于Hu矩轮廓匹配。大家可以试试。
//识别车牌字符
bool License_Recognition(vector<License>&Character_ROI, vector<int>&result_index)
{
string filename = "data/";
vector<Mat>dataset;
if (!Read_Data(filename, dataset)) return false;
for (int i = 0; i < Character_ROI.size(); i++)
{
Mat roi_gray;
cvtColor(Character_ROI[i].mat, roi_gray, COLOR_BGR2GRAY);
Mat roi_thresh;
threshold(roi_gray, roi_thresh, 0, 255, THRESH_BINARY | THRESH_OTSU);
int minCount = 1000000;
int index = 0;
for (int j = 0; j < dataset.size(); j++)
{
Mat temp_gray;
cvtColor(dataset[j], temp_gray, COLOR_BGR2GRAY);
Mat temp_thresh;
threshold(temp_gray, temp_thresh, 0, 255, THRESH_BINARY | THRESH_OTSU);
//计算两张图片的像素差,以此判断两张图片是否相同
Mat dst;
absdiff(roi_thresh, temp_thresh, dst);
int count = pixCount(dst);
if (count < minCount)
{
minCount = count;
index = j;
}
}
result_index.push_back(index);
}
return true;
}
//显示最终效果
bool Draw_Result(Mat src, License &License_ROI, vector<License>&Character_ROI,vector<int>&result_index)
{
rectangle(src, License_ROI.rect, Scalar(0, 255, 0), 2);
vector<string>data_name;
if (!Read_Data("data_name.txt", data_name))return false;
for (int i = 0; i < Character_ROI.size(); i++)
{
cout << data_name[result_index[i]] << " ";
//putText 中文显示会乱码,所以采用下面代码
CvxText text("C://Windows/Fonts/方正粗黑宋简体.ttf");//字体
string str = data_name[result_index[i]]; //string 转 char
const char*msg = str.data();
IplImage *temp; //Mat 转 IplImage
temp = &IplImage(src);
text.putText(temp, msg, Point(License_ROI.rect.x + Character_ROI[i].rect.x, License_ROI.rect.y + Character_ROI[i].rect.y),Scalar(0,0,255));
}
return true;
}
在这里,为了使用putText显示中文,我这里加了一些额外的代码。如果需要使用putText显示中文效果的朋友可以自行百度一下如何配置环境。
最终效果如图所示:



版本一 :putText能够显示中文,需要配置freetype库。目前我使用的环境是:win10、vs2017、opencv4.1。
#include<iostream>
#include<opencv2/opencv.hpp>
#include<fstream> //文本读写
#include<Windows.h> //控制台输出中文乱码
#include"CvxText.h" //putText显示中文乱码
using namespace std;
using namespace cv;
//自定义车牌结构体
struct License
{
Mat mat; //ROI图片
Rect rect; //ROI所在矩形
};
//获取车牌所在ROI区域--车牌定位
bool Get_License_ROI(Mat src, License &License_ROI)
{
Mat gray;
cvtColor(src, gray, COLOR_BGR2GRAY);
Mat thresh;
threshold(gray, thresh, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);
//使用形态学开操作去除一些小轮廓
Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
Mat open;
morphologyEx(thresh, open, MORPH_OPEN, kernel);
//使用 RETR_EXTERNAL 找到最外轮廓
vector<vector<Point>>contours;
findContours(open, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
vector<vector<Point>>conPoly(contours.size());
for (int i = 0; i < contours.size(); i++)
{
double area = contourArea(contours[i]);
double peri = arcLength(contours[i], true);
//根据面积筛选出可能属于车牌区域的轮廓
if (area > 1000)
{
//使用多边形近似,进一步确定车牌区域轮廓
approxPolyDP(contours[i], conPoly[i], 0.02*peri, true);
if (conPoly[i].size() == 4)
{
//计算矩形区域宽高比
Rect box = boundingRect(contours[i]);
double ratio = double(box.width) / double(box.height);
if (ratio > 2 && ratio < 4)
{
//截取ROI区域
Rect rect = boundingRect(contours[i]);
License_ROI = { src(rect),rect };
}
}
}
}
if (License_ROI.mat.empty())
{
return false;
}
return true;
}
//获取车牌每一个字符ROI区域
bool Get_Character_ROI(License &License_ROI, vector<License>&Character_ROI)
{
Mat gray;
cvtColor(License_ROI.mat, gray, COLOR_BGR2GRAY);
Mat thresh;
threshold(gray, thresh, 0, 255, THRESH_BINARY | THRESH_OTSU);
Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
Mat close;
morphologyEx(thresh, close, MORPH_CLOSE, kernel);
vector<vector<Point>>contours;
findContours(close, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
for (int i = 0; i < contours.size(); i++)
{
double area = contourArea(contours[i]);
//由于我们筛选出来的轮廓是无序的,故后续我们需要将字符重新排序
if (area > 200)
{
Rect rect = boundingRect(contours[i]);
//计算外接矩形宽高比
double ratio = double(rect.height) / double(rect.width);
if (ratio > 1)
{
Mat roi = License_ROI.mat(rect);
resize(roi, roi, Size(50, 100), 1, 1, INTER_LINEAR);
Character_ROI.push_back({ roi ,rect });
}
}
}
//将筛选出来的字符轮廓 按照其左上角点坐标从左到右依次顺序排列
//冒泡排序
for (int i = 0; i < Character_ROI.size()-1; i++)
{
for (int j = 0; j < Character_ROI.size() - 1 - i; j++)
{
if (Character_ROI[j].rect.x > Character_ROI[j + 1].rect.x)
{
License temp = Character_ROI[j];
Character_ROI[j] = Character_ROI[j + 1];
Character_ROI[j + 1] = temp;
}
}
}
if (Character_ROI.size() != 7)
{
return false;
}
return true;
}
//从txt文件中读取中文,防止乱码
string UTF8ToGB(const char* str)
{
string result;
WCHAR *strSrc;
LPSTR szRes;
//获得临时变量的大小
int i = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);
strSrc = new WCHAR[i + 1];
MultiByteToWideChar(CP_UTF8, 0, str, -1, strSrc, i);
//获得临时变量的大小
i = WideCharToMultiByte(CP_ACP, 0, strSrc, -1, NULL, 0, NULL, NULL);
szRes = new CHAR[i + 1];
WideCharToMultiByte(CP_ACP, 0, strSrc, -1, szRes, i, NULL, NULL);
result = szRes;
delete[]strSrc;
delete[]szRes;
return result;
}
//读取文件 图片
bool Read_Data(string filename,vector<Mat>&dataset)
{
vector<String>imagePathList;
glob(filename, imagePathList);
if (imagePathList.empty())return false;
for (int i = 0; i < imagePathList.size(); i++)
{
Mat image = imread(imagePathList[i]);
resize(image, image, Size(50, 100), 1, 1, INTER_LINEAR);
dataset.push_back(image);
}
return true;
}
//读取文件 标签
bool Read_Data(string filename, vector<string>&data_name)
{
fstream fin;
fin.open(filename, ios::in);
if (!fin.is_open())
{
cout << "can not open the file!" << endl;
return false;
}
string s;
while (std::getline(fin, s))
{
string str = UTF8ToGB(s.c_str()).c_str();
data_name.push_back(str);
}
fin.close();
return true;
}
//计算像素点个数
int pixCount(Mat image)
{
int count = 0;
if (image.channels() == 1)
{
for (int i = 0; i < image.rows; i++)
{
for (int j = 0; j < image.cols; j++)
{
if (image.at<uchar>(i, j) == 0)
{
count++;
}
}
}
return count;
}
else
{
return -1;
}
}
//识别车牌字符
bool License_Recognition(vector<License>&Character_ROI, vector<int>&result_index)
{
string filename = "data/";
vector<Mat>dataset;
if (!Read_Data(filename, dataset)) return false;
for (int i = 0; i < Character_ROI.size(); i++)
{
Mat roi_gray;
cvtColor(Character_ROI[i].mat, roi_gray, COLOR_BGR2GRAY);
Mat roi_thresh;
threshold(roi_gray, roi_thresh, 0, 255, THRESH_BINARY | THRESH_OTSU);
int minCount = 1000000;
int index = 0;
for (int j = 0; j < dataset.size(); j++)
{
Mat temp_gray;
cvtColor(dataset[j], temp_gray, COLOR_BGR2GRAY);
Mat temp_thresh;
threshold(temp_gray, temp_thresh, 0, 255, THRESH_BINARY | THRESH_OTSU);
//计算两张图片的像素差,以此判断两张图片是否相同
Mat dst;
absdiff(roi_thresh, temp_thresh, dst);
int count = pixCount(dst);
if (count < minCount)
{
minCount = count;
index = j;
}
}
result_index.push_back(index);
}
return true;
}
//显示最终效果
bool Draw_Result(Mat src, License &License_ROI, vector<License>&Character_ROI,vector<int>&result_index)
{
rectangle(src, License_ROI.rect, Scalar(0, 255, 0), 2);
vector<string>data_name;
if (!Read_Data("data_name.txt", data_name))return false;
for (int i = 0; i < Character_ROI.size(); i++)
{
cout << data_name[result_index[i]] << " ";
//putText 中文显示会乱码,所以采用下面代码
CvxText text("C://Windows/Fonts/方正粗黑宋简体.ttf");//字体
string str = data_name[result_index[i]]; //string 转 char
const char*msg = str.data();
IplImage *temp; //Mat 转 IplImage
temp = &IplImage(src);
text.putText(temp, msg, Point(License_ROI.rect.x + Character_ROI[i].rect.x, License_ROI.rect.y + Character_ROI[i].rect.y),Scalar(0,0,255));
}
return true;
}
int main()
{
Mat src = imread("car.jpg");
if (src.empty())
{
cout << "No image!" << endl;
system("pause");
return -1;
}
License License_ROI;
if (Get_License_ROI(src, License_ROI))
{
vector<License>Character_ROI;
if (Get_Character_ROI(License_ROI, Character_ROI))
{
vector<int>result_index;
if (License_Recognition(Character_ROI, result_index))
{
Draw_Result(src, License_ROI, Character_ROI,result_index);
}
else
{
cout << "未能识别字符!" << endl;
system("pause");
return -1;
}
}
else
{
cout << "未能切割出字符!" << endl;
system("pause");
return -1;
}
}
else
{
cout << "未定位到车牌位置!" << endl;
system("pause");
return -1;
}
imshow("src", src);
waitKey(0);
system("pause");
return 0;
}
版本二:很多小伙伴向我反馈由于vs、opencv版本问题,利用putText显示中文会出现各种各样的错误。故在这里提供一个putText不显示中文的版本,所以freetype库也不用配置了,直接就可以运行了。
#include<iostream>
#include<opencv2/opencv.hpp>
#include<fstream> //文本读写
#include<Windows.h> //控制台输出中文乱码
using namespace std;
using namespace cv;
//自定义车牌结构体
struct License
{
Mat mat; //ROI图片
Rect rect; //ROI所在矩形
};
//获取车牌所在ROI区域--车牌定位
bool Get_License_ROI(Mat src, License &License_ROI)
{
Mat gray;
cvtColor(src, gray, COLOR_BGR2GRAY);
Mat thresh;
threshold(gray, thresh, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);
//使用形态学开操作去除一些小轮廓
Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
Mat open;
morphologyEx(thresh, open, MORPH_OPEN, kernel);
//使用 RETR_EXTERNAL 找到最外轮廓
vector<vector<Point>>contours;
findContours(open, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
vector<vector<Point>>conPoly(contours.size());
for (int i = 0; i < contours.size(); i++)
{
double area = contourArea(contours[i]);
double peri = arcLength(contours[i], true);
//根据面积筛选出可能属于车牌区域的轮廓
if (area > 1000)
{
//使用多边形近似,进一步确定车牌区域轮廓
approxPolyDP(contours[i], conPoly[i], 0.02*peri, true);
if (conPoly[i].size() == 4)
{
//计算矩形区域宽高比
Rect box = boundingRect(contours[i]);
double ratio = double(box.width) / double(box.height);
if (ratio > 2 && ratio < 4)
{
//截取ROI区域
Rect rect = boundingRect(contours[i]);
License_ROI = { src(rect),rect };
}
}
}
}
if (License_ROI.mat.empty())
{
return false;
}
return true;
}
//获取车牌每一个字符ROI区域
bool Get_Character_ROI(License &License_ROI, vector<License>&Character_ROI)
{
Mat gray;
cvtColor(License_ROI.mat, gray, COLOR_BGR2GRAY);
Mat thresh;
threshold(gray, thresh, 0, 255, THRESH_BINARY | THRESH_OTSU);
Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
Mat close;
morphologyEx(thresh, close, MORPH_CLOSE, kernel);
vector<vector<Point>>contours;
findContours(close, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
for (int i = 0; i < contours.size(); i++)
{
double area = contourArea(contours[i]);
//由于我们筛选出来的轮廓是无序的,故后续我们需要将字符重新排序
if (area > 200)
{
Rect rect = boundingRect(contours[i]);
//计算外接矩形宽高比
double ratio = double(rect.height) / double(rect.width);
if (ratio > 1)
{
Mat roi = License_ROI.mat(rect);
resize(roi, roi, Size(50, 100), 1, 1, INTER_LINEAR);
Character_ROI.push_back({ roi ,rect });
}
}
}
//将筛选出来的字符轮廓 按照其左上角点坐标从左到右依次顺序排列
//冒泡排序
for (int i = 0; i < Character_ROI.size()-1; i++)
{
for (int j = 0; j < Character_ROI.size() - 1 - i; j++)
{
if (Character_ROI[j].rect.x > Character_ROI[j + 1].rect.x)
{
License temp = Character_ROI[j];
Character_ROI[j] = Character_ROI[j + 1];
Character_ROI[j + 1] = temp;
}
}
}
if (Character_ROI.size() != 7)
{
return false;
}
return true;
}
//从txt文件中读取中文,防止乱码
string UTF8ToGB(const char* str)
{
string result;
WCHAR *strSrc;
LPSTR szRes;
//获得临时变量的大小
int i = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);
strSrc = new WCHAR[i + 1];
MultiByteToWideChar(CP_UTF8, 0, str, -1, strSrc, i);
//获得临时变量的大小
i = WideCharToMultiByte(CP_ACP, 0, strSrc, -1, NULL, 0, NULL, NULL);
szRes = new CHAR[i + 1];
WideCharToMultiByte(CP_ACP, 0, strSrc, -1, szRes, i, NULL, NULL);
result = szRes;
delete[]strSrc;
delete[]szRes;
return result;
}
//读取文件 图片
bool Read_Data(string filename,vector<Mat>&dataset)
{
vector<String>imagePathList;
glob(filename, imagePathList);
if (imagePathList.empty())return false;
for (int i = 0; i < imagePathList.size(); i++)
{
Mat image = imread(imagePathList[i]);
resize(image, image, Size(50, 100), 1, 1, INTER_LINEAR);
dataset.push_back(image);
}
return true;
}
//读取文件 标签
bool Read_Data(string filename, vector<string>&data_name)
{
fstream fin;
fin.open(filename, ios::in);
if (!fin.is_open())
{
cout << "can not open the file!" << endl;
return false;
}
string s;
while (std::getline(fin, s))
{
string str = UTF8ToGB(s.c_str()).c_str();
data_name.push_back(str);
}
fin.close();
return true;
}
//计算像素点个数
int pixCount(Mat image)
{
int count = 0;
if (image.channels() == 1)
{
for (int i = 0; i < image.rows; i++)
{
for (int j = 0; j < image.cols; j++)
{
if (image.at<uchar>(i, j) == 0)
{
count++;
}
}
}
return count;
}
else
{
return -1;
}
}
//识别车牌字符
bool License_Recognition(vector<License>&Character_ROI, vector<int>&result_index)
{
string filename = "data/";
vector<Mat>dataset;
if (!Read_Data(filename, dataset)) return false;
for (int i = 0; i < Character_ROI.size(); i++)
{
Mat roi_gray;
cvtColor(Character_ROI[i].mat, roi_gray, COLOR_BGR2GRAY);
Mat roi_thresh;
threshold(roi_gray, roi_thresh, 0, 255, THRESH_BINARY | THRESH_OTSU);
int minCount = 1000000;
int index = 0;
for (int j = 0; j < dataset.size(); j++)
{
Mat temp_gray;
cvtColor(dataset[j], temp_gray, COLOR_BGR2GRAY);
Mat temp_thresh;
threshold(temp_gray, temp_thresh, 0, 255, THRESH_BINARY | THRESH_OTSU);
//计算两张图片的像素差,以此判断两张图片是否相同
Mat dst;
absdiff(roi_thresh, temp_thresh, dst);
int count = pixCount(dst);
if (count < minCount)
{
minCount = count;
index = j;
}
}
result_index.push_back(index);
}
return true;
}
//显示最终效果
bool Draw_Result(Mat src, License &License_ROI, vector<License>&Character_ROI,vector<int>&result_index)
{
rectangle(src, License_ROI.rect, Scalar(0, 255, 0), 2);
vector<string>data_name;
if (!Read_Data("data_name.txt", data_name))return false;
for (int i = 0; i < Character_ROI.size(); i++)
{
//putText 中文显示会乱码,不进行中文显示
string str = data_name[result_index[i]]; //string 转 char
cout << str << " ";
putText(src, str, Point(License_ROI.rect.x + Character_ROI[i].rect.x, License_ROI.rect.y + Character_ROI[i].rect.y), 3, FONT_HERSHEY_PLAIN, Scalar(0, 0, 255), 2);
}
return true;
}
int main()
{
Mat src = imread("car.jpg");
if (src.empty())
{
cout << "No image!" << endl;
system("pause");
return -1;
}
License License_ROI;
if (Get_License_ROI(src, License_ROI))
{
vector<License>Character_ROI;
if (Get_Character_ROI(License_ROI, Character_ROI))
{
vector<int>result_index;
if (License_Recognition(Character_ROI, result_index))
{
Draw_Result(src, License_ROI, Character_ROI,result_index);
}
else
{
cout << "未能识别字符!" << endl;
system("pause");
return -1;
}
}
else
{
cout << "未能切割出字符!" << endl;
system("pause");
return -1;
}
}
else
{
cout << "未定位到车牌位置!" << endl;
system("pause");
return -1;
}
imshow("src", src);
waitKey(0);
system("pause");
return 0;
}

本文使用OpenCV C++进行车牌号识别,关键步骤有以下几点。
1、车牌定位。案例需求是进行车牌识别。那么我们就得知道车牌在什么位置。将车牌找到之后,需要将车牌切割出来,作为一个整体进行下面工作。
2、字符分割。我们得到了车牌,需要将车牌上的字符一一分割出来才能进行下面的识别工作。有个小细节就是需要将字符重新排序。
3、字符识别。我们将得到的字符与我们准备好的模板一一进行匹配。匹配算法有很多,大家可以自行尝试。我这里使用的是基于两幅图像的像素差进行图像比对。
需要说明的是:本案例是根据特定图像、特定需求设定的算法。并不具有鲁棒性。所有在图像预处理阶段很重要。我们需要提取出我们需要的图像特征,这样才能够进行后续的工作。所以本案例也只是使用传统的图像处理手段实现车牌识别功能。将大致流程作了一个说明,这里只提供一个参考作用!!!
注:关于有很多小伙伴提出的问题“ “ft2build.h": No such file or directory”。这是因为由于OpenCV putText 不支持显示中文,在本案例中,我为了显示中文,故编译了freetype库。如果大家觉得有需要的话,可以自行编译配置环境。如果觉得麻烦的话,将源码中的中文显示函数注释掉也是可以直接运行的。
freetype库下载地址:http://download.savannah.gnu.org/releases/freetype/
下载解压后,选择合适vs版本进行编译就可以啦!!!




编译好之后,像配置OpenCV环境一样,将include、lib文件配置在vs环境中就可以了
欢迎大家点赞、关注,可私信找我领取完整源码、模板图像以及测试图像!!!
欢迎大家交流学习!!!
导读语言模型给我们的生产生活带来了极大便利,但同时不少人也利用他们从事作弊工作。如何规避这些难辨真伪的文字所产生的负面影响也成为一大难题。在3月9日智源Live第33期活动「DetectGPT:判断文本是否为机器生成的工具」中,主讲人Eric为我们讲解了DetectGPT工作背后的思路——一种基于概率曲率检测的用于检测模型生成文本的工具,它可以帮助我们更好地分辨文章的来源和可信度,对保护信息真实、防止欺诈等方面具有重要意义。本次报告主要围绕其功能,实现和效果等展开。(文末点击“阅读原文”,查看活动回放。)Ericmitchell斯坦福大学计算机系四年级博士生,由ChelseaFinn和Chri
之前说过10之后的版本没有3dScan了,所以还是9.8的版本或者之前更早的版本。 3d物体扫描需要先下载扫描的APK进行扫面。首先要在手机上装一个扫描程序,扫描现实中的三维物体,然后上传高通官网,在下载成UnityPackage类型让Unity能够使用这个扫描程序可以从高通官网上进行下载,是一个安卓程序。点到Tools往下滑,找到VuforiaObjectScanner下载后解压数据线连接手机,将apk文件拷入手机安装然后刚才解压文件中的Media文件夹打开,两个PDF图打印第一张A4-ObjectScanningTarget.pdf,主要是用来辅助扫描的。好了,接下来就是扫描三维物体。将瓶
本文主要介绍在使用Selenium进行自动化测试或者任务时,对于使用了iframe的页面,如何定位iframe中的元素文章目录场景描述解决方案具体代码场景描述当我们在使用Selenium进行自动化测试的时候,可能会遇到一些界面或者窗体是使用HTML的iframe标签进行承载的。对于iframe中的标签,如果直接查找是无法找到的,会抛出没有找到元素的异常。比如近在咫尺的例子就是,CSDN的登录窗体就是使用的iframe,大家可以尝试通过F12开发者模式查看到的tag_name,class_name,id或者xpath来定位中的页面元素,会抛出NoSuchElementException异常。解决
Heroku支持人员告诉我,为了在我的Web应用程序中使用自定义字体(未安装在系统中,您可以在bash控制台中使用fc-list查看已安装的字体)我必须部署一个包含所有字体的.fonts文件夹里面的字体。问题是我不知道该怎么做。我的意思是,我不知道文件名是否必须遵循heroku的任何特殊模式,或者我必须在我的代码中做一些事情来考虑这种字体,或者如果我将它包含在文件夹中它是自动的......事实是,我尝试以不同的方式更改字体的文件名,但根本没有使用该字体。为了提供更多详细信息,我们使用字体的过程是将PDF转换为图像,更具体地说,使用rghostgem。并且最终图像根本不使用自定义字体。在
@作者:SYFStrive @博客首页:HomePage📜:微信小程序📌:个人社区(欢迎大佬们加入)👉:社区链接🔗📌:觉得文章不错可以点点关注👉:专栏连接🔗💃:感谢支持,学累了可以先看小段由小胖给大家带来的街舞👉微信小程序(🔥)目录自定义组件-behaviors 1、什么是behaviors 2、behaviors的工作方式 3、创建behavior 4、导入并使用behavior 5、behavior中所有可用的节点 6、同名字段的覆盖和组合规则总结最后自定义组件-behaviors 1、什么是behaviorsbehaviors是小程序中,用于实现
在我让另一个人重做我的前端UI之前,我的Rails应用程序运行平稳。我已经尝试解决此错误3天了。这是错误:Nosuchfileordirectory-identifyExtractedsource(aroundline#59):575859606162@post=Post.find(params[:id])authorize@postif@post.update_attributes(post_params)flash[:notice]="Postwasupdated."redirect_to[@topic,@post]else{"utf8"=>"✓","_method"=>"patc
如果我们有一个数组array=[1,1,0,0,2,3,0,0,0,3,3,3]我们如何识别给定数字的运行(具有相同值的连续数字的数量)?例如:run_pattern_for(array,0)->2run_pattern_for(array,3)->1run_pattern_for(array,1)->1run_pattern_for(array,2)->0没有2的运行,因为没有连续出现2。3有一个运行,因为只有一个幻影以树为连续数字。 最佳答案 尝试:classArraydefcount_runs(element)chunk{|n
技术选型1,前端小程序原生MINA框架cssJavaScriptWxml2,管理后台云开发Cms内容管理系统web网页3,数据后台小程序云开发云函数云开发数据库(基于MongoDB)云存储4,人脸识别算法基于百度智能云实现人脸识别一,用户端效果图预览老规矩我们先来看效果图,如果效果图符合你的需求,就继续往下看,如果不符合你的需求,可以跳过。1-1,登录注册页可以看到登录页有注册入口,注册页如下我们的注册,需要管理员审核,审核通过后才可以正常登录使用小程序1-2,个人中心页登录成功以后,我们会进入个人中心页我们在个人中心页可以注册人脸,因为我们做人脸识别签到,需要先注册人脸才可以进行人脸比对,进
当尝试创建一个heroku应用程序并通过git推送到它时,我收到以下错误:$herokucreate'"C:\ProgramFiles\ruby-1.9.2\bin\ruby.exe"isnotrecognizedasaninternalorexternalcommand,operableprogramorbatchfile.但是,$ruby-vruby1.9.3p125[i386-mingw32]我已经检查了PATH环境,它肯定包含“C:\ProgramFiles(x86)\ruby-1.9.2\bin”。同样有趣的是,当导航到该目录时,它实际上并不包含名为ruby.exe的文件
有点边缘情况,但知道为什么&&=会这样吗?我正在使用1.9.2。obj=Object.newobj.instance_eval{@bar&&=@bar}#=>nil,expectedobj.instance_variables#=>[],soobjhasno@barinstancevariableobj.instance_eval{@bar=@bar&&@bar}#ostensiblythesameas@bar&&=@barobj.instance_variables#=>[:@bar]#whywouldthisversioninitialize@bar?为了比较,||=将实例变量初始