【LeNet5】简单车牌识别


1. 项目准备

1.1. 问题导入

本次实践是一个多分类任务,需要将照片中的每个字符分别进行识别,我们将借助CV2模块完成对车牌图像逐字符划分,然后训练卷积神经网络模型LeNet5完成对车牌的识别。

1.2. 数据集简介

实验数据集中有65个文件夹,包含数字 (0-9)、大写字母 (A-Z) 以及各省简称,每个文件夹存放一类图片,所有的图片均为20 * 20像素的灰度图像。

这是数据集的下载链接:车牌识别数据集 - AI Studio


2. LeNet5模型

2.1. 卷积神经网络

本实验使用的模型属于卷积神经网络模型(Convolutional Neural Network,CNN)。一个卷积神经网络通常包括输入层、输出层和多个隐藏层,而隐藏层通常包括卷积层、池化层和全连接层等。

在卷积运算和池化运算中,如果输入维度为 N N N,卷积核或池化核的大小为 K K K,运算步长为 S S S,填充长度为 P P P,那么输出维度为 ⌊ ( N − K + 2 P ) S ⌋ + 1 ⌊\frac{(N-K+2P)}{S}⌋ + 1 S(NK+2P)+1。并且在卷积运算中,卷积核的数量与输出通道数相等。

2.2. 模型介绍

LeNet-5源自Yann LeCun(1998)的论文Gradient-Based Learning Applied to Document Recognition,是一种用于手写体字符识别的、结构简单、非常高效的卷积神经网络。LeNet-5大体上由提取特征的三个卷积层和两个分类的全连接层组成,在卷积层之间均插入了最大池化层来缩小特征图,以便于后面的网络层提取更大尺度的特征,并且卷积层和全连接层均采用Sigmoid激活函数(现常用ReLU作为激活函数),其网络结构如下图所示。


3. 实验步骤

3.0. 前期准备

  • 导入模块

注意:本案例仅适用于PaddlePaddle 2.0+版本

import os
import cv2              # 在本项目中,它主要用来分割图像
import shutil
import random
import zipfile
import numpy as np
from PIL import Image
import matplotlib.pyplot as plt

import paddle
from paddle import nn
from paddle import metric as M
from paddle.io import DataLoader, Dataset
from paddle.nn import functional as F
from paddle.optimizer import Adam
from paddle.vision import transforms as T
  • 设置超参数
BATCH_SIZE = 128               # 每批次的样本数
INIT_LR = 1e-4                 # 初始学习率
EPOCHS = 25                    # 训练轮数
LOG_GAP = 100                  # 输出训练信息的间隔

SRC_PATH = "./data/characterData.zip"     # 压缩包路径
DATA_PATH = "./data/dataset"              # 数据集路径
INFER_PATH = "./data/infer_license.png"   # 预测图片路径
TEMP_PATH = "./data/infer"                # 临时图片路径
MODEL_PATH = "LeNet5.pdparams"            # 模型参数保存路径

3.1. 数据准备

  • 解压数据集
    由于数据集中的数据是以压缩包的形式存放的,因此我们需要先解压数据压缩包。
if not os.path.isdir(DATA_PATH):
    z = zipfile.ZipFile(SRC_PATH, 'r')      # 打开Zip文件,创建Zip对象
    z.extractall(path=DATA_PATH)            # 解压Zip文件至DATA_PATH
    shutil.rmtree(DATA_PATH + "/__MACOSX")  # 删除无关文件
    z.close()
print("数据集解压完成!")
  • 划分数据集
    我们需要按1:9比例划分测试集和训练集,分别生成两个包含数据路径和标签映射关系的列表。
train_list, test_list = [], []           # 存放数据的路径及标签的映射关系
char_num, label_dict = 0, {}             # 方便字符在整型和字符型之间转换
file_folders = os.listdir(DATA_PATH)     # 统计数据集下的文件夹

for folder in file_folders:
    label_dict[str(char_num)] = folder   # 记录标签和代号的对应关系
    imgs = os.listdir(os.path.join(DATA_PATH, folder))
    for idx, img in enumerate(imgs):
        img_path = os.path.join(DATA_PATH, folder, img)
        value = [img_path, char_num]
        if idx % 10 == 0:      # 按照1:9的比例划分数据集
            test_list.append(value)
        else:
            train_list.append(value)
    char_num += 1
  • 数据预处理
    我们需要先定义一个数据集类,接着对数据集图像进行缩放和归一化处理。
