2022-11-29

车牌识别

问题:给定一张车辆正面或背面拍摄图像,从其中准确地提取出车牌号。

要求:这是一张来源于车牌号公开数据集CCPD: Chinese City Parking Dataset中的图像。要求处理并输出结果:皖A D23628

testimg.jpg

项目流程

1.车牌提取

识别出车牌的位置并提取,矫正,预处理

2.字符识别

针对二值图车牌进行(分割)识别字符

代码

识别方法一:

一、车牌提取

##导入库
import cv2

import numpy as np

import pytesseract

from PIL import Image

from collections import Counter

from matplotlib import pyplot as plt

import os.path

from skimage import io, data

from numpy.linalg import norm

二、读取图片

# 读取图片
img = cv2.imread(r'testimg.jpg')
fig = plt.figure(figsize=(10, 8))
plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)), plt.axis('off')
plt.show()

三、车牌的提取

使用的技术:
二值化,均值滤波和中值滤波,高斯平滑模糊,边缘检测,膨胀,腐蚀,轮廓检测,找出车牌,透视变换

提取方法:

def deal_license(licenseimg):
    # 车牌图片二值化

将图片转为灰度图

    gray_img = cv2.cvtColor(licenseimg, cv2.COLOR_BGR2GRAY)
均值滤波

均值滤波是一种简单典型的线性滤波算法。方法的思想是对当前像素,选择一个模板,该模板为此像素邻近的几个像素组成,用模板的均值替代当前像素的值。其原理如下式所示:


image.png

式中,(x,y)为像素点,g(x, y)为(x,y)的新灰度值,M 为模板 S 包含的像素点个数,模板 S 可以是四邻域或八邻域,分别如下图所示,f (x, y)为(x, y)的原灰度值。


image.png

均值滤波的优点是方法简单,计算速度快,缺点是降低噪声的同时使图像产生模糊,尤其是在边缘和细节部分。
    # 均值滤波,去噪声
    kernel = np.ones((3, 3), np.float32)/9
    gray_img = cv2.filter2D(gray_img, -1, kernel)
二值化

为了将整个图像呈现出明显的黑白效果,我们采用图像二值化。

图像的二值化的基本原理:

图像的二值化处理就是将图像上的点的灰度置为0或255,也就是将整个图像呈现出明显的黑白效果。即将256个亮度等级的灰度图像通过适当的阈值选取而获得仍然可以反映图像整体和局部特征的二值化图像。在数字图像处理中,二值图像占有非常重要的地位,特别是在实用的图像处理中,以二值图像处理实现而构成的系统是很多的,要进行二值图像的处理与分析,首先要把灰度图像二值化,得到二值化图像,这样子有利于在对图像做进一步处理时,图像的集合性质只与像素值为0或255的点的位置有关,不再涉及像素的多级值,使处理变得简单,而且数据的处理和压缩量小。为了得到理想的二值图像,一般采用封闭、连通的边界定义不交叠的区域。所有灰度大于或等于阈值的像素被判定为属于特定物体,其灰度值为255表示,否则这些像素点被排除在物体区域以外,灰度值为0,表示背景或者例外的物体区域。如果某特定物体在内部有均匀一致的灰度值,并且其处在一个具有其他等级灰度值的均匀背景下,使用阈值法就可以得到比较的分割效果。如果物体同背景的差别表现不在灰度值上(比如纹理不同),可以将这个差别特征转换为灰度的差别,然后利用阈值选取技术来分割该图像。动态调节阈值实现图像的二值化可动态观察其分割图像的具体结果。

    # 二值化处理
    ret, thresh = cv2.threshold(gray_img, 110, 255, cv2.THRESH_BINARY)
    return thresh
# 1、imread加载图片
img = cv2.imread(r'./testimg.jpg')

# 2、将图像转换为灰度图

img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
高斯滤波(模糊)

高斯滤波可消除正态分布的高斯噪声,属于线性滤波。一维的高斯函数如式所示,


image.png

在二维中,一个各向同性(即圆对称)的高斯的形式如式所示:


image.png

