项目2 车牌检测
检测车牌
- 1. 基本思想
- 2. 基础知识
- 2.1 YOLOV5(参考鱼苗检测)
- 2.1.1 模型 省略
- 2.1.2 输入输出 省略
- 2.1.3 损失函数 省略
- 2.2 LPRNet
- 2.2.1 模型
- 2.2.2 输入输出
- 2.2.3 损失函数
- 3. 流程
- 3.1 数据处理
- 3.1.1 YOLOV5数据处理
- 3.2.2 LPRNet数据处理
- 3.2 训练
- 3.2.1 YOLOV5训练 省略
- 3.2.2 LPRNet训练
- 3.3 推理
- 3.3.1 YOLOV5推理 省略
- 3.3.2 LPRNet推理
- 3.4 测试
- 3.4.1 YOLOV5测试 省略
- 3.4.2 LPRNet测试
- 3.5 合并检测与识别
- 3.6 结果
1. 基本思想
YOLOv5+LPRNet。先使用YOLOv5检测车牌,再把检测车牌送入LPRNet得到检测结果。
2. 基础知识
2.1 YOLOV5(参考鱼苗检测)
2.1.1 模型 省略
2.1.2 输入输出 省略
2.1.3 损失函数 省略
2.2 LPRNet
2.2.1 模型

