1. 程式人生 > >EasyPR--開發詳解(4)形態學操作、尺寸驗證、旋轉等操作

EasyPR--開發詳解(4)形態學操作、尺寸驗證、旋轉等操作

  在上一篇深度分析與調優討論中,我們介紹了高斯模糊,灰度化和Sobel運算元。在本文中,會分析剩餘的定位步驟。

  根據前文的內容,車牌定位的功能還剩下如下的步驟,見下圖中未塗灰的部分。

圖1 車牌定位步驟

  我們首先從Soble運算元分析出來的邊緣來看。通過下圖可見,Sobel運算元有很強的區分性,車牌中的字元被清晰的描繪出來,那麼如何根據這些資訊定位出車牌的位置呢?

圖2 Sobel後效果

  我們的車牌定位功能做了個假設,即車牌是包含字元圖塊的一個最小的外接矩形。在大部分車牌處理中,這個假設都能工作的很好。我們來看下這個假設是如何工作的。

  車牌定位過程的全部程式碼如下:

  1 //! 定位車牌影象
  2 //! src 原始影象
  3 //! resultVec 一個Mat的向量,儲存所有抓取到的影象
  4 //! 成功返回0,否則返回-1
  5 int CPlateLocate::plateLocate(Mat src, vector<Mat>& resultVec)
  6 {
  7     Mat src_blur, src_gray;
  8     Mat grad;
  9 
 10     int scale = SOBEL_SCALE;
 11     int delta = SOBEL_DELTA;
12 int ddepth = SOBEL_DDEPTH; 13 14 if( !src.data ) 15 { return -1; } 16 17 //高斯模糊。Size中的數字影響車牌定位的效果。 18 GaussianBlur( src, src_blur, Size(m_GaussianBlurSize, m_GaussianBlurSize), 19 0, 0, BORDER_DEFAULT ); 20 21 if(m_debug) 22 { 23 stringstream ss(stringstream::in
| stringstream::out); 24 ss << "tmp/debug_GaussianBlur" << ".jpg"; 25 imwrite(ss.str(), src_blur); 26 } 27 28 /// Convert it to gray 29 cvtColor( src_blur, src_gray, CV_RGB2GRAY ); 30 31 if(m_debug) 32 { 33 stringstream ss(stringstream::in | stringstream::out); 34 ss << "tmp/debug_gray" << ".jpg"; 35 imwrite(ss.str(), src_gray); 36 } 37 38 /// Generate grad_x and grad_y 39 Mat grad_x, grad_y; 40 Mat abs_grad_x, abs_grad_y; 41 42 /// Gradient X 43 //Scharr( src_gray, grad_x, ddepth, 1, 0, scale, delta, BORDER_DEFAULT ); 44 Sobel( src_gray, grad_x, ddepth, 1, 0, 3, scale, delta, BORDER_DEFAULT ); 45 convertScaleAbs( grad_x, abs_grad_x ); 46 47 /// Gradient Y 48 //Scharr( src_gray, grad_y, ddepth, 0, 1, scale, delta, BORDER_DEFAULT ); 49 Sobel( src_gray, grad_y, ddepth, 0, 1, 3, scale, delta, BORDER_DEFAULT ); 50 convertScaleAbs( grad_y, abs_grad_y ); 51 52 /// Total Gradient (approximate) 53 addWeighted( abs_grad_x, SOBEL_X_WEIGHT, abs_grad_y, SOBEL_Y_WEIGHT, 0, grad ); 54 55 //Laplacian( src_gray, grad_x, ddepth, 3, scale, delta, BORDER_DEFAULT ); 56 //convertScaleAbs( grad_x, grad ); 57 58 59 if(m_debug) 60 { 61 stringstream ss(stringstream::in | stringstream::out); 62 ss << "tmp/debug_Sobel" << ".jpg"; 63 imwrite(ss.str(), grad); 64 } 65 66 Mat img_threshold; 67 threshold(grad, img_threshold, 0, 255, CV_THRESH_OTSU+CV_THRESH_BINARY); 68 //threshold(grad, img_threshold, 75, 255, CV_THRESH_BINARY); 69 70 if(m_debug) 71 { 72 stringstream ss(stringstream::in | stringstream::out); 73 ss << "tmp/debug_threshold" << ".jpg"; 74 imwrite(ss.str(), img_threshold); 75 } 76 77 Mat element = getStructuringElement(MORPH_RECT, Size(m_MorphSizeWidth, m_MorphSizeHeight) ); 78 morphologyEx(img_threshold, img_threshold, MORPH_CLOSE, element); 79 80 if(m_debug) 81 { 82 stringstream ss(stringstream::in | stringstream::out); 83 ss << "tmp/debug_morphology" << ".jpg"; 84 imwrite(ss.str(), img_threshold); 85 } 86 87 //Find 輪廓 of possibles plates 88 vector< vector< Point> > contours; 89 findContours(img_threshold, 90 contours, // a vector of contours 91 CV_RETR_EXTERNAL, // 提取外部輪廓 92 CV_CHAIN_APPROX_NONE); // all pixels of each contours 93 94 Mat result; 95 if(m_debug) 96 { 97 //// Draw blue contours on a white image 98 src.copyTo(result); 99 drawContours(result, contours, 100 -1, // draw all contours 101 Scalar(0,0,255), // in blue 102 1); // with a thickness of 1 103 stringstream ss(stringstream::in | stringstream::out); 104 ss << "tmp/debug_Contours" << ".jpg"; 105 imwrite(ss.str(), result); 106 } 107 108 109 //Start to iterate to each contour founded 110 vector<vector<Point> >::iterator itc = contours.begin(); 111 112 vector<RotatedRect> rects; 113 //Remove patch that are no inside limits of aspect ratio and area. 114 int t = 0; 115 while (itc != contours.end()) 116 { 117 //Create bounding rect of object 118 RotatedRect mr = minAreaRect(Mat(*itc)); 119 120 //large the rect for more 121 if( !verifySizes(mr)) 122 { 123 itc = contours.erase(itc); 124 } 125 else 126 { 127 ++itc; 128 rects.push_back(mr); 129 } 130 } 131 132 int k = 1; 133 for(int i=0; i< rects.size(); i++) 134 { 135 RotatedRect minRect = rects[i]; 136 if(verifySizes(minRect)) 137 { 138 // rotated rectangle drawing 139 // Get rotation matrix 140 // 旋轉這部分程式碼確實可以將某些傾斜的車牌調整正, 141 // 但是它也會誤將更多正的車牌搞成傾斜!所以綜合考慮,還是不使用這段程式碼。 142 // 2014-08-14,由於新到的一批圖片中發現有很多車牌是傾斜的,因此決定再次嘗試 143 // 這段程式碼。 144 if(m_debug) 145 { 146 Point2f rect_points[4]; 147 minRect.points( rect_points ); 148 for( int j = 0; j < 4; j++ ) 149 line( result, rect_points[j], rect_points[(j+1)%4], Scalar(0,255,255), 1, 8 ); 150 } 151 152 float r = (float)minRect.size.width / (float)minRect.size.height; 153 float angle = minRect.angle; 154 Size rect_size = minRect.size; 155 if (r < 1) 156 { 157 angle = 90 + angle; 158 swap(rect_size.width, rect_size.height); 159 } 160 //如果抓取的方塊旋轉超過m_angle角度,則不是車牌,放棄處理 161 if (angle - m_angle < 0 && angle + m_angle > 0) 162 { 163 //Create and rotate image 164 Mat rotmat = getRotationMatrix2D(minRect.center, angle, 1); 165 Mat img_rotated; 166 warpAffine(src, img_rotated, rotmat, src.size(), CV_INTER_CUBIC); 167 168 Mat resultMat; 169 resultMat = showResultMat(img_rotated, rect_size, minRect.center, k++); 170 171 resultVec.push_back(resultMat); 172 } 173 } 174 } 175 176 if(m_debug) 177 { 178 stringstream ss(stringstream::in | stringstream::out); 179 ss << "tmp/debug_result" << ".jpg"; 180 imwrite(ss.str(), result); 181 } 182 183 return 0; 184 }
View Code

  首先,我們通過二值化處理將Sobel生成的灰度影象轉變為二值影象。

