車牌識別大體上需要經歷過Sobel定位、顏色定位、SVM對定位來的候選車牌進行評測,給出評分,最後通過提取HOG特徵按照訓練模型進入ANN識別。
這一章節介紹 定位相關的邏輯程式碼,其中定位用到 Sobel定位(邊緣檢測定位), 顏色定位:對應程式碼裡的CarSobelPlateLocation,CarColorPlateLocation;兩者定位後得到一些候選的圖片,把這些圖片送去SVM進行評測,SVM基於HOG提取邊緣資訊特徵,HOG類同之前處理紋理特徵的LBP,專案程式碼在Clion上開發的,原始碼地址前往車牌定位
Sobel定位
CarSobelPlateLocation,通過以下的一些步驟進行降噪:
- 高斯模糊
- 灰度化
- 邊緣化
- 二值化
- 閉操作
高斯模糊
//預處理 :去噪 讓車牌區域更加突出
Mat blur;
//1、高斯模糊(平滑) (1、為了後續操作 2、降噪 )
GaussianBlur(src, blur, Size(5, 5), 0);
//imshow("高斯模糊",blur);
複製程式碼
灰度化
Mat gray;
//2、灰度化 去掉顏色 因為它對於我們這裡沒用 降噪
cvtColor(blur, gray, COLOR_BGR2GRAY);
imshow("灰度", gray);
複製程式碼
邊緣化
Mat sobel_16;
//3、 邊緣檢測 讓車牌更加突出 在呼叫時需要以16位來儲存資料 在後續操作 以及顯示的時候需要轉回8位
Sobel(gray, sobel_16, CV_16S, 1, 0);
//轉為8位
Mat sobel;
convertScaleAbs(sobel_16, sobel);
imshow("Sobel", sobel);
複製程式碼
二值化
//4. 二值化 黑白
Mat shold;
//大律法 最大類間演算法
threshold(sobel, shold, 0, 255, THRESH_OTSU + THRESH_BINARY);
imshow("二值", shold);
複製程式碼
閉操作
//5、閉操作
// 將相鄰的白色區域擴大 連線成一個整體
Mat close;
Mat element = getStructuringElement(MORPH_RECT, Size(17, 3));
morphologyEx(shold, close, MORPH_CLOSE, element);
imshow("閉操作", close);
複製程式碼
以上的操作是在處理降噪,第六步初步賽選。
第六步:最大面積、最小面積.寬高逼。
//6、查詢輪廓
//獲得初步篩選車牌輪廓================================================================
//輪廓檢測
vector< vector<Point>> contours;
//查詢輪廓 提取最外層的輪廓 將結果變成點序列放入 集合
findContours(close, contours, RETR_EXTERNAL, CHAIN_APPROX_NONE);
//遍歷
vector<RotatedRect> vec_sobel_roi;
for(vector<Point> point:contours){
RotatedRect rotatedRect= minAreaRect(point);
//rectangle(src, rotatedRect.boundingRect(), Scalar(255, 0, 255));
//進行初步的篩選 把完全不符合的輪廓給排除掉 ( 比如:1x1,5x1000 )
if (verifySizes(rotatedRect)) {
vec_sobel_roi.push_back(rotatedRect);
}
}
複製程式碼
初步賽選:寬高比 float aspec,把不符合的刪除掉(1 * 1的, 5* 1000的等候選矩形)
int CarPlateLocation::verifySizes(RotatedRect rotated_rect) {
//容錯率
float error = 0.75f;
//訓練時候模型的寬高 136 * 32
//獲得寬高比
float aspect = float(136) / float(32);
//最小 最大面積 不符合的丟棄
//給個大概就行 隨時調整
//儘量給大一些沒關係, 這還是初步篩選。
int min = 20 * aspect * 20;
int max = 180 * aspect * 180;
//比例浮動 error認為也滿足
//最小寬、高比
float rmin = aspect - aspect * error;
//最大的寬高比
float rmax = aspect + aspect * error;
//矩形的面積
float area = rotated_rect.size.height * rotated_rect.size.width;
//矩形的比例
float r = (float) rotated_rect.size.width / (float) rotated_rect.size.height;
if ((area < min || area > max) || (r < rmin || r > rmax))
return 0;
return 1;
}
複製程式碼
把斜的圖片轉正:仿射變換
//1、矯正前 2、矯正後 3、矩形的大小 4、矩形中心點座標 5、角度
void CarPlateLocation::rotation(Mat src, Mat &dst, Size rect_size,
Point2f center, double angle) {
//獲得旋轉矩陣
Mat rot_mat = getRotationMatrix2D(center, angle, 1);
//運用仿射變換
Mat mat_rotated;
//矯正後 大小會不一樣,但是對角線肯定能容納
int max = sqrt(pow(src.rows, 2) + pow(src.cols, 2));
//仿射變換
warpAffine(src, mat_rotated, rot_mat, Size(max, max),
CV_INTER_CUBIC);
imshow("旋轉前", src);
imshow("旋轉", mat_rotated);
//擷取 儘量把車牌多餘的區域擷取掉
getRectSubPix(mat_rotated, Size(rect_size.width, rect_size.height), center, dst);
imshow("擷取", dst);
mat_rotated.release();
rot_mat.release();
}
複製程式碼
顏色定位
HSV顏色模型
色調(H), 飽和度(S), 明度(V);
BGR 轉成 HSV
cvtColor(src,hsv,COLOR_BGR2HSV);
複製程式碼
色調H
用角度度量,取值範圍為0°~360°,從紅色開始按逆時針方向計算,紅色為0°,綠色為120°,藍色為240°。它們的補色是:黃色為60°,青色為180°,品紅為300°;
飽和度S
飽和度S表示顏色接近光譜色的程度。一種顏色,可以看成是某種光譜色與白色混合的結果。其中光譜色所佔的比例愈大,顏色接近光譜色的程度就愈高,顏色的飽和度也就愈高。飽和度高,顏色則深而豔。光譜色的白光成分為0,飽和度達到最高。通常取值範圍為0%~100%,值越大,顏色越飽和。
明度V
明度表示顏色明亮的程度,對於光源色,明度值與發光體的光亮度有關;對於物體色,此值和物體的透射比或反射比有關。通常取值範圍為0%(黑)到100%(白)
在OpenCV中hsv 資料為8UC則取值分別為 0-180 0-255 0-255 ,即藍色應該是120
按照上面的表格找到藍色區域 (100 ~ 124), 然後將HSV中的H、S轉為 0, V變為255。其它區域的HSV賦值為0.
//3通道
int chanles = hsv.channels();
//高
int h = hsv.rows;
//寬資料長度
int w = hsv.cols * 3;
//判斷資料是否為一行儲存的
//記憶體足夠的話 mat的資料是一塊連續的記憶體進行儲存
if (hsv.isContinuous()) {
w *= h;
h = 1;
}
for (size_t i = 0; i < h; ++i) {
//第i 行的資料 hsv的資料 uchar = java byte
uchar *p = hsv.ptr<uchar>(i);
for (size_t j = 0; j < w; j += 3) {
int h = int(p[j]);
int s = int(p[j + 1]);
int v = int(p[j + 2]);
bool blue = false;
//藍色
if (h >= 100 && h <= 124 && s >= 43 && s <= 255 && v >= 46 && v <= 255) {
blue = true;
}
if (blue){
p[j] = 0;
p[j + 1]=0;
p[j + 2]=255;
}else {
//hsv 模型 h:0 紅色 亮度和飽和度都是0 ,也就變成了黑色
p[j] = 0;
p[j + 1] = 0;
p[j + 2] = 0;
}
}
}
複製程式碼
得到下面的圖:
接下來抽取亮度:
//把亮度資料抽出來
//把h、s、v分離出來
vector<Mat> hsv_split;
split(hsv, hsv_split);
複製程式碼
然後跟sobel一樣通過二值化、大律法等操作
// 整個圖片+經過初步賽選的車牌 + 得到的候選車牌
tortuosity(src, vec_sobel_roi, dst);
for (Mat s: dst) {
imshow("候選", s);
waitKey();
}
複製程式碼
篩選出來一個集合:
把兩個結合結合起來,然後通過SVM進行評測, 因為不像人臉檢測是沒有現成的模型。
vector< Mat > sobel_plates;
//sobel定位
plateLocation->location(src, sobel_plates);
//顏色定位
vector< Mat > color_plates;
plateColorLocation->location(src, color_plates);
vector<Mat> plates;
//把sobel_plates的內容 全部加入plates向量
plates.insert(plates.end(),sobel_plates.begin(), sobel_plates.end());
plates.insert(plates.end(), color_plates.begin(), color_plates.end());
複製程式碼
SVM
簡單來說,SVM就是用於區分不同的型別(車牌、非車牌)。SVM的訓練資料既有特徵又有標籤,通過訓練,讓機器可以自己找到特徵和標籤之間的聯絡,在面對只有特徵沒有標籤的資料時,可以判斷出標籤。屬於機器學習中的監督學習。線性可分、線性不可分,不可分的時候用核函式來區分:
核函式: 用於將不同型別進行提維
人臉檢測用的LBP提取特徵,這裡採取HOG來提取特徵。
SVM load模型, 模型是同樣是xml檔案
svm = SVM::load(svm_model);
CarPlateRecgnize p("/Users/xiuchengyin/Documents/Tina-NDK/OpencvCarRecgnize/resource/HOG_SVM_DATA2.xml");
複製程式碼
HOG特徵
區域性歸一化的梯度方向直方圖,是一種對影像區域性重疊區域的密集型描述符, 它通過計算區域性區域的梯度方向直方圖來構成特徵。
引數1(檢測視窗)的寬- 引數2(塊大小)的寬 結果與引數3(塊滑動增量)的餘數要為0 高也一樣
引數4是胞元大小,引數5是梯度方向
HOGDescriptor hog(Size(128, 64), Size(16, 16), Size(8, 8), Size(8, 8), 3);
初始化HOG變數
//引數1的寬-引數2的寬 結果與引數3的餘數為0 高也一樣
svmHog = new HOGDescriptor(Size(128,64),Size(16,16),Size(8,8),Size(8,8),3);
複製程式碼
檢測視窗被分為:((128-16)/8+1)*((64-16)/8+1)=105個塊(Block);
一個Block有4個胞元(Cell);
一個Cell的Hog描述子向量的長度是9;
統計梯度直方圖特徵,就是將梯度方向(0-360)劃分為x個區間,將影像化為16x16的若干個視窗,每個視窗又劃分為x個block,每個block再化為4個cell(8x8)。對每一個cell,算出每一畫素點的梯度方向,按梯度方向增加對應bin的值,最終綜合N個cell的梯度直方圖組成特徵。
簡單來說,車牌的邊緣與內部文字組成的一組資訊(在邊緣和角點的梯度值是很大的,邊緣和角點包含了很多物體的形狀資訊),HOG就是抽取這些資訊組成一個直方圖。
HOG : 梯度方向弱化光照的影響,適合捕獲輪廓。
LBP : 中心畫素的LBP值反映了該畫素周圍區域的紋理資訊。
SVM 依據HOG提取的特徵將所給的候選圖片進行評分,選取最優的:
string CarPlateRecgnize::plateRecgnize(Mat src) {
vector< Mat > sobel_plates;
//sobel定位
sobelPlateLocation->location(src, sobel_plates);
//顏色定位
vector< Mat > color_plates;
colorPlateLocation->location(src, color_plates);
vector< Mat > plates;
//把sobel_plates的內容 全部加入plates向量
plates.insert(plates.end(),sobel_plates.begin(), sobel_plates.end());
plates.insert(plates.end(), color_plates.begin(), color_plates.end());
int index = -1;
float minScore = FLT_MAX; //float的最大值
//使用 svm 進行 評測
for (int i = 0;i< plates.size();++i)
{
Mat plate = plates[i];
//先灰度化,再二值化,灰度化只剩下一個通道
Mat gray;
cvtColor(plate, gray,COLOR_BGR2GRAY);
//二值化 必須是以單通道進行
Mat shold;
threshold(gray, shold, 0, 255, THRESH_OTSU + THRESH_BINARY);
//提取特徵
Mat features;
getHogFeatures(svmHog, shold, features);
//features 進行轉化,把資料儲存成一行
Mat samples = features.reshape(1,1);
//轉化資料儲存格式
samples.convertTo(samples, CV_32FC1 );
//原始模式
// svm: 直接告訴你這個資料是屬於什麼型別.
// RAW_OUTPUT:讓svm 給出一個評分
// char name[100];
// sprintf(name, "候選車牌%d", i);
// imshow(name, plate);
float score = svm->predict(samples, noArray(), StatModel::Flags::RAW_OUTPUT);
printf("評分:%f\n",score);
if (score < minScore) {
minScore = score;
index = i;
}
gray.release();
shold.release();
features.release();
samples.release();
}
Mat dst;
if (index >= 0) {
dst = plates[index].clone();
}
// imshow("車牌", dst);
// waitKey();
// 釋放
for (Mat p : plates) {
p.release();
}
return string("123");
}
複製程式碼
svm評分如下:
/Users/xiuchengyin/Documents/Tina-NDK/OpencvCarRecgnize/cmake-build-debug/OpencvCarRecgnize
評分:-1.224322
評分:1.255759
評分:1.831937
評分:-0.070820
評分:1.525869
評分:1.117042
複製程式碼
測試最終取出來的就是我們的車牌選圖了。