1. 程式人生 > >【計算機視覺】車牌識別開源框架EasyPR介紹

【計算機視覺】車牌識別開源框架EasyPR介紹

之前學習了一個GitHub開源的框架,GitHub地址為: 
https://github.com/liuruoze/EasyPR 
希望通過此篇部落格詳細闡述如何一步步實現車牌的識別過程。 
車牌識別分成了兩個部分,首先是車牌的定位,然後則是車牌的文字識別。 
Plate Detect過程中包含了三個部分,“Plate location”,“SVM train”,”Plate judge”。其中最重要的是“Plate location”部分。 
有人提出了這樣的疑問: 
EasyPR在處理百度圖片時的識別率不高。確實如此,由於工業與生活應用目的不同,拍攝的車牌的大小,角度,色澤,清晰度不一樣。而對影象處理技術而言,一些

演算法對於影象的形式以及結構都有一定的要求或者假設。因此在一個場景下適應的演算法並不適用其他場景。目前EasyPR所有的功能都是基於交通抓拍場景的圖片製作的,因此也就導致了其無法處理生活場景中這些車牌照片。 
因此對於不同的場景,演算法要做不同的適配。對於“Plate Location”過程無法處理生活照片的定位,我們可以對這一部分進行適配改造,但是後面的字元識別過程是相同的,整體架構不變。 
 在EasyPR中,“Plate Locate”過程被封裝成了一個“CPlateLocate” 類,通過“plate_locate.h”宣告,在“plate_locate.cpp”中實現。 
 CPlateLocate包含三個方法以及數個變數。方法提供了車牌定位的主要功能,變數則提供了可定製的引數,有些引數對於車牌定位的效果有非常明顯的影響,例如高斯模糊半徑、Sobel運算元的水平與垂直方向權值、閉操作的矩形寬度。 
 CPlateLocate中最核心的方法是plateLocate方法。 
  //! 車牌定位

    int plateLocate(Mat, vector<Mat>& );
  • 1
  • 1

  方法有兩個引數,第一個引數代表輸入的源影象,第二個引數是輸出陣列,代表所有檢索到的車牌圖塊。返回值為int型,0代表成功,其他代表失敗。plateLocate內部是如何實現的,讓我們再深入下看看。 
如果我們的車牌沒有大的旋轉或變形,那麼其中必然包括很多垂直邊緣(這些垂直邊緣往往緣由車牌中的字元),如果能夠找到一個包含很多垂直邊緣的矩形塊,那麼有很大的可能性它就是車牌。 
識別流程如下: 
1、對原始影象高斯模糊,這步的作用是平滑影象,去除干擾的噪聲。 
2、對灰度影象進行Sobel運算,得到影象的一階水平方向導數。其實是提取出了影象的邊緣資訊。 
3、對影象使用閉操作,可以看到車牌閉操作後被連線成一個矩形的區域。 
這裡寫圖片描述


4、求出上面影象的所有輪廓。對所有的輪廓要進行篩選。 
5、對所有輪廓求最小外接矩形,然後驗證,不滿足條件的被淘汰。驗證一般是按照外接矩形的長寬比例範圍設定一定的閾值。 
6、角度判斷,我們要把傾斜角度大於閾值(如正負30度)的矩形捨棄。餘下的矩形進行微小的旋轉,使其水平。 
這裡寫圖片描述 
7、上面的是 候選的車牌識別矩形。 我們將上面的候選區域的影象統一尺寸。因為機器學習模型,需要統一尺寸。統一尺寸的長度為136,高度為36。 
這些候選車牌有兩個作用:積累下來作為支援向量機模型的訓練集,以此訓練出一個車牌判斷模型;二、在實際的車牌檢測過程中,將這些候選車牌交給訓練好的判斷模型進行判斷。如果車牌判斷模型認為是車牌的話就進入下一步的字元識別過程。

  • 高斯模糊這個過程一定是必要,倘若我們將這句程式碼註釋並稍作修改,重新執行一下。你會發現plateLocate過程在閉操作時就和原來發生了變化。
    有的時候你會發現得到的車牌圖塊中的車牌是斜著的,如果我們的字元識別演算法需要一個水平的車牌圖塊,那麼幾乎肯定我們無法得到正確的字元識別效果。

  • 高斯模糊中的半徑也會給結果帶來明顯的變化。有的圖片,高斯模糊半徑過高了,車牌就定位不出來。有的圖片,高斯模糊半徑偏低了,車牌也定位不出來。因此、高斯模糊的半徑既不宜過高,也不能過低。CPlateLocate類中的值為5的靜態常量DEFAULT_GAUSSIANBLUR_SIZE,是推薦的高斯模糊的半徑。

車牌定位模組的輸出是一些候選車牌的圖片,但如何從這些候選車牌中選出真正的車牌,就是通過SVM模型判斷/預測得到的。

