基于传统图像处理技术的Python OpenCV和PaddleOCR实现车牌识别

目录

一、前言

二、预处理-提取车牌

        1. 转灰度图

        2. 顶帽运算

        3. Sobel算子提取y方向边缘

        4. 自适应二值化

        5. 开运算分割(纵向去噪,分隔)

        6. 闭运算合并

        7. 膨胀/腐蚀

        8. 腐蚀、膨胀:去噪

        9. 获取外轮廓

        10. 遍历所有轮廓,找到车牌轮廓

三、OCR-识别文字

四、优点、不足与改进方向

        1.优点

        2.不足 

        3.改进方向

五、完整代码

六、参考


一、前言

        本文使用了简单的传统图像处理技术,实现了对车牌的提取与字符的识别。

待处理图片

 

        解决思路:

        (1)首先对图像进行处理,使用人工设计的特征提取出图像中的车牌;

        (2)使用OCR库,对提取出的车牌进行字符识别,达到车牌识别的效果。

识别流程

        运行环境 :

              paddleocr == 2.6.1.1

        paddlepaddle == 2.4.0

        matplotlib == 3.4.1

        opencv-python == 4.6.0.66

二、预处理-提取车牌

        预处理的工作,就是使用传统图像处理技术,从图像中找到车牌的位置并截取出来以供OCR使用。

        1. 转灰度图

        由于车牌颜色多种多样,无法使用颜色阈值的方法来提取车牌,因此考虑使用形态学操作的方法来提取车牌。首先将图片转为灰度图,排除颜色对识别的干扰。

import cv2
from paddleocr import PaddleOCR
from matplotlib import pyplot as plt

img = cv2.imread("testimg.jpg")
img = cv2.resize(img, (int(img.shape[1] * 0.5), int(img.shape[0] * 0.5)))
gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)

fig = plt.figure(figsize=(6, 6))
plt.imshow(gray,"gray"), plt.axis('off'),plt.title("gray")
plt.show()
灰度图

        2. 顶帽运算

        图像顶帽运算->原始图像减去图像开运算的结果,得到图像的噪声。

# 创建一个17*17矩阵内核
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (17, 17))
tophat = cv2.morphologyEx(gray, cv2.MORPH_TOPHAT, kernel)

fig = plt.figure(figsize=(6, 6))
plt.imshow(tophat,"gray"), plt.axis('off'),plt.title("tophat")
plt.show()
图像噪声

        3. Sobel算子提取y方向边缘

        使用Sobel算子对字符进行y方向提取,经测试y方向的提取效果优于x方向。

y = cv2.Sobel(tophat, cv2.CV_16S, 1, 0)
absY = cv2.convertScaleAbs(y)
fig = plt.figure(figsize=(6, 6))

plt.imshow(absY,"gray"), plt.axis('off'),plt.title("absY")
plt.show()
y方向边缘

        4. 自适应二值化

        灰度图像二值化,将灰度小于75的像素置为0,大于等于75的像素置为255。

ret, binary = cv2.threshold(absY, 75, 255, cv2.THRESH_BINARY)

fig = plt.figure(figsize=(6, 6))
plt.imshow(binary,"gray"), plt.axis('off'),plt.title("binary")
plt.show()
二值图

        5. 开运算分割(纵向去噪,分隔)

        使用形状为(1,15)的矩形kernel对图像进行Y方向开运算,使图像先腐蚀后膨胀,从而消除图像中的小面积白点。

kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1, 15))
Open = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)

fig = plt.figure(figsize=(6, 6))
plt.imshow(Open,"gray"), plt.axis('off'),plt.title("Open")
plt.show()
开运算处理后的图像

        6. 闭运算合并

        使用形状为(41,15)的矩形kernel对图像进行偏 X方向的闭运算,将图像进行X方向融合找出车牌区域。

kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (41, 15))
close = cv2.morphologyEx(Open, cv2.MORPH_CLOSE, kernel)

fig = plt.figure(figsize=(6, 6))
plt.imshow(close,"gray"), plt.axis('off'),plt.title("close")
plt.show()
闭运算后的图像

        7. 膨胀/腐蚀

        再次使用特定大小的kernel对图像进行膨胀,腐蚀操作去噪,得到车牌区域。