式中,(x, y)为点坐标,δ为标准差。对高斯函数进行离散化,即可得到高斯滤波
器的模板系数。高斯函数具有五个性质,这使得高斯滤波也具有这些性质。
# 2、高斯平滑模糊
# GaussianBlur(InputArray src, OutputArray dst, Size ksize, double sigmaX, double sigmaY=0, int borderType=BORDER_DEFAULT )
# Size ksize必须为正奇数
img = cv2.GaussianBlur(img, (3, 3), 0, 0, cv2.BORDER_DEFAULT)
中值滤波

中值滤波属于非线性图像平滑滤波方法,该算法是对当前像素,选择一个模板,此模板为其邻近几个像素组成,中值滤波是一种类似于卷积的邻域运算,但不是加权求和,而是对模板的像素按灰度级进行排序,然后选择中间值作为输出像素值。
算法表达式为:


image.png

上式中, f (x -i, y-i)为输入像素灰度值,g(x, y)为输出像素灰度值,W 为滑动模板窗口,可以是方形、圆形等。

# 3、中值滤波(池化),消除噪音数据,medianBlur(InputArray src, OutputArray dst, int ksize)   ksize必须为奇数
img = cv2.medianBlur(img, 5)
Sobel

Sobel算子是一种常用的边缘检测算子,是一阶的梯度算法;
对噪声具有平滑作用,提供较为精确的边缘方向信息,边缘定位精度不够高;
当对精度要求不是很高时,是一种较为常用的边缘检测方法。

思想:

算子使用两个3*3的矩阵算子分别和原始图片作卷积,分别得到横向Gx和纵向Gy的梯度值,如果梯度值大于某一个阈值,则认为该点为边缘点;


image.png
# 4、利用Sobel方法可以进行sobel边缘检测,突出边缘
img = cv2.Sobel(img, cv2.CV_8U, 1, 0, ksize=3)

# 图像的二值化就是将图像上的像素点的灰度值设置为0或255,这样将使整个图像呈现出明显的黑白效果,<150的全为黑,>150的全为白
ret, binary = cv2.threshold(img, 100, 200, cv2.THRESH_BINARY)

