1.概述
交通要道的路口上人车穿行,特别是上下班早高峰,且时常发生交通事故。因此对交通路口的车流量和人流量的监测必不可少。
2.检测模型
使用的检测模型为YOLOX模型,模型权重为训练VOC数据集得来,其中包括了二十个类别,但我们主要针对地面交通路口进行监测,选择了最关键的三个监测要素作为监测目标,分别为人、汽车和自行车。YOLOX神经网络模型介绍如下:
YOLOX模型由旷世科技提出,模型结构如下图所示。
主要分为两大部分,分别为主干特征提取网络和多尺度特征金字塔融合结构。
2.1 主干特征提取网络
主干特征提取网络由4个ResBlock堆叠组成,以对输入的图像进行多尺度特征提取,在这里会输出3个最终的有效特征层到特征金字塔融合结构中。
class YOLOPAFPN(nn.Module):
def __init__(self, depth=1.0, width=1.0, in_features=("dark3", "dark4", "dark5"), in_channels=[256, 512, 1024],
depthwise=False, act="silu"):
super().__init__()
Conv = DWConv if depthwise else BaseConv
self.backbone = CSPDarknet(depth, width, depthwise=depthwise, act=act)
self.in_features = in_features
self.upsample = nn.Upsample(scale_factor=2, mode="nearest")
# -------------------------------------------#
# 20, 20, 1024 -> 20, 20, 512
# -------------------------------------------#
self.lateral_conv0 = BaseConv(int(in_channels[2] * width), int(in_channels[1] * width), 1, 1, act=act)
# -------------------------------------------#
# 40, 40, 1024 -> 40, 40, 512
# -------------------------------------------#
self.C3_p4 = CSPLayer(
int(2 * in_channels[1] * width),
int(in_channels[1] * width),
round(3 * depth),
False,
depthwise=depthwise,
act=act,
)
# -------------------------------------------#
# 40, 40, 512 -> 40, 40, 256
# -------------------------------------------#
self.reduce_conv1 = BaseConv(int(in_channels[1] * width), int(in_channels[0] * width), 1, 1, act=act)
# -------------------------------------------#
# 80, 80, 512 -> 80, 80, 256
# -------------------------------------------#
self.C3_p3 = CSPLayer(
int(2 * in_channels[0] * width),
int(in_channels[0] * width),
round(3 * depth),
False,
depthwise=depthwise,
act=act,
)
# -------------------------------------------#
# 80, 80, 256 -> 40, 40, 256
# -------------------------------------------#
self.bu_conv2 = Conv(int(in_channels[0] * width), int(in_channels[0] * width), 3, 2, act=act)
# -------------------------------------------#
# 40, 40, 256 -> 40, 40, 512
# -------------------------------------------#
self.C3_n3 = CSPLayer(
int(2 * in_channels[0] * width),
int(in_channels[1] * width),
round(3 * depth),
False,
depthwise=depthwise,
act=act,
)
# -------------------------------------------#
# 40, 40, 512 -> 20, 20, 512
# -------------------------------------------#
self.bu_conv1 = Conv(int(in_channels[1] * width), int(in_channels[1] * width), 3, 2, act=act)
# -------------------------------------------#
# 20, 20, 1024 -> 20, 20, 1024
# -------------------------------------------#
self.C3_n4 = CSPLayer(
int(2 * in_channels[1] * width),
int(in_channels[2] * width),
round(3 * depth),
False,
depthwise=depthwise,
act=act,
)
def forward(self, input):
out_features = self.backbone.forward(input)
[feat1, feat2, feat3] = [out_features[f] for f in self.in_features]
P5 = self.lateral_conv0(feat3)
P5_upsample = self.upsample(P5)
P5_upsample = torch.cat([P5_upsample, feat2], 1)
P5_upsample = self.C3_p4(P5_upsample)
P4 = self.reduce_conv1(P5_upsample)
P4_upsample = self.upsample(P4)
P4_upsample = torch.cat([P4_upsample, feat1], 1)
P3_out = self.C3_p3(P4_upsample)
P3_downsample = self.bu_conv2(P3_out)
P3_downsample = torch.cat([P3_downsample, P4], 1)
P4_out = self.C3_n3(P3_downsample)
P4_downsample = self.bu_conv1(P4_out)
P4_downsample = torch.cat([P4_downsample, P5], 1)
P5_out = self.C3_n4(P4_downsample)
return (P3_out, P4_out, P5_out)
return (P3_out,P4_out,P5_out)表示主干特征提取网络输出的3个最终特征层。
2.2 多尺度特征金字塔融合结构
在特征金字塔结构中,来自主干特征提取网络的3个有效特征层会进行多尺度的特征融合。因为在高分辨率的特征层具有的细节信息较多,而低分辨率的特征层具有的语义信息较多。首先会通过对低分辨率特征层P5_out进行上采样操作,以便能够和P4_out进行拼接融合。经过多尺度特征金字塔融合结构输出的特征层有三个,以能够实现不同尺寸目标的检测。
多尺度特征金子塔融合结构的代码如下:
class YOLOXHead(nn.Module):
def __init__(self, num_classes, width=1.0, in_channels=[256, 512, 1024], act="silu", depthwise=False, ):
super().__init__()
Conv = DWConv if depthwise else BaseConv
self.cls_convs = nn.ModuleList()
self.reg_convs = nn.ModuleList()
self.cls_preds = nn.ModuleList()
self.reg_preds = nn.ModuleList()
self.obj_preds = nn.ModuleList()
self.stems = nn.ModuleList()
for i in range(len(in_channels)):
self.stems.append(
BaseConv(in_channels=int(in_channels[i] * width), out_channels=int(256 * width), ksize=1, stride=1,
act=act))
self.cls_convs.append(nn.Sequential(*[
Conv(in_channels=int(256 * width), out_channels=int(256 * width), ksize=3, stride=1, act=act),
Conv(in_channels=int(256 * width), out_channels=int(256 * width), ksize=3, stride=1, act=act),
]))
self.cls_preds.append(
nn.Conv2d(in_channels=int(256 * width), out_channels=num_classes, kernel_size=1, stride=1, padding=0)
)
self.reg_convs.append(nn.Sequential(*[
Conv(in_channels=int(256 * width), out_channels=int(256 * width), ksize=3, stride=1, act=act),
Conv(in_channels=int(256 * width), out_channels=int(256 * width), ksize=3, stride=1, act=act)
]))
self.reg_preds.append(
nn.Conv2d(in_channels=int(256 * width), out_channels=4, kernel_size=1, stride=1, padding=0)
)
self.obj_preds.append(
nn.Conv2d(in_channels=int(256 * width), out_channels=1, kernel_size=1, stride=1, padding=0)
)
def forward(self, inputs):
outputs = []
for k, x in enumerate(inputs):
x = self.stems[k](x)
cls_feat = self.cls_convs[k](x)
cls_output = self.cls_preds[k](cls_feat)
reg_feat = self.reg_convs[k](x)
reg_output = self.reg_preds[k](reg_feat)
obj_output = self.obj_preds[k](reg_feat)
output = torch.cat([reg_output, obj_output, cls_output], 1)
outputs.append(output)
return outputs
这里return返回的outputs是一个列表,长度为3,为多尺度特征金字塔融合结构的输出结果。
3.系统可视化
系统使用了PyQt5作为可视化工具,PyQt和C++中的qt类似,具有良好的交互性,包含了日常开发常用的控件,像显示提示控件QLabel,按钮QPushbotton,输入框控件TextBrowser等,并且实现了信号槽机制,能够简单快速的获取页面控件和响应事件。系统整体效果如下:
系统整体布局为线性垂直布局,从上至下依次为系统标题、功能区和显示区。显示区为监测的实时画面显示,这里通过QLabel控件来作为一个容器,来接受opencv库中的视频流。
系统实现了实时摄像头监测功能和上传视频监测功能,
3.1 打开摄像头
当用户点击打开摄像头后,系统将打开电脑的默认摄像头进行画面获取,并将获取的画面进行监测,这里最重要的代码就是定时器函数,因为用户点击打开摄像头后,只是一个瞬间事件,而系统需要将摄像头拍摄的画面进行实时检测,这是一个连续性事件,而下面这两行代码很重要
self.timer_camera = QtCore.QTimer()
self.timer_camera.timeout.connect(self.show_camera) # 将timeout绑定槽函数show_camera
self.timer_camera = QtCore.QTimer()定义了定时器,并通过信号槽self.timer_camera.timeout.connect(self.show_camera)进行了检测事件绑定。
打开摄像头按钮代码如下:
def open_camera_btn(self):
if not self.timer_camera.isActive(): # 定时器未启动
flag = self.cap.open(self.CAM_NUM)
if flag == False:
msg = QtWidgets.QMessageBox.warning(self.window, '警告!', "请检查摄像头是否连接正确",
buttons=QtWidgets.QMessageBox.Ok)
else:
self.timer_camera.start() # 设置30毫秒后,定时器将每隔30毫秒调用timeout函数
self.open_camera.setText('关闭监测')
# 关闭检测按钮事件
else:
self.timer_camera.stop()
self.cap.release()
self.label_show_camera.clear() # 清空视频显示区域
self.open_camera.setText('开始监测')
这里就比较简单了,只需判断当前定时器是否被打开,如果没有就去打开摄像头,并启动定时器,再把摄像头的提示文字修改一下,改为“关闭摄像头”,当用户再次点击按钮时,就可以关闭摄像头,并将定时器暂定,一个按钮实现启动和关闭功能。
3.2 上传视频监测
上传视频监测按钮整体功能与打开摄像头类似,只需要将视频流进行更改,这里通过QFileDialog.getOpenFileName()函数来实现打开资源文件窗口进行视频选择,并将选择的视频文件的绝对路径进行返回,这样就能将视频的绝对路径传给opencv进行视频流读取:
上传视频文件监测按钮代码如下:
def video_detect_btn(self):
fileUrl, _ = QFileDialog.getOpenFileName(self, "Open Video File", QDir.currentPath(),
"Video Files (*.mp4 *.avi *.mov *.wmv);;")
# 视频选择成功
if fileUrl:
print(fileUrl)
self.label_video_url.setText(fileUrl)
if not self.timer_camera.isActive(): # 定时器未启动
flag = self.cap.open(fileUrl)
if flag == False:
msg = QtWidgets.QMessageBox.warning(self.window, '警告!', "请检查摄像头是否连接正确",
buttons=QtWidgets.QMessageBox.Ok)
else:
self.timer_camera.start() # 设置30毫秒后,定时器将每隔30毫秒调用timeout函数
点击上传监测视频后,将打开资源文件窗口进行视频文件选择,效果如下:
用户选择完成后,将开始逐帧检测。
3.3 统计结果显示
页面中的结果显示也采用了垂直线性布局的方式,效果如下:
目标种类和统计结果两个垂直线性布局包裹在一个QWidget控件中,QWidget控件使用水平线性布局的方式。行人、汽车和自行车统计结果在show_res_num()中实现,dict是一个字典类型的变量,key为目标类型,value为目标类别对应的个数。
代码为:
label_list = [*dict]
all = 0
# for i in label_list:
# all = all + int(dict.get(i))
# self.all_result.setText(str(all))
if 'person' in label_list:
self.person_num.setText(str(dict['person']))
all += int(dict['person'])
else:
self.person_num.setText('0')
if 'car' in label_list:
self.car_num.setText(str(dict['car']))
all += int(dict['car'])
else:
self.car_num.setText('0')
if 'bicycle' in label_list:
self.light_num.setText(str(dict['bicycle']))
all += int(dict['bicycle'])
else:
self.light_num.setText('0')
self.all_result.setText(str(all))
4.效果
5.总结
这里的监测模型使用的是旷视科技提出的YOLOX检测模型,并且权重文件也使用的是官方提供的s版,能够检测的类别有二十种,这里只选取了三种监测模型进行了统计结果显示。程序的入口为main函数,并提供了源码。并且较容易对源码进行修改,以训练自己的数据集,针对自己的应用领域。
YOLOX模型github地址:https://github.com/Megvii-BaseDetection/YOLOX
VOC2012数据集地址:https://host.robots.ox.ac.uk/pascal/VOC/voc2012/VOCtrainval_11-May-2012.tar 官方工具文档:http://host.robots.ox.ac.uk/pascal/VOC/voc2012/htmldoc/index.html
6.环境和exe文件运行说明
6.1 源码运行的python环境
python == 3.8
PyQt5 == 5.15.2
torch == 1.9.1+cu111
6.2 exe文件运行说明
main.exe可执行文件较大,因为其中包含了torch库文件,main.py文件下载链接:main.exe下载地址
提取码:8cfk
将main.py文件下载后需要放到output文件夹中,output文件下还有两个文件夹分别为ui和model_data。其中ui文件夹中包含了系统运行所需的页面ui文件;model_data文件夹中包含了检测模型的权重文件和类别文件。双击main.exe文件运行时,可能需要等待一点时间等待系统启动。
参考文章: https://blog.csdn.net/weixin_44791964/article/details/120476949