# 中远距离车牌识别
kernel_x = cv2.getStructuringElement(cv2.MORPH_RECT, (25, 7))
kernel_y = cv2.getStructuringElement(cv2.MORPH_RECT, (1, 11))
 
# 7-1、腐蚀、膨胀(去噪)
erode_y = cv2.morphologyEx(close, cv2.MORPH_ERODE, kernel_y)
dilate_y = cv2.morphologyEx(erode_y, cv2.MORPH_DILATE, kernel_y)
 
# 7-2、膨胀、腐蚀(连接)(二次缝合)
dilate_x = cv2.morphologyEx(dilate_y, cv2.MORPH_DILATE, kernel_x)
erode_x = cv2.morphologyEx(dilate_x, cv2.MORPH_ERODE, kernel_x)

fig = plt.figure(figsize=(15, 15))
plt.subplot(131), plt.imshow(erode_y,"gray"), plt.axis('off'),plt.title("erode_y")
plt.subplot(132), plt.imshow(dilate_y, 'gray'),plt.axis('off'),plt.title("dilate_y")
plt.subplot(133), plt.imshow(erode_x, 'gray'),plt.axis('off'),plt.title("erode_x")
plt.show()
腐蚀膨胀后的图像

  

        8. 腐蚀、膨胀:去噪

        再次使用特定大小的kernel对图像进行膨胀,腐蚀操作去除噪声保留车牌区域。

kernel_e = cv2.getStructuringElement(cv2.MORPH_RECT, (25, 9))
erode = cv2.morphologyEx(erode_x, cv2.MORPH_ERODE, kernel_e)

kernel_d = cv2.getStructuringElement(cv2.MORPH_RECT, (25, 11))
dilate = cv2.morphologyEx(erode, cv2.MORPH_DILATE, kernel_d)


fig = plt.figure(figsize=(10, 10))
plt.subplot(121), plt.imshow(erode,"gray"), plt.axis('off'),plt.title("erode")
plt.subplot(122), plt.imshow(dilate, 'gray'),plt.axis('off'),plt.title("dilate")
plt.show()
去噪后的图像

        9. 获取外轮廓

        对二值图进行轮廓提取。

img_copy = img.copy()
 