# 膨胀,让轮廓突出
element1 = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 7))
img = cv2.dilate(binary, element1, iterations=1)
# 腐蚀
element2 = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 1))
img = cv2.erode(img, element2, iterations=1)
# 膨胀,让轮廓更明显
img = cv2.dilate(img, element1, iterations=3)
# 5查找轮廓(img: 原始图像,contours:矩形坐标点,hierarchy:图像层次)
contours, hierarchy = cv2.findContours(
    img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

max_ratio = -1
ratios = []
num = 0

for i in range(len(contours)):
    cnt = contours[i]
    # 计算轮廓面积
    area = cv2.contourArea(cnt)
    if area < 1000:
        continue

    # 四边形的最小外接矩形,得到最小外接矩形的(中心(x,y), (宽,高), 旋转角度)
    rect = cv2.minAreaRect(cnt)

    # 矩形的四个坐标(顺序不定,但是一定是一个左下角、左上角、右上角、右下角这种循环顺序(开始是哪个点未知))
    box = cv2.boxPoints(rect)
    # 转换为long类型
    box = np.int0(box)

    # 计算长宽高
    height = abs(box[0][1] - box[2][1])
    weight = abs(box[0][0] - box[2][0])
    ratio = float(weight) / float(height)
    # 正常的车牌宽高比在2.7~5之间
    if ratio > max_ratio:
        max_box = box

    if ratio > 5.5 or ratio < 2:
        continue

    num += 1
    ratios.append((max_box, ratio))
# 6返回就是车牌的矩阵的四个点的坐标
box = ratios[0][0]

ys = [box[0, 1], box[1, 1], box[2, 1], box[3, 1]]
xs = [box[0, 0], box[1, 0], box[2, 0], box[3, 0]]

ys_sorted_index = np.argsort(ys)
xs_sorted_index = np.argsort(xs)

# 获取x上的坐标
x1 = box[xs_sorted_index[0], 0]
x2 = box[xs_sorted_index[3], 0]

# 获取y上的坐标
y1 = box[ys_sorted_index[0], 1]
y2 = box[ys_sorted_index[3], 1]

cutimg = cv2.imread(r'./testimg.jpg')
# 7截取图像
cutimg = cutimg[y1:y2, x1:x2]
cv2.imwrite('cut_img.jpg', cutimg)

输出结果

True
h, w, c = cutimg.shape  # h=129  w=326 c=3
print(h, w, c)
# # 读入四个点分别是左上、左下、右下、右上
img_copy = cutimg
src_list = [(20, 25), (16, 123), (297, 100), (308, 15)]
# for i, pt in enumerate(src_list):
#     cv2.circle(img_copy, pt, 5, (0, 0, 255), -1)
#     cv2.putText(img_copy,str(i+1),(pt[0]+5,pt[1]+10),cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255,255, 255), 2)
pts1 = np.float32(src_list)


# # 进行透视变换
print(pts1)
pts2 = np.float32([[0, 0], [0, 100], [300, 100], [300, 0]])
print('pts2', pts2)
matrix = cv2.getPerspectiveTransform(pts1, pts2)
img1 = cv2.warpPerspective(cutimg, matrix, (300, 100))
cv2.imwrite('cut.jpg', img1)
plt.imshow(img1, "gray"),
plt.axis('off')
plt.show()

输出结果

129 326 3
[[ 20.  25.]
 [ 16. 123.]
 [297. 100.]
 [308.  15.]]
pts2 [[  0.   0.]
 [  0. 100.]
 [300. 100.]
 [300.   0.]]
1.png

对提取出的车牌图片进行预处理

# 二值化生成黑白图,均值滤波
thresh = deal_license(img1)
plt.imshow( thresh),plt.axis("off")
输出结果:
(, (-0.5, 299.5, 99.5, -0.5))

2.png
# 形态学操作 (根据需要设置参数(1,2))
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1, 2))  # 去除横向细线
morph1 = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (2, 1))  # 去除纵向细线
morph2 = cv2.morphologyEx(morph1, cv2.MORPH_OPEN, kernel)
fig = plt.figure(figsize=(10, 8))
plt.imshow(morph2, 'gray'), plt.axis('off')
plt.show()
输出结果:
4.png

为了方便pytesseract识别,我们将上面结果取反,得到一副白底黑字的图片

fig = plt.figure(figsize=(10, 8))
cv2.bitwise_not(morph2, morph2)
textImage = Image.fromarray(morph2)
plt.imshow(textImage, 'gray'), plt.axis('off')
plt.show()
输出结果:
3.png
# 膨胀
fig = plt.figure(figsize=(10, 8))
element = cv2.getStructuringElement(cv2.MORPH_RECT, (2, 2))
img = cv2.erode(thresh, element, iterations=2)
cv2.imwrite('t1.tif', img)
plt.imshow(img, 'gray'), plt.axis('off')
plt.show()
输出结果:
8.png

识别方法二:

from imutils.perspective import four_point_transform
import imutils
# # 读入图像
img = cv2.imread("testimg.jpg")
plt.imshow(img),plt.axis("off")
plt.show()
输出结果:
5.png
# # 平滑图像 消除噪声
img1 = cv2.GaussianBlur(img, (5,5), 10)

# 灰度
img2 = cv2.cvtColor(img1,cv2.COLOR_BGR2GRAY)
plt.imshow(img2),plt.axis('off')
plt.show()
#  二值化
ret,binary = cv2.threshold(img2,0,255,cv2.THRESH_BINARY_INV| cv2.THRESH_OTSU)
输出结果:
6.png

由于Canny边缘检测算法对受白噪声影响的阶跃型边缘是最优的,故选择其进行边缘检测,目的是返回一个二值图像,非零数值表示图像中边缘的存在,返回与边缘相关的尺度和方向信息

# Canny边缘检测
img3 = cv2.Canny(img2, 50, 150)

# 形态学处理
kernel = np.ones((60,60), np.uint8)
img4 = cv2.morphologyEx(img3, cv2.MORPH_CLOSE, kernel)
img5 = cv2.morphologyEx(img4, cv2.MORPH_OPEN, kernel)

