学习目标:
- 目标识别
学习内容:
车牌OCR
学习时间:
- 2022年7月26日到2022年7月27日
学习产出:
因为之前用FPGA和基于python的cv做过车牌识别(大创),感觉战队C++用的多,这次用C++实现。
之前做的代码我已经开源、上传到我的仓库了,欢迎大家交流,不过没有完善好,会逐步推进的。
py链接,FPGA的还需要完善,目前对0和8的识别不是太行。
下面是战队的任务,就不过多讲解了。
头文件: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文件也是唯一的一个:
#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.s