# 9-1、得到轮廓
contours, hierarchy = cv2.findContours(dilate, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
 
# 9-2、画出轮廓并显示
cv2.drawContours(img_copy, contours, -1, (255, 0, 255), 2)
 
#cv2.imshow('Contours', img_copy)
fig = plt.figure(figsize=(6, 6))
plt.imshow(img_copy,"gray"), plt.axis('off'),plt.title("Contours")
plt.show()
可视化检测到的轮廓

        10. 遍历所有轮廓,找到车牌轮廓

        使用矩形拟合所有轮廓,将满足:高*3<宽<高*7的矩形轮廓表示的ROI作为车牌区域返回。

count = 0
ROI = None
for contour in contours:
    area = cv2.contourArea(contour)  # 计算轮廓内区域的面积
    # 得到矩形区域:左顶点坐标、宽和高
    x, y, w, h = cv2.boundingRect(contour)  # 获取坐标值和宽度、高度
 
    # 判断宽高比例、面积,截取符合图片
    if h*3 < w < h*7 and area > 1000:
        # 截取车牌并显示
        ROI = img[(y - 5):(y + h + 5), (x - 5):(x + w + 5)]  # 高,宽
        try:
            count += 1
            
            fig = plt.figure(figsize=(6, 6))
            plt.imshow(ROI), plt.axis('off'),plt.title("ROI")
            plt.show()
            #return ROI
 
        except:
            print("ROI提取出错!")
            #return
            pass
提取出的车牌图像

三、OCR-识别文字

        OCR字符识别需要用到OCR库,我首先尝试过使用Tesseract来进行OCR识别,但是效果不尽人意,之后尝试使用百度飞桨的OCR库paddleocr进行识别,效果符合预期。若想尝试使用Tesseract,可以按照这篇文章进行安装使用:

Python+OpenCV+Tesseract实现OCR字符识别_Thomas_221126的博客-CSDN博客本文简要介绍了如何使用Python+OpenCV+Tesseract实现OCR字符识别https://blog.csdn.net/qq_54827663/article/details/128051162        这里我使用paddleocr进行识别,首先,使用pip安装paddleocr库:

pip install paddleocr

        若安装缓慢可以换源安装:

pip install paddleocr -i https://pypi.douban.com/simple

         【注意】 paddleocr的运行依赖paddlepaddle库,若出现报错 No module named 'paddle' 则说明没有安装paddlepaddle库,使用pip安装即可:

pip install paddlepaddle

         若安装缓慢可以换源安装:

pip install paddlepaddle -i https://pypi.douban.com/simple

        安装完成后就可以进行OCR识别了,Paddleocr目前支持的多语言语种可以通过修改lang参数进行切换,例如`ch`, `en`, `fr`, `german`, `korean`, `japan`,支持的语种与缩写可在此网页查看:

PaddleOCR/multi_languages.md at release/2.4 · PaddlePaddle/PaddleOCR (github.com)https://github.com/PaddlePaddle/PaddleOCR/blob/release/2.4/doc/doc_ch/multi_languages.md        使用PaddleOCR时默认会打印出debug信息,可输入参数show_log=False关闭打印debug信息,更多的参数说明可查看此网页:

doc/doc_ch/whl.md · PaddlePaddle/PaddleOCR – Gitee.comhttps://gitee.com/paddlepaddle/PaddleOCR/blob/release/2.0/doc/doc_ch/whl.md#%E5%8F%82%E6%95%B0%E8%AF%B4%E6%98%8E

         【注意】若在jupyter中运行此代码,需要允许重复加载动态链接库,否则运行时内核会挂掉,使用如下代码即可允许重复加载动态链接库:

import os

os.environ['KMP_DUPLICATE_LIB_OK']='True'  # 设置允许重复加载动态链接库,若不允许,使用jupyter运行时内核会挂掉

 下面为实现OCR的代码,已包含上述两行代码:

from paddleocr import PaddleOCR
import os


os.environ['KMP_DUPLICATE_LIB_OK']='True'  # 设置允许重复加载动态链接库,若不允许,使用jupyter运行时内核会挂掉
# Paddleocr目前支持的多语言语种可以通过修改lang参数进行切换
# 例如`ch`, `en`, `fr`, `german`, `korean`, `japan`
ocr = PaddleOCR(use_angle_cls=False, use_gpu=False,
                lang="ch", show_log=False)  # need to run only once to download and load model into memory
if ROI is None:
    print("没有提取到车牌")
    exit()
    
fig = plt.figure(figsize=(6, 6))
plt.imshow(ROI), plt.axis('off'),plt.title("ROI")
plt.show()

ocr_text = ocr.ocr(ROI, cls=False)  # 进行ROI识别
for line in ocr_text:
    number_plate = line[-1][-1][0]
    print("车牌:", end="")
    print(number_plate)
识别结果

四、优点、不足与改进方向

        1.优点

        与使用目标识别算法相比,使用传统图像处理技术来提取车牌所需的算力更小、开发成本更低、对硬件设备的要求更低,同时具有可解释性与一定的泛化能力和鲁棒性。

        2.不足 

        使用传统图像处理技术来提取车牌时,其特征提取主要依赖人工设计的提取器,且受到图像的光照、图像中车牌角度、车牌所占面积等因素影响,泛化能力及鲁棒性较差。

        3.改进方向

        本文只使用了简单的传统图像处理技术,导致算法极易受到各种因素的影响,可以尝试使用更专业高级的图像处理技术来提取特征并尽力减小各种因素的影响,以进一步提高算法的泛化能力与鲁棒性,达到实际可用的程度;或可以使用卷积神经网络来实现对车牌的高精度定位。

五、完整代码

import cv2
from paddleocr import PaddleOCR
import os
from matplotlib import pyplot as plt


def Morph_Distinguish(img):
    # 1、转灰度图
    gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)

