1.研究背景与意义
项目参考AAAI Association for the Advancement of Artificial Intelligence
研究背景与意义
随着城市化进程的加快和汽车拥有量的不断增加,停车位的需求量也在不断增加。然而,停车位的有限性和分布不均匀性给城市居民的停车带来了很大的困扰。为了解决这一问题,许多城市已经开始使用停车位实时检测系统,以提供准确的停车位信息和推荐合适的停车位给驾驶员。
然而,传统的停车位实时检测系统存在一些问题。首先,传统的检测算法通常需要大量的计算资源和时间,导致实时性不足。其次,传统的检测算法对于遮挡、光照变化等复杂场景的适应性较差,导致检测结果的准确性不高。此外,传统的检测算法对于小尺寸的停车位检测效果较差,无法满足实际需求。
为了解决这些问题,本研究提出了一种基于RegNet-Backbone改进YOLOv5的停车位实时检测推荐系统。RegNet-Backbone是一种轻量级的神经网络模型,具有较高的计算效率和较低的模型大小。YOLOv5是一种流行的目标检测算法,具有较高的检测速度和较好的检测精度。通过将RegNet-Backbone与YOLOv5相结合,我们可以在保证实时性的同时提高检测的准确性和适应性。
本研究的主要目标是设计和实现一个高效准确的停车位实时检测推荐系统,以提供准确的停车位信息和推荐合适的停车位给驾驶员。具体来说,本研究的主要研究内容包括以下几个方面:
首先,我们将研究RegNet-Backbone在停车位检测任务中的适应性和性能。通过对RegNet-Backbone进行优化和改进,我们可以提高其在停车位检测任务中的准确性和实时性。
其次,我们将研究如何将RegNet-Backbone与YOLOv5相结合,以进一步提高停车位检测的准确性和实时性。通过将RegNet-Backbone作为YOLOv5的骨干网络,我们可以充分利用RegNet-Backbone的计算效率和模型大小优势,同时保持YOLOv5的检测速度和精度。
最后,我们将设计和实现一个完整的停车位实时检测推荐系统,并进行实验评估。通过大量的实验和对比分析,我们可以验证所提出的系统在准确性、实时性和适应性方面的优势,并为实际应用提供可行的解决方案。
本研究的意义主要体现在以下几个方面:
首先,本研究提出的基于RegNet-Backbone改进YOLOv5的停车位实时检测推荐系统可以提供准确的停车位信息和推荐合适的停车位给驾驶员,从而提高停车位的利用率和停车效率,减少城市交通拥堵和环境污染。
其次,本研究所提出的RegNet-Backbone在停车位检测任务中的适应性和性能研究可以为其他相关领域的研究提供借鉴和参考,如交通管理、智能城市等。
最后,本研究所设计和实现的停车位实时检测推荐系统可以为停车位管理部门和驾驶员提供实用的工具和解决方案,为城市停车管理和交通规划提供科学依据和决策支持。
综上所述,本研究的背景和意义在于通过基于RegNet-Backbone改进YOLOv5的停车位实时检测推荐系统,提高停车位检测的准确性、实时性和适应性,从而提高停车位的利用率和停车效率,减少城市交通拥堵和环境污染,为城市停车管理和交通规划提供科学依据和决策支持。
2.图片演示
3.视频演示
基于RegNet-Backbone改进YOLOv5的停车位实时检测推荐系统_哔哩哔哩_bilibili
4.数据集的采集&标注和整理
图片的收集
首先,我们需要收集所需的图片。这可以通过不同的方式来实现,例如使用现有的公开数据集TrafficDatasets。
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 anynet.py
class AnyNetX(nn.Module):
def __init__(self, ls_num_blocks, ls_block_width, ls_bottleneck_ratio, ls_group_width, stride, se_ratio):
super(AnyNetX, self).__init__()
# For each stage, at each layer, number of channels (block width / bottleneck ratio) must be divisible by group width
for block_width, bottleneck_ratio, group_width in zip(ls_block_width, ls_bottleneck_ratio, ls_group_width):
assert block_width % (bottleneck_ratio * group_width) == 0
self.net = nn.Sequential()
prev_block_width = 32
self.net.add_module("stem", Stem(prev_block_width))
for i, (num_blocks, block_width, bottleneck_ratio, group_width) in enumerate(zip(ls_num_blocks, ls_block_width,
ls_bottleneck_ratio,
ls_group_width)):
self.net.add_module("stage_{}".format(i),
Stage(num_blocks, prev_block_width, block_width, bottleneck_ratio, group_width, stride, se_ratio))
prev_block_width = block_width
self.net.add_module("head", Head(ls_block_width[-1], NUM_CLASSES))
self.initialize_weight()
def initialize_weight(self):
for m in self.modules():
if isinstance(m, nn.Conv2d):
fan_out = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
m.weight.data.normal_(mean=0.0, std=sqrt(2.0 / fan_out))
elif isinstance(m, nn.BatchNorm2d):
m.weight.data.fill_(1.0)
m.bias.data.zero_()
elif isinstance(m, nn.Linear):
m.weight.data.normal_(mean=0.0, std=0.01)
m.bias.data.zero_()
def forward(self, x):
x = self.net(x)
return x
该工程中的程序文件是anynet.py
,该文件实现了一个名为AnyNet的神经网络模型。
AnyNetX类继承自nn.Module类,它是AnyNet模型的基础版本。构造函数__init__
接受一系列参数,包括ls_num_blocks(每个阶段的块数)、ls_block_width(每个块的宽度)、ls_bottleneck_ratio(瓶颈比例)、ls_group_width(组宽度)、stride(步幅)和se_ratio(SE模块的比例)。在构造函数中,首先对ls_block_width、ls_bottleneck_ratio和ls_group_width进行了一些断言,确保它们满足一定的条件。然后通过调用Stem、Stage和Head模块的构造函数,构建了整个网络结构。最后调用initialize_weight函数,对网络的权重进行初始化。forward函数定义了前向传播的过程。
AnyNetXb、AnyNetXc、AnyNetXd和AnyNetXe是AnyNetX的派生类,它们在AnyNetX的基础上进行了一些修改和扩展。AnyNetXb要求ls_bottleneck_ratio的值必须相同;AnyNetXc要求ls_group_width的值必须相同;AnyNetXd要求ls_block_width的值必须递增;AnyNetXe要求ls_num_blocks的值必须递增。
总体来说,该程序文件实现了一个可配置的神经网络模型AnyNet,通过调整不同的参数可以构建不同版本的AnyNet模型。
5.2 dataset.py
class Imagenet(Dataset):
def __init__(self, root_dir, mode="train"):
self.data_dir = os.path.join(root_dir, mode)
self.mode = mode
self.set_name = set
self.load_categories()
def load_categories(self):
self.raw_category_ids = sorted(file_ for file_ in os.listdir(self.data_dir) if re.match(r"^n[0-9]+$", file_))
self.fine_category_ids = {value: key for key, value in enumerate(self.raw_category_ids)}
self.images = []
for raw_id in self.raw_category_ids:
fine_id = self.fine_category_ids[raw_id]
dir = os.path.join(self.data_dir, raw_id)
self.images.extend([{"image": os.path.join(dir, image), "category": fine_id} for image in os.listdir(dir)])
def transform(self, img):
if self.mode == "train":
img = random_sized_crop(
im=img, size=TRAIN_IMAGE_SIZE, area_frac=0.08
)
img = horizontal_flip(im=img, p=0.5, order="HWC")
else:
img = scale(TEST_IMAGE_SIZE, img)
img = center_crop(TRAIN_IMAGE_SIZE, img)
img = img.transpose([2, 0, 1]) / 255
if self.mode == "train":
img = lighting(img, 0.1, np.array(EIGENVALUES), np.array(EIGENVECTORS))
img = color_norm(img, [0.406, 0.456, 0.485], [0.225, 0.224, 0.229])
return img
def __len__(self):
return len(self.images)
def __getitem__(self, index):
img = cv2.imread(self.images[index]["image"])
img = img.astype(np.float32, copy=False)
img = self.transform(img)
category = self.images[index]["category"]
return img, category
这个程序文件是一个名为dataset.py的数据集类的实现。它实现了一个名为Imagenet的数据集类,用于加载和处理图像数据集。
该类的构造函数接受一个root_dir参数,用于指定数据集的根目录,以及一个可选的mode参数,用于指定数据集的模式(默认为"train")。
在初始化过程中,load_categories方法被调用,用于加载数据集的类别信息。它通过遍历数据目录中的文件,筛选出以"n"开头并且后面跟着数字的文件名,将其作为原始类别ID。然后,将原始类别ID映射为细粒度类别ID,并将每个图像的路径和类别ID存储在self.images列表中。
transform方法用于对图像进行预处理。如果模式为"train",则采用随机裁剪和水平翻转的方式增强图像。如果模式为其他值,则采用缩放和中心裁剪的方式处理图像。然后,将图像转换为通道优先的形式,并进行归一化和颜色标准化处理。
__len__方法返回数据集中图像的数量。
__getitem__方法根据给定的索引返回对应的图像和类别。
总体来说,这个程序文件实现了一个用于加载和处理Imagenet数据集的数据集类。它提供了对图像的预处理和数据加载的功能。
5.3 model.py
封装为类后的代码如下:
class ModelProfiler:
def __init__(self, model_name, input_shape):
self.model_name = model_name
self.input_shape = input_shape
self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
self.model = None
def load_model(self):
self.model = timm.create_model(self.model_name, pretrained=False, features_only=True)
self.model.to(self.device)
self.model.eval()
def print_model_info(self):
print(self.model.feature_info.channels())
for feature in self.model(self.dummy_input):
print(feature.size())
def profile_model(self):
flops, params = profile(self.model.to(self.device), (self.dummy_input,), verbose=False)
flops, params = clever_format([flops * 2, params], "%.3f")
print('Total FLOPS: %s' % (flops))
print('Total params: %s' % (params))
def run(self):
self.load_model()
self.print_model_info()
self.profile_model()
if __name__ == "__main__":
model_name = 'regnetv_040'
input_shape = (1, 3, 640, 640)
profiler = ModelProfiler(model_name, input_shape)
profiler.run()
使用时,可以通过创建ModelProfiler
对象并调用run
方法来运行代码。
这个程序文件名为model.py,主要功能是使用torch和timm库来计算一个模型的FLOPS和参数数量。
首先,程序会列出所有可用的模型名称。
然后,程序会检查是否有可用的GPU,如果有则使用GPU,否则使用CPU。
接下来,程序会创建一个大小为(1, 3, 640, 640)的随机输入张量dummy_input,并将其移动到设备上。
然后,程序会创建一个名为’regnetv_040’的模型,该模型不使用预训练权重,并且只返回特征而不进行分类。然后将模型移动到设备上,并设置为评估模式。
接下来,程序会打印模型的通道信息和每个特征的大小。
然后,程序会使用thop库的profile函数来计算模型的FLOPS和参数数量,并使用clever_format函数将其格式化为字符串。
最后,程序会打印总的FLOPS和参数数量。
5.4 modules.py
class Head(nn.Module):
def __init__(self, num_channels, num_classes):
super(Head, self).__init__()
self.pool = nn.AdaptiveAvgPool2d(output_size=1)
self.fc = nn.Linear(num_channels, num_classes)
def forward(self, x):
x = self.pool(x)
x = x.view(x.size(0), -1)
x = self.fc(x)
return x
class Stem(nn.Module):
def __init__(self, out_channels):
super(Stem, self).__init__()
self.conv = nn.Conv2d(3, out_channels, kernel_size=3, stride=2, padding=1, bias=False)
self.bn = nn.BatchNorm2d(out_channels)
self.rl = nn.ReLU()
def forward(self, x):
x = self.conv(x)
x = self.bn(x)
x = self.rl(x)
return x
class XBlock(nn.Module):
def __init__(self, in_channels, out_channels, bottleneck_ratio, group_width, stride, se_ratio=None):
super(XBlock, self).__init__()
inter_channels = out_channels // bottleneck_ratio
groups = inter_channels // group_width
self.conv_block_1 = nn.Sequential(
nn.Conv2d(in_channels, inter_channels, kernel_size=1, bias=False),
nn.BatchNorm2d(inter_channels),
nn.ReLU()
)
self.conv_block_2 = nn.Sequential(
nn.Conv2d(inter_channels, inter_channels, kernel_size=3, stride=stride, groups=groups, padding=1, bias=False),
nn.BatchNorm2d(inter_channels),
nn.ReLU()
)
if se_ratio is not None:
se_channels = in_channels // se_ratio
self.se = nn.Sequential(
nn.AdaptiveAvgPool2d(output_size=1),
nn.Conv2d(inter_channels, se_channels, kernel_size=1, bias=True),
nn.ReLU(),
nn.Conv2d(se_channels, inter_channels, kernel_size=1, bias=True),
nn.Sigmoid(),
)
else:
self.se = None
self.conv_block_3 = nn.Sequential(
nn.Conv2d(inter_channels, out_channels, kernel_size=1, bias=False),
nn.BatchNorm2d(out_channels)
)
if stride != 1 or in_channels != out_channels:
self.shortcut = nn.Sequential(
nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=stride, bias=False),
nn.BatchNorm2d(out_channels)
)
else:
self.shortcut = None
self.rl = nn.ReLU()
def forward(self, x):
x1 = self.conv_block_1(x)
x1 = self.conv_block_2(x1)
if self.se is not None:
x1 = x1 * self.se(x1)
x1 = self.conv_block_3(x1)
if self.shortcut is not None:
x2 = self.shortcut(x)
else:
x2 = x
x = self.rl(x1 + x2)
return x
class Stage(nn.Module):
def __init__(self, num_blocks, in_channels, out_channels, bottleneck_ratio, group_width, stride, se_ratio):
super(Stage, self).__init__()
self.blocks = nn.Sequential()
self.blocks.add_module("block_0", XBlock(in_channels, out_channels, bottleneck_ratio, group_width, stride, se_ratio))
for i in range(1, num_blocks):
self.blocks.add_module("block_{}".format(i),
XBlock(out_channels, out_channels, bottleneck_ratio, group_width, 1, se_ratio))
def forward(self, x):
x = self.blocks(x)
return x
这个程序文件名为modules.py,它实现了一个网络设计空间的范例,该范例是根据Facebook AI Research (FAIR)的论文《Designing Network Design Spaces》实现的。
该文件定义了几个类:
- Head类:实现了网络的头部部分,包括自适应平均池化层和全连接层。
- Stem类:实现了网络的干部部分,包括一个卷积层、批归一化层和ReLU激活函数。
- XBlock类:实现了网络的X块部分,包括一系列卷积层、批归一化层和ReLU激活函数。其中还包括一个可选的SE模块,用于引入注意力机制。如果需要下采样或通道数变化,还包括一个shortcut分支。
- Stage类:实现了网络的阶段部分,包括多个X块组成的序列。
每个类都有一个forward方法,用于定义前向传播的计算过程。
5.5 regnet.py
class RegNetX(AnyNetXe):
def __init__(self, initial_width, slope, quantized_param, network_depth, bottleneck_ratio, group_width, stride, se_ratio=None):
parameterized_width = initial_width + slope * np.arange(network_depth)
parameterized_block = np.log(parameterized_width / initial_width) / np.log(quantized_param)
parameterized_block = np.round(parameterized_block)
quantized_width = initial_width * np.power(quantized_param, parameterized_block)
quantized_width = 8 * np.round(quantized_width / 8)
ls_block_width, ls_num_blocks = np.unique(quantized_width.astype(np.int), return_counts=True)
ls_group_width = np.array([min(group_width, block_width // bottleneck_ratio) for block_width in ls_block_width])
ls_block_width = np.round(ls_block_width // bottleneck_ratio / group_width) * group_width
ls_group_width = ls_group_width.astype(np.int) * bottleneck_ratio
ls_bottleneck_ratio = [bottleneck_ratio for _ in range(len(ls_block_width))]
super(RegNetX, self).__init__(ls_num_blocks, ls_block_width.astype(np.int).tolist(), ls_bottleneck_ratio, ls_group_width.tolist(), stride, se_ratio)
class RegNetY(RegNetX):
def __init__(self, initial_width, slope, quantized_param, network_depth, bottleneck_ratio, group_width, stride, se_ratio):
super(RegNetY, self).__init__(initial_width, slope, quantized_param, network_depth, bottleneck_ratio, group_width, stride, se_ratio)
这个程序文件名为regnet.py,它实现了一个名为RegNetX和RegNetY的网络模型。
RegNetX类继承自AnyNetXe类,它接受一些初始参数,如初始宽度、斜率、量化参数、网络深度、瓶颈比例、组宽度和步长等。它根据这些参数计算出网络的各个阶段的块宽度和块数量,并进行一些调整以确保兼容性。最后,它调用父类AnyNetXe的构造函数来创建网络模型。
RegNetY类继承自RegNetX类,它在RegNetX的基础上添加了SE模块。它接受与RegNetX相同的参数,并在调用父类构造函数时传递给它。
这个程序文件实现了RegNetX和RegNetY网络模型,可以用于网络设计和训练。
5.6 transforms.py
class ImagePreprocessing:
def __init__(self, mean, std, pad_size=0, p=0.5, order="CHW", area_frac=0.08, max_iter=10, alpha_std=0):
self.mean = mean
self.std = std
self.pad_size = pad_size
self.p = p
self.order = order
self.area_frac = area_frac
self.max_iter = max_iter
self.alpha_std = alpha_std
self.eig_val = np.array([0.2175, 0.0188, 0.0045])
self.eig_vec = np.array(
[
[-0.5675, 0.7192, 0.4009],
[-0.5808, -0.0045, -0.8140],
[-0.5836, -0.6948, 0.4203],
]
)
def color_norm(self, im):
"""Performs per-channel normalization (CHW format)."""
for i in range(im.shape[0]):
im[i] = im[i] - self.mean[i]
im[i] = im[i] / self.std[i]
return im
def zero_pad(self, im):
"""Performs zero padding (CHW format)."""
pad_width = ((0, 0), (self.pad_size, self.pad_size), (self.pad_size, self.pad_size))
return np.pad(im, pad_width, mode="constant")
def horizontal_flip(self, im):
"""Performs horizontal flip (CHW or HWC format)."""
assert self.order in ["CHW", "HWC"]
if np.random.uniform() < self.p:
if self.order == "CHW":
im = im[:, :, ::-1]
else:
im = im[:, ::-1, :]
return im
def random_crop(self, im, size):
"""Performs random crop (CHW format)."""
if self.pad_size > 0:
im = self.zero_pad(im=im)
h, w = im.shape[1:]
y = np.random.randint(0, h - size)
x = np.random.randint(0, w - size)
im_crop = im[:, y : (y + size), x : (x + size)]
assert im_crop.shape[1:] == (size, size)
return im_crop
def scale(self, im, size):
"""Performs scaling (HWC format)."""
h, w = im.shape[:2]
if (w <= h and w == size) or (h <= w and h == size):
return im
h_new, w_new = size, size
if w < h:
h_new = int(math.floor((float(h) / w) * size))
else:
w_new = int(math.floor((float(w) / h) * size))
im = cv2.resize(im, (w_new, h_new), interpolation=cv2.INTER_LINEAR)
return im.astype(np.float32)
def center_crop(self, im, size):
"""Performs center cropping (HWC format)."""
h, w = im.shape[:2]
y = int(math.ceil((h - size) / 2))
x = int(math.ceil((w - size) / 2))
im_crop = im[y : (y + size), x : (x + size), :]
assert im_crop.shape[:2] == (size, size)
return im_crop
def random_sized_crop(self, im, size):
"""Performs Inception-style cropping (HWC format)."""
h, w = im.shape[:2]
area = h * w
for _ in range(self.max_iter):
target_area = np.random.uniform(self.area_frac, 1.0) * area
aspect_ratio = np.random.uniform(3.0 / 4.0, 4.0 / 3.0)
w_crop = int(round(math.sqrt(float(target_area) * aspect_ratio)))
h_crop = int(round(math.sqrt(float(target_area) / aspect_ratio)))
if np.random.uniform() < 0.5:
w_crop, h_crop = h_crop, w_crop
if h_crop <= h and w_crop <= w:
y = 0 if h_crop == h else np.random.randint(0, h - h_crop)
x = 0 if w_crop == w else np.random.randint(0, w - w_crop)
im_crop = im[y : (y + h_crop), x : (x + w_crop), :]
assert im_crop.shape[:2] == (h_crop, w_crop)
im_crop = cv2.resize(im_crop, (size, size), interpolation=cv2.INTER_LINEAR)
return im_crop.astype(np.float32)
return self.center_crop(self.scale(im, size), size)
def lighting(self, im):
"""Performs AlexNet-style PCA jitter (CHW format)."""
if self.alpha_std == 0:
return im
alpha = np.random.normal(0, self.alpha_std, size=(1, 3))
rgb = np.sum(
self.eig_vec * np.repeat(alpha, 3, axis=0) * np.repeat(self.eig_val, 3, axis=0), axis=1
)
for i in range(im.shape[0]):
im[i] = im[i] + rgb[2 - i]
return im
这个程序文件名为transforms.py,包含了一些图像处理的函数。
函数列表:
- color_norm(im, mean, std):对图像进行通道归一化,输入参数为图像(CHW格式)、均值和标准差。
- zero_pad(im, pad_size):对图像进行零填充,输入参数为图像(CHW格式)和填充大小。
- horizontal_flip(im, p, order):对图像进行水平翻转,输入参数为图像(CHW或HWC格式)、翻转概率和图像通道顺序。
- random_crop(im, size, pad_size):对图像进行随机裁剪,输入参数为图像(CHW格式)、裁剪大小和填充大小。
- scale(size, im):对图像进行缩放,输入参数为缩放大小和图像(HWC格式)。
- center_crop(size, im):对图像进行中心裁剪,输入参数为裁剪大小和图像(HWC格式)。
- random_sized_crop(im, size, area_frac, max_iter):进行Inception风格的随机裁剪,输入参数为图像(HWC格式)、裁剪大小、面积比例和最大迭代次数。
- lighting(im, alpha_std, eig_val, eig_vec):进行AlexNet风格的PCA扰动,输入参数为图像(CHW格式)、标准差和特征值和特征向量。
这些函数用于对图像进行预处理,包括归一化、填充、翻转、裁剪、缩放和颜色扰动等操作。
6.系统整体结构
整体功能和构架概述:
该项目是一个基于RegNet-Backbone改进YOLOv5的停车位实时检测推荐系统。它包含了多个程序文件,用于实现不同的功能模块。其中,anynet.py实现了一个可配置的神经网络模型AnyNet;dataset.py实现了一个用于加载和处理Imagenet数据集的数据集类;model.py用于计算模型的FLOPS和参数数量;modules.py定义了网络设计空间的范例;regnet.py实现了RegNetX和RegNetY网络模型;train.py用于训练模型;transforms.py包含了一些图像处理的函数;ui.py实现了用户界面;yolo.py实现了YOLOv5模型;models目录下的文件实现了不同的模型结构;utils目录下的文件实现了各种辅助功能,如数据集处理、日志记录、图像处理等。
下表整理了每个文件的功能:
文件路径 | 功能 |
---|---|
anynet.py | 实现可配置的神经网络模型AnyNet |
dataset.py | 加载和处理Imagenet数据集的数据集类 |
model.py | 计算模型的FLOPS和参数数量 |
modules.py | 定义网络设计空间的范例 |
regnet.py | 实现RegNetX和RegNetY网络模型 |
train.py | 训练模型 |
transforms.py | 图像处理函数 |
ui.py | 用户界面 |
yolo.py | 实现YOLOv5模型 |
models/common.py | 实现一些通用的模型组件 |
models/experimental.py | 实现一些实验性的模型组件 |
models/tf.py | 实现TensorFlow模型组件 |
models/yolo.py | 实现YOLO模型组件 |
models/init.py | 模型组件的初始化文件 |
utils/activations.py | 激活函数 |
utils/augmentations.py | 数据增强函数 |
utils/autoanchor.py | 自动锚框生成函数 |
utils/autobatch.py | 自动批处理函数 |
utils/callbacks.py | 回调函数 |
utils/datasets.py | 数据集处理函数 |
utils/downloads.py | 下载函数 |
utils/general.py | 通用函数 |
utils/loss.py | 损失函数 |
utils/metrics.py | 评估指标函数 |
utils/plots.py | 绘图函数 |
utils/torch_utils.py | PyTorch工具函数 |
utils/init.py | 辅助函数的初始化文件 |
utils/aws/resume.py | AWS恢复函数 |
utils/aws/init.py | AWS辅助函数的初始化文件 |
utils/flask_rest_api/example_request.py | Flask REST API示例请求 |
utils/flask_rest_api/restapi.py | Flask REST API实现 |
utils/loggers/init.py | 日志记录器的初始化文件 |
utils/loggers/wandb/log_dataset.py | WandB日志记录器的数据集日志 |
utils/loggers/wandb/sweep.py | WandB日志记录器的超参数搜索 |
utils/loggers/wandb/wandb_utils.py | WandB日志记录器的工具函数 |
utils/loggers/wandb/init.py | WandB日志记录器的初始化文件 |
7.监控视频停车位数目预估
车位状态检测
在停车场监控视频中,首先要找到的是一个运动物体,当它移到了预定的位置不动的时候,就会对当前帧展开截取并进行存储。之后,利用车辆识别算法,就可以对该运动物体是否是一辆车辆进行判定,如果它是一辆车辆,就需要对该车辆是否停靠在车位中,以及停靠在哪个车位中。大多数的探测盒都可以很好的探测到整个交通工具,也就是探测盒可以将整个交通工具都囊括进去。因此,只要通过对检测框与车位的坐标和区域关系的判断,就能确定车辆是否停在车位中,停在哪个车位中。
车位使用状态判别算法
目前,停车场在用状态判定中最常见的方法是:采用 Canny算子进行边界判定,以及采用间接蒙特卡罗算法构建停车场在用状态判定中的概率鉴别模型。Canny算符的探测受外界条件影响大,精度不高。基于蒙特卡罗方法构建的车位概率鉴别模型探测方法,需对车位表面的像素点进行仿真,并根据像素点的重叠程度来判定车位是否处于正常使用状态,这使得该方法的运算量很大,不适用于停车场车位状况发生动态改变的情况。因此,本论文根据车位座标与检测框架座标之间的位置关系,给出了一种判定车位是否被占用的算法。
车位状态检测测试
使用OpenCV中提供的一个可以用来读取监控视频文件的接口 VideoCapture来获取停车场监控视频进行处理。该实验系统通过对停车场监控视频的视频帧进行处理判断停车场的车位使用状态,选择停车场进行车位状态检测试验测试本文算法的泛化性以及准确率。试验场景:某小区露天停车场,监测12个车位,监测时段为9:00-10: 00和15: 00-16: 00两个时段。
8.RegNet骨干网络简介
RegNet结构框架
首先看下下面这幅图,这幅图是原论文中给出的General Network structure,Regnet的框架结构也是一样的。
(a)图中展示了网络主要由三部分组成,stem、body和head。
其中stem就是一个普通的卷积层(默认包含bn以及relu),卷积核大小为3x3,步距为2,卷积核个数为32.
其中body就是由4个stage堆叠组成,如图(b)所示。每经过一个stage都会将输入特征矩阵的height和width缩减为原来的一半。而每个stage又是由一系列block堆叠组成,每个stage的第一个block中存在步距为2的组卷积(主分支上)和普通卷积(捷径分支上),剩下的block中的卷积步距都是1,和ResNet类似。
其中head就是分类网络中常见的分类器,由一个全局平均池化层和全连接层构成。
RegNet block详解
接下来再看下论文中关于block的结构图,(a)图为步距stride=1的情况,(b)图是步距stride=2的情况。
通过上图可知,文中的block和ResNext网络中的block基本一致。主分支都是一个1x1的卷积(包括bn和relu)、一个3x3的group卷积(包括bn和relu)、再接一个1x1的卷积(包括bn)。shortcut捷径分支上当stride=1时不做任何处理,当stride=2时通过一个1x1的卷积(包括bn)进行下采样。图中的r代表分辨率简单理解为特征矩阵的高、宽,当步距s等于1时,输入输出的r保持不变,当s等于2时,输出的r为输入的一半。w代表特征矩阵的channel
而在论文章节四中,作者给出了一个结论:
We also observe that the best models use a bottleneck ratio b of 1.0 (top-middle), which effectively removes the bottleneck (commonly used in practice).
就是说当b取1时效果最好(感觉和ShuffleNetV2中G1准则相似)。下面这副图是我重绘的,比原图要更清楚点。
这里还要注意一点,论文中有RegNetX和RegNetY,两者的区别仅在于RegNetY在block中的Group Conv后接了个SE(Squeeze-and-Excitation)模块。 自从SENet的提出,近些年的网络基本都会使用SE模块。在RegNet中的SE模块与EfficientNet中的SE模块类似。如下图所示,SE模块一般是由一个全局平均池化层和两个全连接层组成。在RegNet中,全连接层1(FC1)的节点个数是等于输入该block的特征矩阵channel的四分之一(不是Group Conv输出特征矩阵channal的四分之一),并且激活函数是ReLU。全连接层2(FC2)的节点个数是等于Group Conv输出特征矩阵的channal,并且激活函数是Sigmoid。
9.训练结果可视化分析
评价指标
epoch : 训练的迭代次数。
train/box_loss, train/obj_loss, train/cls_loss : 训练过程中的盒子损失、目标损失和类别损失。
metrics/ precision、metrics/recall、metrics/mAP_0.5、metrics/mAP_0.5:0.95 :模型性能的关键指标,包括准确度、识别率、平均精度(mAP)。
val/box_loss, val/obj_loss, val/cls_loss : 验证集上的损失值。
x/lr0, x/lr1, x/lr2 : 学习率的变化。
结果可视化
为了进行深入的分析,我们可以创建以下可视化图表:
训练和验证损失随着时代的变化而变化。
准确度、响应率和mAP随epoch的变化。
学习率的变化。
我将首先创建这些图表,然后根据图表进行详细的数据分析。让我们从第一个图表开始:随着纪元的变化进行训练和验证结果。
import matplotlib.pyplot as plt
# Setting up the plotting parameters
plt.figure(figsize=(15, 10))
# Removing leading and trailing whitespaces from column names
data_latest.columns = data_latest.columns.str.strip()
# Plotting training losses
plt.subplot(2, 1, 1)
plt.plot(data_latest['epoch'], data_latest['train/box_loss'], label='Train Box Loss')
plt.plot(data_latest['epoch'], data_latest['train/obj_loss'], label='Train Object Loss')
plt.plot(data_latest['epoch'], data_latest['train/cls_loss'], label='Train Class Loss')
plt.title('Training Losses over Epochs')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.legend()
plt.grid(True)
# Plotting validation losses
plt.subplot(2, 1, 2)
plt.plot(data_latest['epoch'], data_latest['val/box_loss'], label='Validation Box Loss')
plt.plot(data_latest['epoch'], data_latest['val/obj_loss'], label='Validation Object Loss')
plt.plot(data_latest['epoch'], data_latest['val/cls_loss'], label='Validation Class Loss')
plt.title('Validation Losses over Epochs')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()
现在我们有了随着纪元变化的图表的训练和验证损失。接下来,我将根据纪元变化的图表来提高精确度、响应率和平均精度(mAP)。这些图表将帮助我们更好地理解模型性能随着训练进度的改变情况。完成这些图表后,我将进行详细的数据分析。
import matplotlib.pyplot as plt
# Setting up the plotting parameters
plt.figure(figsize=(15, 10))
# Removing leading and trailing whitespaces from column names
data_latest.columns = data_latest.columns.str.strip()
# Plotting training losses
plt.subplot(2, 1, 1)
plt.plot(data_latest['epoch'], data_latest['train/box_loss'], label='Train Box Loss')
plt.plot(data_latest['epoch'], data_latest['train/obj_loss'], label='Train Object Loss')
plt.plot(data_latest['epoch'], data_latest['train/cls_loss'], label='Train Class Loss')
plt.title('Training Losses over Epochs')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.legend()
plt.grid(True)
# Plotting validation losses
plt.subplot(2, 1, 2)
plt.plot(data_latest['epoch'], data_latest['val/box_loss'], label='Validation Box Loss')
plt.plot(data_latest['epoch'], data_latest['val/obj_loss'], label='Validation Object Loss')
plt.plot(data_latest['epoch'], data_latest['val/cls_loss'], label='Validation Class Loss')
plt.title('Validation Losses over Epochs')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()
训练结果分析
这些图表展示了精确度、响应率和平均精度(mAP)随着纪元的变化。现在,我们将进行详细的数据分析。
训练和验证损失(Training and Validation Loss)
损失趋势:训练损失(包括盒子损失、目标损失和类别损失)随着时代的增加呈现出稳定下降的趋势。这表明模型在学习过程中逐步提升了对训练数据的显现能力。
验证损失的突变:损失在某些时刻出现波动,这可能表明模型在新数据上的泛化能力提升空间。验证损失的稳定下降趋势是模型泛化能力提升的一个积极信号。
类别损失的变化:类别损失的下降趋势说明模型在不同类别上取得了进步。
准确度、识别率和mAP(Precision、Recall、mAP)
准确度和识别率:准确度逐步提升,表明模型在正确定位和识别目标上更加精准。识别率的提升则表明模型能够检测到更多的正类样本。
mAP的提升:mAP指标在训练过程中的提升表明模型在整体上对于目标检测的性能提升。特别是mAP_0.5:0.95指标的提升,说明模型在不同的IoU阈值下都表现良好。
学习率(Learning Rate)
学习率变化:学习率随着训练的进行逐渐降低,这是一种常见的策略,用于在训练初期快速下降,并在训练中细调模型参数。
总结
综合这些图表和数据,我们可以看出该模型在“基于RegNet-Backbone改进YOLOv5的停车位实时检测推荐系统”中表现良好,具有稳定的趋势。特别是在准确度、响应率和mAP方面,显示模型对于停车检测的有效性。然而,验证损失的波动和学习率的调整提醒我们,可能需要进一步调整模型结构或提高训练策略,以模型的泛化能力和稳定性。
其他结果分析
混淆矩阵分析
混淆矩阵显示了模型在测试过程中如何对不同的类别进行分类。在一个好的模型中,我们期望沿着对角线看到高值(较深的颜色),其中预测类别与真实类别相匹配。在这里,我们要寻找两个主要因素:
高真阳性 (TP):对于“汽车”和“金刚”类别,我们预计沿对角线看到较高的值,表明模型正确识别了这些类别。
低假阳性 (FP) 和假阴性 (FN):非对角线单元格的值应较低,表明错误分类很少。
精确率-召回率 (PR) 曲线
PR 曲线是显示不同阈值设置下精度和召回率之间权衡的图表。具有完美精确度和召回率的模型将具有一条紧贴图表右上角的曲线。
精度:衡量阳性预测的准确性。
召回率:衡量正确识别出多少实际阳性结果。
平均精度 (AP):将精度-召回率曲线总结为每个阈值实现的精度的加权平均值。
F1分数曲线
F1 分数是精确率和召回率的调和平均值,是结合了这两个方面的单一指标。理想系统的 F1 分数为 1。
F1 与置信度阈值:该曲线将显示 F1 分数如何随着预测类别的置信度阈值的变化而变化。
实例直方图
该直方图显示了训练集中每个类的实例分布。尽管可以通过过采样、类权重或专门的损失函数等技术来解决类不平衡问题,但我们期望获得平衡的数据集以获得最佳模型性能。
散点图
散点图有助于理解边界框属性的分布,例如长宽比和面积。它们可以指示数据集中检测或表示对象的方式是否存在特定模式或偏差。
精确率和召回率曲线
这些曲线可以深入了解模型在不同召回级别下的精确度,反之亦然。它们对于理解不同阈值和不同类别的性能非常重要。
训练批次可视化
可视化训练批次可以深入了解所使用的数据增强策略、数据的多样性以及边界框是否准确。
超参数 (hyp.yaml)
该文件将包含用于训练模型的超参数。分析这一点可以揭示学习率、批量大小、权重衰减等是否适合该任务。
10.系统整合
参考博客《基于RegNet-Backbone改进YOLOv5的停车位实时检测推荐系统》
11.参考文献
[1]余以春,闫红梅.临时停车场自动收费系统[J].计算机系统应用.2021,(5).DOI:10.15888/j.cnki.csa.007879 .
[2]郭云鹏,邹凯,陈升东,等.面向车路协同的路侧感知仿真系统[J].计算机系统应用.2021,(5).DOI:10.15888/j.cnki.csa.007907 .
[3]孙会君,傅丹华,吕莹,等.基于共享停车的车位租用与分配模型[J].交通运输系统工程与信息.2020,(3).DOI:10.16097/j.cnki.1009-6744.2020.03.020 .
[4]江浩斌,王成雨,马世典,等.基于图像梯度匹配的自动泊车系统车位识别方法[J].江苏大学学报(自然科学版).2020,(6).DOI:10.3969/j.issn.1671-7775.2020.06.001 .
[5]朱旺旺,黄宏成,马晋兴.基于图像识别的泊车车位检测算法研究[J].汽车工程.2019,(7).DOI:10.19562/j.chinasae.qcgc.2019.07.003 .
[6]张学工.关于统计学习理论与支持向量机[J].自动化学报.2000,(1).
[7]徐乐先,陈西江,班亚,等.基于深度学习的车位智能检测方法[J].中国激光.2019,(4).DOI:10.3788/CJL201946.0404013 .
[8]霍宏涛. 数字图像处理 [M].机械工业出版社,2003.
[9]Chunxiang Wang,Hengrun Zhang,Ming Yang,等.Automatic Parking Based on a Bird’s Eye View Vision System[J].Advances in Mechanical Engineering.2014,2014(Pt.3).
[10]Susan Grant-Muller,Mark Usher.Intelligent Transport Systems: The propensity for environmental and economic benefits[J].Technological Forecasting & Social Change.2013.82149-166.DOI:10.1016/j.techfore.2013.06.010 .