四.二值化

  二值化演算法非常簡單,就是對影象的每個畫素做一個閾值處理。

 1.目標

  為後續的形態學運算元Morph等準備二值化的影象。 

 2.效果

  經過二值化處理後的影象效果為下圖,與灰度影象仔細區分下,二值化影象中的白色是沒有顏色強與暗的區別的。

圖3 二值化後效果

  3.理論

  在灰度影象中,每個畫素的值是0-255之間的數字,代表灰暗的程度。如果設定一個閾值T,規定畫素的值x滿足如下條件時則:

 if x < t then x = 0; if x >= t then x = 1

  如此一來,每個畫素的值僅有{0,1}兩種取值,0代表黑、1代表白,影象就被轉換成了二值化的影象。在上面的公式中,閾值T應該取多少?由於不同影象的光造程度不同,導致作為二值化區分的閾值T也不一樣。因此一個簡單的做法是直接使用opencv的二值化函式時加上自適應閾值引數。如下:

threshold(src, dest, 0, 255, CV_THRESH_OTSU+CV_THRESH_BINARY); 

  通過這種方法,我們不需要計算閾值的取值,直接使用即可。
  threshold函式是二值化函式,引數src代表源影象,dest代表目標影象,兩者的型別都是cv::Mat型,最後的引數代表二值化時的選項,
