記一種車牌矯正或精修方法

        最近在做一個車牌識別的小項目,之前也沒這方面的經驗,我大概也就是按照一般步驟:1). 車牌檢測;2).車牌校正;3).車牌文字識別這麼個三步走的策略來弄。在現實場景中,檢測出來的車牌通常都不是那麼正規正矩,帶有一定的傾斜角度。因此在識別之前要進行車牌矯正,矯正的結果直接影響車牌識別的效果。這篇文章是我在看了 開源項目HyperLPR(基於深度學習高性能中文車牌識別)中關於車牌矯正部分的代碼後的一個記錄。

        例如下面這張汽車尾部圖,因爲車子帶有一定的傾角,在用矩形框檢出車牌時會包含一部分汽車尾部背景圖案。嚴格意義上說,我們只需要關注車牌文字部分的圖像,但這需要借用一定的技術對圖片進行精修、矯正。精修的意思是儘可能地去除干擾信息,矯正是指將圖片擺正。

step1:擴充

        首先,作者對檢出的車牌框按下面的公式進行擴充。這裏面的參數還是比較考究,我粗略地調了調其它參數,沒有默認參數來得好。車牌框的標註格式爲(x,y,w,h),其中(x,y)爲左上角點座標,w、h 分別爲其寬和高。

                                  x -= w * 0.14
                                  w += w * 0.28
                                  y -= h * 0.6
                                  h += h * 1.1

step2:二值化

        擴展後的車牌圖像作者會進行一個二值化操作。圖像的二值化操作就是將圖像上的像素點的灰度值設置爲0或255,這樣將使整個圖像呈現出明顯的黑白效果,從而能凸現出目標的輪廓。在後面將進一步說明車牌圖像中輪廓的重要意義。二值化的方式有多種,如全局二值化,局部二值化,局部自適應二值化。在訪項目中作者使用的是局部自適應二值化,相比全局以及局部二值化,它對於細節表現更加明顯。這在下面這張對比圖中可以明顯地看出來,最右邊的即是局部自適應二值化。

OpenCV 中提供了自適應二值化接口 adaptiveThreshold()。     

原型:    
    cv2.adaptiveThreshold(src, x, adaptive_method, threshold_type, block_size, param1)
參數:
    第一個參數          src          指原圖像,原圖像應該是灰度圖。
    第二個參數            x          指當像素值高於(有時是小於)閾值時應該被賦予的新的像素值
    第三個參數  adaptive_method  指CV_ADAPTIVE_THRESH_MEAN_C或 CV_ADAPTIVE_THRESH_GAUSSIAN_C
    第四個參數   threshold_type    指取閾值類型:必須是下者之一                                  • CV_THRESH_BINARY,二值化
      • CV_THRESH_BINARY_INV,反二值化
    第五個參數    block_size      指用來計算閾值的象素鄰域大小: 3, 5, 7, ...
    第六個參數    param1      指與方法有關的參數。對方法CV_ADAPTIVE_THRESH_MEAN_C 和 CV_ADAPTIVE_THRESH_GAUSSIAN_C, 它是一個從均值或加權均值提取的常數, 儘管它可以是負數。自適應閾值:  對方法CV_ADAPTIVE_THRESH_MEAN_C,先求出塊中的均值,再減掉param1。對方法 CV_ADAPTIVE_THRESH_GAUSSIAN_C ,先求出塊中的加權和(gaussian), 再減掉param1。

擴展的車牌圖像二值化後的效果如下圖:

step3:輪廓

        二值化並不是目的,二值化的目的是凸顯出輪廓。在二值化後的車牌圖像上通過調用opencv 的 findContours 接口來查找檢測物體的輪廓。

原型:
    cv2.findContours(image, mode, method[, contours[, hierarchy[, offset ]]])
參數:
    第一個參數是輸入圖像
    第二個參數表示輪廓的檢索模式,有4種:
        cv2.RETR_EXTERNAL表示只檢測外輪廓
        cv2.RETR_LIST檢測的輪廓不建立等級關係
        cv2.RETR_CCOMP建立兩個等級的輪廓,上面的一層爲外邊界,裏面的一層爲內孔的邊界信息。如果內孔內還有一個連通物體,這個物體的邊界也在頂層。
        cv2.RETR_TREE建立一個等級樹結構的輪廓。
    第三個參數method爲輪廓的近似辦法:
        cv2.CHAIN_APPROX_NONE存儲所有的輪廓點,相鄰的兩個點的像素位置差不超過1,即max(abs(x1-x2),abs(y2-y1))==1
        cv2.CHAIN_APPROX_SIMPLE壓縮水平方向,垂直方向,對角線方向的元素,只保留該方向的終點座標,例如一個矩形輪廓只需4個點來保存輪廓信息
        cv2.CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS使用teh-Chinl chain近似算法

因爲我們關注的是文字部分的輪廓,所以對cv2.findContours檢出的輪廓矩形框會進行過濾,剩餘的輪廓矩形框才能爲我所用。

step4:上下邊界擬合

    藉助於輪廓矩形框,通過調用 cv2.fitLine 函數可以擬合出上下兩條邊界線。

 

step5: 校正

    確定了圖像的上下邊界,一步對圖像進行校正,這一步由仿射變換來實現。

注意到圖片是校正了,但是左右邊界還夾雜太多的背景信息,所以還要對左右邊界進行一次迴歸。

step6: 左右邊界迴歸

    這一步作者直接使用了一個訓練好的模型來回歸出左右邊界。

