这篇文章主要为大家详细介绍了OpenCV3实现车牌识别功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
本文实例为大家分享了OpenCV3实现车牌识别的具体代码,供大家参考,具体内容如下
车牌识别(基于OpenCV3.4.7+VS2017)
视频识别
蓝色车牌识别
视觉入坑的第一个Demo(注释很详细),因为本人之前拖延,一直没能写详细实现博客,先将代码贴出来供大家交流,个人认为精华部分在字符切割(直接用指针遍历像素加限制条件切割),车牌模板已上传,整个工程也已上传,后续完善各环节实现步骤详解。
头文件:Global.h
#ifdef GLOBAL
extern int flag_1;
extern bool flag;
extern bool specialFlag;
extern int captureRead
extern string carPlate;
extern char test[10];
extern struct stu1
{
char number;
Mat image;
double matchDegree;
};
extern struct stu
{
Mat image;
double matchDegree;
};
#endif唯一的.cpp文件:PlateIdentify.cpp(说实话,这Demo挺 “C” 的)
#include <opencv2/opencv.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<opencv2/highgui/highgui.hpp>
#include"Global.h"
#include <windows.h>
#include <string>
using namespace std;
using namespace cv;
void fillHole(const Mat srcBw, Mat &dstBw); //填补算法
Mat cutOne(Mat cutImage); //边框切割算法
void CharCut(Mat srcImage); //单个字符切割算法
Mat Location(Mat srcImage); //图像识别算法
void SingleCharCut(Mat doubleImage, int k1, int k2);
void ShowChar();
void MatchProvince();
void MatchNumber();
void readProvince();
void readNumber();
void VideoShow(Mat videoImage);
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, bool italic, bool underline);
int flag_1; //判断是否倾斜,需不需要二次定位车牌
bool flag; //判断提取是否成功
bool specialFlag = false; //针对嵌套车牌
int captureRead = 0;
int videoFlag = 0;
string carPlateProvince = " ";
string carPlate = " ";
char test[10];
vector<Mat> singleChar; //字符图片容器
int main(int argc, char *argv[])
{
//计时开始
double time0 = static_cast<double>(getTickCount());
//视频操作
VideoCapture capture("1.mp4");
Mat srcImage;
Mat theFirst;
int singleCharLength;
//读取字符图片
readProvince();
readNumber();
while (1) {
capture >> srcImage;
try {
if (!srcImage.data) { printf("视频识别结束 \n"); return 0; }
if (srcImage.rows >= srcImage.cols)
{
resize(srcImage, srcImage, Size(640, 640 * srcImage.rows / srcImage.cols));
}
else
{
resize(srcImage, srcImage, Size(400 * srcImage.cols / srcImage.rows, 400));
}
//车牌定位
theFirst = Location(srcImage);
if (flag)
{
if (flag_1 == 1) //旋转后要再次定位去上下杂边
{
theFirst = Location(theFirst);
flag_1 = 0;
}
}
if (flag)
{
//车牌切割(切割上下边,去除干扰)
theFirst = cutOne(theFirst);
//单个字符切割
CharCut(theFirst);
singleCharLength = singleChar.size();
printf("采取字符轮廓数 %d\n", singleCharLength);
ShowChar();
if (singleCharLength >= 7)
{
MatchProvince();
MatchNumber();
}
singleChar.clear();
}
}
catch (Exception e) {
cout << "Standard ecxeption : " << e.what() << " \n" << endl;
}
if (waitKey(30) >= 0)
break;
//延时30ms
}
//imwrite("match\\xxxxxx.bmp", singleChar[2]);
time0 = ((double)getTickCount() - time0) / getTickFrequency();
cout << "运行时间" << time0 << "秒" << endl;
waitKey(0);
}
void fillHole(const Mat srcBw, Mat &dstBw)
{
Size imageSize = srcBw.size();
Mat Temp = Mat::zeros(imageSize.height + 2, imageSize.width + 2, srcBw.type());//延展图像
srcBw.copyTo(Temp(Range(1, imageSize.height + 1), Range(1, imageSize.width + 1)));
cv::floodFill(Temp, Point(0, 0), Scalar(255));
Mat cutImg;//裁剪延展的图像
Temp(Range(1, imageSize.height + 1), Range(1, imageSize.width + 1)).copyTo(cutImg);
dstBw = srcBw | (~cutImg);
}
Mat Location(Mat srcImage)
{
//判断变量重赋值
flag = false;
//用于旋转车牌
int imageWidth, imageHeight; //输入图像的长和宽
imageWidth = srcImage.rows; //获取图片的宽
imageHeight = srcImage.cols; //获取图像的长
//!!!!!!!!!!!!!!!!!!!
Mat blueROI = srcImage.clone();
cvtColor(blueROI, blueROI, CV_BGR2HSV);
//namedWindow("hsv图");
//imshow("hsv图", blueROI);
//中值滤波操作
medianBlur(blueROI, blueROI, 3);
//namedWindow("medianBlur图");
//imshow("medianBlur图", blueROI);
//将蓝色区域二值化
inRange(blueROI, Scalar(100, 130, 50), Scalar(124, 255, 255), blueROI);
//namedWindow("blue图");
//imshow("blue图", blueROI);
Mat element1 = getStructuringElement(MORPH_RECT, Size(2, 2)); //size()对速度有影响
morphologyEx(blueROI, blueROI, MORPH_OPEN, element1);
//namedWindow("0次K运算后图像");
//imshow("0次K运算后图像", blueROI);
Mat element0 = getStructuringElement(MORPH_ELLIPSE, Size(10, 10)); //size()对速度有影响
morphologyEx(blueROI, blueROI, MORPH_CLOSE, element0);
//namedWindow("0次闭运算后图像");
//imshow("0次闭运算后图像", blueROI);
vector<vector<Point>> contours;
findContours(blueROI, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
int cnt = contours.size();
cout << "number of contours " << cnt << endl; //打印轮廓个数
if (cnt == 0)
{
if (!flag) //在视频中显示
{
cout << "图中无车牌 " << endl;
//namedWindow("提取车牌结果图");
//imshow("提取车牌结果图", srcImage); //显示最终结果图
VideoShow(srcImage);
return srcImage;
}
}
double area;
double longside, temp, shortside, long2short;
float angle = 0;
Rect rect;
RotatedRect box; //可旋转的矩形盒子
Point2f vertex[4]; //四个顶点
Mat image = srcImage.clone(); //为后来显示做准备
Mat rgbCutImg; //车牌裁剪图
//box.points(vertex); //获取矩形四个顶点坐标
//length=arcLength(contour[i]); //获取轮廓周长
//area=contourArea(contour[i]); //获取轮廓面积
//angle=box.angle; //得到车牌倾斜角度
for (int i = 0; i < cnt; i++)
{
area = contourArea(contours[i]); //获取轮廓面积
if (area > 600 && area < 15000) //矩形区域面积大小判断
{
rect = boundingRect(contours[i]); //计算矩形边界
box = minAreaRect(contours[i]); //获取轮廓的矩形
box.points(vertex); //获取矩形四个顶点坐标
angle = box.angle; //得到车牌倾斜角度
longside = sqrt(pow(vertex[1].x - vertex[0].x, 2) + pow(vertex[1].y - vertex[0].y, 2));
shortside = sqrt(pow(vertex[2].x - vertex[1].x, 2) + pow(vertex[2].y - vertex[1].y, 2));
if (shortside > longside) //短轴大于长轴,交换数据
{
temp = longside;
longside = shortside;
shortside = temp;
cout << "交换" << endl;
}
else
angle += 90;
long2short = longside / shortside;
if (long2short > 1.5 && long2short < 4.5)
{
flag = true;
for (int i = 0; i < 4; ++i) //划线框出车牌区域
line(image, vertex[i], vertex[((i + 1) % 4) ? (i + 1) : 0], Scalar(0, 255, 0), 1, CV_AA);
if (!flag_1) //在视频中显示
{
printf("提取成功\n");
/*namedWindow("提取车牌结果图");
imshow("提取车牌结果图", image); */ //显示最终结果图
VideoShow(image);
}
rgbCutImg = srcImage(rect);
//namedWindow("车牌图");
//imshow("车牌图", rgbCutImg);//裁剪出车牌
break; //退出循环,以免容器中变量变换
}
}
}
cout << "倾斜角度:" << angle << endl;
if (flag && fabs(angle) > 0.8) //车牌过偏,转一下 偏移角度小时可不调用,后续找到合适范围再改进
{
flag_1 = 1;
Mat RotractImg(imageWidth, imageHeight, CV_8UC1, Scalar(0, 0, 0)); //倾斜矫正图片
Point2f center = box.center; //获取车牌中心坐标
Mat M2 = getRotationMatrix2D(center, angle, 1); //计算旋转加缩放的变换矩阵
warpAffine(srcImage, RotractImg, M2, srcImage.size(), 1, 0, Scalar(0)); //进行倾斜矫正
//namedWindow("倾斜矫正后图片",0);
//imshow("倾斜矫正后图片", RotractImg);
rgbCutImg = RotractImg(rect); //截取车牌彩色照片
//namedWindow("矫正后车牌照");
//imshow("矫正后车牌照", rgbCutImg);
/*cout << "矩形中心:" << box.center.x << "," << box.center.y << endl;*/
return rgbCutImg;
}
if (flag == false) {
printf("提取失败\n"); //后期加边缘检测法识别
if (!flag_1) //在视频中显示
{
/*namedWindow("提取车牌结果图");
imshow("提取车牌结果图", image); */ //显示最终结果图
VideoShow(image);
}
}
return rgbCutImg;
}
Mat cutOne(Mat cutImage)
{
//打印车牌长宽
try {
/*cout << " cutImage.rows : " << cutImage.rows << endl;
cout << " cutImage.cols : " << cutImage.cols << endl;*/
if(cutImage.rows >= cutImage.cols)
resize(cutImage, cutImage, Size(320, 320 * cutImage.rows / cutImage.cols));
}
catch (Exception e)
{
resize(cutImage, cutImage, Size(320, 100));
}
/*namedWindow("Resize车牌图");
imshow("Resize车牌图", cutImage);*/
int height = cutImage.rows;
cout << "\tHeight:" << height << "\tWidth:" << 320 << endl;
if (height < 86)
{
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!处理新型嵌套车牌!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
printf("嵌套车牌\n");
specialFlag = true;
}
Mat whiteROI = cutImage.clone();
if (specialFlag)
{
cvtColor(whiteROI, whiteROI, CV_BGR2HSV);
//将白色区域二值化
//inRange(whiteROI, Scalar(0, 0, 0), Scalar(130, 50, 245), whiteROI); //增大 S 即饱和度可以使hsv白色检测范围更大
inRange(whiteROI, Scalar(0, 0, 0), Scalar(180, 100, 245), whiteROI);
//namedWindow("specialFlagwhiteROI图");
//imshow("specialFlagwhiteROI图", whiteROI);
}
else
{
GaussianBlur(whiteROI, whiteROI, Size(3, 3), 0, 0);
/*namedWindow("GaussianBlur车牌图");
imshow("GaussianBlur车牌图", whiteROI);*/
cvtColor(whiteROI, whiteROI, CV_BGR2HSV);
//medianBlur(whiteROI, whiteROI, 3);
//namedWindow("Src_medianBlur图");
//imshow("Src_medianBlur图", whiteROI);
//将白色区域二值化
//inRange(whiteROI, Scalar(0, 0, 10), Scalar(180, 30, 255), whiteROI); //增大 S 即饱和度可以使hsv白色检测范围更大
inRange(whiteROI, Scalar(0, 0, 10), Scalar(180, 120, 255), whiteROI);
//namedWindow("whiteROI图");
//imshow("whiteROI图", whiteROI);
}
/*
Mat element0 = getStructuringElement(MORPH_ELLIPSE, Size(4, 4)); //size()对速度有影响
morphologyEx(whiteROI, whiteROI, MORPH_OPEN, element0);
namedWindow("OPEN图");
imshow("OPEN图", whiteROI);
*/
Mat dstImage = cutImage.clone();
vector<vector<Point>> contours;
findContours(whiteROI, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
drawContours(dstImage, contours, -1, Scalar(0, 0, 255), 1);
//namedWindow("疑似字符轮廓识别图");
//imshow("疑似字符轮廓识别图", dstImage);
inRange(dstImage, Scalar(0, 0, 255), Scalar(0, 0, 255), dstImage);
//namedWindow("字符大轮廓图");
//imshow("字符大轮廓图", dstImage);
/*fillHole(dstImage, dstImage);
namedWindow("填补轮廓后图");
imshow("填补轮廓后图", dstImage);*/
int row1 = 2;
int row2 = dstImage.rows;
int rowMax = dstImage.rows - 1; //开区间,防止越界
int colMax = dstImage.cols - 1; //开区间,防止越界
int addFirst = 10;
int addFirst0 = 0;
int addFirst1 = 0;
int addFirst2 = 0;
//测中间像素
//dstImage.at<uchar>(rowMax-1, colMax-1);
//cout << "Width:" << j << endl;
int addFirstTemp = addFirst; //第一次用时已经改变数值,容易忽略!!!!!
uchar* data;
//裁剪上下边。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
//上边
for (int i = 2; i < rowMax / 3; i++, addFirst1 = 0) // 6 刚刚好
{
data = dstImage.ptr<uchar>(i);
for (int j = 2; j < colMax; j++)
{
if (data[j] == 255)
{
addFirst1++;
}
}
if (addFirst1 < addFirst) //筛选最小值所在行
{
row1 = i;
addFirst = addFirst1 + 3;
//cout << "行头" << row1 << endl;
//flag_x = 1;
}
}
//下边
for (int i = rowMax - 2; i > rowMax - rowMax / 4; i--, addFirst2 = 0) // 6 刚刚好
{
data = dstImage.ptr<uchar>(i);
for (int j = 2; j < colMax; j++)
{
if (data[j] == 255)
{
addFirst2++;
}
}
if (addFirst2 < addFirstTemp) //筛选最小值所在行
{
row2 = i;
addFirstTemp = addFirst2 + 3;
//cout << "行底" << row2 << endl;
//flag_y = 1;
}
}
int orow;
orow = row2 - row1;
Mat w_image;
Mat rgb_w_image;
w_image = dstImage(Rect(0, row1, colMax, orow));
rgb_w_image = cutImage(Rect(0, row1, colMax, orow));
//namedWindow("裁剪上下图");
//imshow("裁剪上下图", w_image);
int rowMax_ALT = w_image.rows - 1; //开区间,防止越界(注意,裁剪完上下后要重新写行和宽,因为行和宽已经改变)
int colMax_ALT = w_image.cols - 1; //开区间,防止越界(注意,裁剪完上下后要重新写行和宽,因为行和宽已经改变)
int col_1 = 2;
int col_2 = w_image.cols;
int add = 2;
int add1 = 0;
int add2 = 0;
int addTemp = add; //第一次用时已经改变数值,容易忽略!!!!!
//裁剪左右边。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
//左边
//for (int i = 0; i < colMax_ALT / 18; i++, add1 = 0) // 刚刚好
//{
// for (int j = 2; j < rowMax_ALT; j++)
// {
// data = dstImage.ptr<uchar>(j);
// if (data[i] == 255)
// {
// add1++;
// }
// }
// if (add1 < add) //筛选最小值所在列
// {
// col_1 = i;
// add = add1 + 1;
// }
/
织梦狗教程
本文标题为:OpenCV3实现车牌识别(C++版)
基础教程推荐
猜你喜欢
- [C语言]二叉搜索树 2023-09-07
- 全面了解C语言 static 关键字 2023-03-26
- C++实战之二进制数据处理与封装 2023-05-29
- 带你深度走入C语言取整以及4种函数 2022-09-17
- C语言实现宾馆管理系统课程设计 2023-03-13
- C语言编程C++旋转字符操作串示例详解 2022-11-20
- C++实现ETW进行进程变动监控详解 2023-05-15
- C语言 详解字符串基础 2023-03-27
- [c语言-函数]不定量参数 2023-09-08
- centos 7 vscode cmake 编译c++工程 2023-09-17