# 腐蚀 膨胀
erosion = cv2.erode(img5,kernel,iterations = 1)
dilation = cv2.dilate(img5,kernel,iterations = 1)
plt.imshow(img5,'gray'),plt.axis('off')
plt.show()
输出结果:
7.png

通过使用cv.findContours()寻找轮廓

# 找出所有轮廓
contours, hierarchy = cv2.findContours(img5, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 绘制所有轮廓
img6 = cv2.drawContours(img, contours, -1, (0,0,255), 3)
plt.imshow(img6),plt.axis('off')
plt.show()
9.png

通过处理图像中的轮廓,找到车牌四个角的坐标从而截取出车牌

max_ratio = -1
ratios = []
num = 0
 
for i in range(len(contours)):
    cnt = contours[i]
     #计算轮廓面积
    area = cv2.contourArea(cnt)
    if area < 1000:
        continue

    #四边形的最小外接矩形,得到最小外接矩形的(中心(x,y), (宽,高), 旋转角度)
    rect = cv2.minAreaRect(cnt)

    # 矩形的四个坐标(顺序不定,但是一定是一个左下角、左上角、右上角、右下角这种循环顺序(开始是哪个点未知))
    box = cv2.boxPoints(rect)
    # 转换为long类型
    box = np.int0(box)

    # 计算长宽高
    height = abs(box[0][1] - box[2][1])
    weight = abs(box[0][0] - box[2][0])
    ratio = float(weight) / float(height)
    # 正常的车牌宽高比在2.7~5之间
    if ratio > max_ratio:
        max_box = box

    if ratio > 5.5 or ratio < 2:
        continue

    num +=1
    ratios.append((max_box,ratio))
    
    
#返回就是车牌的矩阵的四个点的坐标
box = ratios[0][0]
print(box)
print(box[0,1])
 
ys = [box[0, 1], box[1, 1], box[2, 1], box[3, 1]]
print(ys)
xs = [box[0, 0], box[1, 0], box[2, 0], box[3, 0]]
 
ys_sorted_index = np.argsort(ys)
print(ys_sorted_index)
xs_sorted_index = np.argsort(xs)
 
# 获取x上的坐标
x1 = box[xs_sorted_index[0], 0]
# print(x1)
x2 = box[xs_sorted_index[3], 0]
# print(x2)
 
# 获取y上的坐标
y1 = box[ys_sorted_index[0], 1]
# print(y1)
y2 = box[ys_sorted_index[3], 1]
#
cutimg = cv2.imread(r'./testimg.jpg')
# # 截取图像
cutimg = cutimg[y1:y2, x1:x2]
cv2.imwrite('cut_img.jpg', cutimg)
cut_img.jpg
输出结果:
[[210 397]
 [509 381]
 [518 538]
 [219 554]]
397
[397, 381, 538, 554]
[1 0 2 3]

True

自动获取图像顶点变换:以灰度图读入,腐蚀膨胀,闭合等操作,二值化图像,获取图像顶点,透视矫正

该方法不具有普适性,只针对比较干净对比度高的图像

def Get_Outline(input_dir):
    image = cv2.imread(input_dir)
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    blurred = cv2.GaussianBlur(gray, (5,5),0)
    edged = cv2.Canny(blurred,75,200)
    return image,gray,edged

def Get_cnt(edged):
    cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
    cnts = cnts[1] if imutils.is_cv3() else cnts[0]
    docCnt =None

    if len(cnts) > 0:
        cnts =sorted(cnts,key=cv2.contourArea,reverse=True)
        for c in cnts:
            peri = cv2.arcLength(c,True)                   # 轮廓按大小降序排序
            approx = cv2.approxPolyDP(c,0.02 * peri,True)  # 获取近似的轮廓
            if len(approx) ==4:                            # 近似轮廓有四个顶点
                docCnt = approx
                break
    return docCnt


input_dir = "cut_img.jpg"
image,gray,edged = Get_Outline(input_dir)
docCnt = Get_cnt(edged)
result_img = four_point_transform(image, docCnt.reshape(4,2)) # 对原始图像进行四点透视变换
cv2.imwrite('result.jpg', result_img)

输出结果:
True
# 灰度
img1=cv2.imread('result.jpg')
img2 = cv2.cvtColor(img1,cv2.COLOR_BGR2GRAY)
plt.imshow(img2),plt.axis('off')
plt.show()
# 二值化
ret,binary = cv2.threshold(img2,0,255,cv2.THRESH_BINARY_INV| cv2.THRESH_OTSU)
输出结果:
10.png
# 形态学操作 (根据需要设置参数(1,2))
fig = plt.figure(figsize=(10, 8))
kernel = cv2.getStructuringElement(cv2.MORPH_RECT,(1,2))  #去除横向细线
morph1 = cv2.morphologyEx(binary,cv2.MORPH_OPEN,kernel)
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (2, 1)) #去除纵向细线
morph2 = cv2.morphologyEx(morph1,cv2.MORPH_OPEN,kernel)
plt.imshow(morph2,'gray'),plt.axis('off')
plt.show()
输出结果:
11.png
# 黑底白字取非,变为白底黑字(便于pytesseract 识别)
fig = plt.figure(figsize=(10, 8))
cv2.bitwise_not(morph2,morph2)
textImage = Image.fromarray(morph2)
plt.imshow(textImage,'gray'),plt.axis('off')
plt.show()
输出结果:
12.png