至此,車牌圖像的矯正就算是完成了,效果還是可以接受的。

部分源代碼(我加了點註釋)

def findContoursAndDrawBoundingBox(image_rgb):


    line_upper  = [];
    line_lower = [];

    line_experiment = []
    grouped_rects = []
    gray_image = cv2.cvtColor(image_rgb,cv2.COLOR_BGR2GRAY)

    # for k in np.linspace(-1.5, -0.2,10):
    for k in np.linspace(-50, 0, 15):

        # thresh_niblack = threshold_niblack(gray_image, window_size=21, k=k)
        # binary_niblack = gray_image > thresh_niblack
        # binary_niblack = binary_niblack.astype(np.uint8) * 255

        # 當一幅圖像上的不同部分具有不同亮度時,我們需要採用自適應閾值.此時的閾值是根據圖像上的每一個小區域計算與其
        # 對應的閾值.因此,在同一幅圖像上的不同區域採用的是不同的閾值,從而使我們能在亮度不同的情況下得到更好的結果.
        """
        Args:
         - src, 原圖像,應該是灰度圖
         -  x, 指當像素值高於(有時是低於)閾值時應該被賦予新的像素值, 255是白色
         - adaptive_method, CV_ADAPTIVE_THRESH_MEAN_C 或 CV_ADAPTIVE_THRESH_GAUSSIAN_C
         - threshold_type: 指取閾值類型
          . CV_THRESH_BINARY, 二進制閾值化
          . CV_THRESH_BINARY_INV, 反二進制閾值化
         - block_size: 用來計算閾值的像素鄰域大小(塊大小):3,5,7,...
         - param1: 指與方法有關的參數.對方法CV_ADAPTIVE_THRESH_MEAN_C和CV_ADAPTIVE_THRESH_GAUSSIAN_C,它是一個從均值或加權均值提取的常數,儘管它可以是負數。
          . 對方法 CV_ADAPTIVE_THRESH_MEAN_C,先求出塊中的均值,再減掉param1。
          . 對方法 CV_ADAPTIVE_THRESH_GAUSSIAN_C ,先求出塊中的加權和(gaussian), 再減掉param1。

        """
        binary_niblack = cv2.adaptiveThreshold(gray_image,255,cv2.ADAPTIVE_THRESH_MEAN_C,cv2.THRESH_BINARY,17,k) #鄰域大小17是不是太大了??
        #cv2.imshow("image1",binary_niblack)
        #cv2.waitKey(0)
        #imagex, contours, hierarchy = cv2.findContours(binary_niblack.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
        contours, hierarchy = cv2.findContours(binary_niblack.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)  # modified by bigz
        for contour in contours:
            #用一個最小的矩形,把找到的形狀包起來
            bdbox = cv2.boundingRect(contour)
            if (bdbox[3]/float(bdbox[2])>0.7 and bdbox[3]*bdbox[2]>100 and bdbox[3]*bdbox[2]<1200) or (bdbox[3]/float(bdbox[2])>3 and bdbox[3]*bdbox[2]<100):
                # cv2.rectangle(rgb,(bdbox[0],bdbox[1]),(bdbox[0]+bdbox[2],bdbox[1]+bdbox[3]),(255,0,0),1)
                line_upper.append([bdbox[0],bdbox[1]])
                line_lower.append([bdbox[0]+bdbox[2],bdbox[1]+bdbox[3]])

                line_experiment.append([bdbox[0],bdbox[1]])
                line_experiment.append([bdbox[0]+bdbox[2],bdbox[1]+bdbox[3]])
                # grouped_rects.append(bdbox)

    """
    想爲圖像周圍建一個邊使用訪函數,這經常在卷積運算或0填充時被用到
    Args:
     - src: 輸入圖像
     - top,bottom,left,right 對應邊界的像素數目
     - borderType: 要添加哪種類型的邊界
       - BORDER_CONSTANT #邊緣填充用固定像素值,比如填充黑邊,就用0,白邊255
       - BORDER_REPLICATE #用原始圖像相應的邊緣的像素去做填充,看起來有一種把圖像邊緣"拉糊了"的效果
    """
    rgb = cv2.copyMakeBorder(image_rgb,30,30,0,0,cv2.BORDER_REPLICATE)
    leftyA, rightyA = fitLine_ransac(np.array(line_lower),3)
    rows,cols = rgb.shape[:2]

    # rgb = cv2.line(rgb, (cols - 1, rightyA), (0, leftyA), (0, 0, 255), 1,cv2.LINE_AA)

    leftyB, rightyB = fitLine_ransac(np.array(line_upper),-3)

    rows,cols = rgb.shape[:2]

    # rgb = cv2.line(rgb, (cols - 1, rightyB), (0, leftyB), (0,255, 0), 1,cv2.LINE_AA)
    pts_map1  = np.float32([[cols - 1, rightyA], [0, leftyA],[cols - 1, rightyB], [0, leftyB]])
    pts_map2 = np.float32([[136,36],[0,36],[136,0],[0,0]])
    mat = cv2.getPerspectiveTransform(pts_map1,pts_map2)
    image = cv2.warpPerspective(rgb,mat,(136,36),flags=cv2.INTER_CUBIC)
    #校正角度
    #cv2.imshow("校正前",image)
    #cv2.waitKey(0)
    image,M = deskew.fastDeskew(image)
    #cv2.imshow("校正後",image)
    #cv2.waitKey(0)
    return image

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章