class MyDataset(Dataset):
    ''' 自定义的数据集类 '''

    def __init__(self, label_list, transform=None):
        '''
        * `label_list`: 标签与文件路径的映射列表
        * `transform`:数据处理函数
        '''
        super(MyDataset, self).__init__()
        random.shuffle(label_list)      # 打乱映射列表
        self.label_list = label_list
        self.transform = transform
    
    def __getitem__(self, index):
        ''' 根据位序获取对应数据 '''
        img_path, label = self.label_list[index]
        image = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE)
        image = image.astype('float32')
        if self.transform is not None:
            image = self.transform(image)
        image = paddle.to_tensor(image)
        return image, int(label)
    
    def __len__(self):
        ''' 获取数据集样本总数 '''
        return len(self.label_list)
transform = T.Compose([
    T.Resize(size=(32, 32)),                                   # 缩放大小
    T.Normalize(mean=[127.5], std=[127.5], data_format='CHW')  # 归一化
])

train_dataset = MyDataset(train_list, transform)  # 训练集
test_dataset = MyDataset(test_list, transform)    # 测试集
  • 定义数据提供器
    我们需要分别构建用于训练和测试的数据提供器,其中训练数据提供器是乱序、按批次提供数据的。
train_loader = DataLoader(train_dataset,            # 训练数据集
                          batch_size=BATCH_SIZE,    # 每批读取的样本数
                          num_workers=1,            # 加载数据的子进程个数
                          shuffle=True,             # 打乱训练数据集
                          drop_last=False)          # 不丢弃不完整的样本

test_loader = DataLoader(test_dataset,              # 测试数据集
                         batch_size=BATCH_SIZE,     # 每批读取的样本数
                         num_workers=1,             # 加载数据的子进程个数
                         shuffle=False,             # 不打乱测试数据集
                         drop_last=False)           # 不丢弃不完整的样本

3.2. 网络配置

class LeNet5(nn.Layer):
    def __init__(self, in_channels=1, n_classes=10):
        '''
        * `in_channels`: 输入的通道数
        * `n_classes`: 输出分类数量
        '''
        super(LeNet5, self).__init__()
        # Conv2D(输入通道数,输出通道数,卷积核大小,卷积步长)
        # MaxPool2D(池化核大小,池化步长)
        self.conv1 = nn.Conv2D(in_channels, 6, 5, 1)
        self.pool1 = nn.MaxPool2D(2, 2)
        self.conv2 = nn.Conv2D(6, 16, 5, 1)
        self.pool2 = nn.MaxPool2D(2, 2)
        self.conv3 = nn.Conv2D(16, 120, 5, 1)
        self.fc1 = nn.Linear(120, 84)
        self.fc2 = nn.Linear(84, n_classes)
    
    def forward(self, x):
        x = F.relu(self.conv1(x))  # C1输出维度为28*28*6
        x = self.pool1(x)          # S2输出维度为14*14*6
        x = F.relu(self.conv2(x))  # C3输出维度为10*10*6
        x = self.pool2(x)          # S4输出维度为5*5*16
        x = F.relu(self.conv3(x))  # C5输出维度为120
        x = paddle.flatten(x, 1, -1)
        x = F.relu(self.fc1(x))    # F6输出维度为84
        x = F.dropout(x, p=0.25)
        y = self.fc2(x)            # F7输出维度为10
        return y
  • 模型实例化
model = LeNet5(in_channels=1, n_classes=char_num)

3.3. 模型训练

model.train()                # 开启训练模式
opt = Adam(learning_rate=INIT_LR,
           parameters=model.parameters())  # 定义Adam优化器
loss_arr, acc_arr = [], []   # 用于可视化

for ep in range(EPOCHS):
    for batch_id, data in enumerate(train_loader()):
        x_data, y_data = data
        y_data = y_data[:, np.newaxis]          # 增加一维维度
        y_pred = model(x_data)                  # 预测结果
        acc = M.accuracy(y_pred, y_data)        # 计算准确率
        loss = F.cross_entropy(y_pred, y_data)  # 计算交叉熵
        if batch_id != 0 and batch_id % LOG_GAP == 0:   # 定期输出训练结果
            print("Epoch:%d,Batch:%3d,Loss:%.5f,Acc:%.5f"\
                % (ep, batch_id, loss, acc))
        acc_arr.append(acc.item())
        loss_arr.append(loss.item())
        opt.clear_grad()
        loss.backward()
        opt.step()

paddle.save(model.state_dict(), MODEL_PATH)  # 保存训练好的模型

模型训练结果如下:

Epoch:0,Batch:100,Loss:0.93938,Acc:0.77344
Epoch:1,Batch:100,Loss:0.45156,Acc:0.88281
Epoch:2,Batch:100,Loss:0.37834,Acc:0.90625
Epoch:3,Batch:100,Loss:0.33006,Acc:0.92188
Epoch:4,Batch:100,Loss:0.28153,Acc:0.92969
Epoch:5,Batch:100,Loss:0.14206,Acc:0.96875
Epoch:6,Batch:100,Loss:0.17640,Acc:0.94531
Epoch:7,Batch:100,Loss:0.14656,Acc:0.97656
Epoch:8,Batch:100,Loss:0.17134,Acc:0.96875
Epoch:9,Batch:100,Loss:0.13322,Acc:0.95312
Epoch:10,Batch:100,Loss:0.10637,Acc:0.96875
Epoch:11,Batch:100,Loss:0.06380,Acc:0.97656
Epoch:12,Batch:100,Loss:0.06698,Acc:0.97656
Epoch:13,Batch:100,Loss:0.01808,Acc:1.00000
Epoch:14,Batch:100,Loss:0.03210,Acc:0.99219
Epoch:15,Batch:100,Loss:0.01909,Acc:1.00000
Epoch:16,Batch:100,Loss:0.06952,Acc:0.97656
Epoch:17,Batch:100,Loss:0.01585,Acc:1.00000
Epoch:18,Batch:100,Loss:0.03883,Acc:0.98438
Epoch:19,Batch:100,Loss:0.01998,Acc:1.00000
  • 可视化训练过程
fig = plt.figure(figsize=[10, 8])

# 训练误差图像:
ax1 = fig.add_subplot(211, facecolor="#E8E8F8")
ax1.set_ylabel("Loss", fontsize=18)
plt.tick_params(labelsize=14)
ax1.plot(range(len(loss_arr)), loss_arr, color="orangered")
ax1.grid(linewidth=1.5, color="white")  # 显示网格

# 训练准确率图像:
ax2 = fig.add_subplot(212, facecolor="#E8E8F8")
ax2.set_xlabel("Training Steps", fontsize=18)
ax2.set_ylabel("Accuracy", fontsize=18)
plt.tick_params(labelsize=14)
ax2.plot(range(len(acc_arr)), acc_arr, color="dodgerblue")
ax2.grid(linewidth=1.5, color="white")  # 显示网格

fig.tight_layout()
plt.show()
plt.close()

3.4. 模型评估

model.eval()                 # 开启评估模式
test_costs, test_accs = [], []

for batch_id, data in enumerate(test_loader()):
    x_data, y_data = data
    y_data = y_data[:, np.newaxis]          # 增加一维维度
    y_pred = model(x_data)                  # 预测结果
    acc = M.accuracy(y_pred, y_data)        # 计算准确率
    loss = F.cross_entropy(y_pred, y_data)  # 计算交叉熵
    test_accs.append(acc.item())
    test_costs.append(loss.item())
test_loss = np.mean(test_costs)    # 每轮测试的平均误差
test_acc = np.mean(test_accs)      # 每轮测试的平均准确率
print("Eval \t Loss:%.5f,Acc:%.5f" % (test_loss, test_acc))

模型评估结果如下:

Eval 	 Loss:0.16533,Acc:0.96663

3.5. 模型预测

  • 预测图片预处理
    由于车牌图片是由多个字符构成的RGB模式的图片,因此在进行预测之前需要将车牌图片转化为灰度图并按字符划分子图像,以便于模型逐字符进行预测。
def load_image(path):      # 图像整体预处理
    img = Image.open(path).convert("L")    # 将图像打开并转为灰度图
    img = img.resize((32, 32), Image.ANTIALIAS)
    img = np.array(img).reshape(1, 1, 32, 32)\
        .astype('float32')          # 把图像变成numpy数组
    img = img / 255.0 * 2.0 - 1.0   # 将数据归一化到[-1, 1]之间
    img = paddle.to_tensor(img)
    return img


def divide_picture(path):      # 分割出车牌图像中的每一个字符并保存
    # (1) 图片灰度化处理:
    license = cv2.imread(path)
    gray_img = cv2.cvtColor(license, cv2.COLOR_RGB2GRAY)  # 将车牌转化为灰度图
    retval, bin_img = cv2.threshold(                      # 进行图像二值化操作
        gray_img, 100, 255, cv2.THRESH_BINARY  # 源图片、起始阈值、最大阈值、阈值类型
    )   # 函数返回值:retval是阈值;bin_img是根据阈值处理后的图像

    # (2) 按列统计像素分布:
    result = []
    for col in range(bin_img.shape[1]):
        result.append(0)
        for row in range(bin_img.shape[0]):
            result[col] += bin_img[row][col] / 255.0    # 统计归一化后的像素分布

    # (3) 记录车牌中的字符的位置:
    place_dict, num, i = {}, 0, 0
    while i < len(result):
        if result[i] == 0:
            i += 1
        else:
            index = i + 1
            while result[index] != 0:
                index += 1
            place_dict[num] = [i, index-1]
            num += 1
            i = index

    # (4) 将每个字符填充并存储:
    characters = []
    if not os.path.exists(TEMP_PATH):
        os.mkdir(TEMP_PATH)
    for i in range(8):
        if i == 2:   # 跳过蓝牌中的“•”号
            continue
        padding = (170 - (place_dict[i][1] - place_dict[i][0])) / 2
        ndarray = np.pad(     # 将单个字符图像填充为170*170
            bin_img[:, place_dict[i][0]: place_dict[i][1]],
            ((0, 0), (int(padding), int(padding))),
            "constant",
            constant_values=(0, 0)
        )
        ndarray = cv2.resize(ndarray, (32, 32))
        cv2.imwrite(TEMP_PATH + "/%d.png" % i, ndarray)   # 保存划分后的单字符图片
        characters.append(ndarray)