#     fig = plt.figure(figsize=(6, 6))
#     plt.imshow(gray, "gray"), plt.axis('off'), plt.title("gray")
#     plt.show()

    # 2、顶帽运算
    # 创建一个17*17矩阵内核
    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (17, 17))
    # cv2.morphologyEx:形态学操作,将腐蚀膨胀结合使用
    # cv2.MORPH_TOPHAT :顶帽操作(原图像-开运算结果:突出灰度中亮的区域)
    # tophat = cv2.morphologyEx(gray, cv2.MORPH_BLACKHAT, kernel)
    tophat = cv2.morphologyEx(gray, cv2.MORPH_TOPHAT, kernel)

#     fig = plt.figure(figsize=(6, 6))
#     plt.imshow(tophat, "gray"), plt.axis('off'), plt.title("tophat")
#     plt.show()

    # 3、Sobel算子提取y方向边缘(揉成一坨)
    y = cv2.Sobel(tophat, cv2.CV_16S, 1, 0)
    absY = cv2.convertScaleAbs(y)

#     fig = plt.figure(figsize=(6, 6))
#     plt.imshow(absY, "gray"), plt.axis('off'), plt.title("absY")
#     plt.show()


    # 4、自适应二值化(阈值自己可调)
    ret, binary = cv2.threshold(absY, 75, 255, cv2.THRESH_BINARY)

#     fig = plt.figure(figsize=(6, 6))
#     plt.imshow(binary, "gray"), plt.axis('off'), plt.title("binary")
#     plt.show()

    # 5、开运算分割(纵向去噪,分隔)
    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1, 15))
    Open = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)

#     fig = plt.figure(figsize=(6, 6))
#     plt.imshow(Open, "gray"), plt.axis('off'), plt.title("Open")
#     plt.show()

    # 6、闭运算合并,把图像闭合、揉团,使图像区域化,便于找到车牌区域,进而得到轮廓
    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (41, 15))
    close = cv2.morphologyEx(Open, cv2.MORPH_CLOSE, kernel)

#     fig = plt.figure(figsize=(6, 6))
#     plt.imshow(close, "gray"), plt.axis('off'), plt.title("close")
#     plt.show()

    # 7、膨胀/腐蚀(去噪得到车牌区域)
    # 中远距离车牌识别
    kernel_x = cv2.getStructuringElement(cv2.MORPH_RECT, (25, 7))
    kernel_y = cv2.getStructuringElement(cv2.MORPH_RECT, (1, 11))
    # 近距离车牌识别
    # kernel_x = cv2.getStructuringElement(cv2.MORPH_RECT, (79, 15))
    # kernel_y = cv2.getStructuringElement(cv2.MORPH_RECT, (1, 31))
    # 7-1、腐蚀、膨胀(去噪)
    erode_y = cv2.morphologyEx(close, cv2.MORPH_ERODE, kernel_y)
    dilate_y = cv2.morphologyEx(erode_y, cv2.MORPH_DILATE, kernel_y)

    # 7-2、膨胀、腐蚀(连接)(二次缝合)
    dilate_x = cv2.morphologyEx(dilate_y, cv2.MORPH_DILATE, kernel_x)
    erode_x = cv2.morphologyEx(dilate_x, cv2.MORPH_ERODE, kernel_x)

#     fig = plt.figure(figsize=(15, 15))
#     plt.subplot(131), plt.imshow(erode_y, "gray"), plt.axis('off'), plt.title("erode_y")
#     plt.subplot(132), plt.imshow(dilate_y, 'gray'), plt.axis('off'), plt.title("dilate_y")
#     plt.subplot(133), plt.imshow(erode_x, 'gray'), plt.axis('off'), plt.title("erode_x")
#     plt.show()

    # 8、腐蚀、膨胀:去噪
    kernel_e = cv2.getStructuringElement(cv2.MORPH_RECT, (25, 9))
    erode = cv2.morphologyEx(erode_x, cv2.MORPH_ERODE, kernel_e)

    kernel_d = cv2.getStructuringElement(cv2.MORPH_RECT, (25, 11))
    dilate = cv2.morphologyEx(erode, cv2.MORPH_DILATE, kernel_d)