CV_THRESH_OTSU代表自適應閾值,CV_THRESH_BINARY代表正二值化。正二值化意味著畫素的值越接近0,越可能被賦值為0,反之則為1。而另外一種二值化方法表示反二值化,其含義是畫素的值越接近0,越可能被賦值1,,計算公式如下: 

 if x < t then x = 1; if x >= t then x = 0

  如果想使用反二值化,可以使用引數CV_THRESH_BINARY_INV代替CV_THRESH_BINARY即可。在後面的字元識別中我們會同時使用到正二值化與反二值化兩種例子。因為中國的車牌有很多型別,最常見的是藍牌和黃牌。其中藍牌字元淺,背景深,黃牌則是字元深,背景淺,因此需要正二值化方法與反二值化兩種方法來處理,其中正二值化處理藍牌,反二值化處理黃牌。

五.閉操作

閉操作是個非常重要的操作,我會花很多的字數與圖片介紹它。

 1.目標

  將車牌字母連線成為一個連通域,便於取輪廓。 

 2.效果

  我們這裡看下經過閉操作後圖像連線的效果。

圖4 閉操作後效果

 3.理論

  在做閉操作的說明前,必須簡單介紹一下腐蝕和膨脹兩個操作。

  在影象處理技術中,有一些的操作會對影象的形態發生改變,這些操作一般稱之為形態學操作。形態學操作的物件是二值化影象。
有名的形態學操作中包括腐蝕,膨脹,開操作,閉操作等。其中腐蝕,膨脹是許多形態學操作的基礎。

  腐蝕操作:
  顧名思義,是將物體的邊緣加以腐蝕。具體的操作方法是拿一個寬m,高n的矩形作為模板,對影象中的每一個畫素x做如下處理:畫素x至於模板的中心,根據模版的大小,遍歷所有被模板覆蓋的其他畫素,修改畫素x的值為所有畫素中最小的值。這樣操作的結果是會將影象外圍的突出點加以腐蝕。如下圖的操作過程:

圖5 腐蝕操作原理 

  上圖演示的過程是背景為黑色,物體為白色的情況。腐蝕將白色物體的表面加以“腐蝕”。在opencv的官方教程中,是以如下的圖示說明腐蝕過程的,與我上面圖的區別在於:背景是白色,而物體為黑色(這個不太符合一般的情況,所以我沒有拿這張圖作為通用的例子)。讀者只需要瞭解背景為不同顏色時腐蝕也是不同的效果就可以了。

  圖6 腐蝕操作原理2

  膨脹操作:
  膨脹操作與腐蝕操作相反,是將影象的輪廓加以膨脹。操作方法與腐蝕操作類似,也是拿一個矩形模板,對影象的每個畫素做遍歷處理。不同之處在於修改畫素的值不是所有畫素中最小的值,而是最大的值。這樣操作的結果會將影象外圍的突出點連線並向外延伸。如下圖的操作過程:


  圖7 膨脹操作原理

  下面是在opencv的官方教程中,膨脹過程的圖示:

  圖8 膨脹操作原理2

  開操作:
  開操作就是對影象先腐蝕,再膨脹。其中腐蝕與膨脹使用的模板是一樣大小的。為了說明開操作的效果,請看下圖的操作過程:

