文章目录

  • 1. 项目准备
    • 1.1. 问题导入
    • 1.2. 数据集简介
  • 2. LeNet5模型
    • 2.1. 卷积神经网络
    • 2.2. 模型介绍
  • 3. 实验步骤
    • 3.0. 前期准备
    • 3.1. 数据准备
    • 3.2. 网络配置
    • 3.3. 模型训练
    • 3.4. 模型评估
    • 3.5. 模型预测
  • 写在最后

1. 项目准备

1.1. 问题导入

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

1.2. 数据集简介

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

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


2. LeNet5模型

2.1. 卷积神经网络

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

在卷积运算和池化运算中,如果输入维度为NNN,卷积核或池化核的大小为KKK,运算步长为SSS,填充长度为PPP,那么输出维度为⌊(N−K+2P)S⌋+1⌊\frac{(N-K+2P)}{S}⌋ + 1⌊S(N−K+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 pltimport 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_PATHshutil.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_listself.transform = transformdef __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*6x = self.pool1(x)          # S2输出维度为14*14*6x = F.relu(self.conv2(x))  # C3输出维度为10*10*6x = self.pool2(x)          # S4输出维度为5*5*16x = F.relu(self.conv3(x))  # C5输出维度为120x = paddle.flatten(x, 1, -1)x = F.relu(self.fc1(x))    # F6输出维度为84x = F.dropout(x, p=0.25)y = self.fc2(x)            # F7输出维度为10return 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 = datay_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 = datay_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 imgdef 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, 0while i < len(result):if result[i] == 0:i += 1else:index = i + 1while result[index] != 0:index += 1place_dict[num] = [i, index-1]num += 1i = index# (4) 将每个字符填充并存储:characters = []if not os.path.exists(TEMP_PATH):os.mkdir(TEMP_PATH)for i in range(8):if i == 2:   # 跳过蓝牌中的“•”号continuepadding = (170 - (place_dict[i][1] - place_dict[i][0])) / 2ndarray = np.pad(     # 将单个字符图像填充为170*170bin_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 += "•"continuechar_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互粉吧,等你哦~
  • 【友链滴滴】欢迎大家随时访问我的个人博客~

【LeNet5】简单车牌识别相关推荐

  1. opencv学习—简单车牌识别操作(python)

    opencv学习-简单车牌识别操作(python) 目录 opencv学习-简单车牌识别操作(python) 利用opencv进行车牌识别的详细流程如下: 1.车牌检测 2.分割车牌号并进行识别 3. ...

  2. 从零使用OpenCV快速实现简单车牌识别系统

    这篇文章献给所有第一次听说车牌识别ANPR但需要短时间实现的苦逼同学们. 最近的小学期实训做的是一个车牌识别系统,说实话真不知道学校怎么想的,虽然说图像处理也算的上是数字媒体很重要的一块分支了,但咱这 ...

  3. 学习Halcon之简单车牌识别

    最近由于要接触halcon就开始学习halcon课程,看得是超人视觉得视频,今天学了车牌识别,觉得使用halcon确实很方便,学习的思路也很重要,所以花点时间记一记. 首先,获取一张含有车牌的图片后将 ...

  4. 机器学习笔记 - 模式识别的应用场景之一简单车牌识别

    一.关于模式识别 模式(Pattern)是指具有某种特定性质的感知对象.一般情况下,待观察的事物都具有时空分布信息.模式识别(Pattern Recognition)又称为模式分类,指对待观察事物的各 ...

  5. 基于OpenCV简单的车牌识别

    OpenCV是计算机视觉中经典的专用库,其支持多语言.跨平台,功能强大. OpenCV-Python为OpenCV提供了Python接口,使得使用者在Python中能够调用C/C++,在保证易读性和运 ...

  6. 如何从零开始教你写一款车牌识别脚本,用我这个方法非常简单。

    前言 为了感谢每一个关注我的小可爱:每篇文章的项目源码都是无偿分享滴. 最后--如果文章有帮助到你,记得"关注"."点赞"."评论"三连哦! ...

  7. python 车牌识别简单_智能车牌识别 停车如此简单

    随着人们经济.生活水平的提高,我国民用汽车保有量不断攀升,产生了巨量的停车需求,而在国家"新基建"的政策推动以及市场需求的驱动下,停车产业将迎来更广阔的发展空间,智慧停车解决方案的 ...

  8. python 车牌识别简单_ZKTeco 智能车牌识别 停车如此简单

    随着人们经济.生活水平的提高,我国民用汽车保有量不断攀升,产生了巨量的停车需求,而在国家"新基建"的政策推动以及市场需求的驱动下,停车产业将迎来更广阔的发展空间,智慧停车解决方案的 ...

  9. Python图像处理(车牌识别)简单

    利用python进行车牌识别 图像读取 图像增强 车牌位置识别 排除无用信息 文字提取 文本提取没有进行cnn学习,识别度不高,要求车牌方向不太歪,字迹清晰 相关说明: 利用pip install 导 ...

最新文章

  1. 微软发布新Azure 媒体服务 API(V3),现已全面可用
  2. JavaScript:prototype属性使用说明
  3. linux 实时 网口 速率_linux 下查看网卡工作速率-阿里云开发者社区
  4. java8新特性(6)— 日期与时间
  5. 不选择互联网行业,学弟学妹可以选择这些新兴科技行业发展!
  6. CSS的样式小计(1)
  7. MyCat分布式数据库集群架构工作笔记0008---Mycat主--从复制原理
  8. 06.Python网络爬虫之requests模块(2)
  9. 小腿抽筋了,按摩承山穴,外加念观世音菩萨是不是迷信
  10. idea 字体颜色设置 + 背景图片
  11. JxBrowser 7.15 for java Crack
  12. c语言12个实验报告,C语言实验报告合集
  13. STM32学习之SHT20温湿度传感器
  14. WEB-互补色与对比色的计算与获取 (实用、赞)
  15. github提交代码出现remote: Support for password authentication was removed on August 13, 2021.?
  16. MOOS通信框架初探(一)
  17. Hive HBase
  18. 29转换为8位二进制的转换过程
  19. dvd光盘安装linux系统,从单DVD光盘上安装openSUSE
  20. PTA出现EOFError

热门文章

  1. 17173游戏网-王者荣耀代码优化分析(含内链)(更多内容请访问http://www.eduaskx6.com/)...
  2. 单片机---12---DMX512
  3. 大一大学计算机期末试卷,大一大学计算机基础教程期末考试题
  4. 最全的Snagit 2022快捷键大全
  5. AWS EKS使用Pod安全组
  6. seo里的长尾关键词是什么
  7. 阵列算法matlab,阵列信号处理的常见算法
  8. Lyapunov稳定性分析2(连续时间系统)
  9. pcs7加热炉画面流程组态_毕业设计(论文)-基于PCS7的管式加热炉控制系统设计.doc...
  10. 超详细,win7虚拟机安装