[图片上传中...(cut_img.jpg-abdfcf-1669897952979-0)]

字符的提取

提取完车牌的图片后,我们对其进行字符提取,从而得到准确的车牌号。
使用的技术:pytesseract、SVM模型

提取方法一:

采用pytesseract进行识别

Tesseract,一款由HP实验室开发由Google维护的开源OCR(Optical Character Recognition , 光学字符识别)引擎,与Microsoft Office Document Imaging(MODI)相比,我们可以不断的训练的库,使图像转换文本的能力不断增强;如果团队深度需要,还可以以它为模板,开发出符合自身需求的OCR引擎。

在使用前需要进行安装并配置环境,并去官网下载了最新的中文语言包和英文语言包

在训练前,可以使用命令行简单地测试一下图片,但显然效果不是特别好。

训练的大体流程为:安装jTessBoxEditor -> 获取样本文件 -> Merge样本文件 –> 生成BOX文件 -> 定义字符配置文件 -> 字符矫正 -> 执行批处理文件 -> 将生成的traineddata放入tessdata中

1.安装jTessBoxEditor:在运行jTessBoxEditor前先安装JRE(Java Runtime Environment,Java运行环境)。

2.获取样本文件:获取一定数量的训练样本(在参考了一些博主文章后,这里的训练样本应当尽可能地多一些,最少也要四五十张,但本文并没有采用这么多)

3.Merge样本文件:将图片合并为tif格式,打开jTessBoxEditor.jar,Tools->Merge TIFF,将样本文件全部选上,并将合并文件保存为name.tif

4.生成box文件:在当前文件夹路径下(tif和文件路径)使用命令行指令tesseract name.tif name (-l t1) batch.nochop makebox (括号中可添加已有语言)

语法:tesseract [lang].[fontname].exp[num].tif [lang].[fontname].exp[num] batch.nochop makebox

lang为语言名称,fontname为字体名称,num为序号;在tesseract中,一定要注意格式。

5.定义字符配置文件:在目标文件夹内生成一个名为font(可以随意命名但后面训练需要对应)的文本文件,内容为name 0 0 0 0 0 (记住该文件不应当加后缀)

语法:

fontname为字体名称,italic为斜体,bold为黑体字,fixed为默认字体,serif为衬线字体,fraktur德文黑字体,1和0代表有和无,精细区分时可使用。

6.字符矫正:打开jTessBoxEditorjar,BOX Editor -> Open,打开name.tif,对框进行调整,在下方可以更换图片,如遇到无法显示中文字体,则需要到settings设置字体格式,矫正完成一定要记得保存。更改完后可以打开box查看是否改正成功。

7.生成tr文件:在命令行中输入tesseract name.tif name nobatch box.train

8.生成字符集:在命令行中输入unicharset_extractor name.box(记得加上name的前缀)