def match_labels(label_dict):   # 返回将标签与汉字的映射关系
    temp = {'yun': '云', 'cuan': '川', 'hei': '黑', 'zhe': '浙', 'ning': '宁', 
            'yu': '豫', 'ji': '冀', 'hu': '沪', 'jl': '吉', 'sx': '晋', 'lu': '鲁', 
            'qing': '青', 'zang': '藏', 'e1': '鄂', 'meng': '蒙', 'gan1': '甘',
            'qiong': '琼', 'shan': '陕', 'min': '闽', 'su': '苏', 'xin': '新',
            'wan': '皖', 'jing': '京', 'xiang': '湘', 'gui': '贵', 'yu1': '渝', 
            'jin': '津', 'gan': '赣', 'yue': '粤', 'gui1': '桂', 'liao': '辽'}
    name_dict = {}
    for key, val in label_dict.items():      # 本次转换的目的是转换字母和汉字
        name_dict[key] = temp.get(val, val)
    return name_dict
name_dict = match_labels(label_dict)    # 获取转换标签的字典
divide_picture(INFER_PATH)              # 按车牌字符划分图片
display(Image.open(INFER_PATH))         # 展示预测车牌

  • 载入模型并开始预测
model.eval()                 # 开启评估模式
model.set_state_dict(
    paddle.load(MODEL_PATH)
)   # 载入预训练模型参数

infer_label = ""                         # 存储预测结果
for i in range(8):
    if i == 2:     # 跳过蓝牌中的“•”号
        infer_label += "•"
        continue
    char_img = load_image(TEMP_PATH + "/%d.png" % i)
    result = model(char_img)            # 模型预测,返回一个概率数组
    lab = np.argmax(result.numpy())     # 返回数组result中的最大值的索引值
    infer_label += name_dict[str(lab)]  # 将字符的预测结果加入到总结果中
print("\n该车牌的预测结果为:", infer_label)     # 展示预测结果

模型预测结果如下:

该车牌的预测结果为: 苏A•UP678

写在最后

  • 如果您发现项目存在问题,或者如果您有更好的建议,欢迎在下方评论区中留言讨论~
  • 这是本项目的链接:实验项目 - AI Studio,点击fork可直接在AI Studio运行~
  • 这是我的个人主页:个人主页 - AI Studio,来AI Studio互粉吧,等你哦~
  • 【友链滴滴】欢迎大家随时访问我的个人博客~
  • 6
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
目标识别是计算机视觉一个重要的研究领域,由此延伸出的车辆型号识别具有重 要的实际应用价值,特别是在当今交通状况复杂的大城市,智能交通系统成为发展趋 势,这离不开对车辆型号进行识别和分类的工作,本文围绕如何利用计算机视觉的方 法进行车辆型号的识别和分类展开了一系列研究: 本文对当前的目标识别和分类的特征和算法做了总结和归纳。分析比较了作为图 像特征描述常见的特征算子,总结归纳了他们的提取方法、特征性能以及相互之间的 关联。另外,介绍了在目标识别工作中常用的分类方法,阐述了他们各自的原理和工作 方法。研究了深度神经网络的理论依据,分析比较了深度神经网络不同的特征学习方 法,以及卷积神经网络的训练方法。分析比较不同特征学习方法的特点选取 k-means 作为本文使用的特征学习方法,利用卷积神经网络结构搭建深度学习模型,进行车辆 车型识别工作。 本文为了测试基于深度学习的车辆型号分类算法的性能在 30 个不同型号共 7158 张图片上进行实验;并在相同数据上利用改进了的 SIFT 特征匹配的算法进行对比实验; 进过实验测试,深度学习方法在进行车型分类的实验中取得 94%的正确率,并在与 SIFT 匹配实验结果对比后进一步证实:深度学习的方法能够应用在车辆型号识别领域
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值