圖9 開操作原理

  由於開操作是先腐蝕,再膨脹。因此可以結合圖5和圖7得出圖9,其中圖5的輸出是圖7的輸入,所以開操作的結果也就是圖7的結果。


  閉操作:
  閉操作就是對影象先膨脹,再腐蝕。閉操作的結果一般是可以將許多靠近的圖塊相連稱為一個無突起的連通域。在我們的影象定位中,使用了閉操作去連線所有的字元小圖塊,然後形成一個車牌的大致輪廓。閉操作的過程我會講的細緻一點。為了說明字元圖塊連線的過程。在這裡選取的原圖跟上面三個操作的原圖不大一樣,是一個由兩個分開的圖塊組成的圖。原圖首先經過膨脹操作,將兩個分開的圖塊結合起來(注意我用偏白的灰色圖塊表示由於膨脹操作而產生的新的白色)。接著通過腐蝕操作,將連通域的邊緣和突起進行削平(注意我用偏黑的灰色圖塊表示由於腐蝕被侵蝕成黑色圖塊)。最後得到的是一個無突起的連通域(純白的部分)。

 圖10 閉操作原理

 4.程式碼 

  在opencv中,呼叫閉操作的方法是首先建立矩形模板,矩形的大小是可以設定的,由於矩形是用來覆蓋以中心畫素的所有其他畫素,因此矩形的寬和高最好是奇數。
  通過以下程式碼設定矩形的寬和高。

    Mat element = getStructuringElement(MORPH_RECT, Size(m_MorphSizeWidth, m_MorphSizeHeight) );

  在這裡,我們使用了類成員變數,這兩個類成員變數在建構函式中被賦予了初始值。寬是17,高是3.

  設定完矩形的寬和高以後,就可以呼叫形態學操作了。opencv中所有形態學操作有一個統一的函式,通過引數來區分不同的具體操作。例如MOP_CLOSE代表閉操作,MOP_OPEN代表開操作。

morphologyEx(img_threshold, img_threshold, MORPH_CLOSE, element);

  如果我對二值化的影象進行開操作,結果會是什麼樣的?下圖是影象使用閉操作與開操作處理後的一個區別:

  圖11 開與閉的對比

  暈,怎麼開操作後圖像沒了?原因是:開操作第一步腐蝕的效果太強,直接導致接下來的膨脹操作幾乎沒有效果,所以影象就變幾乎沒了。
  可以看出,使用閉操作以後,車牌字元的圖塊被連線成了一個較為規則的矩形,通過閉操作,將車牌中的字元連成了一個圖塊,同時將突出的部分進行裁剪,圖塊成為了一個類似於矩形的不規則圖塊。我們知道,車牌應該是一個規則的矩形,因此獲取規則矩形的辦法就是先取輪廓,再接著求最小外接矩形。

  這裡需要注意的是,矩形模板的寬度,17是個推薦值,低於17都不推薦。
  為什麼這麼說,因為有一個”斷節“的問題。中國車牌有一個特點,就是表示城市的字母與右邊相鄰的字元距離遠大於其他相鄰字元之間的距離。如果你設定的不夠大,結果導致左邊的字元與右邊的字元中間斷開了,如下圖:

  圖12 “斷節”效果

  這種情況我稱之為“斷節”如果你不想字元從中間被分成"蘇A"和"7EUK22"的話,那麼就必須把它設定大點。

  另外還有一種討厭的情況,就是右邊的字元第一個為1的情況,例如蘇B13GH7。在這種情況下,由於1的字元的形態原因,導致跟左邊的B的字元的距離更遠,在這種情況下,低於17都有很大的可能性會斷節。下圖說明了矩形模板寬度過小時(例如設定為7)面對不同車牌情況下的效果。其中第二個例子選取了蘇E開頭的車牌,由於E在Sobel運算元運算過後僅存有左邊的豎槓,因此也會導致跟右邊的字元相距過遠的情況!

  圖13 “斷節”發生示意


  寬度過大也是不好的,因為它會導致閉操作連線不該連線的部分,例如下圖的情況。


  圖14 矩形模板寬度過大

  這種情況下,你取輪廓獲得矩形肯定會大於你設定的校驗規則,即便通過校驗了,由於圖塊中有不少不是車牌的部分,會給字元識別帶來麻煩。

  因此,矩形的寬度是一個需要非常細心權衡的值,過大過小都不好,取決於你的環境。至於矩形的高度,3是一個較好的值,一般來說都能工作的很好,不需要改變。

  記得我在前一篇文章中提到,工業用圖片與生活場景下圖片的區別麼。筆者做了一個實驗,下載了30多張左右的百度車牌圖片。用plateLocate過程去識別他們。如果按照下面的方式設定引數,可以保證90%以上的定位成功率。

    CPlateLocate plate;
    plate.setDebug(1);
    plate.setGaussianBlurSize(5);
    plate.setMorphSizeWidth(7);
    plate.setMorphSizeHeight(3);
    plate.setVerifyError(0.9);
    plate.setVerifyAspect(4);
    plate.setVerifyMin(1);
    plate.setVerifyMax(30);

  在EasyPR的下一個版本中,會增加對於生活場景下圖片的一個模式。只要選擇這個模式,就適用於百度圖片這種日常生活抓拍圖片的效果。但是,仍然有一些圖片是EasyPR不好處理的。或者可以說,按照目前的邊緣檢測演算法,難以處理的。

  請看下面一張圖片:


圖15 難以權衡的一張圖片

  這張圖片最麻煩的地方在於車牌左右兩側凹下去的邊側,這個邊緣在Sobel運算元中非常明顯,如果矩形模板過長,很容易跟它們連線起來。更麻煩的是這個車牌屬於上面說的“斷節”很容易發生的型別,因為車牌右側字元的第一個字母是“1”,這個導致如果矩形模板過短,則很容易車牌斷成兩截。結果最後導致瞭如下的情況。

  如果我設定矩形模板寬度為12,則會發生下面的情況:

  圖16 車牌被一分為二

  如果我增加矩形模板寬度到13,則又會發生下面的情況。

圖17 車牌區域被不不正確的放大

  因此矩形模板的寬度是個整數值,在12和13中間沒有中間值。這個導致幾乎沒有辦法處理這幅車牌影象。

  上面的情況屬於車尾車牌的一種沒辦法解決的情況。下面所說的情況屬於車頭的情況,相比前者,錯誤檢測的機率高的多!為什麼,因為是一型別車牌無法處理。要問我這家車是哪家,我只能說:碰到開奧迪Q5及其系列的,早點嫁了吧。傷不起。

    圖18 奧迪Q5前部垂直邊緣太多

  這麼多的垂直邊緣,極為容易檢錯。已經試過了,幾乎沒有辦法處理這種車牌。只能替換邊緣檢測這種思路,採用顏色區分等方法。奧體Q系列前臉太多垂直邊緣了,給跪。

六.取輪廓

取輪廓操作是個相對簡單的操作,因此只做簡短的介紹。

 1.目標

  將連通域的外圍勾畫出來,便於形成外接矩形。 

 2.效果

  我們這裡看下經過取輪廓操作的效果。

圖19 取輪廓操作

  在圖中,紅色的線條就是輪廓,可以看到,有非常多的輪廓。取輪廓操作就是將影象中的所有獨立的不與外界有交接的圖塊取出來。然後根據這些輪廓,求這些輪廓的最小外接矩形。這裡面需要注意的是這裡用的矩形是RotatedRect,意思是可旋轉的。因此我們得到的矩形不是水平的,這樣就為處理傾斜的車牌打下了基礎。

  取輪廓操作的程式碼如下:

1     vector< vector< Point> > contours;
2     findContours(img_threshold,
3         contours, // a vector of contours
4         CV_RETR_EXTERNAL, // 提取外部輪廓
5         CV_CHAIN_APPROX_NONE); // all pixels of each contours

七.尺寸判斷

尺寸判斷操作是對外接矩形進行判斷,以判斷它們是否是可能的候選車牌的操作。

 1.目標

  排除不可能是車牌的矩形。 

 2.效果

  經過尺寸判斷,會排除大量由輪廓生成的不合適尺寸的最小外接矩形。效果如下圖:

圖20 尺寸判斷操作
  

  通過對影象中所有的輪廓的外接矩形進行遍歷,我們呼叫CplateLocate的另一個成員方法verifySizes,程式碼如下:

 1 //! 對minAreaRect獲得的最小外接矩形,用縱橫比進行判斷
 2 bool CPlateLocate::verifySizes(RotatedRect mr)
 3 {
 4     float error = m_error;
 5     //Spain car plate size: 52x11 aspect 4,7272
 6     //China car plate size: 440mm*140mm,aspect 3.142857
 7     float aspect = m_aspect;
 8     //Set a min and max area. All other patchs are discarded
 9     //int min= 1*aspect*1; // minimum area
10     //int max= 2000*aspect*2000; // maximum area
11     int min= 44*14*m_verifyMin; // minimum area
12     int max= 44*14*m_verifyMax; // maximum area
13     //Get only patchs that match to a respect ratio.
14     float rmin= aspect-aspect*error;
15     float rmax= aspect+aspect*error;
16 
17     int area= mr.size.height * mr.size.width;
18     float r = (float)mr.size.width / (float)mr.size.height;
19     if(r < 1)
20     {
21         r= (float)mr.size.height / (float)mr.size.width;
22     }
23 
24     if(( area < min || area > max ) || ( r < rmin || r > rmax ))
25     {
26         return false;
27     }
28     else
29     {
30         return true;
31     }
32 }
View Code


  在原先的verifySizes方法中,使用的是針對西班牙車牌的檢測。而我們的系統需要檢測的是中國的車牌。因此需要對中國的車牌大小有一個認識。

  中國車牌的一般大小是440mm*140mm,面積為440*140,寬高比為3.14。verifySizes使用如下方法判斷矩形是否是車牌:

  1.設立一個偏差率error,根據這個偏差率計算最大和最小的寬高比rmax、rmin。判斷矩形的r是否滿足在rmax、rmin之間。
  2.設定一個面積最大值max與面積最小值min。判斷矩形的面積area是否滿足在max與min之間。

  以上兩個條件必須同時滿足,任何一個不滿足都代表這不是車牌。

  偏差率和麵積最大值、最小值都可以通過引數設定進行修改,且他們都有一個預設值。如果發現verifySizes方法無法發現你圖中的車牌,試著修改這些引數。

  另外,verifySizes方法是可選的。你也可以不進行verifySizes直接處理,但是這會大大加重後面的車牌判斷的壓力。一般來說,合理的verifySizes能夠去除90%不合適的矩形。

八.角度判斷