9.生成shape文件、聚集字符特征文件、字符正常化特征文件,在命令行中输入mftraining -F font -U unicharset name.tr

10.聚集tesseract识别的训练文件,在命令行中输入cntraining name.tr(给unicharset, inttemp, normproto, pfftable,shapetable文件加上nmae的前缀)

11.生成字典文件:在命令行中输入combine_tessdata name.

12.将生成的traineddata文件放在pytesseract文件夹下tessdata中即可完成,在使用时只需要加上该语言库的名字即可

# 图片转文字
text = pytesseract.image_to_string(thresh, lang='t1')
print("识别结果:%s" % text)
识别结果:皖A D23628

识别方法二

字符分割后,使用SVM模型对每个字符进行识别

image = cv2.imread("license.jpg")
plt.imshow(image),plt.axis('off')
输出结果:
(, (-0.5, 282.5, 98.5, -0.5))
13.png

将彩色图像转为黑白图像

image = cv2.GaussianBlur(image, (3, 3), 0)
gray_image = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
ret, img = cv2.threshold(gray_image, 0, 255, cv2.THRESH_OTSU)
plt.imshow(img,cmap='gray'),plt.axis('off')
输出结果:
14.png

将黑子白底化为白底黑字

# 保证要提取的字符为白色
area_white = 0
area_black = 0
height, width = img.shape
for i in range(height):
    for j in range(width):
        if img[i, j] == 255:
            area_white += 1
        else:
            area_black += 1
if area_white > area_black:
    img = cv2.bitwise_not(img)
plt.imshow(img,cmap='gray'),plt.axis('off')
输出结果:
(, (-0.5, 282.5, 98.5, -0.5))
15.png

字符分割

#根据设定的阈值和图片直方图,找出波峰,用于分隔字符
def find_waves(threshold, histogram):
    up_point = -1#上升点
    is_peak = False
    if histogram[0] > threshold:
        up_point = 0
        is_peak = True
    wave_peaks = []
    for i,x in enumerate(histogram):
        if is_peak and x < threshold:
            if i - up_point > 2:
                is_peak = False
                wave_peaks.append((up_point, i))
        elif not is_peak and x >= threshold:
            is_peak = True
            up_point = i
    if is_peak and up_point != -1 and i - up_point > 4:
        wave_peaks.append((up_point, i))
    return wave_peaks
gray_img = img.copy()
x_histogram  = np.sum(gray_img, axis=1)
x_min = np.min(x_histogram)
x_average = np.sum(x_histogram) / x_histogram.shape[0]
x_threshold = (x_min + x_average)/2
wave_peaks = find_waves(x_threshold, x_histogram)
if len(wave_peaks) == 0:
    print("peak less 0:")
#认为水平方向,最大的波峰为车牌区域
wave = max(wave_peaks, key=lambda x:x[1]-x[0])
gray_img = gray_img[wave[0]:wave[1]]
#查找垂直直方图波峰
row_num, col_num= gray_img.shape[:2]
#去掉车牌上下边缘1个像素,避免白边影响阈值判断
gray_img = gray_img[1:row_num-1]

plt.axis('off'),plt.imshow(gray_image,cmap='gray')

y_histogram = np.sum(gray_img, axis=0)
y_min = np.min(y_histogram)
y_average = np.sum(y_histogram)/y_histogram.shape[0]
y_threshold = (y_min + y_average)/5 #U和0要求阈值偏小,否则U和0会被分成两半

wave_peaks = find_waves(y_threshold, y_histogram)
输出结果:
16.png
wave = max(wave_peaks, key=lambda x:x[1]-x[0])
max_wave_dis = wave[1] - wave[0]
#判断是否是左侧车牌边缘
if wave_peaks[0][1] - wave_peaks[0][0] < max_wave_dis/3 and wave_peaks[0][0] == 0:
    wave_peaks.pop(0)

#组合分离汉字
cur_dis = 0
for i,wave in enumerate(wave_peaks):
    if wave[1] - wave[0] + cur_dis > max_wave_dis * 0.6:
        break
    else:
        cur_dis += wave[1] - wave[0]
