開發環境: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);
}
歡迎交流學習,若有不足,還望指正!