基於OpenCV的車牌識別(Sobel、顏色定位)
車牌識別大體上需要經歷過Sobel定位、顏色定位、SVM對定位來的候選車牌進行評測,給出評分,最後通過提取HOG特徵按照訓練模型進入ANN識別。
這一章節介紹 定位相關的邏輯程式碼,其中定位用到 Sobel定位(邊緣檢測定位), 顏色定位:對應程式碼裡的CarSobelPlateLocation,CarColorPlateLocation;兩者定位後得到一些候選的圖片,把這些圖片送去SVM進行評測,SVM基於HOG提取邊緣資訊特徵,HOG類同之前處理紋理特徵的LBP,專案程式碼在Clion上開發的,原始碼地址前往 ofollow,noindex">車牌定位
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 複製程式碼
測試最終取出來的就是我們的車牌選圖了。