基于机器学习的车牌识别系统(Python实现基于SVM支持向量机的车牌分类)
一、数据集说明
训练样本来自于github上的EasyPR的c++版本,包含一万三千多张数字及大写字母的图片以及三千多张中文字符图片。图片为已经处理好的二值化图像,已贴好标签(见每个子文件夹名),像素均为20×20。数字和大写字母图片保存在train\chars2目录下,中文字符图片保存在train\ charsChinese目录下。
测试样本来源于较为广泛,从各种网站搜集得到的各类车辆图片,保存在test目录下。
二、模型概述
本模型主要由四部分构成:训练字符分类器、车牌定位、字符分割、字符识别。其中训练字符分类器部分,将训练样本图片进行进一步抗扭曲处理后提取特征向量——方向梯度直方图,然后利用OpenCV自带的SVM模型训练模型,模型分为两个分类器,其中一个存放中文字符另外一个存放数字及大写字母。车牌定位部分是将测试集图片进行灰度化处理、高斯去躁、开运算和闭运算、Canny算子边缘检测、根据矩形框位置及颜色检测等处理,将车牌位置找到。字符分割部分从图片的波形直方图中找到波峰的位置,每个波峰范围内是一个字符,将字符分割开。字符识别部分就是将分割开的字符分别放入对应的分类器中进行识别,给出测试结果。下面我将结合具体代码及结果就以上四个部分给出详细的说明。
三、训练字符分类器
本字符分类器的训练是运用SVM的思想,利用OpenCV自带的SVM模型进行训练。
(一)SVM介绍
支持向量机(support vector machines, SVM)是一种二分类模型,它的基本模型是定义在特征空间上的间隔最大的线性分类器,间隔最大使它有别于感知机;SVM还包括核技巧,这使它成为实质上的非线性分类器。SVM的的学习策略就是间隔最大化,可形式化为一个求解凸二次规划的问题,也等价于正则化的合页损失函数的最小化问题。SVM的的学习算法就是求解凸二次规划的最优化算法。
(二)具体实现过程
1、定义一个SVM类,用到OpenCV自带的方法
(1)参数说明
SVM模型有两个非常重要的参数C与gamma。其中C是惩罚系数,即对误差的宽容度。C越高,说明越不能容忍出现误差,容易过拟合。C越小,容易欠拟合。C过大或过小,泛化能力变差。
gamma是选择RBF函数作为kernel后(本模型即选用RBF为核函数),该函数自带的一个参数。隐含地决定了数据映射到新的特征空间后的分布,gamma越大,支持向量越少,gamma值越小,支持向量越多。支持向量的个数影响训练与预测的速度。
(2)相关代码
class SVM(StatModel):def __init__(self, C = 1, gamma = 0.5):self.model = cv2.ml.SVM_create()self.model.setGamma(gamma) #设置gamma参数self.model.setC(C) #设置正则化参数self.model.setKernel(cv2.ml.SVM_RBF) #核函数为RBF(径向基函数)self.model.setType(cv2.ml.SVM_C_SVC) #SVC为分类,SVR为回归
#训练svmdef train(self, samples, responses):self.model.train(samples, cv2.ml.ROW_SAMPLE, responses)
#cv2.ml.ROW_SAMPLE表示每一行是一个样本
#字符识别def predict(self, samples):r = self.model.predict(samples)return r[1].ravel() #返回值为一个行向量
2、训练集特征向量抗扭曲处理
相关代码:
#训练数据中有些图像是扭曲的,需要做抗扭曲处理,也就是把歪了的图片摆正
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
3.获取训练集图片特征:方向梯度
(1)方向梯度直方图
对于本分类器研究的问题,图片的边缘十分重要,因此提取特征时应重点关注图像边缘。沿着一张图片X和Y轴的方向上的梯度是很有用的,因为在边缘和角点的梯度值是很大的,边缘和角点包含了很多物体的形状信息。因此本模型采用方向梯度直方图(HOG)中梯度的方向作为特征。
(2)具体步骤:
a.计算梯度图像:直接用OpenCV里面的kernel大小为1的Sobel算子来计算。
b.计算梯度的幅值g和方向theta。
c.将梯度量化为16×16个整数值。把每个图像分成四个子图方块。对于每个子正方形,计算加权其幅度的方向(16×16bins)的直方图。因此,每个子图有一个包含16×16个值的向量。四个这样的向量(分别代表四个子图的16×16向量)一起给我们一个特征向量包含1024个值。这就是我们用来训练数据的特征向量。
(3)HOG方法优缺点分析
优点:HOG表示的是边缘(梯度)的结构特征,因此可以描述局部的形状信息;位置和方向空间的量化一定程度上可以抑制平移和旋转带来的影响;采取在局部区域归一化直方图,可以部分抵消光照变化带来的影响。由于一定程度忽略了光照颜色对图像造成的影响,使得图像所需要的表征数据的维度降低了。
缺点:描述子生成过程冗长,导致速度慢,实时性差;很难处理遮挡问题;由于梯度的性质,该描述子对噪点相当敏感。
(4)相关代码:
#获取每张图片的特征,特征是方向梯度直方图
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) #计算梯度的幅值g和方向thetabin_n = 16bin = 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:]
#bincount()统计出现的次数
#ravel()将数组降为一维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 kerneleps = 1e-7hist /= hist.sum() + epshist = np.sqrt(hist)hist /= norm(hist) + epssamples.append(hist)return np.float32(samples)
4.将数据导入分类器进行训练
相关代码:
def train_svm(self):self.model = SVM(C=1, gamma=0.5) #大写英文字母和数字分类器self.modelchinese = SVM(C=1, gamma=0.5) #中文字符分类器if os.path.exists("svm.dat"): self.model.load("svm.dat") #用于导入之前训练好的字母和数字分类模型else: #导入数据开始训练chars_train = []chars_label = []for root, dirs, files in os.walk("train\\chars2"):
#root为正在遍历的文件夹的名字 dirs为子文件夹的集合
#files为在遍历的文件夹中的文件集合if len(os.path.basename(root))> 1:
#os.path.basename(root)返回文件名continueroot_int = ord(os.path.basename(root))
#ord返回的是ASCII值,此处是文件夹名字的ASCII值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(root_int) #标签是它文件夹名字的ASCII码chars_train = list(map(deskew, chars_train)) #得到抗扭曲后的图像chars_train = preprocess_hog(chars_train) #获取样本方向梯度直方图chars_label = np.array(chars_label) #把图片的标签存起来self.model.train(chars_train, chars_label) #数据喂进分类器if os.path.exists("svmchinese.dat"): #开始训练汉字分类器,具体流程同上self.modelchinese.load("svmchinese.dat")else:chars_train = []chars_label = []for root, dirs, files in os.walk("train\\charsChinese"):if not os.path.basename(root).startswith("zh_"):continuepinyin = os.path.basename(root)index = provinces.index(pinyin) + PROVINCE_START + 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(index)chars_train = list(map(deskew, chars_train))chars_train = preprocess_hog(chars_train)chars_label = np.array(chars_label)print(chars_train.shape)self.modelchinese.train(chars_train, chars_label)
数据集及所有代码资源链接:https://download.csdn.net/download/creampang/85766802