if i > 0:
    wave = (wave_peaks[0][0], wave_peaks[i][1])
    wave_peaks = wave_peaks[i+1:]
    wave_peaks.insert(0, wave)

#去除车牌上的分隔点
point = wave_peaks[2]
if point[1] - point[0] < max_wave_dis/3:
    point_img = gray_img[:,point[0]:point[1]]
    if np.mean(point_img) < 255/5:
        wave_peaks.pop(2)
def seperate_card(img, waves):
    part_cards = []
    for wave in waves:
        part_cards.append(img[:, wave[0]:wave[1]])
    return part_cards
part_cards = seperate_card(gray_img, wave_peaks)
len(part_cards)
输出结果:
8
for i,char_img in enumerate(part_cards):
    plt.subplot(1,8,i+1),plt.imshow(char_img,cmap='gray'),plt.axis('off')
输出结果:
17.png

训练SVM模型并识别

SZ = 20          #训练图片长宽
MAX_WIDTH = 1000 #原始图片最大宽度
Min_Area = 2000  #车牌区域允许最大面积
class SVM():
    def __init__(self, C = 1, gamma = 0.5):
        self.model = cv2.ml.SVM_create()#生成一个SVM模型
        self.model.setGamma(gamma) #设置Gamma参数,demo中是0.5
        self.model.setC(C)# 设置惩罚项, 为:1
        self.model.setKernel(cv2.ml.SVM_RBF)#设置核函数
        self.model.setType(cv2.ml.SVM_C_SVC)#设置SVM的模型类型:SVC是分类模型,SVR是回归模型

    #训练svm
    def train(self, samples, responses):
        self.model.train(samples, cv2.ml.ROW_SAMPLE, responses)
        
    #字符识别
    def predict(self, samples):
        r = self.model.predict(samples)
        return r[1].ravel()
def deskew(img):
    m = cv2.moments(img)
    if abs(m['mu02']) < 1e-2:
        return img.copy()
    skew = m['mu11']/m['mu02']
    M = np.float32([[1, skew, -0.5*SZ*skew], [0, 1, 0]])
    img = cv2.warpAffine(img, M, (SZ, SZ), flags=cv2.WARP_INVERSE_MAP | cv2.INTER_LINEAR)
    
    return img
#来自opencv的sample,用于svm训练
def preprocess_hog(digits):
    samples = []
    for img in digits:
        gx = cv2.Sobel(img, cv2.CV_32F, 1, 0)
        gy = cv2.Sobel(img, cv2.CV_32F, 0, 1)
        mag, ang = cv2.cartToPolar(gx, gy)
        bin_n = 16
        bin = np.int32(bin_n*ang/(2*np.pi))
        bin_cells = bin[:10,:10], bin[10:,:10], bin[:10,10:], bin[10:,10:]
        mag_cells = mag[:10,:10], mag[10:,:10], mag[:10,10:], mag[10:,10:]
        hists = [np.bincount(b.ravel(), m.ravel(), bin_n) for b, m in zip(bin_cells, mag_cells)]
        hist = np.hstack(hists)

        # transform to Hellinger kernel
        eps = 1e-7
        hist /= hist.sum() + eps
        hist = np.sqrt(hist)
        hist /= norm(hist) + eps

        samples.append(hist)
        
    return np.float32(samples)
provinces = [
    "zh_cuan", "川",
    "zh_e", "鄂",
    "zh_gan", "赣",
    "zh_gan1", "甘",
    "zh_gui", "贵",
    "zh_gui1", "桂",
    "zh_hei", "黑",
    "zh_hu", "沪",
    "zh_ji", "冀",
    "zh_jin", "津",
    "zh_jing", "京",
    "zh_jl", "吉",
    "zh_liao", "辽",
    "zh_lu", "鲁",
    "zh_meng", "蒙",
    "zh_min", "闽",
    "zh_ning", "宁",
    "zh_qing", "靑",
    "zh_qiong", "琼",
    "zh_shan", "陕",
    "zh_su", "苏",
    "zh_sx", "晋",
    "zh_wan", "皖",
    "zh_xiang", "湘",
    "zh_xin", "新",
    "zh_yu", "豫",
    "zh_yu1", "渝",
    "zh_yue", "粤",
    "zh_yun", "云",
    "zh_zang", "藏",
    "zh_zhe", "浙"
]
#识别英文字母和数字
model = SVM(C=100, gamma=0.2)
#识别中文
modelchinese = SVM(C=1, gamma=0.5)

