2021SC@SDUSC
接下来分析SOTA Anchor Free算法:
简要介绍:
PaddleDetection2.0更新后的招牌模型算法之一
PAFNet(Paddle Anchor Free)& PAFNet-Lite
相较于SSD、RCNN等系列各种Anchor-Based算法,Anchor-Free算法拥有更少的超参、更易配置、对多尺度目标检测效果更好等优点,但也存在检测结果不稳定、训练时间长等问题,是近些年科研领域的热点方向。飞桨当然一直紧跟全球科研动向,基于TTFNet进行多维度的优化,推出了在COCO数据集精度42.2,V100预测速度67FPS, 处于anchor free领域SOTA水平的PAFNet(Paddle Anchor Free)算法!同时提供移动端轻量级模型PAFNet-Lite,COCO数据集精度达到23.9,麒麟990芯片延时26ms。
一、Anchor-Based方法回顾
比如两阶段就以Fasrer RCNN为例,他就会在特征图的每一个点生成大小比例不一的anchor,然后通过RPN阶段对anchor进行筛选,训练阶段就是用anchor和真实框做一个编码得到训练目标,预测阶段就是通过anchor和预测出来的偏移量,然后解码得到一个预测框。单阶段就以yolo为例,yolo把原图分为若干个网格,然后通过聚类的方法,每个网格当中得到不同尺寸的anchor,然后再和真实框做一个IoU的比较,编码得到训练目标,或者说是监督信息;预测阶段,就是通过预测出来的偏移量,还有网格当中的anchor解码得到预测框。
以上就是对之前博文目标检测的一些回顾。
此时我们就会有一个问题“必须使用Anchor进行目标检测吗?”
基于Anchor的检测算法具有如下特点:
在同一像素点上生成多个不同大小和比例的候选框,并对其进行筛选,然后再进行分类和回归,一定程度上能够解决目标尺度不一和遮挡的问题,提高检测精度。
就比如有两个物体靠的很近,这样的话,他们的中心点有可能在特征图上就对准到来一个点,这个时候,要是你不考虑多个anchor的话,就很有可能丢失到其中一个物体的信息。
那么用anchor有哪些弊端呢?
该方法弊端有:
依赖过多的手动设计,也就是它的超参数是很难调的,比如说anchor box的数量、大小、宽高比呀等,而这往往需要根据项目的实际情况来设计,就比如说,在车牌的检测当中,就需要根据车牌的比例,比如说5:1、7:1等,这样的anchor box设计才比较合理,但又存在不同国家不同车牌比例不一样。但是人脸检测你就不能再用5:1的anchor了,这个时候就需要设置1:1了。一来这往往需要根据项目的实际情况和数据集来设计,二来基于anchor的方法,都需要在特征图上预设很多的anchor,这个过程就很耗时,同时为了增大召回率,有需要布局很多很密集的anchor,用这种方法来覆盖数据集中所有的真实框,这就会导致我们训练和预测过程的低效。另外一点,这么多的anchor,其实呢,只有很少一部分与真实框达到足够大的IoU来使其成为正样本,也就是说有绝大部分的anchor其实就是负样本,这就导致训练时,正负样本不平衡问题。
弊端总结为:
依赖过多的手动设计!
训练和预测过程低效!
正负样本不均问题!
去除anchor???
二、Anchor Free系列方法简介
1. Anchor Free系列算法历史
去除Anchor后如何表示检测框呢?
Anchor-based方法中通过Anchor和对应的编码信息来表示检测框。对应的编码信息换言之也就是偏移量。
Anchor Free目前主要分如下两类表示检测框的方法:
基于关键点的检测算法
先检测目标左上角和右下角点,再通过角点组合形成检测框。
基于中心的检测算法
直接检测物体的中心区域和边界信息,将分类和回归解耦为两个子网格。
如下图所示:
这两大算法都取得了很大效果。
2. Anchor free经典算法详解
anchor_te.py
import os
import cPickle
import numpy as np
import matplotlib.pyplot as plt
root_dir = 'data/caltech/train_3'
# root_dir = 'data/caltech/test'
all_img_path = os.path.join(root_dir, 'images')
all_anno_path = os.path.join(root_dir, 'annotations_new/')
res_path_gt = 'data/cache/caltech/train_gt'
res_path_nogt = 'data/cache/caltech/train_nogt'
rows, cols = 480, 640
image_data_gt, image_data_nogt = [], []
valid_count = 0
iggt_count = 0
box_count = 0
files = sorted(os.listdir(all_anno_path))
for l in range(len(files)):
gtname = files[l]
imgname = files[l].split('.')[0]+'.jpg'
img_path = os.path.join(all_img_path, imgname)
gt_path = os.path.join(all_anno_path, gtname)
boxes = []
ig_boxes = []
with open(gt_path, 'rb') as fid:
lines = fid.readlines()
if len(lines)>1:
for i in range(1, len(lines)):
info = lines[i].strip().split(' ')
label = info[0]
occ, ignore = info[5], info[10]
x1, y1 = max(int(float(info[1])), 0), max(int(float(info[2])), 0)
w, h = min(int(float(info[3])), cols - x1 - 1), min(int(float(info[4])), rows - y1 - 1)
box = np.array([int(x1), int(y1), int(x1) + int(w), int(y1) + int(h)])
if int(ignore) == 0:
boxes.append(box)
else:
ig_boxes.append(box)
boxes = np.array(boxes)
ig_boxes = np.array(ig_boxes)
annotation = {}
annotation['filepath'] = img_path
box_count += len(boxes)
iggt_count += len(ig_boxes)
annotation['bboxes'] = boxes
annotation['ignoreareas'] = ig_boxes
if len(boxes) == 0:
image_data_nogt.append(annotation)
else:
image_data_gt.append(annotation)
valid_count += 1
print '{} images and {} valid images, {} valid gt and {} ignored gt'.format(len(files), valid_count, box_count, iggt_count)
if not os.path.exists(res_path_gt):
with open(res_path_gt, 'wb') as fid:
cPickle.dump(image_data_gt, fid, cPickle.HIGHEST_PROTOCOL)
if not os.path.exists(res_path_nogt):
with open(res_path_nogt, 'wb') as fid:
cPickle.dump(image_data_nogt, fid, cPickle.HIGHEST_PROTOCOL)
基于关键点:CornerNet、CornerNet-Lite
基于中心区域