记一种车牌矫正或精修方法

        最近在做一个车牌识别的小项目,之前也没这方面的经验,我大概也就是按照一般步骤: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

 

### 车牌矫正技术的实现方法 在现代计算机视觉领域,车牌矫正图像处理的重要环节之一。由于拍摄角度、光线条件等因素的影响,获取的车牌图像往往存在倾斜其他畸变现象。为了提高后续字符分割和识别的准确性,必须对这些图像进行矫正。 #### 1. 使用轮廓提取技术定位车牌区域 轮廓提取是车牌矫正的第一步。通过 `cv2.findContours` 函数可以从二值化后的图像中找到所有可能的目标边界[^4]。具体过程如下: ```python import cv2 import numpy as np # 加载原始图像并转换为灰度图 image = cv2.imread('license_plate.jpg') gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 阈值化处理得到二值图 _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU) # 查找轮廓 contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) ``` 上述代码实现了从输入图像中提取出所有的外部轮廓,并存储在变量 `contours` 中。 --- #### 2. 利用最小外接矩形计算倾斜角 一旦获得了目标物体的轮廓,就可以利用 `cv2.minAreaRect` 计算其最小外接矩形。该函数返回的结果是一个元组 `(center (x,y), (width, height), angle)`,其中 `angle` 表示旋转的角度。 ```python for contour in contours: rect = cv2.minAreaRect(contour) box = cv2.boxPoints(rect) # 获取四个顶点坐标 box = np.int0(box) # 将浮点数转为整型 # 绘制包围框 cv2.drawContours(image, [box], 0, (0, 255, 0), 2) # 提取旋转角度 (_, _), _, angle = rect break # 假设只有一块车牌区域 ``` 如果检测到的角度不在合理范围内,则说明需要对该部分进行校正。 --- #### 3. 应用仿射变换完成最终矫正 根据前面获得的信息构建透视矩阵者仿射变换矩阵来进行几何变形调整。这里采用的是四边形映射至矩形的方式[^2]: ```python def order_points(pts): """ 对四个点按照左上->右上->右下->左下的顺序排列 """ rect = np.zeros((4, 2), dtype="float32") s = pts.sum(axis=1) rect[0] = pts[np.argmin(s)] # 左上方 rect[2] = pts[np.argmax(s)] # 右下方 diff = np.diff(pts, axis=1) rect[1] = pts[np.argmin(diff)] # 右上方 rect[3] = pts[np.argmax(diff)] # 左下方 return rect src_pts = np.float32(order_points(box)) dst_pts = np.float32([[0, 0], [400, 0], [400, 100], [0, 100]]) M = cv2.getPerspectiveTransform(src_pts, dst_pts) warped = cv2.warpPerspective(image, M, (400, 100)) cv2.imshow("Corrected Plate", warped) cv2.waitKey(0) cv2.destroyAllWindows() ``` 以上脚本完成了将不规则形状拉伸成标准尺寸的操作。 --- #### 总结 综上所述,借助 OpenCV 的强大功能可以高效地实施车牌矫正工作流。这包括但不限于以下几个方面: - **轮廓发现**:用于初步划定感兴趣区域; - **霍夫直线变换/极小外包络线求解**:帮助判定偏移量大小方向; - **ROI 定义与裁剪**:限定范围减少冗余数据干扰; - **仿射投影变换**:达成最后一步的空间位置修正目的。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值