哈哈,我又来了!!! 再次立下flag,开学之后还是要保持更新频率!!!
本次用efficientdet来对抽烟检测,检测出是否抽烟。那么,老规矩,先上结果图!!!
那么,接下来,还是原先一套流程。走起!!!
一、环境配置
python==3.7.4
tensorflow-gpu==1.14.0
keras==2.2.4
numpy==1.17.4
本次,在租的gpu的机器上的。没办法,efficientnet这个网络占据显存太大了。本次机器带不动呀。
二、抽烟数据集
本次数据集是用labelme标注的,提供的json格式的数据集,但本次我们的voc格式的xml数据集,所以需要对json格式的数据进行转换。
图片:
标注的json格式数据:
转换后的xml格式
本次json转xml的源码如下:
# -*- coding: utf-8 -*-
"""
Created on Sun May 31 10:19:23 2020
@author: ywx
"""
import os
from typing import List, Any
import numpy as np
import codecs
import json
from glob import glob
import cv2
import shutil
from sklearn.model_selection import train_test_split
# 1.标签路径
labelme_path = "annotations/"
#原始labelme标注数据路径
saved_path = "VOC2007/"
# 保存路径
isUseTest=True#是否创建test集
# 2.创建要求文件夹
if not os.path.exists(saved_path + "Annotations"):
os.makedirs(saved_path + "Annotations")
if not os.path.exists(saved_path + "JPEGImages/"):
os.makedirs(saved_path + "JPEGImages/")
if not os.path.exists(saved_path + "ImageSets/Main/"):
os.makedirs(saved_path + "ImageSets/Main/")
# 3.获取待处理文件
files = glob(labelme_path + "*.json")
files = [i.replace("\\","/").split("/")[-1].split(".json")[0] for i in files]
print(files)
# 4.读取标注信息并写入 xml
for json_file_ in files:
json_filename = labelme_path + json_file_ + ".json"
json_file = json.load(open(json_filename, "r", encoding="utf-8"))
height, width, channels = cv2.imread('jpeg/' + json_file_ + ".jpg").shape
with codecs.open(saved_path + "Annotations/" + json_file_ + ".xml", "w", "utf-8") as xml:
xml.write('\n')
xml.write('\t' + 'WH_data' + '\n')
xml.write('\t' + json_file_ + ".jpg" + '\n')
xml.write('\t\n')
xml.write('\t\tWH Data\n')
xml.write('\t\tWH\n')
xml.write('\t\tflickr\n')
xml.write('\t\tNULL\n')
xml.write('\t\n')
xml.write('\t\n')
xml.write('\t\tNULL\n')
xml.write('\t\tWH\n')
xml.write('\t\n')
xml.write('\t\n')
xml.write('\t\t' + str(width) + '\n')
xml.write('\t\t' + str(height) + '\n')
xml.write('\t\t' + str(channels) + '\n')
xml.write('\t\n')
xml.write('\t\t0\n')
for multi in json_file["shapes"]:
points = np.array(multi["points"])
labelName=multi["label"]
xmin = min(points[:, 0])
xmax = max(points[:, 0])
ymin = min(points[:, 1])
ymax = max(points[:, 1])
label = multi["label"]
if xmax <= xmin:
pass
elif ymax <= ymin:
pass
else:
xml.write('\t\n')
xml.write('\t\t' + labelName+ '\n')
xml.write('\t\tUnspecified\n')
xml.write('\t\t1\n')
xml.write('\t\t0\n')
xml.write('\t\t\n')
xml.write('\t\t\t' + str(int(xmin)) + '\n')
xml.write('\t\t\t' + str(int(ymin)) + '\n')
xml.write('\t\t\t' + str(int(xmax)) + '\n')
xml.write('\t\t\t' + str(int(ymax)) + '\n')
xml.write('\t\t\n')
xml.write('\t\n')
print(json_filename, xmin, ymin, xmax, ymax, label)
xml.write('')
# 5.复制图片到 VOC2007/JPEGImages/下
image_files = glob("jpeg/" + "*.jpg")
print("copy image files to VOC007/JPEGImages/")
for image in image_files:
shutil.copy(image, saved_path + "JPEGImages/")
# 6.split files for txt
txtsavepath = saved_path + "ImageSets/Main/"
ftrainval = open(txtsavepath + '/trainval.txt', 'w')
ftest = open(txtsavepath + '/test.txt', 'w')
ftrain = open(txtsavepath + '/train.txt', 'w')
fval = open(txtsavepath + '/val.txt', 'w')
total_files = glob("./VOC2007/Annotations/*.xml")
total_files = [i.replace("\\","/").split("/")[-1].split(".xml")[0] for i in total_files]
trainval_files=[]
test_files=[]
if isUseTest:
trainval_files, test_files = train_test_split(total_files, test_size=0.15, random_state=55)
else:
trainval_files=total_files
for file in trainval_files:
ftrainval.write(file + "\n")
# split
train_files, val_files = train_test_split(trainval_files, test_size=0.15, random_state=55)
# train
for file in train_files:
ftrain.write(file + "\n")
# val
for file in val_files:
fval.write(file + "\n")
for file in test_files:
print(file)
ftest.write(file + "\n")
ftrainval.close()
ftrain.close()
fval.close()
ftest.close()
三、EfficientDet理论介绍
EfficientDet是基于Efficientnet的目标检测网络,所以需要先读懂Efficientnet,这里可以先去看我之前写的卷积神经网络发展史中有关于Efficientnet的介绍。
简短来说,EfficientNet是将图片的分辨率,网络的宽度,网络的深度这三者结合起来,通过α实现缩放模型,不同的α有不同的模型精度。
总的来说,efficientdet目标检测网络,是以efficientnet为主干网络,之后经过bifpn特征特征网络,之后再输出检测结果。
1.EfficientNet
EfficientNet主要由Efficient Blocks构成,在其中小残差边以及大残差边构成,并在其中添加了注意力模块。
def mb_conv_block(inputs, block_args, activation, drop_rate=None, prefix='', ):
"""Mobile Inverted Residual Bottleneck."""
has_se = (block_args.se_ratio is not None) and (0 < block_args.se_ratio <= 1)
bn_axis = 3 if backend.image_data_format() == 'channels_last' else 1
# workaround over non working dropout with None in noise_shape in tf.keras
Dropout = get_dropout(
backend=backend,
layers=layers,
models=models,
utils=keras_utils
)
# Expansion phase
filters = block_args.input_filters * block_args.expand_ratio
if block_args.expand_ratio != 1:
x = layers.Conv2D(filters, 1,
padding='same',
use_bias=False,
kernel_initializer=CONV_KERNEL_INITIALIZER,
name=prefix + 'expand_conv')(inputs)
x = layers.BatchNormalization(axis=bn_axis, name=prefix + 'expand_bn')(x)
x = layers.Activation(activation, name=prefix + 'expand_activation')(x)
else:
x = inputs
# Depthwise Convolution
x = layers.DepthwiseConv2D(block_args.kernel_size,
strides=block_args.strides,
padding='same',
use_bias=False,
depthwise_initializer=CONV_KERNEL_INITIALIZER,
name=prefix + 'dwconv')(x)
x = layers.BatchNormalization(axis=bn_axis, name=prefix + 'bn')(x)
x = layers.Activation(activation, name=prefix + 'activation')(x)
# Squeeze and Excitation phase
if has_se:
num_reduced_filters = max(1, int(
block_args.input_filters * block_args.se_ratio
))
se_tensor = layers.GlobalAveragePooling2D(name=prefix + 'se_squeeze')(x)
target_shape = (1, 1, filters) if backend.image_data_format() == 'channels_last' else (filters, 1, 1)
se_tensor = layers.Reshape(target_shape, name=prefix + 'se_reshape')(se_tensor)
se_tensor = layers.Conv2D(num_reduced_filters, 1,
activation=activation,
padding='same',
use_bias=True,
kernel_initializer=CONV_KERNEL_INITIALIZER,
name=prefix + 'se_reduce')(se_tensor)
se_tensor = layers.Conv2D(filters, 1,
activation='sigmoid',
padding='same',
use_bias=True,
kernel_initializer=CONV_KERNEL_INITIALIZER,
name=prefix + 'se_expand')(se_tensor)
if backend.backend() == 'theano':
# For the Theano backend, we have to explicitly make
# the excitation weights broadcastable.
pattern = ([True, True, True, False] if backend.image_data_format() == 'channels_last'
else [True, False, True, True])
se_tensor = layers.Lambda(
lambda x: backend.pattern_broadcast(x, pattern),
name=prefix + 'se_broadcast')(se_tensor)
x = layers.multiply([x, se_tensor], name=prefix + 'se_excite')
# Output phase
x = layers.Conv2D(block_args.output_filters, 1,
padding='same',
use_bias=False,
kernel_initializer=CONV_KERNEL_INITIALIZER,
name=prefix + 'project_conv')(x)
x = layers.BatchNormalization(axis=bn_axis, name=prefix + 'project_bn')(x)
if block_args.id_skip and all(
s == 1 for s in block_args.strides
) and block_args.input_filters == block_args.output_filters:
if drop_rate and (drop_rate > 0):
x = Dropout(drop_rate,
noise_shape=(None, 1, 1, 1),
name=prefix + 'drop')(x)
x = layers.add([x, inputs], name=prefix + 'add')
return x
2.BiFPN
改进了FPN中的多尺度特征融合方式,提出了加权双向特征金字塔网络BiFPN。BiFPN 引入了一种自顶向下的路径,融合P3~P7的多尺度特征
BiFPN模块类似于FPN网络(特征金字塔网络),不过比FPN更复杂些。其主要是为了增强特征,提取更有代表性的特征。
下图展示一下FPN网络:
而这是BiFPN的网络图:
其中的一个BiFPN模块为:
四、训练过程
1.准备数据集
准备抽烟数据,使用VOC格式的数据进行训练
训练前将标签文件放在VOCdevkit文件夹下的VOC2007文件夹下的Annotation中。
训练前将图片文件放在VOCdevkit文件夹下的VOC2007文件夹下的JPEGImages中。
在训练前利用voc2efficientdet.py文件生成对应的txt。
VOCdevkit
-VOC2007
├─ImageSets # 存放数据集列表文件,由voc2yolo3.py文件生成
├─Annotations # 存放数据集中图片文件
├─JPEGImages # 存放图片标签,xml 格式
└─voc2yolo4.py # 用来生成数据集列表文件
2.运行生成EfficientDet所需的数据
再运行根目录voc_annotation.py,运行前需要将voc_annotation文件中classes改成你自己的classes。
每一行对应其图片位置及其真实框的位置
3.修改voc_classes.txt
在训练前需要修改model_data里面的voc_classes.txt文件,需要将classes改成你自己的classes。
4.修改yolo_anchors.txt
运行kmeans_for_anchors.py生成yolo_anchors.txt
5.运行
运行train.py
在main函数下:phi来控制efficient的版本
在main函数下:model_path参数用于控制预训练权重
6.测试图片
需修改efficientdet.py文件中模型的位置,替换成你训练好的模型并修改phi为efficientdet的版本。然后在根目录下,运行python predict.py进行测试。
好了,本次就到此结束了!!!
哦,突然想起,我还保存了logs,在展示一波的训练过程吧!!!
好了,下次看情况,更新点别的东西,更新点有关bert文本的吧!!!