int num = inVec.size();
    for (int j = 0; j < num; j++)
    {
        Mat inMat = inVec[j];
        Mat p = histeq(inMat).reshape(1, 1);
        //reshape第一個引數是通道數,第二個引數是rows行數,這裡為1行
        p.convertTo(p, CV_32FC1);
        int response = (int)svm.predict(p);
        if (response == 1)
        {
            resultVec.push_back(inMat);
        }
    }
    return 0;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

從預測的原始碼可以看出,他是將整個候選區域的Mat 變形為一個行向量作為特徵向量。然後對這個特徵向量進行預測。 (這個地方也可以使用其他的特徵,一方面可以降低維數,一方面也能深層刻畫特徵)。

SVM貼標籤加資料格式準備之前在專案中也用到過。最考驗能力的是配置SVM模型的訓練引數。SVM模型的訓練需要一個CvSVMParams的物件,這個類是SVM模型中訓練物件的引數的組合,如何給這些引數賦值,是很有研究的一個工作。機器學習最後模型的效果差異有很大因素取決於模型訓練時的引數。

    CvSVMParams SVM_params;
    SVM_params.svm_type = CvSVM::C_SVC;
    SVM_params.kernel_type = CvSVM::LINEAR; //CvSVM::LINEAR;
    SVM_params.degree = 0;
    SVM_params.gamma = 1;
    SVM_params.coef0 = 0;
    SVM_params.C = 1;
    SVM_params.nu = 0;
    SVM_params.p = 0;
    SVM_params.term_crit = cvTermCriteria(CV_TERMCRIT_ITER, 1000, 0.01);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 通過使用測試資料進行演算法的評估

一般使用查準率和查全率進行刻畫。 
precision指標的期望含義就是要“查的準”,recall的期望含義就是“不要漏”。查全率還有一個翻譯叫做“召回率”。 
如果precision和recall這兩個值越高越好,但是如果一個高一個地則怎麼刻畫呢?為了能夠數字化這些情況,有一種FScore計算方法 
這裡寫圖片描述

  • SVM調優

      SVM調優部分,是通過對SVM的原理進行了解,並運用機器學習的一些調優策略進行優化的步驟。

      在這個部分裡,最好要懂一點機器學習的知識。同時,本部分也會講的儘量通俗易懂,讓人不會有理解上的負擔。在EasyPR1.0版本中,SVM模型的程式碼完全參考了mastering OpenCV書裡的實現思路。從1.1版本開始,EasyPR對車牌判斷模組進行了優化,使得模型最後的效果有了較大的改善。

      具體說來,本部分主要包括如下幾個子部分:1.RBF核;2.引數調優;3.特徵提取;4.介面函式;5.自動化。

      下面分別對這幾個子部分展開介紹。

      1.RBF核

      SVM中最關鍵的技巧是核技巧。“核”其實是一個函式,通過一些轉換規則把低維的資料對映為高維的資料。在機器學習裡,資料跟向量是等同的意思。例如,一個 [174, 72]表示人的身高與體重的資料就是一個兩維的向量。在這裡,維度代表的是向量的長度。(務必要區分“維度”這個詞在不同語境下的含義,有的時候我們會說向量是一維的,矩陣是二維的,這種說法針對的是資料展開的層次。機器學習裡講的維度代表的是向量的長度,與前者不同)

      簡單來說,低維空間到高維空間對映帶來的好處就是可以利用高維空間的線型切割模擬低維空間的非線性分類效果。也就是說,SVM模型其實只能做線型分類,但是線上型分類前,它可以通過核技巧把資料對映到高維,然後在高維空間進行線型切割。高維空間的線型切割完後在低維空間中最後看到的效果就是劃出了一條複雜的分線型分類界限。從這點來看,SVM並沒有完成真正的非線性分類,而是通過其它方式達到了類似目的,可謂“曲徑通幽”。

      SVM模型總共可以支援多少種核呢。根據官方文件,支援的核型別有以下幾種:

liner核,也就是無核。 
rbf核,使用的是高斯函式作為核函式。 
poly核,使用多項式函式作為核函式。 
sigmoid核,使用sigmoid函式作為核函式。 
  liner核和rbf核是所有核中應用最廣泛的。

  liner核,雖然名稱帶核,但它其實是無核模型,也就是沒有使用核函式對資料進行轉換。因此,它的分類效果僅僅比邏輯迴歸好一點。在EasyPR1.0版中,我們的SVM模型應用的是liner核。我們用的是影象的全部畫素作為特徵。

  rbf核,會將輸入資料的特徵維數進行一個維度轉換,具體會轉換為多少維?這個等於你輸入的訓練量。假設你有500張圖片,rbf核會把每張圖片的資料轉 換為500維的。如果你有1000張圖片,rbf核會把每幅圖片的特徵轉到1000維。這麼說來,隨著你輸入訓練資料量的增長,資料的維數越多。更方便在高維空間下的分類效果,因此最後模型效果表現較好。

  既然選擇SVM作為模型,而且SVM中核心的關鍵技巧是核函式,那麼理應使用帶核的函式模型,充分利用資料高維化的好處,利用高維的線型分類帶來低維空間下的非線性分類效果。但是,rbf核的使用是需要條件的。

  當你的資料量很大,但是每個資料量的維度一般時,才適合用rbf核。相反,當你的資料量不多,但是每個資料量的維數都很大時,適合用線型核。

  在EasyPR1.0版中,我們用的是影象的全部畫素作為特徵,那麼根據車牌影象的136×36的大小來看的話,就是4896維的資料,再加上我們輸入的 是彩色影象,也就是說有R,G,B三個通道,那麼數量還要乘以3,也就是14688個維度。這是一個非常龐大的資料量,你可以把每幅圖片的資料理解為長度 為14688的向量。這個時候,每個資料的維度很大,而資料的總數很少,如果用rbf核的話,相反效果反而不如無核。

  在EasyPR1.1版本時,輸入訓練的資料有3000張圖片,每個資料的特徵改用直方統計,共有172個維度。這個場景下,如果用rbf核的話,就會將每個資料的維度轉化為與資料總數一樣的數量,也就是3000的維度,可以充分利用資料高維化後的好處。

  因此可以看出,為了讓EasyPR新版使用rbf核技巧,我們給訓練資料做了增加,擴充了兩倍的資料,同時,減小了每個資料的維度。以此滿足了rbf核的使用條件。通過使用rbf核來訓練,充分發揮了非線性模型分類的優勢,因此帶來了較好的分類效果。  

  但是,使用rbf核也有一個問題,那就是引數設定的問題。在rbf訓練的過程中,引數的選擇會顯著的影響最後rbf核訓練出模型的效果。因此必須對引數進行最優選擇。

  2.引數調優

  傳統的引數調優方法是人手完成的。機器學習工程師觀察訓練出的模型與引數的對應關係,不斷調整,尋找最優的引數。由於機器學習工程師大部分時間在調整模型的引數,也有了“機器學習就是調參”這個說法。

  幸好,opencv的svm方法中提供了一個自動訓練的方法。也就是由opencv幫你,不斷改變引數,訓練模型,測試模型,最後選擇模型效果最好的那些引數。整個過程是全自動的,完全不需要你參與,你只需要輸入你需要調整引數的引數型別,以及每次引數調整的步長即可。

  現在有個問題,如何驗證svm引數的效果?你可能會說,使用訓練集以外的那30%測試集啊。但事實上,機器學習模型中專門有一個數據集,是用來驗證引數效果的。也就是交叉驗證集(cross validation set,簡稱validate data) 這個概念。

  validate data就是專門從train data中取出一部分資料,用這部分資料來驗證引數調整的效果。比方說現在有70%的訓練資料,從中取出20%的資料,剩下50%資料用來訓練,再用訓練出來的模型在20%資料上進行測試。這20%的資料就叫做validate data。真正拿來訓練的資料僅僅只是50%的資料。

  正如上面把資料劃分為test data和train data的理由一樣。為了驗證引數在新資料上的推廣性,我們不能用一個訓練資料集,所以我們要把訓練資料集再細分為train data和validate data。在train data上訓練,然後在validate data上測試引數的效果。所以說,在一個更一般的機器學習場景中,機器學習工程師會把資料分為train data,validate data,以及test data。在train data上訓練模型,用validate data測試引數,最後用test data測試模型和引數的整體表現。

  說了這麼多,那麼,大家可能要問,是不是還缺少一個數據集,需要再劃分出來一個validate data吧。但是答案是No。opencv的train_auto函式幫你完成了所有工作,你只需要告訴它,你需要劃分多少個子分組,以及validate data所佔的比例。然後train_auto函式會自動幫你從你輸入的train data中劃分出一部分的validate data,然後自動測試,選擇表現效果最好的引數。

  感謝train_auto函式!既幫我們劃分了引數驗證的資料集,還幫我們一步步調整引數,最後選擇效果最好的那個引數,可謂是節省了調優過程中80%的工作。 
  
  ” 在rbf核介紹時提到過,輸入資料的特徵的維度現在是172,那麼這個數字是如何計算出來的?現在的特徵用的是直方統計函式,也就是先把影象二值化,然後統計影象中一行元素中1的數目,由於輸入影象有36行,因此有36個值,再統計影象中每一列中1的數目,影象有136列,因此有136個值,兩者相加正好等於172。”我們輸入資料的特徵不再是全部的三原色的畫素值了,而是抽取過的一些特徵。從原始的影象到抽取後的特徵的過程就被稱為特徵提取的過程。在1.0版中沒有特徵提取的概念,是直接把影象中全部畫素作為特徵的。這要感謝群裡的“如果有一天”同學,他堅持認為全部畫素的輸入是最低階的做法,認為用特徵提取後的效果會好多。我問大概能到多少準確率,當時的準確率有92%,我以為已經很高了,結果他說能到99%。在半信半疑中我嘗試了,果真如他所說,結合了rbf核與新特徵訓練的模型達到的precise在99%左右,而且recall也有98%,這真是個令人咋舌並且非常驚喜的成績。 
其實這是一個很顯然的突破點,但是如何提取特徵卻需要自己採用不同的方法進行建模。