公司动态

P10周:Pytorch实现车牌识别

📅 2026/8/1 15:23:30
P10周:Pytorch实现车牌识别
本文为365天深度学习训练营中的学习记录博客原作者K同学啊学习目的自定义一个MyDataset加载车牌数据集并完成车牌识别一、 前期准备关于环境语言环境Python3.13编译器vsCode深度学习环境torch2.11.0cu130torchvision0.26.0cu130torchvision0.26.0cu1301.设置GPU设置分析环境from torchvision.transforms import transforms from torch.utils.data import DataLoader from torchvision import datasets import torchvision.models as models import torch.nn.functional as F import torch.nn as nn import torch,torchvision device torch.device(cuda if torch.cuda.is_available() else cpu) device运行结果2. 获取类别名import os,PIL,random,pathlib import matplotlib.pyplot as plt # 支持中文 plt.rcParams[font.sans-serif] [SimHei] # 用来正常显示中文标签 plt.rcParams[axes.unicode_minus] False # 用来正常显示负号 data_dir ./015_licence_plate/ data_dir pathlib.Path(data_dir) data_paths list(data_dir.glob(*)) classeNames [str(path).split(\\)[1].split(_)[1].split(.)[0] for path in data_paths] print(classeNames)运行结果data_paths list(data_dir.glob(*)) data_paths_str [str(path) for path in data_paths] data_paths_str运行结果3. 数据可视化plt.figure(figsize(14,5)) plt.suptitle(数据示例K同学啊,fontsize15) for i in range(18): plt.subplot(3,6,i1) # plt.xticks([]) # plt.yticks([]) # plt.grid(False) # 显示图片 images plt.imread(data_paths_str[i]) plt.imshow(images) plt.show()运行结果4. 标签数字化import numpy as np char_enum [京,沪,津,渝,冀,晋,蒙,辽,吉,黑,苏,浙,皖,闽,赣,鲁,\ 豫,鄂,湘,粤,桂,琼,川,贵,云,藏,陕,甘,青,宁,新,军,使] number [str(i) for i in range(0, 10)] # 0 到 9 的数字 alphabet [chr(i) for i in range(65, 91)] # A 到 Z 的字母 char_set char_enum number alphabet char_set_len len(char_set) label_name_len len(classeNames[0]) # 将字符串数字化 def text2vec(text): vector np.zeros([label_name_len, char_set_len]) for i, c in enumerate(text): idx char_set.index(c) vector[i][idx] 1.0 return vector all_labels [text2vec(i) for i in classeNames]这段代码的核心目的是为了给每个图片进行编码把所有车牌的字符串都转成这种 7x69 的数字矩阵作为训练时的“标准答案”。5. 加载数据文件import os import pandas as pd from torchvision.io import read_image from torch.utils.data import Dataset import torch.utils.data as data from PIL import Image class MyDataset(data.Dataset): def __init__(self, all_labels, data_paths_str, transform): self.img_labels all_labels # 获取标签信息 self.img_dir data_paths_str # 图像目录路径 self.transform transform # 目标转换函数 def __len__(self): return len(self.img_labels) def __getitem__(self, index): image Image.open(self.img_dir[index]).convert(RGB)#plt.imread(self.img_dir[index]) # 使用 torchvision.io.read_image 读取图像 label self.img_labels[index] # 获取图像对应的标签 if self.transform: image self.transform(image) return image, label # 返回图像和标签total_datadir ./03_traffic_sign/ # 关于transforms.Compose的更多介绍可以参考https://blog.csdn.net/qq_38251616/article/details/124878863 train_transforms transforms.Compose([ transforms.Resize([224, 224]), # 将输入图片resize成统一尺寸 transforms.ToTensor(), # 将PIL Image或numpy.ndarray转换为tensor并归一化到[0,1]之间 transforms.Normalize( # 标准化处理--转换为标准正太分布高斯分布使模型更容易收敛 mean[0.485, 0.456, 0.406], std [0.229, 0.224, 0.225]) # 其中 mean[0.485,0.456,0.406]与std[0.229,0.224,0.225] 从数据集中随机抽样计算得到的。 ]) total_data MyDataset(all_labels, data_paths_str, train_transforms) total_data6.划分数据集train_size int(0.8 * len(total_data)) test_size len(total_data) - train_size train_dataset, test_dataset torch.utils.data.random_split(total_data, [train_size, test_size]) train_size,test_size运行结果train_loader torch.utils.data.DataLoader(train_dataset, batch_size16, shuffleTrue) test_loader torch.utils.data.DataLoader(test_dataset, batch_size16, shuffleTrue) print(The number of images in a training set is: , len(train_loader)*16) print(The number of images in a test set is: , len(test_loader)*16) print(The number of batches per epoch is: , len(train_loader))运行结果for X, y in test_loader: print(Shape of X [N, C, H, W]: , X.shape) print(Shape of y: , y.shape, y.dtype) break运行结果查看一个批次的数据形状X是图像张量形状为[16, 3, 224, 224]16张3 通道高宽 224y是标签形状为[16769]类型为torch.int64二、自建模型class Network_bn(nn.Module): def __init__(self): super(Network_bn, self).__init__() nn.Conv2d()函数 第一个参数in_channels是输入的channel数量 第二个参数out_channels是输出的channel数量 第三个参数kernel_size是卷积核大小 第四个参数stride是步长默认为1 第五个参数padding是填充大小默认为0 self.conv1 nn.Conv2d(in_channels3, out_channels12, kernel_size5, stride1, padding0) self.bn1 nn.BatchNorm2d(12) self.conv2 nn.Conv2d(in_channels12, out_channels12, kernel_size5, stride1, padding0) self.bn2 nn.BatchNorm2d(12) self.pool nn.MaxPool2d(2,2) self.conv4 nn.Conv2d(in_channels12, out_channels24, kernel_size5, stride1, padding0) self.bn4 nn.BatchNorm2d(24) self.conv5 nn.Conv2d(in_channels24, out_channels24, kernel_size5, stride1, padding0) self.bn5 nn.BatchNorm2d(24) self.fc1 nn.Linear(24*50*50, label_name_len*char_set_len) self.reshape Reshape([label_name_len,char_set_len]) def forward(self, x): x F.relu(self.bn1(self.conv1(x))) x F.relu(self.bn2(self.conv2(x))) x self.pool(x) x F.relu(self.bn4(self.conv4(x))) x F.relu(self.bn5(self.conv5(x))) x self.pool(x) x x.view(-1, 24*50*50) x self.fc1(x) # 最终reshape x self.reshape(x) return x # 定义Reshape层 class Reshape(nn.Module): def __init__(self, shape): super(Reshape, self).__init__() self.shape shape def forward(self, x): return x.view(x.size(0), *self.shape) device cuda if torch.cuda.is_available() else cpu print(Using {} device.format(device)) model Network_bn().to(device) modelview是 PyTorch 改变张量形状的方法。x.size(0)是批次大小16*self.shape是[7, 69]。最终输出形状为[16, 7, 69]即每个字符对应一个 69 维的独热向量。运行结果import torchsummary 显示网络结构 torchsummary.summary(model, (3, 224, 224))运行结果三、模型训练1. 优化器与损失函数optimizer torch.optim.Adam(model.parameters(), lr1e-4, weight_decay0.0001) loss_model nn.CrossEntropyLoss()2.编写训练和测试函数from torch.autograd import Variable def test(model, test_loader, loss_model): size len(test_loader.dataset) num_batches len(test_loader) model.eval() test_loss, correct 0, 0 with torch.no_grad(): for X, y in test_loader: X, y X.to(device), y.to(device) pred model(X) test_loss loss_model(pred, y).item() test_loss / num_batches print(fAvg loss: {test_loss:8f} \n) return correct,test_loss def train(model,train_loader,loss_model,optimizer): modelmodel.to(device) model.train() for i, (images, labels) in enumerate(train_loader, 0): #0是标起始位置的值。 images Variable(images.to(device)) labels Variable(labels.to(device)) optimizer.zero_grad() outputs model(images) loss loss_model(outputs, labels) loss.backward() optimizer.step() if i % 1000 0: print([%5d] loss: %.3f % (i, loss))3. 模型的训练test_acc_list [] test_loss_list [] epochs 30 for t in range(epochs): print(fEpoch {t1}\n-------------------------------) train(model,train_loader,loss_model,optimizer) test_acc,test_loss test(model, test_loader, loss_model) test_acc_list.append(test_acc) test_loss_list.append(test_loss) print(Done!)运行结果四、结果分析import numpy as np import matplotlib.pyplot as plt from datetime import datetime current_time datetime.now() # 获取当前时间 x [i for i in range(1,31)] plt.plot(x, test_loss_list, labelLoss, alpha0.8) plt.xlabel(Epoch) plt.ylabel(Loss) plt.title(current_time) # 打卡请带上时间戳否则代码截图无效 plt.legend() plt.show()运行结果五、ACC统计加入def test(model, test_loader, loss_model): size len(test_loader.dataset) num_batches len(test_loader) model.eval() test_loss, correct 0, 0 with torch.no_grad(): for X, y in test_loader: X, y X.to(device), y.to(device) pred model(X) test_loss loss_model(pred, y).item() # 预测索引 pred_ids pred.argmax(dim2) true_ids y.argmax(dim2) # 统计完全匹配的样本数 correct (pred_ids true_ids).all(dim1).sum().item() test_loss / num_batches accuracy correct / size print(fAvg loss: {test_loss:8f}, Accuracy: {accuracy:.4f} ({correct}/{size})\n) return correct, test_loss, accuracy模型输出pred形状是[16, 7, 69]。这意味着16张图每张图有7个位置每个位置上有69个概率值每个字符的概率。1.模型输出的是概率69个数字 → 2.用argmax转成具体的字符编号→ 3.和真实编号逐位比较→ 4.用all要求 7 个字符全对→ 5.累加正确的个数→ 6.除以总数得到准确率。六、总结本周主要是学习对于不同数据命名格式的时候要如何处理对于车牌号的识别考虑到每个车牌号都是一个独立的名称因此无法像之前的分类任务一样根据不同类型进行分类。在车牌号中一共有三个类型的变量字符、数字和字母所以为进行识别就需要对车牌号进行独热编码。