1.研究背景与意义
项目参考AAAI Association for the Advancement of Artificial Intelligence
研究背景与意义:
随着车辆数量的快速增长,车牌识别系统在交通管理、安全监控和智能交通等领域发挥着重要作用。然而,传统的车牌识别系统在低光照条件下的识别效果较差,这是因为低光照条件下图像的亮度较低,车牌上的字符难以清晰可见。因此,开发一种基于OpenCV的低光照车牌识别系统具有重要的研究意义和实际应用价值。
首先,低光照条件下的车牌识别系统可以提高交通管理的效率和准确性。在夜间或恶劣天气条件下,传统的车牌识别系统往往无法准确识别车牌上的字符,导致交通违法行为无法及时监测和处理。而基于OpenCV的低光照车牌识别系统可以通过图像增强和字符分割等技术,提高车牌识别的准确性和稳定性,从而更好地支持交通管理工作。
其次,低光照条件下的车牌识别系统可以提高安全监控的效果。在夜间或光线较暗的环境中,传统的安全监控系统往往无法清晰地捕捉到车辆的车牌信息,从而无法准确追踪和识别嫌疑车辆。而基于OpenCV的低光照车牌识别系统可以通过图像增强和特征提取等技术,提高车牌识别的成功率和精度,从而更好地支持安全监控工作。
此外,低光照条件下的车牌识别系统还可以应用于智能交通领域。随着智能交通技术的不断发展,车辆的自动驾驶和智能导航等功能越来越受到关注。然而,在低光照条件下,传统的车牌识别系统往往无法准确识别车辆的车牌信息,从而影响自动驾驶和智能导航的效果。而基于OpenCV的低光照车牌识别系统可以通过图像增强和模式识别等技术,提高车牌识别的成功率和稳定性,从而更好地支持智能交通的发展。
综上所述,基于OpenCV的低光照车牌识别系统具有重要的研究意义和实际应用价值。通过提高车牌识别的准确性和稳定性,该系统可以提高交通管理的效率和准确性,改善安全监控的效果,促进智能交通的发展。因此,开展相关研究对于推动交通领域的科技创新和社会进步具有重要意义。
2.图片演示
3.视频演示
基于OpenCV的低光照车牌识别系统_哔哩哔哩_bilibili
4.数据集的采集&标注和整理
图片的收集
首先,我们需要收集所需的图片。这可以通过不同的方式来实现,例如使用现有的公开数据集licenseplates。
labelImg是一个图形化的图像注释工具,支持VOC和YOLO格式。以下是使用labelImg将图片标注为VOC格式的步骤:
(1)下载并安装labelImg。
(2)打开labelImg并选择“Open Dir”来选择你的图片目录。
(3)为你的目标对象设置标签名称。
(4)在图片上绘制矩形框,选择对应的标签。
(5)保存标注信息,这将在图片目录下生成一个与图片同名的XML文件。
(6)重复此过程,直到所有的图片都标注完毕。
由于YOLO使用的是txt格式的标注,我们需要将VOC格式转换为YOLO格式。可以使用各种转换工具或脚本来实现。
下面是一个简单的方法是使用Python脚本,该脚本读取XML文件,然后将其转换为YOLO所需的txt格式。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import xml.etree.ElementTree as ET
import os
classes = [] # 初始化为空列表
CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
def convert(size, box):
dw = 1. / size[0]
dh = 1. / size[1]
x = (box[0] + box[1]) / 2.0
y = (box[2] + box[3]) / 2.0
w = box[1] - box[0]
h = box[3] - box[2]
x = x * dw
w = w * dw
y = y * dh
h = h * dh
return (x, y, w, h)
def convert_annotation(image_id):
in_file = open('./label_xml\%s.xml' % (image_id), encoding='UTF-8')
out_file = open('./label_txt\%s.txt' % (image_id), 'w') # 生成txt格式文件
tree = ET.parse(in_file)
root = tree.getroot()
size = root.find('size')
w = int(size.find('width').text)
h = int(size.find('height').text)
for obj in root.iter('object'):
cls = obj.find('name').text
if cls not in classes:
classes.append(cls) # 如果类别不存在,添加到classes列表中
cls_id = classes.index(cls)
xmlbox = obj.find('bndbox')
b = (float(xmlbox.find('xmin').text), float(xmlbox.find('xmax').text), float(xmlbox.find('ymin').text),
float(xmlbox.find('ymax').text))
bb = convert((w, h), b)
out_file.write(str(cls_id) + " " + " ".join([str(a) for a in bb]) + '\n')
xml_path = os.path.join(CURRENT_DIR, './label_xml/')
# xml list
img_xmls = os.listdir(xml_path)
for img_xml in img_xmls:
label_name = img_xml.split('.')[0]
print(label_name)
convert_annotation(label_name)
print("Classes:") # 打印最终的classes列表
print(classes) # 打印最终的classes列表
整理数据文件夹结构
我们需要将数据集整理为以下结构:
-----data
|-----train
| |-----images
| |-----labels
|
|-----valid
| |-----images
| |-----labels
|
|-----test
|-----images
|-----labels
确保以下几点:
所有的训练图片都位于data/train/images目录下,相应的标注文件位于data/train/labels目录下。
所有的验证图片都位于data/valid/images目录下,相应的标注文件位于data/valid/labels目录下。
所有的测试图片都位于data/test/images目录下,相应的标注文件位于data/test/labels目录下。
这样的结构使得数据的管理和模型的训练、验证和测试变得非常方便。
模型训练
Epoch gpu_mem box obj cls labels img_size
1/200 20.8G 0.01576 0.01955 0.007536 22 1280: 100%|██████████| 849/849 [14:42<00:00, 1.04s/it]
Class Images Labels P R mAP@.5 mAP@.5:.95: 100%|██████████| 213/213 [01:14<00:00, 2.87it/s]
all 3395 17314 0.994 0.957 0.0957 0.0843
Epoch gpu_mem box obj cls labels img_size
2/200 20.8G 0.01578 0.01923 0.007006 22 1280: 100%|██████████| 849/849 [14:44<00:00, 1.04s/it]
Class Images Labels P R mAP@.5 mAP@.5:.95: 100%|██████████| 213/213 [01:12<00:00, 2.95it/s]
all 3395 17314 0.996 0.956 0.0957 0.0845
Epoch gpu_mem box obj cls labels img_size
3/200 20.8G 0.01561 0.0191 0.006895 27 1280: 100%|██████████| 849/849 [10:56<00:00, 1.29it/s]
Class Images Labels P R mAP@.5 mAP@.5:.95: 100%|███████ | 187/213 [00:52<00:00, 4.04it/s]
all 3395 17314 0.996 0.957 0.0957 0.0845
5.核心代码讲解
5.1 compute-cifar10-mean.py
根据代码,可以将其封装为一个名为DataPreprocessing
的类,其中包含一个normalize
方法用于计算数据集的均值和标准差,并进行归一化处理。
class DataPreprocessing:
def __init__(self, root='cifar'):
self.root = root
def normalize(self):
data = dset.CIFAR10(root=self.root, train=True, download=True,
transform=transforms.ToTensor()).train_data
data = data.astype(np.float32) / 255.
means = []
stdevs = []
for i in range(3):
pixels = data[:, i, :, :].ravel()
means.append(np.mean(pixels))
stdevs.append(np.std(pixels))
print("means: {}".format(means))
print("stdevs: {}".format(stdevs))
print('transforms.Normalize(mean = {}, std = {})'.format(means, stdevs))
使用时,可以创建一个DataPreprocessing
对象,并调用normalize
方法进行数据预处理。
preprocessing = DataPreprocessing()
preprocessing.normalize()
这个程序文件的名称是compute-cifar10-mean.py,它的功能是计算CIFAR-10数据集的均值和标准差。
首先,程序导入了必要的库和模块,包括argparse、torch、torchvision等。
然后,程序通过dset.CIFAR10函数加载CIFAR-10数据集,并将数据转换为Tensor格式。接着,程序将数据转换为浮点型,并将像素值归一化到0到1之间。
接下来,程序定义了一个空列表means和stdevs,用于存储计算得到的均值和标准差。
然后,程序通过循环遍历数据的三个通道,计算每个通道的像素均值和标准差,并将结果添加到means和stdevs列表中。
最后,程序打印出计算得到的均值和标准差,并输出一个字符串,用于将这些值用于transforms.Normalize函数。
总结起来,这个程序文件的主要功能是计算CIFAR-10数据集的均值和标准差,并输出一个字符串,用于将这些值用于数据预处理中的归一化操作。
5.2 densenet.py
class Bottleneck(nn.Module):
def __init__(self, nChannels, growthRate):
super(Bottleneck, self).__init__()
interChannels = 4*growthRate
self.bn1 = nn.BatchNorm2d(nChannels)
self.conv1 = nn.Conv2d(nChannels, interChannels, kernel_size=1,
bias=False)
self.bn2 = nn.BatchNorm2d(interChannels)
self.conv2 = nn.Conv2d(interChannels, growthRate, kernel_size=3,
padding=1, bias=False)
def forward(self, x):
out = self.conv1(F.relu(self.bn1(x)))
out = self.conv2(F.relu(self.bn2(out)))
out = torch.cat((x, out), 1)
return out
class SingleLayer(nn.Module):
def __init__(self, nChannels, growthRate):
super(SingleLayer, self).__init__()
self.bn1 = nn.BatchNorm2d(nChannels)
self.conv1 = nn.Conv2d(nChannels, growthRate, kernel_size=3,
padding=1, bias=False)
def forward(self, x):
out = self.conv1(F.relu(self.bn1(x)))
out = torch.cat((x, out), 1)
return out
class Transition(nn.Module):
def __init__(self, nChannels, nOutChannels):
super(Transition, self).__init__()
self.bn1 = nn.BatchNorm2d(nChannels)
self.conv1 = nn.Conv2d(nChannels, nOutChannels, kernel_size=1,
bias=False)
def forward(self, x):
out = self.conv1(F.relu(self.bn1(x)))
out = F.avg_pool2d(out, 2)
return out
class DenseNet(nn.Module):
def __init__(self, growthRate, depth, reduction, nClasses, bottleneck):
super(DenseNet, self).__init__()
nDenseBlocks = (depth-4) // 3
if bottleneck:
nDenseBlocks //= 2
nChannels = 2*growthRate
self.conv1 = nn.Conv2d(3, nChannels, kernel_size=3, padding=1,
bias=False)
self.dense1 = self._make_dense(nChannels, growthRate, nDenseBlocks, bottleneck)
nChannels += nDenseBlocks*growthRate
nOutChannels = int(math.floor(nChannels*reduction))
self.trans1 = Transition(nChannels, nOutChannels)
nChannels = nOutChannels
self.dense2 = self._make_dense(nChannels, growthRate, nDenseBlocks, bottleneck)
nChannels += nDenseBlocks*growthRate
nOutChannels = int(math.floor(nChannels*reduction))
self.trans2 = Transition(nChannels, nOutChannels)
nChannels = nOutChannels
self.dense3 = self._make_dense(nChannels, growthRate, nDenseBlocks, bottleneck)
nChannels += nDenseBlocks*growthRate
self.bn1 = nn.BatchNorm2d(nChannels)
self.fc = nn.Linear(nChannels, nClasses)
for m in self.modules():
if isinstance(m, nn.Conv2d):
n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
m.weight.data.normal_(0, math.sqrt(2. / n))
elif isinstance(m, nn.BatchNorm2d):
m.weight.data.fill_(1)
m.bias.data.zero_()
elif isinstance(m, nn.Linear):
m.bias.data.zero_()
def _make_dense(self, nChannels, growthRate, nDenseBlocks, bottleneck):
layers = []
for i in range(int(nDenseBlocks)):
if bottleneck:
layers.append(Bottleneck(nChannels, growthRate))
else:
layers.append(SingleLayer(nChannels, growthRate))
nChannels += growthRate
return nn.Sequential(*layers)
def forward(self, x):
out = self.conv1(x)
out = self.trans1(self.dense1(out))
out = self.trans2(self.dense2(out))
out = self.dense3(out)
out = torch.squeeze(F.avg_pool2d(F.relu(self.bn1(out)), 8))
out = F.log_softmax(self.fc(out))
return out
这是一个名为DenseNet的深度学习模型的实现。DenseNet是一种密集连接的卷积神经网络,具有很强的特征重用能力。
该模型包含了几个不同的模块:
- Bottleneck:一个瓶颈层,用于增加通道数和特征图的大小。
- SingleLayer:一个单层,用于增加通道数。
- Transition:一个过渡层,用于减小特征图的大小。
- DenseNet:整个DenseNet模型的主体部分,包含了多个密集块和过渡层。
在DenseNet模型中,有几个重要的参数:
- growthRate:每个密集块中特征图的增长率。
- depth:DenseNet的深度,即密集块的数量。
- reduction:过渡层中特征图的缩小比例。
- nClasses:输出的类别数量。
- bottleneck:是否使用瓶颈层。
模型的前向传播过程如下:
- 输入通过一个卷积层进行特征提取。
- 特征图经过第一个密集块和过渡层。
- 特征图再经过第二个密集块和过渡层。
- 特征图再经过第三个密集块。
- 特征图通过均值池化层进行空间降维。
- 特征图通过全连接层进行分类。
- 输出通过log_softmax函数进行概率计算。
整个模型的参数初始化使用了一些常用的方法,如正态分布初始化和批归一化。
这个程序文件实现了DenseNet模型的定义和前向传播过程,可以用于图像分类等任务。
5.3 Light_Enhancement.py
class BrightnessRecognitionNetwork(nn.Module):
def __init__(self):
super(BrightnessRecognitionNetwork, self).__init__()
# Define the layers for brightness recognition network
self.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1)
self.conv2 = nn.Conv2d(64, 128, kernel_size=3, stride=1, padding=1)
self.fc1 = nn.Linear(128*64*64, 128)
self.fc2 = nn.Linear(128, 1)
def forward(self, x):
x = F.relu(self.conv1(x))
x = F.relu(self.conv2(x))
x = x.view(-1, 128*64*64)
x = F.relu(self.fc1(x))
x = self.fc2(x)
return x
class NoiseRecognitionNetwork(nn.Module):
def __init__(self):
super(NoiseRecognitionNetwork, self).__init__()
# Define the layers for noise recognition network
self.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1)
self.conv2 = nn.Conv2d(64, 128, kernel_size=3, stride=1, padding=1)
self.fc1 = nn.Linear(128*64*64, 128)
self.fc2 = nn.Linear(128, 1)
def forward(self, x):
x = F.relu(self.conv1(x))
x = F.relu(self.conv2(x))
x = x.view(-1, 128*64*64)
x = F.relu(self.fc1(x))
x = self.fc2(x)
return x
class ImageEnhancementNetwork(nn.Module):
def __init__(self):
super(ImageEnhancementNetwork, self).__init__()
# Define the layers for image enhancement network
self.conv1 = nn.Conv2d(6, 64, kernel_size=3, stride=1, padding=1)
self.conv2 = nn.Conv2d(64, 128, kernel_size=3, stride=1, padding=1)
self.fc1 = nn.Linear(128*64*64, 128)
self.fc2 = nn.Linear(128, 3)
def forward(self, x):
x = F.relu(self.conv1(x))
x = F.relu(self.conv2(x))
x = x.view(-1, 128*64*64)
x = F.relu(self.fc1(x))
x = self.fc2(x)
return x
这个程序文件名为Light_Enhancement.py,它包含了三个神经网络模型:BrightnessRecognitionNetwork、NoiseRecognitionNetwork和ImageEnhancementNetwork。这些模型分别用于亮度识别、噪声识别和图像增强。每个模型都有一些卷积层和全连接层,用于对输入图像进行处理和分类。
程序还包括了一些辅助函数。generate_low_light_dataset函数用于生成低光照数据集,train_models函数用于在数据集上训练模型,test_models函数用于在低光照图像上测试模型并生成增强后的图像。
整个程序的目标是通过训练模型来实现图像增强功能,使得输入的低光照图像变得更亮、更清晰。
5.4 make_graph.py
from graphviz import Digraph
from torch.autograd import Variable
class LRPVisualizer:
def __init__(self):
self.dot = Digraph(comment='LRP',
node_attr={'style': 'filled', 'shape': 'box'})
self.seen = set()
def add_nodes(self, var):
if var not in self.seen:
if isinstance(var, Variable):
self.dot.node(str(id(var)), str(var.size()), fillcolor='lightblue')
else:
self.dot.node(str(id(var)), type(var).__name__)
self.seen.add(var)
if hasattr(var, 'previous_functions'):
for u in var.previous_functions:
self.dot.edge(str(id(u[0])), str(id(var)))
self.add_nodes(u[0])
def save(self, fname, creator):
self.add_nodes(creator)
self.dot.save(fname)
这个程序文件名为make_graph.py,它使用了graphviz和torch.autograd库。该程序的功能是创建一个图形化的LRP(Layer-wise Relevance Propagation)图,并将其保存为一个文件。
程序首先导入了需要的库,包括graphviz和torch.autograd。然后定义了一个save函数,该函数接受两个参数:fname表示保存图形的文件名,creator表示要创建图形的对象。
在函数内部,首先创建了一个Digraph对象dot,用于表示图形。通过设置node_attr属性,指定了节点的样式和形状。
接下来,定义了一个辅助函数add_nodes,用于递归地添加节点到图形中。该函数首先检查节点是否已经存在于seen集合中,如果不存在,则将节点添加到图形中。如果节点是Variable类型的对象,则将其表示为一个带有尺寸信息的节点,并设置节点的填充颜色为’lightblue’。如果节点不是Variable类型的对象,则将其表示为其类型的名称。然后将节点添加到seen集合中,并检查节点是否有previous_functions属性。如果有,则遍历previous_functions属性中的每个元素,并将其与当前节点连接起来,并递归地调用add_nodes函数。
最后,调用add_nodes函数,将creator对象作为参数传递给它,以开始创建图形。最后,调用dot对象的save方法,将图形保存为指定的文件名。
5.5 plot.py
class PlotLossError:
def __init__(self, expDir):
self.expDir = expDir
def load_data(self):
trainP = os.path.join(self.expDir, 'train.csv')
trainData = np.loadtxt(trainP, delimiter=',').reshape(-1, 3)
testP = os.path.join(self.expDir, 'test.csv')
testData = np.loadtxt(testP, delimiter=',').reshape(-1, 3)
return trainData, testData
def rolling(self, N, i, loss, err):
i_ = i[N-1:]
K = np.full(N, 1./N)
loss_ = np.convolve(loss, K, 'valid')
err_ = np.convolve(err, K, 'valid')
return i_, loss_, err_
def plot_loss(self, trainI, trainLoss, testI, testLoss):
fig, ax = plt.subplots(1, 1, figsize=(6, 5))
plt.plot(trainI, trainLoss, label='Train')
plt.plot(testI, testLoss, label='Test')
plt.xlabel('Epoch')
plt.ylabel('Cross-Entropy Loss')
plt.legend()
ax.set_yscale('log')
loss_fname = os.path.join(self.expDir, 'loss.png')
plt.savefig(loss_fname)
print('Created {}'.format(loss_fname))
def plot_error(self, trainI, trainErr, testI, testErr):
fig, ax = plt.subplots(1, 1, figsize=(6, 5))
plt.plot(trainI, trainErr, label='Train')
plt.plot(testI, testErr, label='Test')
plt.xlabel('Epoch')
plt.ylabel('Error')
ax.set_yscale('log')
plt.legend()
err_fname = os.path.join(self.expDir, 'error.png')
plt.savefig(err_fname)
print('Created {}'.format(err_fname))
def plot_loss_error(self):
trainData, testData = self.load_data()
N = 392*2 # Rolling loss over the past epoch.
trainI, trainLoss, trainErr = np.split(trainData, [1,2], axis=1)
trainI, trainLoss, trainErr = [x.ravel() for x in (trainI, trainLoss, trainErr)]
trainI_, trainLoss_, trainErr_ = self.rolling(N, trainI, trainLoss, trainErr)
testI, testLoss, testErr = np.split(testData, [1,2], axis=1)
self.plot_loss(trainI_, trainLoss_, testI, testLoss)
self.plot_error(trainI_, trainErr_, testI, testErr)
def create_loss_error_image(self):
loss_fname = os.path.join(self.expDir, 'loss.png')
err_fname = os.path.join(self.expDir, 'error.png')
loss_err_fname = os.path.join(self.expDir, 'loss-error.png')
os.system('convert +append {} {} {}'.format(loss_fname, err_fname, loss_err_fname))
print('Created {}'.format(loss_err_fname))
这个程序文件名为plot.py,主要功能是绘制训练和测试数据的损失和错误曲线图。程序首先通过命令行参数获取实验目录expDir,然后加载训练数据train.csv和测试数据test.csv。数据分为三列,分别是训练/测试次数、损失和错误。接下来,程序使用rolling函数对训练数据进行滚动平均处理,得到滚动平均后的训练次数、损失和错误。然后,程序使用matplotlib库绘制两个子图,分别是损失曲线图和错误曲线图。最后,程序将绘制的图保存为图片文件,并输出保存的文件名。
5.6 predict.py
class StatModel(object):
#加载训练好的模型
def load(self, fn):
self.model = self.model.load(fn)
#保存训练好的模型
def save(self, fn):
self.model.save(fn)
class SVM(StatModel):
#创建SVM分类器,以及设置相关参数
def __init__(self, C = 1, gamma = 0.5):
self.model = cv2.ml.SVM_create()
self.model.setGamma(gamma)
self.model.setC(C)
self.model.set
该程序是一个车牌识别程序,用于识别车牌照片中的字符。程序主要包括以下几个部分:
-
导入相关库:导入需要使用的库,包括cv2、numpy、sys、os、json和matplotlib.pyplot。
-
定义一些常量:定义一些常量,包括训练图片的长宽、原始图片的最大宽度、车牌区域允许的最大面积和省份编码的起始值。
-
定义一些函数:定义一些辅助函数,
6.系统整体结构
整体功能和构架概述:
该项目是一个基于OpenCV的低光照车牌识别系统。它包含了多个程序文件,每个文件负责不同的功能模块。以下是每个文件的功能概述:
文件名 | 功能概述 |
---|---|
densenet.py | 实现DenseNet深度学习模型 |
Light_Enhancement.py | 包含亮度识别、噪声识别和图像增强的神经网络模型 |
make_graph.py | 创建图形化的LRP(Layer-wise Relevance Propagation)图 |
plot.py | 绘制训练和测试数据的损失和错误曲线图 |
predict.py | 车牌识别程序,用于识别车牌照片中的字符 |
pretreatment.py | 图像预处理,包括图像增强、车牌区域提取等 |
train.py | 训练模型的主程序 |
ui.py | 用户界面程序,用于与用户交互 |
attic\compare-pytorch-and-torch-grads.py | 比较PyTorch和Torch的梯度计算 |
attic\numcheck-grads.py | 数值检查梯度计算的正确性 |
以上是每个文件的功能概述,它们共同构成了基于OpenCV的低光照车牌识别系统的不同模块和功能。
7.低光照车牌图像增强
低光照数据集生成
在低光照车牌图像增强模型中我们需要获得一批低光照数据集完成模型的有监督训练。在此前很多工作中已经证实合成的数据集在图像处理模型中是有效的。当然,在进行低光照数据集的合成时,我们需要注意很多的问题,包括候选图片的选取、噪声的处理、亮度的处理等,这些都会影响最终低光照数据的生成效果。
图像生成低亮度的图像,以此构成输入数据和输出标签一一对应的关系。虽然我们的车牌数据集数量是巨大的,但是数据的质量是参差不齐的,高质量的图像经过一系列变化得到的低光照图像更加自然。本节我们从亮度、模糊程度两个角度度图像进行筛选,为下一步低光照图像的生成做准备。
(1) 亮度
为了对图像的亮度进行筛选,我们对图像进行过度分割,接着恢复过度分割结果。根据分割所得结果计算图像的HSV颜色空间中V分量的均值和方差。V分量的均值和方差大于设定的阈值时认为此部分属于高亮度区域。最终,我们选择图片中有超过85%的区域是高亮度的图片。
(2)模糊程度
模糊程度也是判断一张图片质量高低的重要标准,图片拍摄时如果聚焦不准确、车辆移动速度较大时得到的图片是模糊的。为了获得整体较清晰的图片,我们使用拉普拉斯边缘提取算法,并计算所有输出像素的方差,以方差500为阈值将图片进行筛选。
经过以上两个步骤的筛选,最终符合条件的图片有20000张。为了完成后面低光照数据增强模型的训练,我们将数据按照6:2:2的比例将数据分为训练数据、验证数据和测试数据。
低光照数据集生成
经过上节我们完成了高质量车牌数据的筛选得到了20000张图片,为了完成有监督的模型训练我们需要将所有高亮度图像一对一的生成低亮度图像。低亮度图像与高亮度图像相比有两方面差异较大,分别是亮度和噪声。以下就分别针对这两方面对图像进行变化。
(1)亮度减弱
经过多次试验,本文使用线性变换和伽马变换的结合的方法对图像的亮度进行调整,实现亮度的减弱,公式如下式(3-1)所示。
式中,α和β表示线性变换,xv表示gamma变换,这三个参数是从均匀分布中采样的: α~U(0.9,1),β U(0.5,1),yU(1.5,5)。
经过亮度减弱后的图像效果如下图所示。
由以上对比可以说明,通过线性变换和伽马变换的结合的方法可以使图像的亮度更加自然的降低。
(2)噪声增强
通常亮度越低的图像噪声越高,所以我们需要在降低图像亮度的同时增加图像的噪音水平。上步我们只是减弱了图像的亮度,该步我们对图像的噪声进行调整,使之更接近自然低亮度图像。针对噪声的特点,本文使用高斯-泊松混合噪声模型对生成的低亮度图像的噪声进行调整,如式(3-2)所示。
式中,p(x)表示添加具有方差的泊松噪声o, ﹔NG表示添加噪声o的AWGN; f(x)表示原始车牌图像;M(x)表示将RGB图像转换为 Bayer 图像的函数。噪声增强前后的对比结果如下图所示。
8.DenseNet 网络结构介绍
DenseNet卷积神经网络特点是密集连接、以feature作为突破点,在不断提高识别效果的同时更好的减少了训练参数,同时也解决了梯度消失等问题。在传统卷积神经网络中,L层神经网络会产生L个连接,但是DenseNet密集神经网络中存在L(L+1)/2个连接,简洁来说就是每一层神经网络的输入来自前面所有网络层的输出,即L;-H([Lo,L1…Li-i]),这种传递过程与ResNet有些类似,都可以重复利用每层网络特征,并且通过设计K值(dense block的层数),使得网络变得更窄,参数变得更少,这种方式可以使网络特征更有效的传递,模型更加容易训练。
Dense Block
DenseNet卷积神经网络中Dense Block模块是密集型网络,其中包括BN、Conv、ReLU、Pooling层,在模块中传递方式为将前面所有层的feature进行 concat操作然后传递到下一层,即第1层的channel数量为Ko+(1-1)K,每个Dense Block内的feature map的尺度都是相同的,并且每个Dense Block都是通过Transtion模块进行过渡连接。
增长率表示的是dense block密集层中每层的channel 数量,增长率不宜过大,本文中主要使用K=32和K=48,实验表明,较小的增长率会使特征更好的进行保留,有利于特征的传递。
Bottleneck
Dense Block中组合网络层是由BN+ReLU+3×3Conv构成,通过上面的分析可知,每传递一层,虽然只是产生增长率为K的特征图,但还是有些复杂,所以在3×3卷积步骤前增加一层1×1卷积层用来降低feature层个数,增加lxl卷积结构的Dense Block称为Bottleneck结构。下面介绍一下Bottlneck 的特点和计算方式:
- Bottleneck中每层输出的特征通道数量是相同的。
2)采用激活函数在卷积层前的结构,提高模型性能。 - 1x1卷积具有降维、固定输出通道数的作用。比如K=32,通道数为64,在进行15个 Bottleneck 后,输出的通道数为64+15×32 = 544,接下来经过第16个时,在不使用1x1卷积的情况下,输出的参数量为3×3×544×32=156672,反之参数量为1×1×544×128+3×3×128×32=106496,由此可见参数降低到原来的2/3。
Transiton layer
相邻两个Dense Block之间连接是通过Transition层来实现的,也是为了降低参数数量,其中有个设计参数为reduction,表示将输出参数缩小到之前的倍数,默认值为0.5,可以成倍数的降低参数数量。
9.车牌字符识别
基于模板匹配算法的车牌字符识别
模板匹配是较早用于车牌识别的算法,也是字符识别领域应用比较频繁的算法,此算法的应用场景是检测子付种尖牧少,烟之然门出时,首先需要将待识别一个高标准的字符样本模板库,当找们进行牛l于N以耐输出后之匹配度最高字符图像进行标准化,然后将分割的字符与模板进行匹配,输出与之匹配度最高的字符。
此算法主要应用于车牌这种字符类别较少的场景中,计算量很小,运行速度页系~快。由于必须使用固定的比对模板,因此算法抗干扰能力很差,面对字符字符扭曲、字符污损等都很难达到好的效果,仔在一疋以网限量的块大。出对的行优化,可以采取对模板字符的增广措施,但是随着模板数据量的增大,比对的次数就会变多,就背驰了传统方法速度快的初衷。
基于机器学习的车牌字符识别
机器学习算法已广泛应用于字符识别,其中主流算法主要有支持向量机(SVM),贝叶斯分类器、神经网络等。其中HOG+SVM是分类识别中比较具有代表性的算法,HOG算法的作用是用来进行特征提取,并使用SVM构建分类器,二者结合实现车牌字符识别。HOG实现步骤如图所示。
(1)标准化颜色空间与大小。调节区域图像的对比度,降低光照不均匀造成的影响,并且还可以有效的抑制噪声的干扰。
(2)梯度计算。精准的获取轮廓信息,进一步弱化光照的影响。
(3)对HOG特征向量进行归一化处理。
(4)生成图像对应HOG特征。
如图所示,首先将样本数据通过上面的操作得到一系列HOG特征数据,接下来训练SVM分类器,可以根据车牌字符中汉字和字母的区别训练不同的分类器。这种字符识别方式在传统方式中是一种可行性很强的算法,但是由于拍摄环境复杂,有些汉字特征比较复杂,还有很多不确定因素,因此字符识别的成功率也会大打折扣,普适性效果不佳,逐步被卷积神经网络所替代。
车牌矫正处理与 DenseNet 网络结构
受自然环境的影响,车牌图案呈现多种多样形式,影响主要来自光照、污损、车牌倾斜等情况,在这种情况下传统车牌识别难以发挥作用,本文通过分析车牌特征与实际应用场景,选取基于DenseNet卷积神经网络结构来进行端到端的车牌字符识别。在进行车牌字符识别之前需要对车牌进行矫正预处理,通过一系列水平矫正和竖直矫正,使得车牌字符与车牌框之间的夹角在可控范围内并作为字符识别的输入图像。在 DenseNet车牌字符识别网络中,使用密集连接层进行特征提取并完美传递,还可以不断减少网络参数,最终通过全连接层进行字符识别分类。通过实验表明使用卷积神经网络虽然在速度上难以和传统方法进行匹敌,但是在精确度方面有着很大的优势,并且具有可模块化、可移植化、兼顾多场景,具有很高的实用性。
车牌图像通过车牌定位步骤后会输出每张车牌所在矩形的位置坐标,但是由于拍摄场景及其角度的不同,会导致输出的车牌倾斜化,如果直接输入到卷积神经网络进行端到端的字符识别,会大大降低字符识别的准确度。因此首先需要对车牌进行倾斜校正,保证输入图像保持水平竖直的特性。由于实验中进行车牌定位的方法有两类,一种是传统图像处理方法,另一种是基于深度学习的目标定位,传统方法可以定位车牌图像的轮廓,但无法给出车牌四个点具体的坐标,对此使用的车牌校正方法主要有:Hough变换、Radon变换、旋转投影法。基于深度学习的目标定位可以输出车牌的坐标,因此可以通过矩形坐标和车牌坐标之间的关系进行车牌校正,实验中采用透视变换进行车牌图像校正。
(1)车牌矫正之Hough变换
Hough变换可以检测每个间断点之间形状,主要检测椭圆、弧、圆、直线等多种几何形状,在本文车牌校正中采用最常见的直线检测,通过直线与车牌对应矩形的夹角进行图像移动,最终使得车牌水平。Hough直线检测利用点与线之间的对偶原理,将空间直线与参数中的点相互对应,以此为基础得出以下两条结论作为直线检测的支撑点。
1)图像中每条直线对应的参数空间都有单一的一个点来进行表示
2)图像中每条直线中任意部分线段都对应参数空间中的一个点。
如图所示,Hough 变换图,直线的表示方程可以由K(斜率)和截距(b)表示,此表示方式称为斜截式,具体表示方程为: y = kx +b。直线中每个点可以由(x, y)表示,并且通过(Xi,yi)的直线有无数条。但是如果我们使用参数空间可以用(b,m)这种斜率和截距的组合来表示一条直线,再通过进一步变换,可以使用(y,e)极坐标对参数空间进行表示,对车牌图像进行车牌边缘检测,将非零像素点对应到极坐标中的每一条直线,然后利用同一条直线中的每一个点在极坐标下会生成多条直线并相交于一点,最后确定车牌中直线。
如图所示,使用Hough 变换对车牌进行倾斜矫正,首先对倾斜图像进行灰度化,然后进行滤波、边缘检测,最关键的步骤为Hough 变换直线检测并求倾斜角度,最终根据角度进行车牌矫正。
车牌矫正之Radon变换
经过定位后的车牌图像经过二值化和边缘检测,使用Radon变换算法对图像中的直线进行检测,然后计算出图像的倾斜角度,最终经过水平旋转和竖直旋转完成车牌的水平矫正。Radon(拉东)算法的核心为“线积分”,即设定一个方向并计算这个方向的“列向量和”,这种方法具有特性为,当图像中存在直线段时,沿着一个方向进行积分得到积分图不会发生变化,但是与之垂直方向就会发生“突变”,用来计算图像的倾斜角度,完成车牌的倾斜矫正,Radon变换函数为:
式中8(x)=十二0,D表示为图像的整个平面,f(x,y)表示为图像某个点像素灰度值,狄拉克函数8代表特征函数,p为原点到直线的距离,0为直线与原点之间的夹角。通过使用特征函数可以将积分沿着p =xcosO+ysin0路线进行操作。
Radon旋转角度参数计算方式如图所示:
旋转投影法是通过不断增加角度对图像进行仿射变换,对二值化的图像沿X轴的垂直方向进行投影,当变换到一定角度时,会出现为0 的列数最多,以此角度为旋转角度进行车牌矫正。旋转投影车牌矫正流程图如所示:
10.系统整合
11.参考文献
[1]饶文军,谷玉海,朱腾腾,等.基于深度学习的车牌智能识别方法[J].重庆理工大学学报(自然科学版).2021,(3).DOI:10.3969/j.issn.1674-8425(z).2021.03.016 .
[2]刘靖钰,刘德儿,杨鹏,等.基于CNN网络的带遮挡车牌识别[J].测控技术.2021,(2).DOI:10.19708/j.ckjs.2021.02.010 .
[3]咸志杰,储若男,方芹芹,等.基于混合BP神经网络的车牌识别研究[J].信息技术与信息化.2021,(2).DOI:10.3969/j.issn.1672-9528.2021.02.029 .
[4]漆世钱.基于轮廓识别和BGR颜色空间的车牌定位[J].计算机技术与发展.2020,(12).DOI:10.3969/j.issn.1673-629X.2020.12.031 .
[5]程聃,陆华才,高文根.基于改进Canny算子边缘检测和数学形态学的车牌定位算法[J].黑龙江工业学院学报(综合版).2019,(12).
[6]潘翔,王恒.基于深度学习的车牌相似字符识别[J].计算机科学.2017,(z1).
[7]孙红,郭凯.融合字符纹理特征与RGB颜色特征的车牌定位[J].光电工程.2015,(6).DOI:10.3969/j.issn.1003-501X.2015.06.003 .
[8]孙金岭,庞娟,张泽龙.基于颜色特征和改进 Canny 算子的车牌图像定位[J].吉林大学学报(理学版).2015,(4).DOI:10.13413/j.cnki.jdxblxb.2015.04.19 .
[9]何兆成,佘锡伟,余文进,等.字符多特征提取方法及其在车牌识别中的应用[J].计算机工程与应用.2011,(23).DOI:10.3778/j.issn.1002-8331.2011.23.064 .
[10]张晓娜,何仁,陈士安,等.基于主动学习AdaBoost算法与颜色特征的车牌定位[J].交通运输工程学报 .2013,(1).121-126.