#     fig = plt.figure(figsize=(10, 10))
#     plt.subplot(121), plt.imshow(erode, "gray"), plt.axis('off'), plt.title("erode")
#     plt.subplot(122), plt.imshow(dilate, 'gray'), plt.axis('off'), plt.title("dilate")
#     plt.show()

    # 9、获取外轮廓
    img_copy = img.copy()

    # 9-1、得到轮廓
    contours, hierarchy = cv2.findContours(dilate, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    # 9-2、画出轮廓并显示
    cv2.drawContours(img_copy, contours, -1, (255, 0, 255), 2)

#     fig = plt.figure(figsize=(6, 6))
#     plt.imshow(img_copy, "gray"), plt.axis('off'), plt.title("Contours")
#     plt.show()

    # 10、遍历所有轮廓,找到车牌轮廓
    count = 0
    for contour in contours:
        area = cv2.contourArea(contour)  # 计算轮廓内区域的面积
        # 10-1、得到矩形区域:左顶点坐标、宽和高
        x, y, w, h = cv2.boundingRect(contour)  # 获取坐标值和宽度、高度

        # 10-2、获取轮廓区域的形状信息
        perimeter = cv2.arcLength(contour, True)  # 计算轮廓周长
        # 以精度0.02近似拟合轮廓
        approx = cv2.approxPolyDP(contour, 0.02 * perimeter, True)  # 获取轮廓角点坐标
        CornerNum = len(approx)  # 轮廓角点的数量
        # cv2.polylines(img_copy1, [approx], True, (0, 255, 0), 3)
        # cv2.imshow('approx',  img_copy1)
        # cv2.waitKey(0)

        # 10-2、判断宽高比例、面积、轮廓角点数量,截取符合图片
        # if (w > h * 3 and w < h * 7 )and area>1000 and CornerNum<=5:
        if h*3 < w < h*7 and area > 1000:
            # print(count)
            # print(f"CornerNum:{CornerNum},area:{area}")
            # 截取车牌并显示
            # print(x, y, w, h)
            ROI = img[(y - 5):(y + h + 5), (x - 5):(x + w + 5)]  # 高,宽
            try:
                count += 1

#                 fig = plt.figure(figsize=(6, 6))
#                 plt.imshow(ROI), plt.axis('off'), plt.title("img")
#                 plt.show()

                return ROI

            except:
                print("ROI提取出错!")
                return
                pass
            

if __name__ == '__main__':
    os.environ['KMP_DUPLICATE_LIB_OK'] = 'True'  # 设置允许重复加载动态链接库,若不允许,使用jupyter运行时内核会挂掉

    # Paddleocr目前支持的多语言语种可以通过修改lang参数进行切换
    # 例如`ch`, `en`, `fr`, `german`, `korean`, `japan`
    ocr = PaddleOCR(use_angle_cls=False, use_gpu=False,
                    lang="ch", show_log=False)  # need to run only once to download and load model into memory

    img = cv2.imread("testimg.jpg")
    img = cv2.resize(img, (int(img.shape[1] * 0.5), int(img.shape[0] * 0.5)))

    img = Morph_Distinguish(img.copy())  # 获取车牌ROI

    if img is None:
        print("没有提取到车牌")
        exit()

    fig = plt.figure(figsize=(6, 6))
    plt.imshow(img), plt.axis('off'), plt.title("img")
    plt.show()

    ocr_text = ocr.ocr(img, cls=False)
    for line in ocr_text:
        number_plate = line[-1][-1][0]
        print("车牌:", end="")
        print(number_plate)

六、参考

1、 OpenCV(项目)车牌识别1 — 车牌提取(形态学)__睿智_的博客-CSDN博客

2、Jupyter Notebook报错_写代码的林克的博客-CSDN博客_jupyter initilizing

3、jupyter内核挂掉重启 – 掘金 (juejin.cn)

4、Anaconda报NotWritableError错时解决的方法_花黄的博客-CSDN博客

物联沃分享整理
物联沃-IOTWORD物联网 » 基于传统图像处理技术的Python OpenCV和PaddleOCR实现车牌识别

发表评论