资讯专栏INFORMATION COLUMN

OpenCV(项目)车牌识别2 -- 车牌字符分割

lufficc / 2532人阅读

目录

试错

1、没有膨胀/膨胀过小:无法连接上单个字符。 

2、膨胀过大:错误连接相邻字符。

一、直方图处理原理

1、横向分割

2、纵向分割

过程:

一、中值滤波、灰度化

二、二值化(统一黑底白字)

三、膨胀处理

四、统计各行各列白色像素个数(为绘制直方图做准备)

五、绘制直方图(横、纵)

六、分割车牌图像

1、横向分割:分割上下边框

1-1、下半图波谷

 1-2、上半图波谷

代码 及效果

2、纵向分割:分割字符

2-0、极大值判断(去除左右两边过大的噪声)

2-1、前谷(前一个波谷)

 2-2、字符开始(上升)

2-3、字符结束

2-4、分割尾部杂质

 2-5、最后的处理:检查收尾情况

 总代码及效果

参考资料


试错

一开始尝试的是形态学提取字符,但发现有些情况是相悖的,会顾此失彼,无法兼顾很多情况。 

1、没有膨胀/膨胀过小:无法连接上单个字符。 

可以看到,一些没有连接上的汉字,轮廓提取就把它们当成了多个字符。

2、膨胀过大:错误连接相邻字符。

给予膨胀之后,膨胀又太大了,虽然同一个字符的不同笔画连接成功,但是又错误地把两个字符连接在了一起。 

 

  

        上面的算法感觉很难行得通了,除非借助其他分割算法。下面是优化后的更佳算法,用直方图处理

一、直方图处理原理

 

1、横向分割

分割上下边框

        把图像分为上下两部分,根据像素点的个数,找到最小的像素行最小波谷)。 上半部分最小波谷作为下半部分最小波谷作为

 

2、纵向分割

切割字符

每一个字符前面都一定会有波谷出现,波谷出现再上升的时候,作为字符开始的判断标志。

 

 

 

过程:

一、中值滤波、灰度化

# 1、中值滤波    mid = cv.medianBlur(image, 5)    # 2、灰度化    gray = cv.cvtColor(mid, cv.COLOR_BGR2GRAY)

二、二值化(统一黑底白字)

# 3、二值化    ret, binary = cv.threshold(gray, 0, 255, cv.THRESH_OTSU)    # 统一得到黑底白字    if(IsWhiteMore(binary)):     #白色部分多则为真,意味着背景是白色,需要黑底白字        ret, binary = cv.threshold(gray, 0, 255, cv.THRESH_OTSU | cv.THRESH_BINARY_INV)    cv.imshow("binary", binary)
# 得到黑底白字(白色多则返回真)def IsWhiteMore(binary):    white = black = 0    height, width = binary.shape    # 遍历每个像素    for i in range(height):        for j in range(width):            if binary[i,j]==0:                black+=1            else:                white+=1    if white >= black:        return True    else:        return False

 

三、膨胀处理

 主要就是为了连接像“川”这样的横向不连接的字符。

# 4、膨胀(粘贴横向字符)    kernel = cv.getStructuringElement(cv.MORPH_RECT, (7,1))     #横向连接字符    dilate = cv.dilate(binary, kernel)    cv.imshow("dilate", dilate)

 

 

四、统计各行各列白色像素个数(为绘制直方图做准备)

# 5、统计各行各列白色像素个数(为了得到直方图横纵坐标)    ptx, pty = White_Statistic(dilate)
# 二-5、统计白色像素点(分别统计每一行、每一列)def White_Statistic(image):    ptx = []  # 每行白色像素个数    pty = []  # 每列白色像素个数    height, width = image.shape    # 逐行遍历    for i in range(height):        num = 0        for j in range(width):            if(image[i][j]==255):                num = num+1        ptx.append(num)    # 逐列遍历    for i in range(width):        num = 0        for j in range(height):            if (image[j][i] == 255):                num = num + 1        pty.append(num)    return ptx, pty

五、绘制直方图(横、纵)

有了直方图,可以很直观地看出每一行、每一列的像素分布情况。 

# 6、绘制直方图(横、纵)    Draw_Hist(ptx, pty)
# 二-6、绘制直方图def Draw_Hist(ptx, pty):    # 依次得到各行、列    rows, cols = len(ptx), len(pty)    row = [i for i in range(rows)]    col = [j for j in range(cols)]    # 横向直方图    plt.barh(row, ptx, color="black", height=1)    #       纵    横    plt.show()    # 纵向直方图    plt.bar(col, pty, color="black", width=1)    #       横    纵    plt.show()

 

 