角度判斷操作通過角度進一步排除一部分車牌。

 1.目標

  排除不可能是車牌的矩形。 

  通過verifySizes的矩形,還必須進行一個篩選,即角度判斷。一般來說,在一副圖片中,車牌不太會有非常大的傾斜,我們做如下規定:如果一個矩形的偏斜角度大於某個角度(例如30度),則認為不是車牌並捨棄。

  對上面的尺寸判斷結果的六個黃色矩形應用角度判斷後結果如下圖:


  圖21 角度判斷後的候選車牌

  可以看出,原先的6個候選矩形只剩3個。車牌兩側的車燈的矩形被成功篩選出來。角度判斷會去除verifySizes篩選餘下的7%矩形,使得最終進入車牌判斷環節的矩形只有原先的全部矩形的3%。

  角度判斷以及接下來的旋轉操作的程式碼如下:

 1 int k = 1;
 2     for(int i=0; i< rects.size(); i++)
 3     {
 4         RotatedRect minRect = rects[i];
 5         if(verifySizes(minRect))
 6         {    
 7             // rotated rectangle drawing 
 8             // Get rotation matrix
 9             // 旋轉這部分程式碼確實可以將某些傾斜的車牌調整正,
10             // 但是它也會誤將更多正的車牌搞成傾斜!所以綜合考慮,還是不使用這段程式碼。
11             // 2014-08-14,由於新到的一批圖片中發現有很多車牌是傾斜的,因此決定再次嘗試
12             // 這段程式碼。
13             if(m_debug)
14             { 
15                 Point2f rect_points[4]; 
16                 minRect.points( rect_points );
17                 for( int j = 0; j < 4; j++ )
18                     line( result, rect_points[j], rect_points[(j+1)%4], Scalar(0,255,255), 1, 8 );
19             }
20 
21             float r = (float)minRect.size.width / (float)minRect.size.height;
22             float angle = minRect.angle;
23             Size rect_size = minRect.size;
24             if (r < 1)
25             {
26                 angle = 90 + angle;
27                 swap(rect_size.width, rect_size.height);
28             }
29             //如果抓取的方塊旋轉超過m_angle角度,則不是車牌,放棄處理
30             if (angle - m_angle < 0 && angle + m_angle > 0)
31             {
32                 //Create and rotate image
33                 Mat rotmat = getRotationMatrix2D(minRect.center, angle, 1);
34                 Mat img_rotated;
35                 warpAffine(src, img_rotated, rotmat, src.size(), CV_INTER_CUBIC);
36 
37                 Mat resultMat;
38                 resultMat = showResultMat(img_rotated, rect_size, minRect.center, k++);
39 
40                 resultVec.push_back(resultMat);
41             }
42         }
View Code

九.旋轉

旋轉操作是為後面的車牌判斷與字元識別提高成功率的關鍵環節。

 1.目標

  旋轉操作將偏斜的車牌調整為水平。 

 2.效果

  假設待處理的圖片如下圖:

圖22 傾斜的車牌
  

  使用旋轉與不適用旋轉的效果區別如下圖:

相關推薦

EasyPR--開發4形態學操作尺寸驗證旋轉操作

  在上一篇深度分析與調優討論中,我們介紹了高斯模糊,灰度化和Sobel運算元。在本文中,會分析剩餘的定位步驟。  根據前文的內容,車牌定位的功能還剩下如下的步驟,見下圖中未塗灰的部分。 圖1 車牌定位步驟   我們首先從Soble運算元分析出來的邊緣來看。通過下圖可見,Sobel運算元有很強的區分性

EasyPR--開發8文字定位

dont bubuko 通用 設置 光照 detect improve nmp easy 轉自https://www.cnblogs.com/subconscious/p/5637735.html 今天我們來介紹車牌定位中的一種新方法--文字定位方法(MSER),包括其主要

EasyPR--開發7顏色定位與偏斜扭轉

  本篇文章介紹EasyPR裡新的定位功能:顏色定位與偏斜扭正。希望這篇文件可以幫助開發者與使用者更好的理解EasyPR的設計思想。   讓我們先看一下示例圖片,這幅圖片中的車牌通過顏色的定位法進行定位並從偏斜的視角中扭正為正視角(請看右圖的左上角)。 圖1 新版本的定位效果     下面內容會對這兩

EasyPR--開發7字元分割

大家好,好久不見了。   一轉眼距離上一篇部落格已經是4個月前的事了。要問博主這段時間去幹了什麼,我只能說:我去“外面看了看”。 圖1 我想去看看    在外面跟幾家創業公司談了談,交流了一些大資料與機器視覺相關的心得與經驗。不過由於各種原因,博主又回來了。

EasyPR--開發9文字定位

  今天我們來介紹車牌定位中的一種新方法–文字定位方法(MSER),包括其主要設計思想與實現。接著我們會介紹一下EasyPR v1.5-beta版本中帶來的幾項改動。 一. 文字定位法   在EasyPR前面幾個版本中,最為人所詬病的就是定位效果不佳,尤其是在面對生活場景(例如手機拍攝)時。由於EasyP

EasyPR--開發6SVM開發

  在前面的幾篇文章中,我們介紹了EasyPR中車牌定位模組的相關內容。本文開始分析車牌定位模組後續步驟的車牌判斷模組。車牌判斷模組是EasyPR中的基於機器學習模型的一個模組,這個模型就是作者前文中從機器學習談起中提到的SVM(支援向量機)。  我們已經知道,車牌定位模組的輸出是一些候選車牌的圖片。但如何從

EasyPR--開發5顏色定位與偏斜扭轉

  本篇文章介紹EasyPR裡新的定位功能:顏色定位與偏斜扭正。希望這篇文件可以幫助開發者與使用者更好的理解EasyPR的設計思想。   讓我們先看一下示例圖片,這幅圖片中的車牌通過顏色的定位法進行定位並從偏斜的視角中扭正為正視角(請看右圖的左上角)。 圖1 新版本的定位效果     下

EasyPR--開發3高斯模糊灰度化和Sobel運算元

在上篇文章中我們瞭解了PlateLocate的過程中的所有步驟。在本篇文章中我們對前3個步驟,分別是高斯模糊、灰度化和Sobel運算元進行分析。 一、高斯模糊  1.目標   對影象去噪,為邊緣檢測演算法做準備。    2.效果   在我們的車牌定位中的第一步就是高斯模糊處理。    圖1 高斯

EasyPR--開發2車牌定位

  這篇文章是一個系列中的第三篇。前兩篇的地址貼下:介紹、詳解1。我撰寫這系列文章的目的是:1、普及車牌識別中相關的技術與知識點;2、幫助開發者瞭解EasyPR的實現細節;3、增進溝通。   EasyPR的專案地址在這:GitHub。要想執行EasyPR的程式,首先必須配置好openCV,具體可以參照這篇文

STM32開發 -- 4G模組開發4

前面用了三篇來講基礎知識,根據這些東西可以簡單的實現4G模組的通訊了。但是離專案要求還遠遠不夠的。接下來看一下,程式碼的邏輯架構。 一、4G模組連線伺服器 前面有講到AT指令的操作,那它們該怎麼使用呢? 1、聯網階段 首先將4G連線TCP伺服器這

iBatis開發4-----------select

 <select>是iBatis已經對映的語句型別,就是查詢了,為了配合說明,這裡再介紹兩個標記:<sql>和<include>,前者用來建立一個文字片段,這些片段可以組合起來建立完整的SQL語句;後者很顯然就是包含的意思了。假設我們有如

EasyPR--中文開源車牌識別系統 開發1

  在上篇文件中作者已經簡單的介紹了EasyPR,現在在本文件中詳細的介紹EasyPR的開發過程。   正如淘寶誕生於一個購買來的LAMP系統,EasyPR也有它誕生的原型,起源於CSDN的taotao1233的一個部落格,博主以讀書筆記的形式記述了通過閱讀“Mastering OpenCV”這本書完成的一

企業級搜尋應用伺服器Solr4.10.4部署開發3- Solr使用-使用java客戶端solrj進行增刪改查開發

(一)使用java客戶端solrj進行增刪改查開發         前兩章講的是如何搭建部署Solr環境和使用Solr建立資料集合進行儲存查詢,下面我們需要更進一步,直接使用客戶端API進行開發,直接操作資料集合,進行增刪改查。 1. 加入客戶端API的jar包 使用客戶端

Mysql加鎖過程4-select for update/lock in share mode 對事務並發性影響

per inno targe 允許 evel transacti 修改 not null warn select for update/lock in share mode 對事務並發性影響 事務並發性理解 事務並發性,粗略的理解就是單位時間內能夠執行的事務數量,常見的單

PHP與Java集成開發

new 編程語言 到你 其中 web-inf request 測試 add 輸入 很久以前,有人從www上看到看到天空上一個很亮的亮點,它就是Java語言,與此同時,在另一個地方一位夢想家也看到了一個亮點,它就是PHP。 時間一天天過去,這兩個亮點也變得越來越亮,很快,它

git 使用4—— commit -a -m/diff --staged/rm/mv

art client -s 做的 res use 擴展名 ems 也會 查看已暫存和未暫存的更新 實際上 git status的顯示比較簡單,僅僅是 列出了(修改過的、新創建的、已經暫存但未提交的)文件,如果要查看具體修改了什麽地方,可以用git diff 命令。稍後我們會

Java4--練習題

一、入門練習 1、必做題 ∆ 計算1000以內的偶數數之和 ∆ 補全下列程式碼執行出結果後,替換continue使用break執行檢視結果 for (int i = 0; i < total; i++) { System.out.print(“請輸入第” + (i + 1) + "

SpringBoot開發--SpringBoot配置檔案YML注意事項

轉載自:https://blog.csdn.net/qq_31001665/article/details/70197543#commentBox 一、多重層級讀取 在YML中我們再新增一個ZZP2的配置資訊,其中包含了girl節點以及girl下的name,age屬性。 zzp2:

SpringBoot開發--SpringBoot的配置檔案以及註解

轉載自:https://blog.csdn.net/qq_31001665/article/details/69938750 一、Spring Boot註解 通過上一篇文章,我們已經快速構建了一個spring boot的專案,那spring boot專案和我們之前使用的springMVC專案

SpringBoot開發--初識SpringBoot

轉載自:https://blog.csdn.net/qq_31001665/article/details/54803354 一、寫在前面的話: 越來越多的公司開始使用sprinbgboot作為後臺伺服器開發的框架,作為目前微服務框架的佼佼者,現在學習springboot框架是一個很好的機會