开发环境:Qt
车牌定位的大致流程:
- 读取原图
imread(); - 灰度化操作
cvtColor();
或者在读取原图时直接做灰度化处理 - 滤波去噪
blur(); - 梯度运算(sobel算子)
- 阈值化操作(二值化)
threshold();// CV_THRESH_OTSU大律法 - 利用形态学闭运算(先膨胀再腐蚀),闭运算能够排除小型黑洞,将白色区域连成一块。膨胀操作会使物体的边界向外扩张,如果物体内部存在小空洞的话,经过膨胀操作这些洞将被补上,因而不再是边界了。再进行腐蚀操作时,外部边界将变回原来的样子,而这些内部空洞则永远消失了。
- 轮廓检测
//Point->vector<Point>->vector<vector<Point>>//一个轮廓由几个点构成 vector<vector<Point>> contours;//定义存储轮廓的向量 //轮廓又是 点的向量 //CV_RETR_EXTERNAL:只检测外轮廓 //CV_CHAIN_APPROX_SIMPLE 近似方法:只存储垂直/水平/对角直线的起始点-->就是只需要边缘点 findContours(closed,contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
- 绘制轮廓
- 求轮廓的最小外接矩形
- 验证过滤,通过面积和宽高比,验证是否为车牌区域
int i; // key(整型下标), value(旋转矩形-待选车牌区域) map<int, RotatedRect> imap; //映射/哈希,保存 待选车牌区域 for(i=0; i<contours.size(); i++) { // 所有轮廓 下标 颜色 线条粗细 //drawContours(thresh, contours, i, Scalar(255), 3); //9.求轮廓最小外接矩形 RotatedRect rect;//RotatedRect:旋转矩形 rect = minAreaRect(contours[i]); //10.验证:过滤-->通过面积和宽高比 验证是否为车牌区域 if(!verify(rect)) continue; else imap[i] = rect;//哈希存储,用数组下标的方式-->第几个轮廓以及它的旋转矩形 //https://www.cnblogs.com/fnlingnzb-learner/p/5833051.html //获取每个矩形的四个顶点 Point2f vertices[4]; rect.points(vertices); //画直线,看效果 int j; for(j=0; j<4; j++) line(closed,vertices[j],vertices[(j+1)%4], Scalar(255), 3); }
- 利用矩形度来验证
利用迭代器,通过循环找出矩形度和标准车牌最接近的矩形/* * 矩形度:周长的平方除以面积 * (2w+2h)*(2w+2h)/w*h * (4w^2 + 4h^2 + 4w*h)/w*h * 4w/h + 4h/w + 8 * 4*4.7272 + 4*(1/4.7272) + 4 = 27.75 */ float min_diff = 10000;//任意初始值 int index = 0; map<int,RotatedRect>::iterator iter;//迭代器 for(iter = imap.begin(); iter!=imap.end(); iter++)//通过循环,找出矩形度与标准车牌的矩形度最接近的 { //求面积: int area = contourArea(contours[iter->first]); int perimeter = arcLength(contours[iter->first],true); if(area != 0) { int squareness = perimeter * perimeter / area;//矩形度 float diff = abs(squareness - 27.75);//差值 if(diff<min_diff) { min_diff = diff;//当前最小差值 index = iter->first;//对应的下标 } } }
- 绘制最接近的矩形
- 图像切割
以下为本模块全部代码:
#include "locate.h"
#include <opencv.hpp>
#include <map>
using namespace cv;
#define Debug 1
void myDebug(const string &winname, const Mat &image)//调试代码
{
#if Debug
imshow(winname,image);
#endif
}
bool verify(RotatedRect rect)//宽高比的算法
{
const float aspect = 4.7272;//宽高比(西班牙车牌)
int min = 15 * aspect * 15;//面积下限(宽*高)
int max = 125 * aspect * 125;//面积上限
//宽高比误差范围
float error = 0.1;
float rmin = aspect - aspect * error; //误差
float rmax = aspect + aspect * error;
int area = rect.size.width * rect.size.height; //实际面积
float rate = rect.size.width / rect.size.height; //实际宽高比
return area>=min && area<=max && rate>=rmin && rate<=rmax;
}
void locate(const string &filename)
{
//1.读入图像
Mat image;
image = imread(filename);
if(image.empty())
return;
myDebug("src",image);
//2.灰度化
Mat gray;
cvtColor(image,gray,COLOR_BGR2GRAY);
myDebug("gray",gray);
//3.滤波去噪
Mat blured;
blur(gray,blured,Size(5,5));
myDebug("blured",blured);
//4.梯度运算,求边缘--定位出可能的位置-->利用sobel算子
Mat xsobel,ysobel;
Sobel(blured, xsobel, CV_8U, 1, 0, 3);
Sobel(blured, ysobel, CV_8U, 0, 1, 3);
myDebug("xsobel",xsobel);
myDebug("ysobel",ysobel);
//5.阈值化--得到最优的二值图像
Mat thresh;
threshold(xsobel,thresh, 0, 255, CV_THRESH_OTSU);//大律法
myDebug("thresh",thresh);
//6.形态学闭运算--白色连成一片-->获取结构元素/闭运算
//使用闭运算(先膨胀再腐蚀)连通临近区域,说白了就是将亮的区域连在一起
Mat closed;
Mat element = getStructuringElement(MORPH_RECT,Size(17,3));
morphologyEx(thresh,closed,MORPH_CLOSE,element);
myDebug("closed",closed);
//7.轮廓检测
//Point->vector<Point>->vector<vector<Point>>//一个轮廓由几个点构成
vector<vector<Point>> contours;//定义存储轮廓的向量
//轮廓又是 点的向量
//CV_RETR_EXTERNAL:只检测外轮廓
//CV_CHAIN_APPROX_SIMPLE 近似方法:只存储垂直/水平/对角直线的起始点-->就是只需要边缘点
findContours(closed,contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
//8.绘制轮廓
int i;
// key(整型下标), value(旋转矩形-待选车牌区域)
map<int, RotatedRect> imap; //映射/哈希,保存 待选车牌区域
for(i=0; i<contours.size(); i++)
{
// 所有轮廓 下标 颜色 线条粗细
//drawContours(thresh, contours, i, Scalar(255), 3);
//9.求轮廓最小外接矩形
RotatedRect rect;//RotatedRect:旋转矩形
rect = minAreaRect(contours[i]);
//10.验证:过滤-->通过面积和宽高比 验证是否为车牌区域
if(!verify(rect))
continue;
else
imap[i] = rect;//哈希存储,用数组下标的方式-->第几个轮廓以及它的旋转矩形
//https://www.cnblogs.com/fnlingnzb-learner/p/5833051.html
//获取每个矩形的四个顶点
Point2f vertices[4];
rect.points(vertices);
//画直线,看效果
int j;
for(j=0; j<4; j++)
line(closed,vertices[j],vertices[(j+1)%4], Scalar(255), 3);
}
myDebug("contours", closed);
cout << imap.size() << endl;
//11.利用矩形度来验证
/*
矩形度:周长的平方除以面积
(2w+2h)*(2w+2h)/w*h
(4w^2 + 4h^2 + 4w*h)/w*h
4w/h + 4h/w + 8
4*4.7272 + 4*(1/4.7272) + 4 = 27.75
*/
float min_diff = 10000;//任意初始值
int index = 0;
map<int,RotatedRect>::iterator iter;//迭代器
for(iter = imap.begin(); iter!=imap.end(); iter++)//通过循环,找出矩形度与标准车牌的矩形度最接近的
{
//求面积:
int area = contourArea(contours[iter->first]);
int perimeter = arcLength(contours[iter->first],true);
if(area != 0)
{
int squareness = perimeter * perimeter / area;//矩形度
float diff = abs(squareness - 27.75);//差值
if(diff<min_diff)
{
min_diff = diff;//当前最小差值
index = iter->first;//对应的下标
}
}
}
//12.绘制最接近的矩形
RotatedRect rect2 = imap[index];
Point2f vertices2[4];
rect2.points(vertices2);
for(int i=0; i<4; i++)
{
line(image,vertices2[i],vertices2[(i+1)%4], Scalar(0,255,0), 3);
}
myDebug("final",image);
//13.图像切割 始终保持 宽 > 高
Mat locate;
RotatedRect rest = imap[index];
Size size = rest.size;
if(size.width < size.height)
swap(size.width, size.height);
// 原图像 大小(宽高) 中心点 输出结果
getRectSubPix(image, size, rest.center, locate);
imwrite("locate1.jpg",locate);
waitKey(0);
}
欢迎交流学习,若有不足,还望指正!