图像统一尺寸后输入到模型,先经过Backbone得到特征f2、f6、 f13、 f22,四个特征经过Neck处理后拼接在一起,最后经过检测头得到[bs,68,18]的结果,18表示模型输出18个字符,每个字符有68类。代码如下:
import torch.nn as nn
import torch
CHARS = ['京', '沪', '津', '渝', '冀', '晋', '蒙', '辽', '吉', '黑','苏', '浙', '皖', '闽', '赣', '鲁', '豫', '鄂', '湘', '粤','桂', '琼', '川', '贵', '云', '藏', '陕', '甘', '青', '宁','新','0', '1', '2', '3', '4', '5', '6', '7', '8', '9','A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K','L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V','W', 'X', 'Y', 'Z', 'I', 'O', '-']
class small_basic_block(nn.Module):def __init__(self, ch_in, ch_out):super(small_basic_block, self).__init__()self.block = nn.Sequential(nn.Conv2d(ch_in, ch_out // 4, kernel_size=1),nn.ReLU(),nn.Conv2d(ch_out // 4, ch_out // 4, kernel_size=(3, 1), padding=(1, 0)),nn.ReLU(),nn.Conv2d(ch_out // 4, ch_out // 4, kernel_size=(1, 3), padding=(0, 1)),nn.ReLU(),nn.Conv2d(ch_out // 4, ch_out, kernel_size=1),)def forward(self, x):return self.block(x)class LPRNet(nn.Module):def __init__(self, lpr_max_len, phase, class_num, dropout_rate):super(LPRNet, self).__init__()self.phase = phaseself.lpr_max_len = lpr_max_lenself.class_num = class_numself.backbone = nn.Sequential(nn.Conv2d(in_channels=3, out_channels=64, kernel_size=3, stride=1), # 0 -> [bs,3,24,94] -> [bs,64,22,92]nn.BatchNorm2d(num_features=64), # 1 -> [bs,64,22,92]nn.ReLU(), # 2 -> [bs,64,22,92]nn.MaxPool3d(kernel_size=(1, 3, 3), stride=(1, 1, 1)), # 3 -> [bs,64,20,90]small_basic_block(ch_in=64, ch_out=128), # 4 -> [bs,128,20,90]nn.BatchNorm2d(num_features=128), # 5 -> [bs,128,20,90]nn.ReLU(), # 6 -> [bs,128,20,90]nn.MaxPool3d(kernel_size=(1, 3, 3), stride=(2, 1, 2)), # 7 -> [bs,64,18,44]small_basic_block(ch_in=64, ch_out=256), # 8 -> [bs,256,18,44]nn.BatchNorm2d(num_features=256), # 9 -> [bs,256,18,44]nn.ReLU(), # 10 -> [bs,256,18,44]small_basic_block(ch_in=256, ch_out=256), # 11 -> [bs,256,18,44]nn.BatchNorm2d(num_features=256), # 12 -> [bs,256,18,44]nn.ReLU(), # 13 -> [bs,256,18,44]nn.MaxPool3d(kernel_size=(1, 3, 3), stride=(4, 1, 2)), # 14 -> [bs,64,16,21]nn.Dropout(dropout_rate), # 0.5 dropout rate # 15 -> [bs,64,16,21]nn.Conv2d(in_channels=64, out_channels=256, kernel_size=(1, 4), stride=1), # 16 -> [bs,256,16,18]nn.BatchNorm2d(num_features=256), # 17 -> [bs,256,16,18]nn.ReLU(), # 18 -> [bs,256,16,18]nn.Dropout(dropout_rate), # 0.5 dropout rate 19 -> [bs,256,16,18]nn.Conv2d(in_channels=256, out_channels=class_num, kernel_size=(13, 1), stride=1), # class_num=68 20 -> [bs,68,4,18]nn.BatchNorm2d(num_features=class_num), # 21 -> [bs,68,4,18]nn.ReLU(), # 22 -> [bs,68,4,18])self.container = nn.Sequential(nn.Conv2d(in_channels=448+self.class_num, out_channels=self.class_num, kernel_size=(1, 1), stride=(1, 1)),def forward(self, x):keep_features = list()for i, layer in enumerate(self.backbone.children()):x = layer(x)if i in [2, 6, 13, 22]:keep_features.append(x)global_context = list()# keep_features: [bs,64,22,92] [bs,128,20,90] [bs,256,18,44] [bs,68,4,18]for i, f in enumerate(keep_features):if i in [0, 1]:# [bs,64,22,92] -> [bs,64,4,18]# [bs,128,20,90] -> [bs,128,4,18]f = nn.AvgPool2d(kernel_size=5, stride=5)(f)if i in [2]:# [bs,256,18,44] -> [bs,256,4,18]f = nn.AvgPool2d(kernel_size=(4, 10), stride=(4, 2))(f)# 没看懂这是在干嘛?有上面的avg提取上下文信息不久可以了?f_pow = torch.pow(f, 2) # [bs,64,4,18] 所有元素求平方f_mean = torch.mean(f_pow) # 1 所有元素求平均f = torch.div(f, f_mean) # [bs,64,4,18] 所有元素除以这个均值global_context.append(f)x = torch.cat(global_context, 1) # [bs,516,4,18]x = self.container(x) # -> [bs, 68, 4, 18] head头logits = torch.mean(x, dim=2) # -> [bs, 68, 18] # 68 字符类数 18字符return logitsif __name__=="__main__":lpr_max_len=18; phase=False; class_num=68; dropout_rate=0.5i = torch.rand([6,3,24,94])Net = LPRNet(lpr_max_len, phase, class_num, dropout_rate)o = Net(i) # torch.Size([6, 68, 18])
2.2.2 输入输出
- 模型输入
图像处理步骤:
(1) 处理图片。读入图片,把图片的通道由BGR转换成RGB,统一图片尺寸为[94,24],通过transform对图片归一化及改变通道位置。
(2) 生成标签。图片的地址是标签,去除地址后缀得到标签,把标签转换成数字,判断标签是否正确。
(3) 返回图片数组,标签及标签长度。标签长度在模型损失中使用。代码如下:
from imutils import paths
import numpy as np
import random
import cv2
import osfrom torch.utils.data import DatasetCHARS = ['京', '沪', '津', '渝', '冀', '晋', '蒙', '辽', '吉', '黑','苏', '浙', '皖', '闽', '赣', '鲁', '豫', '鄂', '湘', '粤','桂', '琼', '川', '贵', '云', '藏', '陕', '甘', '青', '宁','新','0', '1', '2', '3', '4', '5', '6', '7', '8', '9','A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K','L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V','W', 'X', 'Y', 'Z', 'I', 'O', '-']CHARS_DICT = {char:i for i, char in enumerate(CHARS)}class LPRDataLoader(Dataset):def __init__(self, img_dir, imgSize, lpr_max_len, PreprocFun=None):self.img_dir = img_dirself.img_paths = []for i in range(len(img_dir)):self.img_paths += [el for el in paths.list_images(img_dir[i])]random.shuffle(self.img_paths)self.img_size = imgSize # [94, 24]self.lpr_max_len = lpr_max_len # 8if PreprocFun is not None:self.PreprocFun = PreprocFunelse:self.PreprocFun = self.transformdef __len__(self):return len(self.img_paths)def __getitem__(self, index):filename = self.img_paths[index]Image = cv2.imdecode(np.fromfile(filename, dtype=np.uint8), -1)Image = cv2.cvtColor(Image, cv2.COLOR_RGB2BGR)height, width, _ = Image.shapeif height != self.img_size[1] or width != self.img_size[0]:Image = cv2.resize(Image, self.img_size)Image = self.PreprocFun(Image)basename = os.path.basename(filename) # 'datasets/rec_images/train/沪A9B821.jpg'-->'沪A9B821.jpg'imgname, suffix = os.path.splitext(basename) # '沪A9B821.jpg' --> ('沪A9B821', '.jpg')imgname = imgname.split("-")[0].split("_")[0]label = list()for c in imgname:label.append(CHARS_DICT[c])if len(label) == 8:if self.check(label) == False:print(imgname)assert 0, "Error label ^~^!!!"return Image, label, len(label)def transform(self, img):img = img.astype('float32') # 图片由Uint8转换为float32类型img -= 127.5 # 图片减均值乘方差倒数实现归一化,去除噪声影响img *= 0.0078125img = np.transpose(img, (2, 0, 1)) # [h,w,c]-->[c,h,w]return imgdef check(self, label): # 检测标签是否正确if label[2] != CHARS_DICT['D'] and label[2] != CHARS_DICT['F'] \and label[-1] != CHARS_DICT['D'] and label[-1] != CHARS_DICT['F']:print("Error label, Please check!")return Falseelse:return Trueif __name__ == "__main__":train_img_dirs = "datasets/rec_images/train"img_size = [94, 24]lpr_max_len = 8train_dataset = LPRDataLoader(train_img_dirs.split(','), img_size, lpr_max_len)
2.模型输出步骤:
(1) 图片输入模型得到logits。
(2)对logits转换通道[6, 68,18]–>[18, 6, 68],
其中6是batch_size,68是一共68个类别,18是输出18个字符序列。
(3)用softmax把logits最后一维变成概率。代码如下:
logits = lprnet(images)
log_probs = logits.permute(2, 0, 1) # for ctc loss: T x N x C torch.Size([18, 6, 68])
log_probs = log_probs.log_softmax(2).requires_grad_() # [18, bs, 68]
2.2.3 损失函数
ctc_loss用来处理不等长序列的损失,用动态规划的方法找到有标签匹配的各种序列,通过使序列概率最大化来更新参数。代码如下:
loss = ctc_loss(log_probs, labels, input_lengths=input_lengths, target_lengths=target_lengths)
# input_lengths[18,18,18,...,18] 18是模型输出的字符数。target_lengths[7,7,7,...,7] 7是真实标签的的字符数,有些车牌是8个字符,依实际情况而定。
3. 流程
3.1 数据处理
3.1.1 YOLOV5数据处理
数据集 官方CCPD数据https://github.com/detectRecog/CCPD
- CCPD数据集中图片名称包含车牌框box的位置信息和车牌号,数据处理的目的是把获取车牌的中心点及高宽在图像中的相对位置并以txt格式保存。

- 代码
import shutil
import cv2
import osdef txt_translate(path, txt_path):''' 根据图片的地址获取车牌的左上角和右下角坐标,把左上角和右下角坐标转成中心点和宽高格式,最后中心点和宽高格式除以图片的宽高以.txt格式保存在指定位置'''for filename in os.listdir(path):print(filename)if not "-" in filename: # 对于np等无标签的图片,过滤continuesubname = filename.split("-", 3)[2] # 第一次分割,以减号'-'做分割,提取车牌两角坐标. '231&522_405&574'extension = filename.split(".", 1)[1] #判断车牌是否为图片if not extension == 'jpg':continuelt, rb = subname.split("_", 1) # 第二次分割,以下划线'_'做分割lx, ly = lt.split("&", 1) # 左上角坐标rx, ry = rb.split("&", 1) # 右下角坐标width = int(rx) - int(lx) # 车牌宽度height = int(ry) - int(ly) # bounding box的宽和高cx = float(lx) + width / 2cy = float(ly) + height / 2 # bounding box中心点img = cv2.imread(os.path.join(path , filename))if img is None: # 自动删除失效图片(下载过程有的图片会存在无法读取的情况)os.remove(os.path.join(path, filename))continuewidth = width / img.shape[1]height = height / img.shape[0]cx = cx / img.s相关文章:
项目2 车牌检测
检测车牌 1. 基本思想2. 基础知识2.1 YOLOV5(参考鱼苗检测)2.1.1 模型 省略2.1.2 输入输出 省略2.1.3 损失函数 省略2.2 LPRNet2.2.1 模型2.2.2 输入输出2.2.3 损失函数3. 流程3.1 数据处理3.1.1 YOLOV5数据处理3.2.2 LPRNet数据处理3.2 训练3.2.1 YOLOV5训练 省略3.2.2 LPRN…...
Linux: 网络基础
1.协议 为什么要有协议:减少通信成本。所有的网络问题,本质是传输距离变长了。 什么是协议:用计算机语言表达的约定。 2.分层 软件设计方面的优势—低耦合。 一般我们的分层依据:功能比较集中,耦合度比较高的模块层…...
【实战篇】巧用 DeepSeek,让 Excel 数据处理更高效
一、为何选择用 DeepSeek 处理 Excel 在日常工作与生活里,Excel 是我们频繁使用的工具。不管是统计公司销售数据、分析学生成绩,还是梳理个人财务状况,Excel 凭借其强大的功能,如数据排序、筛选和简单公式计算,为我们提供了诸多便利。但当面对复杂的数据处理任务,比如从…...
Flink CDC YAML:面向数据集成的 API 设计
摘要:本文整理自阿里云智能集团 、Flink PMC Member & Committer 徐榜江(雪尽)老师在 Flink Forward Asia 2024 数据集成(一)专场中的分享。主要分为以下四个方面: Flink CDC YAML API Transform A…...
RabbitMQ技术深度解析:打造高效消息传递系统
引言 在当前的分布式系统架构中,消息队列作为一种高效的消息传递机制,扮演着越来越重要的角色。RabbitMQ,作为广泛使用的开源消息代理,以其高可用性、扩展性和灵活性赢得了众多开发者的青睐。本文将深入探讨RabbitMQ的核心概念、…...
DeepSeek与人工智能的结合:探索搜索技术的未来
云边有个稻草人-CSDN博客 目录 引言 一、DeepSeek的技术背景 1.1 传统搜索引擎的局限性 1.2 深度学习在搜索中的优势 二、DeepSeek与人工智能的结合 2.1 自然语言处理(NLP) 示例代码:基于BERT的语义搜索 2.2 多模态搜索 示例代码&…...
TAPEX:通过神经SQL执行器学习的表格预训练
摘要 近年来,语言模型预训练的进展通过利用大规模非结构化文本数据取得了巨大成功。然而,由于缺乏大规模高质量的表格数据,在结构化表格数据上应用预训练仍然是一个挑战。本文提出了TAPEX,通过在一个合成语料库上学习神经SQL执行…...
Qt:Qt基础介绍
目录 Qt背景介绍 什么是Qt Qt的发展史 Qt支持的平台 Qt版本 Qt的优点 Qt的应用场景 Qt的成功案例 Qt的发展前景及就业分析 Qt背景介绍 什么是Qt Qt是⼀个跨平台的C图形用户界面应用程序框架。它为应用程序开发者提供了建立艺术级图形界面所需的所有功能。它是完全面向…...
加速度计信号处理
【使用 DSP 滤波器加速速度和位移】使用信号处理算法过滤加速度数据并将其转换为速度和位移研究(Matlab代码实现)_加速度计滤波器-CSDN博客 https://wenku.baidu.com/view/622d38b90f22590102020740be1e650e52eacff9.html?_wkts_1738906719916&bdQ…...
基于SpringBoot养老院平台系统功能实现六
一、前言介绍: 1.1 项目摘要 随着全球人口老龄化的不断加剧,养老服务需求日益增长。特别是在中国,随着经济的快速发展和人民生活水平的提高,老年人口数量不断增加,对养老服务的质量和效率提出了更高的要求。传统的养…...
Conmi的正确答案——Rider中添加icon作为exe的图标
C#版本:.net 8.0 Rider版本:#RD-243.22562.250(非商业使用版) 1、添加图标到解决方案下: 2、打开“App.xaml”配置文件,添加配置: <Applicationx:Class"ComTransmit.App"xmlns&q…...
机试题——DNS本地缓存
题目描述 正在开发一个DNS本地缓存系统。在互联网中,DNS(Domain Name System)用于将域名(例如www.example.com)解析为IP地址,以便将请求发送到正确的服务器上。通常情况下,DNS请求会发送到互联…...
Day38【AI思考】-彻底打通线性数据结构间的血脉联系
文章目录 **彻底打通线性数据结构间的血脉联系****数据结构家族谱系图****一、线性表(老祖宗的规矩)****核心特征** **二、嫡系血脉解析**1. **数组(规矩森严的长子)**2. **链表(灵活变通的次子)** **三、庶…...
【LeetCode】152、乘积最大子数组
【LeetCode】152、乘积最大子数组 文章目录 一、dp1.1 dp1.2 简化代码 二、多语言解法 一、dp 1.1 dp 从前向后遍历, 当遍历到 nums[i] 时, 有如下三种情况 能得到最大值: 只使用 nums[i], 例如 [0.1, 0.3, 0.2, 100] 则 [100] 是最大值使用 max(nums[0…i-1]) * nums[i], 例…...
[MRCTF2020]Ez_bypass1(md5绕过)
[MRCTF2020]Ez_bypass1(md5绕过) 这道题就是要绕过md5强类型比较,但是本身又不相等: md5无法处理数组,如果传入的是数组进行md5加密,会直接放回NULL,两个NuLL相比较会等于true; 所以?id[]1&gg…...
MySQL 缓存机制与架构解析
目录 一、MySQL缓存机制概述 二、MySQL整体架构 三、SQL查询执行全流程 四、MySQL 8.0为何移除查询缓存? 五、MySQL 8.0前的查询缓存配置 六、替代方案:应用层缓存与优化建议 总结 一、MySQL缓存机制概述 MySQL的缓存机制旨在提升数据访问效率&am…...
LabVIEW自定义测量参数怎么设置?
以下通过一个温度采集案例,说明在 LabVIEW 中设置自定义测量参数的具体方法: 案例背景 假设使用 NI USB-6009 数据采集卡 和 热电偶传感器 监测温度,需自定义以下参数: 采样率:1 kHz 输入量程:0~10 V&a…...
海思的一站式集成环境Hispark Studio更新了
HiSpark Studio是海思提供的面向智能设备开发者提供一站式集成开发环境,支持代码编辑、编译、烧录和调试等功能。我以前在评测星闪芯片的时候用过,当时写了篇博客:【星闪开发连载】WS63E开发板Windows环境的构建_hispark studio-CSDN博客。那…...
TresJS:用Vue组件构建3D场景的新选择
在当今数字化时代,3D图形技术正以前所未有的速度发展,从游戏开发到虚拟现实(VR)、增强现实(AR),再到各种沉浸式体验,3D技术的应用场景日益丰富。TresJS作为一款基于Three.js的Web3D开…...
Axure设计教程:动态排名图(中继器实现)
一、开篇 在Axure原型设计中,动态图表是展示数据和交互效果的重要元素。今天,我们将学习如何使用中继器来创建一个动态的排名图,该图表不仅支持自动轮播,还可以手动切换,极大地增强了用户交互体验。此教程旨在提供一个…...
XML Group端口详解
在XML数据映射过程中,经常需要对数据进行分组聚合操作。例如,当处理包含多个物料明细的XML文件时,可能需要将相同物料号的明细归为一组,或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码,增加了开…...
【Oracle APEX开发小技巧12】
有如下需求: 有一个问题反馈页面,要实现在apex页面展示能直观看到反馈时间超过7天未处理的数据,方便管理员及时处理反馈。 我的方法:直接将逻辑写在SQL中,这样可以直接在页面展示 完整代码: SELECTSF.FE…...
Unity3D中Gfx.WaitForPresent优化方案
前言 在Unity中,Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染(即CPU被阻塞),这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案: 对惹,这里有一个游戏开发交流小组&…...
循环冗余码校验CRC码 算法步骤+详细实例计算
通信过程:(白话解释) 我们将原始待发送的消息称为 M M M,依据发送接收消息双方约定的生成多项式 G ( x ) G(x) G(x)(意思就是 G ( x ) G(x) G(x) 是已知的)࿰…...
Cinnamon修改面板小工具图标
Cinnamon开始菜单-CSDN博客 设置模块都是做好的,比GNOME简单得多! 在 applet.js 里增加 const Settings imports.ui.settings;this.settings new Settings.AppletSettings(this, HTYMenusonichy, instance_id); this.settings.bind(menu-icon, menu…...
css的定位(position)详解:相对定位 绝对定位 固定定位
在 CSS 中,元素的定位通过 position 属性控制,共有 5 种定位模式:static(静态定位)、relative(相对定位)、absolute(绝对定位)、fixed(固定定位)和…...
PL0语法,分析器实现!
简介 PL/0 是一种简单的编程语言,通常用于教学编译原理。它的语法结构清晰,功能包括常量定义、变量声明、过程(子程序)定义以及基本的控制结构(如条件语句和循环语句)。 PL/0 语法规范 PL/0 是一种教学用的小型编程语言,由 Niklaus Wirth 设计,用于展示编译原理的核…...
图表类系列各种样式PPT模版分享
图标图表系列PPT模版,柱状图PPT模版,线状图PPT模版,折线图PPT模版,饼状图PPT模版,雷达图PPT模版,树状图PPT模版 图表类系列各种样式PPT模版分享:图表系列PPT模板https://pan.quark.cn/s/20d40aa…...
使用Spring AI和MCP协议构建图片搜索服务
目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式(本地调用) SSE模式(远程调用) 4. 注册工具提…...
现有的 Redis 分布式锁库(如 Redisson)提供了哪些便利?
现有的 Redis 分布式锁库(如 Redisson)相比于开发者自己基于 Redis 命令(如 SETNX, EXPIRE, DEL)手动实现分布式锁,提供了巨大的便利性和健壮性。主要体现在以下几个方面: 原子性保证 (Atomicity)ÿ…...