六、分割车牌图像

# 二-7、分割车牌图像(根据直方图)def Cut_Image(ptx, pty, binary, dilate):    h1 = h2 = 0    #顶  底    begin = False        #标记开始/结束    # 1、依次得到各行、列    rows, cols = len(ptx), len(pty)    row = [i for i in range(rows)]    col = [j for j in range(cols)]    # 2、横向分割:上下边框    h1, h2 = Cut_X(ptx, rows)    # 3、纵向分割:分割字符    Cut_Y(pty, cols, h1, h2, binary)

1、横向分割:分割上下边框

        把图像分为两部分:上半图和下半图,分别找它们的波谷,就可以确定字符上下边缘,进行分割。 

1-1、下半图波谷

在下半图找波谷,确定字符的下边缘。 

# 1、下半图波谷    min, r = 300, 0    for i in range(int(rows / 2)):        if ptx[i] < min:            min = ptx[i]            r = i    h1 = r  # 添加下行(作为顶)

 1-2、上半图波谷

在上半图找波谷,确定字符的上边缘。 

# 2、上半图波谷    min, r = 300, 0    for i in range(int(rows / 2), rows):        if ptx[i] < min:            min = ptx[i]            r = i    h2 = r  # 添加上行(作为底)

代码 及效果

# 2、横向分割:上下边框    h1, h2 = Cut_X(ptx, rows)    cut_x = binary[h1:h2, :]    cv.imshow("cut_x", cut_x)
# 二-7-2、横向分割:上下边框def Cut_X(ptx, rows):    # 横向切割(分为上下两张图,分别找其波谷,确定顶和底)    # 1、下半图波谷    min, r = 300, 0    for i in range(int(rows / 2)):        if ptx[i] < min:            min = ptx[i]            r = i    h1 = r  # 添加下行(作为顶)    # 2、上半图波谷    min, r = 300, 0    for i in range(int(rows / 2), rows):        if ptx[i] < min:            min = ptx[i]            r = i    h2 = r  # 添加上行(作为底)    return h1, h2

 可以看到,上下边框明显得到了适当的切割,尤其是下边框。

 

 

 

2、纵向分割:分割字符

2-0、极大值判断(去除左右两边过大的噪声)

如果在左边或者右边出现极大值(左右按10%计算),则视为噪声,进行处理。

 

 

 

 

 

 

# 0、极大值判断        if pty[j] == max(pty):            if j < 30:  # 左边(跳过)                w2 = j                if begin == True:                    begin = False                continue            elif j > 270:  # 右边(直接收尾)                if begin == True:                    begin = False                w2 = j                b_copy = binary.copy()                b_copy = b_copy[h1:h2, w1:w2]                cv.imshow("binary%d-%d" % (count, con), b_copy)                cv.imwrite("car_characters/image%d-%d.jpg" % (count, con), b_copy)                con += 1                break

2-1、前谷(前一个波谷)

# 1、前谷(前面的波谷)        if pty[j] < 12 and begin == False:  # 前谷判断:像素数量<12            last = pty[j]            w = j

 

 2-2、字符开始(上升)

# 2、字符开始(上升)        elif last < 12 and pty[j] > 20:            last = pty[j]            w1 = j            begin = True

 

 

2-3、字符结束

情景一:单个字符:直接分割(判断语句中过滤掉噪声)

# 3-1、分割并显示(排除过小情况)            if 10 < width < WIDTH + 3:  # 要排除掉干扰,又不能过滤掉字符”1“                b_copy = binary.copy()                b_copy = b_copy[h1:h2, w1:w2]                cv.imshow("binary%d-%d" % (count, con), b_copy)                cv.imwrite("car_characters/image%d-%d.jpg" % (count, con), b_copy)                con += 1

 

情景二:多个字符:从多字符中分割单字符

 

 

 

# 3-2、从多个贴合字符中提取单个字符            elif width >= WIDTH + 3:                # 统计贴合字符个数                num = int(width / WIDTH + 0.5)  # 四舍五入                for k in range(num):                    # w1和w2坐标向后移(用w3、w4代替w1和w2)                    w3 = w1 + k * WIDTH                    w4 = w1 + (k + 1) * WIDTH                    b_copy = binary.copy()                    b_copy = b_copy[h1:h2, w3:w4]                    cv.imshow("binary%d-%d" % (count, con), b_copy)                    cv.imwrite("car_characters/image%d-%d.jpg" % (count, con), b_copy)                    con += 1

2-4、分割尾部杂质

由于车牌提取的时候没处理好,导致车牌尾部有些许杂质 。

 

