项目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原型设计中,动态图表是展示数据和交互效果的重要元素。今天,我们将学习如何使用中继器来创建一个动态的排名图,该图表不仅支持自动轮播,还可以手动切换,极大地增强了用户交互体验。此教程旨在提供一个…...

IDEA运行Tomcat出现乱码问题解决汇总
最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…...
云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?
大家好,欢迎来到《云原生核心技术》系列的第七篇! 在上一篇,我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在,我们就像一个拥有了一块崭新数字土地的农场主,是时…...

家政维修平台实战20:权限设计
目录 1 获取工人信息2 搭建工人入口3 权限判断总结 目前我们已经搭建好了基础的用户体系,主要是分成几个表,用户表我们是记录用户的基础信息,包括手机、昵称、头像。而工人和员工各有各的表。那么就有一个问题,不同的角色…...

Springcloud:Eureka 高可用集群搭建实战(服务注册与发现的底层原理与避坑指南)
引言:为什么 Eureka 依然是存量系统的核心? 尽管 Nacos 等新注册中心崛起,但金融、电力等保守行业仍有大量系统运行在 Eureka 上。理解其高可用设计与自我保护机制,是保障分布式系统稳定的必修课。本文将手把手带你搭建生产级 Eur…...
Spring AI 入门:Java 开发者的生成式 AI 实践之路
一、Spring AI 简介 在人工智能技术快速迭代的今天,Spring AI 作为 Spring 生态系统的新生力量,正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务(如 OpenAI、Anthropic)的无缝对接&…...

第 86 场周赛:矩阵中的幻方、钥匙和房间、将数组拆分成斐波那契序列、猜猜这个单词
Q1、[中等] 矩阵中的幻方 1、题目描述 3 x 3 的幻方是一个填充有 从 1 到 9 的不同数字的 3 x 3 矩阵,其中每行,每列以及两条对角线上的各数之和都相等。 给定一个由整数组成的row x col 的 grid,其中有多少个 3 3 的 “幻方” 子矩阵&am…...
React---day11
14.4 react-redux第三方库 提供connect、thunk之类的函数 以获取一个banner数据为例子 store: 我们在使用异步的时候理应是要使用中间件的,但是configureStore 已经自动集成了 redux-thunk,注意action里面要返回函数 import { configureS…...

Netty从入门到进阶(二)
二、Netty入门 1. 概述 1.1 Netty是什么 Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients. Netty是一个异步的、基于事件驱动的网络应用框架,用于…...
Go 并发编程基础:通道(Channel)的使用
在 Go 中,Channel 是 Goroutine 之间通信的核心机制。它提供了一个线程安全的通信方式,用于在多个 Goroutine 之间传递数据,从而实现高效的并发编程。 本章将介绍 Channel 的基本概念、用法、缓冲、关闭机制以及 select 的使用。 一、Channel…...

基于IDIG-GAN的小样本电机轴承故障诊断
目录 🔍 核心问题 一、IDIG-GAN模型原理 1. 整体架构 2. 核心创新点 (1) 梯度归一化(Gradient Normalization) (2) 判别器梯度间隙正则化(Discriminator Gradient Gap Regularization) (3) 自注意力机制(Self-Attention) 3. 完整损失函数 二…...