opencv實現車牌識別之字元分割
阿新 • • 發佈:2018-12-31
簡介
在前一篇中,我們已經定位出來了在圖片中車牌號的位置,並且將車牌號圖片複製成了新圖片,並顯示出來,本章在這些被截取出來的圖片上繼續處理。 截取出來的新圖片如下:
影象灰階/二值化
首先也是選擇將影象進行灰階,然後採用以255一遍開始,取佔了總pixel為5%的地方作為閥值,進行二值化。 程式碼如下:
#include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <math.h> #include <string.h> #include <opencv/cv.h> #include <stdio.h> #include "lib/normal.h" #include "lib/cutchar.h" #define DEBUG #ifdef DEBUG #define DE(format, ...) printf(format, ## __VA_ARGS__) #else #define DE(format, ...) while(0) #endif int main(int argc, char** argv){ Mat img, img_2, img_3, img_4, img_5, img_w; IplImage pI_1; IplImage pI_2; int width, reWidth=30, wWidth=20, pic_width; int height, reHeight=100, wHeight = 20; char str[2]; int i = 0, j = 0, k; int threshold = 0, pic_ArrNumber, tmp; int vArr[reHeight]; int **pic_Arr; CvScalar s1; float percentage = 0.0; if(argc < 2){ DE("Please input argv[1]\n"); return -1; } img = cv::imread(argv[1]); namedWindow(str); imshow(str, img); width = img.rows; height = img.cols; pic_gray(img, img_2); threshold = histogram_Calculate(img_2, 5); DE("threshold:%d\n",threshold); pic_Thresholding(img_2, threshold); sprintf(str, "%d", i+1); namedWindow(str); imshow(str, img_2); waitKey(0); return 0; }
首先裝載截取出來的車牌號圖片到img,然後pic_gray進行灰階化到img_2,接著計算出5%時候的pixel閥值threshold,最後對灰階影象img_2進行二值化操作。 結果顯示如下:
上下邊緣分離
從圖片和周圍,我們知道車牌號的四周被白色的邊框包圍著,所以我們需要排除掉這部分干擾,這裡我們首先來去除掉邊框的上線邊緣干擾。 程式碼如下:
int detectionChange(Mat& mat1, Mat& mat2, int number){ IplImage pI_1 = mat1, pI_2; CvScalar s1, s2; int width = mat1.rows; int height = mat1.cols; int sum = 0, sum_2 = 0, width_1 = 0, width_2 = 0; int i, j; for(i=0; i<width; i++){ sum = 0; sum_2 = 0; for(j=0; j<height-1; j++){ s1 = cvGet2D(&pI_1, i, j); s2 = cvGet2D(&pI_1, i, j+1); if(((int)s1.val[0]) != ((int)s2.val[0])){ sum += 1; sum_2 = 0; }else{ sum_2 += 1; } if(sum_2 != 0){ if(height / sum_2 < 5){ sum = 0; break; } } } if(sum >= number){ width_1 = i; break; }else{ width_1 = i; } } for(i=width-1; i> 0; i--){ sum = 0; sum_2 = 0; for(j=0; j<height-1; j++){ s1 = cvGet2D(&pI_1, i, j); s2 = cvGet2D(&pI_1, i, j+1); if(((int)s1.val[0]) != ((int)s2.val[0])){ sum += 1; sum_2 = 0; }else{ sum_2 += 1; } if(sum_2 != 0){ if(height / sum_2 < 1){ sum = 0; break; } } } if(sum >= number){ width_2 = i; break; }else{ width_2 = i; } } if(width_2 <= width_1){ width_2 = width; } mat2 = cv::Mat(width_2 - width_1 + 1, height, CV_8UC1, 1); pI_2 = mat2; for(i=width_1; i<= width_2; i++){ for(j=0; j<height; j++){ s1 = cvGet2D(&pI_1, i, j); cvSet2D(&pI_2, i-width_1, j, s1); } } } int main(int argc, char** argv){ pic_Thresholding(img_2, threshold); sprintf(str, "%d", i+1); namedWindow(str); imshow(str, img_2); detectionChange(img_2, img_3, 7); sprintf(str, "%d", i+2); namedWindow(str); imshow(str, img_3); waitKey(0); return 0; }
重點就是函式detectionChange,在這個函式中主要是進行那個判斷,首先判斷一行中,是否有連續的255畫素大於了一定該行寬度的一定比例, 正常的牌照單個字元,它的字元寬度肯定小於整個車牌寬度的1/6;然後還判斷一行中pixel從0到255或者從255到0的跳變有沒有大於一定的數量,在 車牌號所在的行中,該跳變至少是7次。 detectionChange中首先將img_2從頭開始掃描,找到車牌號真正開始的行頭。然後反過來,從尾部開始掃描,找到車牌字元真正結束時候的尾部。 最後將這部分影象,複製到img_3中。 影象結果顯示如下:
字元分割
經過如上之後,接著就是根據車牌圖片的垂直投影寬度和積累的數值,進行字元分割。 具體程式碼如下:
void verProjection_calculate(Mat& mat1, int* vArr, int number){
IplImage pI_1 = mat1;
CvScalar s1;
int width = mat1.rows;
int height = mat1.cols;
int i, j;
for(i=0; i< number; i++){
vArr[i] = 0;
}
for(j=0; j<height; j++){
for(i=0; i<width; i++){
s1 = cvGet2D(&pI_1, i, j);
if(s1.val[0] > 20){
vArr[j] += 1;
}
}
}
}
int** verProjection_cut(int* vArr, int width, int* number){
int **a;
int i, flag = 0;
int num = 0;
int threshold = 2;
a = (int**)malloc(width / 2 * sizeof(int*));
for(i=0; i<width-1; i++){
if((vArr[i] <= threshold) && (vArr[i+1] > threshold)){
a[num] = (int* )malloc(2 * sizeof(int));
a[num][0] = i;
flag = 1;
}else if((vArr[i] > threshold) && (vArr[i+1] <= threshold) && (flag != 0)){
a[num][1] = i;
num += 1;
flag = 0;
}
}
*number = num;
return a;
int main(int argc, char** argv){
int width, reWidth=30, wWidth=20, pic_width;
int height, reHeight=100, wHeight = 20;
................
carCard_Resize(img_3, img_4, reWidth, reHeight);
pic_Thresholding(img_4, 60);
pI_1 = img_4;
verProjection_calculate(img_4, vArr, reHeight);
pic_Arr = verProjection_cut(vArr, reHeight, &pic_ArrNumber);
for(i=0; i< pic_ArrNumber; i++){
printf("pic_Arr[%d]:%d, %d\n", i, pic_Arr[i][0], pic_Arr[i][1]);
}
sprintf(str, "%d", i+3);
namedWindow(str);
imshow(str, img_4);
waitKey(0);
return 0;
}
這一步中,首先將消除了上下邊緣的img_3,放大儲存到img_4(reWidth=30,reHeight=100),接著將放大後圖片img_4從新以閥值60來二值化,接著用 verProjection_calculate計算出img_4的垂直投影資料,儲存到一維陣列vArr中;然後verProjection_cut函式利用垂直投影資料vArr來分割出字元寬度。 在verProjection_cut中,到某一列的垂直投影資料小於等於2,就表示該位置不是字元。 打印出來的字元分割寬度位置和影象表現如下:
後續處理
在寬度分割出來之後,就可以在img_4上將對應的字元圖片分割複製出來,然後在排除掉左右兩邊的邊緣干擾和車牌的中間那一點的干擾,就獲取到了合適的 車牌字元圖片了。對應程式碼如下:
float pixelPercentage(Mat& mat1){
IplImage pI_1 = mat1;
CvScalar s1;
int width = mat1.rows;
int height = mat1.cols;
int i, j;
float sum = 0, allSum = 0, tmp;
for(i=0; i<width; i++){
for(j=0; j<height; j++){
s1 = cvGet2D(&pI_1, i, j);
if(s1.val[0] > 20){
sum += 1;
}
allSum += 1;
}
}
tmp = sum / allSum;
return tmp;
}
int main(int argc, char** argv){
......................
verProjection_calculate(img_4, vArr, reHeight);
pic_Arr = verProjection_cut(vArr, reHeight, &pic_ArrNumber);
for(i=0; i< pic_ArrNumber; i++){
pic_width = pic_Arr[i][1] - pic_Arr[i][0];
if(pic_width < 3){
continue;
}
img_5 = cv::Mat(reWidth, pic_Arr[i][1] - pic_Arr[i][0], CV_8UC1, 1);
pI_2 = img_5;
for(j=0; j<reWidth; j++){
for(k=pic_Arr[i][0]; k<pic_Arr[i][1]; k++){
s1 = cvGet2D(&pI_1, j, k);
cvSet2D(&pI_2, j, k-pic_Arr[i][0], s1);
}
}
percentage = pixelPercentage(img_5);
if(percentage < 0.1){
continue;
}
if(pic_width < 6){
printf("the %d is 1\n", i);
continue;
}
carCard_Resize(img_5, img_w, wWidth, wHeight);
pic_Thresholding(img_w, 60);
sprintf(str, "%d", i+10);
namedWindow(str);
imshow(str, img_w);
}
sprintf(str, "%d", i+3);
namedWindow(str);
imshow(str, img_4);
waitKey(0);
return 0;
}
在程式碼中,首先計算出分割出來的字元寬度pic_width,如果寬度小於3,表示不是正常的車牌字元,將該圖片排除掉。如果滿足大於2,則將分割字元圖片 複製到img_5中,然後使用函式pixelPercentage計算出img_5中圖片255的pixel佔了總畫素比例的比值,如果小於0.1,則表示該影象是車牌中的那個點。那麼該 圖片也排除掉,接著再寬度判斷,如果寬度大於2而小於6,則表示該圖片應該是1,因為1的垂直投影和其他字元相比,相差很多(注意:該方法很容易導致左右 邊沿也被檢測成了1)。最後在一次將篩選分割出來的字元img_5,歸一化為wWidth=20,wHeight = 20的img_5,在以60為閥值的二值化後,將它們分別顯示出來。 最後的顯示效果如下:
效果演示
使用該方法做的效果並不好,如下是一些效果演示:
在這圖片中,因為1之前已經判斷篩選了,所以不會顯示出1。
如圖所示,該圖片的效果就很差。
程式碼下載位置:http://download.csdn.net/detail/u011630458/8440123