# 训练英文
chars_train = []
chars_label = []

for root, dirs, files in os.walk("train\\chars2"):
    if len(os.path.basename(root)) > 1:
        continue
    root_int = ord(os.path.basename(root))
    for filename in files:
        filepath = os.path.join(root,filename)
        digit_img = cv2.imread(filepath)
        digit_img = cv2.cvtColor(digit_img, cv2.COLOR_BGR2GRAY)
        chars_train.append(digit_img)
        #chars_label.append(1)
        chars_label.append(root_int)

chars_train = list(map(deskew, chars_train))
#print(chars_train)
chars_train = preprocess_hog(chars_train)
#print(chars_train)
#chars_train = chars_train.reshape(-1, 20, 20).astype(np.float32)
chars_label = np.array(chars_label)
model.train(chars_train, chars_label)

# 训练中文
chars_train = []
chars_label = []
for root, dirs, files in os.walk("train\\charsChinese"):
    if not os.path.basename(root).startswith("zh_"):
        continue
    pinyin = os.path.basename(root)
    index = provinces.index(pinyin) + 1 #1是拼音对应的汉字
    for filename in files:
        filepath = os.path.join(root,filename)
        digit_img = cv2.imread(filepath)
        digit_img = cv2.cvtColor(digit_img, cv2.COLOR_BGR2GRAY)
        chars_train.append(digit_img)
        #chars_label.append(1)
        chars_label.append(index)
        
chars_train = list(map(deskew, chars_train))
chars_train = preprocess_hog(chars_train)
#chars_train = chars_train.reshape(-1, 20, 20).astype(np.float32)
chars_label = np.array(chars_label)
print(chars_train.shape)
modelchinese.train(chars_train, chars_label)
输出结果:
(3232, 64)
predict_result = []
for i, part_card in enumerate(part_cards):
    # 跳过固定车牌的铆钉
    if np.mean(part_card) < 255/5:
        print("a point")
        continue
    part_card_old = part_card
    #w = abs(part_card.shape[1] - SZ)//2
    w = part_card.shape[1] // 3
    part_card = cv2.copyMakeBorder(part_card, 0, 0, w, w, cv2.BORDER_CONSTANT, value = [0,0,0])
    part_card = cv2.resize(part_card, (SZ, SZ), interpolation=cv2.INTER_AREA)
    #cv2.imshow("part", part_card_old)
    #cv2.waitKey(0)
    
    plt.subplot(1,8,i+1),plt.axis('off'),plt.imshow(part_card)  

    #cv2.imwrite("u.jpg", part_card)
    #part_card = deskew(part_card)
    part_card = preprocess_hog([part_card]) #preprocess_hog([part_card])
    if i == 0:
        resp = modelchinese.predict(part_card)#第一个字符调用中文svm模型
        charactor = provinces[int(resp[0])]
    else:
        resp = model.predict(part_card)#其他字符调用字母数字svm模型
        charactor = chr(resp[0])
    # 判断最后一个数是否是车牌边缘,假设车牌边缘被认为是1
    if charactor == "1" and i == len(part_cards) - 1:
        if part_card_old.shape[0]/part_card_old.shape[1] >= 8: # 1太细,认为是边缘
            print(part_card_old.shape)
            continue
    predict_result.append(charactor)
    
print('resize后的图片:')
C:\Users\82484\AppData\Local\Temp\ipykernel_15244\1829045690.py:25: DeprecationWarning: an integer is required (got type numpy.float32).  Implicit conversion to integers using __int__ is deprecated, and may be removed in a future version of Python.
  charactor = chr(resp[0])
resize后的图片:
18.png

打印结果

predict_result

输出:

['皖', 'A', 'D', '2', '3', '6', '2', '8']

你可能感兴趣的:(2022-11-29)