先放论文:Single-Shot_Refinement_Neural_CVPR_2018_paper
官方代码:[official code - caffe]
pytorch版本:RefineDet.PyTorch
其他版本也可以在github上找到,识别的准确度没有多少差距
具体网络结构及原理等在这里不作阐述。
这里我测试了PyTorch版本的,因为作者上传的代码使用比较简单,大家要测试的话要仔细阅读作者的说明。
我使用的是VOC类型的数据,若是不会制作自己数据的话可以去搜索一下也可以直接下载现成的VOC2007和VOC2012的数据。
把代码和数据下载下来之后,放到文件夹下,具体目录如下:
RefineDet.PyTorch/data/voc0712.py
"""VOC Dataset Classes
Original author: Francisco Massa
https://github.com/fmassa/vision/blob/voc_dataset/torchvision/datasets/voc.py
Updated by: Ellis Brown, Max deGroot
"""
from .config import HOME
import os.path as osp
import sys
import torch
import torch.utils.data as data
import cv2
import numpy as np
if sys.version_info[0] == 2:
import xml.etree.cElementTree as ET
else:
import xml.etree.ElementTree as ET
VOC_CLASSES = ( # always index 0 这里改成你自己的标签
'aeroplane', 'bicycle', 'bird', 'boat',
'bottle', 'bus', 'car', 'cat', 'chair',
'cow', 'diningtable', 'dog', 'horse',
'motorbike', 'person', 'pottedplant',
'sheep', 'sofa', 'train', 'tvmonitor')
后面代码省略 有需要可以自己去改..........
RefineDet.PyTorch/data/config.py
我使用的是VOC数据直接找到voc_refinedet 去修改
# config.py
import os.path
# gets home dir cross platform
# HOME = os.path.expanduser("~")
HOME = '/data'
# for making bounding boxes pretty
COLORS = ((255, 0, 0, 128), (0, 255, 0, 128), (0, 0, 255, 128),
(0, 255, 255, 128), (255, 0, 255, 128), (255, 255, 0, 128))
MEANS = (104, 117, 123)
# SSD CONFIGS
voc = {
'300': {
'num_classes': 21,
'lr_steps': (80000, 100000, 120000),
'max_iter': 120000,
'feature_maps': [38, 19, 10, 5, 3, 1],
'min_dim': 300,
'steps': [8, 16, 32, 64, 100, 300],
'min_sizes': [30, 60, 111, 162, 213, 264],
'max_sizes': [60, 111, 162, 213, 264, 315],
'aspect_ratios': [[2], [2, 3], [2, 3], [2, 3], [2], [2]],
'variance': [0.1, 0.2],
'clip': True,
'name': 'VOC_300',
},
'512': {
'num_classes': 21,
'lr_steps': (80000, 100000, 120000),
'max_iter': 120000,
'feature_maps': [64, 32, 16, 8, 4, 2, 1],
'min_dim': 512,
'steps': [8, 16, 32, 64, 128, 256, 512],
'min_sizes': [20, 51, 133, 215, 296, 378, 460],
'max_sizes': [51, 133, 215, 296, 378, 460, 542],
'aspect_ratios': [[2], [2, 3], [2, 3], [2, 3], [2, 3], [2], [2]],
'variance': [0.1, 0.2],
'clip': True,
'name': 'VOC_512',
}
}
coco = {
'num_classes': 201,
'lr_steps': (280000, 360000, 400000),
'max_iter': 400000,
'feature_maps': [38, 19, 10, 5, 3, 1],
'min_dim': 300,
'steps': [8, 16, 32, 64, 100, 300],
'min_sizes': [21, 45, 99, 153, 207, 261],
'max_sizes': [45, 99, 153, 207, 261, 315],
'aspect_ratios': [[2], [2, 3], [2, 3], [2, 3], [2], [2]],
'variance': [0.1, 0.2],
'clip': True,
'name': 'COCO',
}
# RefineDet CONFIGS
voc_refinedet = {
'320': {
'num_classes': 21, #这里改成标签数+1,使用320还是512的自己修改就行了
'lr_steps': (80000, 100000, 120000),
'max_iter': 120000, #一共迭代12W次
'feature_maps': [40, 20, 10, 5],
'min_dim': 320,
'steps': [8, 16, 32, 64],
'min_sizes': [32, 64, 128, 256],
'max_sizes': [],
'aspect_ratios': [[2], [2], [2], [2]],
'variance': [0.1, 0.2],
'clip': True,
'name': 'RefineDet_VOC_320',
},
'512': {
'num_classes': 21,
'lr_steps': (80000, 100000, 120000),
'max_iter': 120000,
'feature_maps': [64, 32, 16, 8],
'min_dim': 512,
'steps': [8, 16, 32, 64],
'min_sizes': [32, 64, 128, 256],
'max_sizes': [],
'aspect_ratios': [[2], [2], [2], [2]],
'variance': [0.1, 0.2],
'clip': True,
'name': 'RefineDet_VOC_320',
}
}
coco_refinedet = {
'num_classes': 201,
'lr_steps': (280000, 360000, 400000),
'max_iter': 400000,
'feature_maps': [38, 19, 10, 5, 3, 1],
'min_dim': 300,
'steps': [8, 16, 32, 64, 100, 300],
'min_sizes': [21, 45, 99, 153, 207, 261],
'max_sizes': [45, 99, 153, 207, 261, 315],
'aspect_ratios': [[2], [2, 3], [2, 3], [2, 3], [2], [2]],
'variance': [0.1, 0.2],
'clip': True,
'name': 'COCO',
}
RefineDet.PyTorch/models/refinedet.py
这里修改两处找到两个num_classes = 21,把21改成自己的标签数+1
import torch
import torch.nn as nn
import torch.nn.functional as F
from layers import *
from data import voc_refinedet, coco_refinedet
import os
class RefineDet(nn.Module):
"""Single Shot Multibox Architecture
The network is composed of a base VGG network followed by the
added multibox conv layers. Each multibox layer branches into
1) conv2d for class conf scores
2) conv2d for localization predictions
3) associated priorbox layer to produce default bounding
boxes specific to the layer's feature map size.
See: https://arxiv.org/pdf/1512.02325.pdf for more details.
Args:
phase: (string) Can be "test" or "train"
size: input image size
base: VGG16 layers for input, size of either 300 or 500
extras: extra layers that feed to multibox loc and conf layers
head: "multibox head" consists of loc and conf conv layers
"""
def __init__(self, phase, size, base, extras, ARM, ODM, TCB, num_classes):
super(RefineDet, self).__init__()
self.phase = phase
self.num_classes = num_classes
self.cfg = (coco_refinedet, voc_refinedet)[num_classes == 21] #这里一个
self.priorbox = PriorBox(self.cfg[str(size)])
with torch.no_grad():
self.priors = self.priorbox.forward()
self.size = size
# SSD network
self.vgg = nn.ModuleList(base)
# Layer learns to scale the l2 normalized features from conv4_3
self.conv4_3_L2Norm = L2Norm(512, 10)
self.conv5_3_L2Norm = L2Norm(512, 8)
self.extras = nn.ModuleList(extras)
self.arm_loc = nn.ModuleList(ARM[0])
self.arm_conf = nn.ModuleList(ARM[1])
self.odm_loc = nn.ModuleList(ODM[0])
self.odm_conf = nn.ModuleList(ODM[1])
#self.tcb = nn.ModuleList(TCB)
self.tcb0 = nn.ModuleList(TCB[0])
self.tcb1 = nn.ModuleList(TCB[1])
self.tcb2 = nn.ModuleList(TCB[2])
if phase == 'test':
self.softmax = nn.Softmax(dim=-1)
self.detect = Detect_RefineDet(num_classes, self.size, 0, 1000, 0.01, 0.45, 0.01, 500)
def forward(self, x):
"""Applies network layers and ops on input image(s) x.
Args:
x: input image or batch of images. Shape: [batch,3,300,300].
Return:
Depending on phase:
test:
Variable(tensor) of output class label predictions,
confidence score, and corresponding location predictions for
each object detected. Shape: [batch,topk,7]
train:
list of concat outputs from:
1: confidence layers, Shape: [batch*num_priors,num_classes]
2: localization layers, Shape: [batch,num_priors*4]
3: priorbox layers, Shape: [2,num_priors*4]
"""
sources = list()
tcb_source = list()
arm_loc = list()
arm_conf = list()
odm_loc = list()
odm_conf = list()
# apply vgg up to conv4_3 relu and conv5_3 relu
for k in range(30):
x = self.vgg[k](x)
if 22 == k:
s = self.conv4_3_L2Norm(x)
sources.append(s)
elif 29 == k:
s = self.conv5_3_L2Norm(x)
sources.append(s)
# apply vgg up to fc7
for k in range(30, len(self.vgg)):
x = self.vgg[k](x)
sources.append(x)
# apply extra layers and cache source layer outputs
for k, v in enumerate(self.extras):
x = F.relu(v(x), inplace=True)
if k % 2 == 1:
sources.append(x)
# apply ARM and ODM to source layers
for (x, l, c) in zip(sources, self.arm_loc, self.arm_conf):
arm_loc.append(l(x).permute(0, 2, 3, 1).contiguous())
arm_conf.append(c(x).permute(0, 2, 3, 1).contiguous())
arm_loc = torch.cat([o.view(o.size(0), -1) for o in arm_loc], 1)
arm_conf = torch.cat([o.view(o.size(0), -1) for o in arm_conf], 1)
#print([x.size() for x in sources])
# calculate TCB features
#print([x.size() for x in sources])
p = None
for k, v in enumerate(sources[::-1]):
s = v
for i in range(3):
s = self.tcb0[(3-k)*3 + i](s)
#print(s.size())
if k != 0:
u = p
u = self.tcb1[3-k](u)
s += u
for i in range(3):
s = self.tcb2[(3-k)*3 + i](s)
p = s
tcb_source.append(s)
#print([x.size() for x in tcb_source])
tcb_source.reverse()
# apply ODM to source layers
for (x, l, c) in zip(tcb_source, self.odm_loc, self.odm_conf):
odm_loc.append(l(x).permute(0, 2, 3, 1).contiguous())
odm_conf.append(c(x).permute(0, 2, 3, 1).contiguous())
odm_loc = torch.cat([o.view(o.size(0), -1) for o in odm_loc], 1)
odm_conf = torch.cat([o.view(o.size(0), -1) for o in odm_conf], 1)
#print(arm_loc.size(), arm_conf.size(), odm_loc.size(), odm_conf.size())
if self.phase == "test":
#print(loc, conf)
output = self.detect(
arm_loc.view(arm_loc.size(0), -1, 4), # arm loc preds
self.softmax(arm_conf.view(arm_conf.size(0), -1,
2)), # arm conf preds
odm_loc.view(odm_loc.size(0), -1, 4), # odm loc preds
self.softmax(odm_conf.view(odm_conf.size(0), -1,
self.num_classes)), # odm conf preds
self.priors.type(type(x.data)) # default boxes
)
else:
output = (
arm_loc.view(arm_loc.size(0), -1, 4),
arm_conf.view(arm_conf.size(0), -1, 2),
odm_loc.view(odm_loc.size(0), -1, 4),
odm_conf.view(odm_conf.size(0), -1, self.num_classes),
self.priors
)
return output
def load_weights(self, base_file):
other, ext = os.path.splitext(base_file)
if ext == '.pkl' or '.pth':
print('Loading weights into state dict...')
self.load_state_dict(torch.load(base_file,
map_location=lambda storage, loc: storage))
print('Finished!')
else:
print('Sorry only .pth and .pkl files supported.')
# This function is derived from torchvision VGG make_layers()
# https://github.com/pytorch/vision/blob/master/torchvision/models/vgg.py
def vgg(cfg, i, batch_norm=False):
layers = []
in_channels = i
for v in cfg:
if v == 'M':
layers += [nn.MaxPool2d(kernel_size=2, stride=2)]
elif v == 'C':
layers += [nn.MaxPool2d(kernel_size=2, stride=2, ceil_mode=True)]
else:
conv2d = nn.Conv2d(in_channels, v, kernel_size=3, padding=1)
if batch_norm:
layers += [conv2d, nn.BatchNorm2d(v), nn.ReLU(inplace=True)]
else:
layers += [conv2d, nn.ReLU(inplace=True)]
in_channels = v
pool5 = nn.MaxPool2d(kernel_size=2, stride=2, padding=0)
conv6 = nn.Conv2d(512, 1024, kernel_size=3, padding=3, dilation=3)
conv7 = nn.Conv2d(1024, 1024, kernel_size=1)
layers += [pool5, conv6,
nn.ReLU(inplace=True), conv7, nn.ReLU(inplace=True)]
return layers
def add_extras(cfg, size, i, batch_norm=False):
# Extra layers added to VGG for feature scaling
layers = []
in_channels = i
flag = False
for k, v in enumerate(cfg):
if in_channels != 'S':
if v == 'S':
layers += [nn.Conv2d(in_channels, cfg[k + 1],
kernel_size=(1, 3)[flag], stride=2, padding=1)]
else:
layers += [nn.Conv2d(in_channels, v, kernel_size=(1, 3)[flag])]
flag = not flag
in_channels = v
return layers
def arm_multibox(vgg, extra_layers, cfg):
arm_loc_layers = []
arm_conf_layers = []
vgg_source = [21, 28, -2]
for k, v in enumerate(vgg_source):
arm_loc_layers += [nn.Conv2d(vgg[v].out_channels,
cfg[k] * 4, kernel_size=3, padding=1)]
arm_conf_layers += [nn.Conv2d(vgg[v].out_channels,
cfg[k] * 2, kernel_size=3, padding=1)]
for k, v in enumerate(extra_layers[1::2], 3):
arm_loc_layers += [nn.Conv2d(v.out_channels, cfg[k]
* 4, kernel_size=3, padding=1)]
arm_conf_layers += [nn.Conv2d(v.out_channels, cfg[k]
* 2, kernel_size=3, padding=1)]
return (arm_loc_layers, arm_conf_layers)
def odm_multibox(vgg, extra_layers, cfg, num_classes):
odm_loc_layers = []
odm_conf_layers = []
vgg_source = [21, 28, -2]
for k, v in enumerate(vgg_source):
odm_loc_layers += [nn.Conv2d(256, cfg[k] * 4, kernel_size=3, padding=1)]
odm_conf_layers += [nn.Conv2d(256, cfg[k] * num_classes, kernel_size=3, padding=1)]
for k, v in enumerate(extra_layers[1::2], 3):
odm_loc_layers += [nn.Conv2d(256, cfg[k] * 4, kernel_size=3, padding=1)]
odm_conf_layers += [nn.Conv2d(256, cfg[k] * num_classes, kernel_size=3, padding=1)]
return (odm_loc_layers, odm_conf_layers)
def add_tcb(cfg):
feature_scale_layers = []
feature_upsample_layers = []
feature_pred_layers = []
for k, v in enumerate(cfg):
feature_scale_layers += [nn.Conv2d(cfg[k], 256, 3, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(256, 256, 3, padding=1)
]
feature_pred_layers += [nn.ReLU(inplace=True),
nn.Conv2d(256, 256, 3, padding=1),
nn.ReLU(inplace=True)
]
if k != len(cfg) - 1:
feature_upsample_layers += [nn.ConvTranspose2d(256, 256, 2, 2)]
return (feature_scale_layers, feature_upsample_layers, feature_pred_layers)
base = {
'320': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'C', 512, 512, 512, 'M',
512, 512, 512],
'512': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'C', 512, 512, 512, 'M',
512, 512, 512],
}
extras = {
'320': [256, 'S', 512],
'512': [256, 'S', 512],
}
mbox = {
'320': [3, 3, 3, 3], # number of boxes per feature map location
'512': [3, 3, 3, 3], # number of boxes per feature map location
}
tcb = {
'320': [512, 512, 1024, 512],
'512': [512, 512, 1024, 512],
}
def build_refinedet(phase, size=320, num_classes=21):#这里一个
if phase != "test" and phase != "train":
print("ERROR: Phase: " + phase + " not recognized")
return
if size != 320 and size != 512:
print("ERROR: You specified size " + repr(size) + ". However, " +
"currently only RefineDet320 and RefineDet512 is supported!")
return
base_ = vgg(base[str(size)], 3)
extras_ = add_extras(extras[str(size)], size, 1024)
ARM_ = arm_multibox(base_, extras_, mbox[str(size)])
ODM_ = odm_multibox(base_, extras_, mbox[str(size)], num_classes)
TCB_ = add_tcb(tcb[str(size)])
return RefineDet(phase, size, base_, extras_, ARM_, ODM_, TCB_, num_classes)
还有别的训练参数在RefineDet.PyTorch/train_refinedet.py,可以自行修改
下载预训练好的vgg16权重
mkdir weights
cd weights
wget https://s3.amazonaws.com/amdegroot-models/vgg16_reducedfc.pth
就是下载好放到RefineDet.PyTorch\weights文件夹下
开始训练,可以运行脚本
./train_refinedet320.sh #./train_refinedet512.sh
或者直接运行脚本里面的命令
python train_refinedet.py --save_folder weights/RefineDet320/ --input_size 320
测试数据:自己制作的VOC类型的数据;测试环境:一块 tesla P4
训练参数:
parser.add_argument('--dataset', default='VOC', choices=['VOC', 'COCO'],
type=str, help='VOC or COCO')
parser.add_argument('--input_size', default='320', choices=['320', '512'],
type=str, help='RefineDet320 or RefineDet512')
parser.add_argument('--dataset_root', default=VOC_ROOT,
help='Dataset root directory path')
parser.add_argument('--basenet', default='./weights/vgg16_reducedfc.pth',
help='Pretrained base model')
parser.add_argument('--batch_size', default=16, type=int,
help='Batch size for training')
parser.add_argument('--resume', default=None, type=str,
help='Checkpoint state_dict file to resume training from')
parser.add_argument('--start_iter', default=0, type=int,
help='Resume training at this iter')
parser.add_argument('--num_workers', default=8, type=int,
help='Number of workers used in dataloading')
parser.add_argument('--cuda', default=True, type=str2bool,
help='Use CUDA to train model')
parser.add_argument('--lr', '--learning-rate', default=1e-3, type=float,
help='initial learning rate')
parser.add_argument('--momentum', default=0.9, type=float,
help='Momentum value for optim')
parser.add_argument('--weight_decay', default=5e-4, type=float,
help='Weight decay for SGD')
parser.add_argument('--gamma', default=0.1, type=float,
help='Gamma update for SGD')
训练平均用时:
iter 100950 || ARM_L Loss: 0.6326 ARM_C Loss: 1.4300 ODM_L Loss: 0.8379 ODM_C Loss: 0.8354 || timer: 0.3315 sec
测试平均用时一张图0.05S到0.06S
关于准确度,如果用VOC2007和2012的话应该是可以达到作者的
VOC2007 Test
mAP (Single Scale Test)
Arch Paper Caffe Version Our PyTorch Version
RefineDet320 80.0% 79.52% 79.81%
RefineDet512 81.8% 81.85% 80.50%
因为我是使用自己的测试数据,所以具体准确度也没有做测试,前面我还测试过Faster rcnn,SSD,YOLOv3,RetinaNet,对比来说其实准确度还挺高的,可能是因为比较符合我的数据吧。
这个项目其实是2019年3月的事情了,从开始的入门看Faster rcnn,SSD,YOLO系列,到后面的RFCN,RetinaNet,RefineDet等等。因为实际使用要快和准,所以我比较关注one-stage的算法。
我认为其中较为重要的概念有:特征提取网络FPN、vgg、resnet、Darknet、DenseNet等;解决one-stage目标检测中正负样本比例严重失衡的问题的Focal loss(RetinaNet中提出的)、为了解决这个问题的RefineDet也有ARM和ODM;另外其实应用中最重要的就是数据了,一个目标检测算法是否准确,数据可以说有一半的功劳。
初接触目标检测的时候,我是依靠github上的一个项目来了解他的历史的,给大家推荐一下:
deep_learning_object_detection
新的算法每一年都发表,但是万变不离其宗,核心还是那些,不过还是要关注新的算法,因为可能有新的发现。
文章写得比较简陋,后续可能还有补充,各位读者若有什么问题,可以留言。
谢谢大家阅读
侵删