# 4、分割尾部噪声(距离过远默认没有字符了)        elif begin == False and (j - w2) > 30:            break

 

 2-5、最后的处理:检查收尾情况

 如果最后没有下降沿(波谷),那么它就不会判断为字符,那么可能会遗漏掉最后一个字符。所以我们进行一个收尾的操作。

 

# 最后检查收尾情况    if begin == True:        w2 = 295        b_copy = binary.copy()        b_copy = b_copy[h1:h2, w1:w2]        cv.imshow("binary%d-%d" % (count, con), b_copy)        cv.imwrite("car_characters/image%d-%d.jpg" % (count, con), b_copy)

 

 总代码及效果

# 车牌识别import cv2 as cvimport numpy as npimport osfrom matplotlib import pyplot as plt# 得到黑底白字(白色多则返回真)def IsWhiteMore(binary):    white = black = 0    height, width = binary.shape    # 遍历每个像素    for i in range(height):        for j in range(width):            if binary[i,j]==0:                black+=1            else:                white+=1    if white >= black:        return True    else:        return False# 限制图像大小(车牌)def Limit(image):    height, width, channel = image.shape    # 设置权重    weight = width/300    # 计算输出图像的宽和高    last_width = int(width/weight)    last_height = int(height/weight)    image = cv.resize(image, (last_width, last_height))    return image# 二-5、统计白色像素点(分别统计每一行、每一列)def White_Statistic(image):    ptx = []  # 每行白色像素个数    pty = []  # 每列白色像素个数    height, width = image.shape    # 逐行遍历    for i in range(height):        num = 0        for j in range(width):            if(image[i][j]==255):                num = num+1        ptx.append(num)    # 逐列遍历    for i in range(width):        num = 0        for j in range(height):            if (image[j][i] == 255):                num = num + 1        pty.append(num)    return ptx, pty# 二-6、绘制直方图def Draw_Hist(ptx, pty):    # 依次得到各行、列    rows, cols = len(ptx), len(pty)    row = [i for i in range(rows)]    col = [j for j in range(cols)]    # 横向直方图    plt.barh(row, ptx, color="black", height=1)    #       纵    横    plt.show()    # 纵向直方图    plt.bar(col, pty, color="black", width=1)    #       横    纵    plt.show()# 二-7-2、横向分割:上下边框def Cut_X(ptx, rows):    # 横向切割(分为上下两张图,分别找其波谷,确定顶和底)    # 1、下半图波谷    min, r = 300, 0    for i in range(int(rows / 2)):        if ptx[i] < min:            min = ptx[i]            r = i    h1 = r  # 添加下行(作为顶)    # 2、上半图波谷    min, r = 300, 0    for i in range(int(rows / 2), rows):        if ptx[i] < min:            min = ptx[i]            r = i    h2 = r  # 添加上行(作为底)    return h1, h2# 二-7-3、纵向分割:分割字符def Cut_Y(pty, cols, h1, h2, binary):    WIDTH = 32          # 经过测试,一个字符宽度约为32    w = w1 = w2 = 0     # 前谷 字符开始 字符结束    begin = False       # 字符开始标记    last = 10           # 上一次的值    con = 0             # 计数    # 纵向切割(正式切割字符)    for j in range(int(cols)):        # 0、极大值判断        if pty[j] == max(pty):            if j < 30:  # 左边(跳过)                w2 = j                if begin == True:                    begin = False                continue            elif j > 270:  # 右边(直接收尾)                if begin == True:                    begin = False                w2 = j                b_copy = binary.copy()                b_copy = b_copy[h1:h2, w1:w2]                cv.imshow("binary%d-%d" % (count, con), b_copy)                cv.imwrite("car_characters/image%d-%d.jpg" % (count, con), b_copy)                con += 1                break        # 1、前谷(前面的波谷)        if pty[j] < 12 and begin == False:  # 前谷判断:像素数量<12            last = pty[j]            w = j        # 2、字符开始(上升)        elif last < 12 and pty[j] > 20:            last = pty[j]            w1 = j            begin = True        # 3、字符结束        elif pty[j] < 13 and begin == True:            begin = False            last = pty[j]            w2 = j            width = w2 - w1            # 3-1、分割并显示(排除过小情况)            if 10 < width < WIDTH + 3:  # 要排除掉干扰,又不能过滤掉字符”1“                b_copy = binary.copy()                b_copy = b_copy[h1:h2, w1:w2]                cv.imshow("binary%d-%d" % (count, con), b_copy)                cv.imwrite("car_characters/image%d-%d.jpg" % (count, con), b_copy)                con += 1            # 3-2、从多个贴合字符中提取单个字符            elif width >= WIDTH + 3:                # 统计贴合字符个数                num = int(width / WIDTH + 0.5)  # 四舍五入                for k in range(num):                    # w1和w2坐标向后移(用w3、w4代替w1和w2)                    w3 = w1 + k * WIDTH                    w4 = w1 + (k + 1) * WIDTH                    b_copy = binary.copy()                    b_copy = b_copy[h1:h2, w3:w4]                    cv.imshow("binary%d-%d" % (count, con), b_copy)                    cv.imwrite("car_characters/image%d-%d.jpg" % (count, con), b_copy)                    con += 1        # 4、分割尾部噪声(距离过远默认没有字符了)        elif begin == False and (j - w2) > 30:            break    # 最后检查收尾情况    if begin == True:        w2 = 295        b_copy = binary.copy()        b_copy = b_copy[h1:h2, w1:w2]        cv.imshow("binary%d-%d" % (count, con), b_copy)        cv.imwrite("car_characters/image%d-%d.jpg" % (count, con), b_copy)# 二-7、分割车牌图像(根据直方图)def Cut_Image(ptx, pty, binary, dilate):    h1 = h2 = 0    #顶  底    begin = False        #标记开始/结束    # 1、依次得到各行、列    rows, cols = len(ptx), len(pty)    row = [i for i in range(rows)]    col = [j for j in range(cols)]    # 2、横向分割:上下边框    h1, h2 = Cut_X(ptx, rows)    # cut_x = binary[h1:h2, :]    # cv.imshow("cut_x", cut_x)    # 3、纵向分割:分割字符    Cut_Y(pty, cols, h1, h2, binary)# 一、形态学提取车牌def Get_Licenses(image):    # 1、转灰度图    gray = cv.cvtColor(image, cv.COLOR_RGB2GRAY)    # cv.imshow("gray", gray)    # 2、顶帽运算    # gray = cv.equalizeHist(gray)    kernel = cv.getStructuringElement(cv.MORPH_RECT, (17,17))    tophat = cv.morphologyEx(gray, cv.MORPH_TOPHAT, kernel)    # cv.imshow("tophat", tophat)    # 3、Sobel算子提取y方向边缘(揉成一坨)    y = cv.Sobel(tophat, cv.CV_16S, 1,     0)    absY = cv.convertScaleAbs(y)    # cv.imshow("absY", absY)    # 4、自适应二值化(阈值自己可调)    ret, binary = cv.threshold(absY, 75, 255, cv.THRESH_BINARY)    # cv.imshow("binary", binary)    # 5、开运算分割(纵向去噪,分隔)    kernel = cv.getStructuringElement(cv.MORPH_RECT, (1, 15))    Open = cv.morphologyEx(binary, cv.MORPH_OPEN, kernel)    # cv.imshow("Open", Open)    # 6、闭运算合并,把图像闭合、揉团,使图像区域化,便于找到车牌区域,进而得到轮廓    kernel = cv.getStructuringElement(cv.MORPH_RECT, (41, 15))    close = cv.morphologyEx(Open, cv.MORPH_CLOSE, kernel)    # cv.imshow("close", close)    # 7、膨胀/腐蚀(去噪得到车牌区域)    # 中远距离车牌识别    kernel_x = cv.getStructuringElement(cv.MORPH_RECT, (25, 7))    kernel_y = cv.getStructuringElement(cv.MORPH_RECT, (1, 11))    # 近距离车牌识别    # kernel_x = cv.getStructuringElement(cv.MORPH_RECT, (79, 15))    # kernel_y = cv.getStructuringElement(cv.MORPH_RECT, (1, 31))    # 7-1、腐蚀、膨胀(去噪)    erode_y = cv.morphologyEx(close, cv.MORPH_ERODE, kernel_y)    # cv.imshow("erode_y", erode_y)    dilate_y = cv.morphologyEx(erode_y, cv.MORPH_DILATE, kernel_y)    # cv.imshow("dilate_y", dilate_y)    # 7-1、膨胀、腐蚀(连接)(二次缝合)    dilate_x = cv.morphologyEx(dilate_y, cv.MORPH_DILATE, kernel_x)    # cv.imshow("dilate_x", dilate_x)    erode_x = cv.morphologyEx(dilate_x, cv.MORPH_ERODE, kernel_x)    # cv.imshow("erode_x", erode_x)    # 8、腐蚀、膨胀:去噪    kernel_e = cv.getStructuringElement(cv.MORPH_RECT, (25, 9))    erode = cv.morphologyEx(erode_x, cv.MORPH_ERODE, kernel_e)    # cv.imshow("erode", erode)    kernel_d = cv.getStructuringElement(cv.MORPH_RECT, (25, 11))    dilate = cv.morphologyEx(erode, cv.MORPH_DILATE, kernel_d)    # cv.imshow("dilate", dilate)    # 9、获取外轮廓    img_copy = image.copy()    # 9-1、得到轮廓    contours, hierarchy = cv.findContours(dilate, cv.RETR_EXTERNAL, cv.CHAIN_APPROX_SIMPLE)    # 9-2、画出轮廓并显示    cv.drawContours(img_copy, contours, -1, (255, 0, 255), 2)    # cv.imshow("Contours", img_copy)    # 10、遍历所有轮廓,找到车牌轮廓    i = 0    for contour in contours:        # 10-1、得到矩形区域:左顶点坐标、宽和高        rect = cv.boundingRect(contour)        # 10-2、判断宽高比例是否符合车牌标准,截取符合图片        if rect[2]>rect[3]*3 and rect[2]

 

 

 (“沪”前面的痕迹由于提取的过程处理不到位,这里实在是不好处理,直方图上和它都连一起了,所以无能为力,有办法的伙伴可以建议建议)

 (好家伙,直方图上看完全就是一个字了。。。)

 

         后面的一部分车牌提取都做的不到位,字符也分离自然不是很好。目前做的还挺菜的,有好建议的伙伴可以提出来。

 

 

参考资料

https://www.bilibili.com/video/BV1yg4y187kU?p=2

https://blog.csdn.net/shanglianlm/article/details/78045170?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522163037372316780271535395%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=163037372316780271535395&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~baidu_landing_v2~default-4-78045170.first_rank_v2_pc_rank_v29&utm_term=%E8%BD%A6%E7%89%8C%E8%AF%86%E5%88%AB%E5%AD%97%E7%AC%A6%E5%88%86%E5%89%B2%E6%96%B9%E6%B3%95&spm=1018.2226.3001.4187

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/119120.html

相关文章

  • 使用深度学习进行自动车牌检测和识别

    摘要:在智能设备中,提到了车辆牌照检测和识别系统。道路安全该系统用于检测超过一定速度的车牌,将车牌读取系统与道路雷达耦合。车牌检测为了检测许可证,我们将使用基于卷积神经网络的深度学习对象检测体系结构。为确保正确分割,必须执行初步处理。 点击上方小白学视觉,选择加星标或置顶 重磅干货,第一时间送...

    fsmStudy 评论0 收藏0
  • OpenCV项目车牌识别4 -- 总结篇

    摘要:后期进军深度学习机器学习,可能会对这些进行优化。有什么好的建议大家可以提出来,共同进步,谢谢 目录 一、效果 1、成功案例 2、经典失败案例(单字符识别成类似字符) 3、其他失败案例 二、总结 三、车牌识别总代码 一、效果 1、成功案例   2、经典失败案例(单字符识别成类似字符)  ...

    YacaToy 评论0 收藏0
  • OpenCV项目车牌识别1 -- 车牌提取

    摘要:横纵方向腐蚀膨胀膨胀腐蚀去噪得到车牌区域中远距离车牌识别近距离车牌识别腐蚀膨胀去噪膨胀腐蚀连接二次缝合纵方向腐蚀膨胀分割上下距离比较近的物体。 目录 一、形态学车牌提取(简单:单情景)  1、读取图片,转灰度图  2、提取轮廓ÿ...

    番茄西红柿 评论0 收藏2637
  • OpenCV项目车牌识别3 -- 模板匹配

    摘要:目录一基础理论思想大致过程二详细过程首先需要模板库得到模板原图限定大小模板匹配匹配所有子文件夹,保存最佳得分最匹配项三大致过程细分类,节省时间汉字匹配英文字符匹配数字英文匹配   目录 一、基础理论 1、思想 2、大致过程 二、详细过程 1、首先需要模板库 2、得到模板 3、原图限定大...

    jayce 评论0 收藏0
  • 原理讲解-项目实战 &lt;-&gt; DeepSORT算法实现车辆和行人跟踪计数和

    摘要:点击上方码农的后花园,选择星标公众号精选文章,第一时间送达上一期中我们讲解了多目标跟踪算法算法的原理实现,今天就给大家带来基于算法和算法实现智能交通场景下车辆和行人跟踪计数和车辆是否道路违规检测的落地项目。 点击上方码农的后花园,选择星标 公众号 精选文章,第一时间送达 上一期中我们讲解...

    FleyX 评论0 收藏0

发表评论

0条评论

lufficc

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<