基于普通目标检测的有向目标检测


注:由于该文章基于2020年十月份的火箭军科四初赛而写,所以有部分内容是介绍比赛,文中没有实例图样,若官方要求下架请联系我。

赛程简介

参赛时间

2020年的比赛分为预热赛(10月1日-10月10日)、初赛(10月10日-10月25日)、决赛(…)。(本人只是研一的小渣渣,并没有过初赛,所以如果需要直接性的技术指导烦请绕道,本文仅针对本团队的方法总结)

数据概述

  1. 预热赛阶段比赛方发放720张训练样本(均为卫星影像,1024*1024),这720张图片均为海上船只对象,其中类别分为五类,官方没有提供类别说明(此处仅透露官方提供的说明)
  2. 初赛的数据官方发布了2008张训练样本(均为卫星影像,10241024)和260张待检测图片(均为卫星影像,10241024),其检测类别与预热赛一致,其中的2008张为带标签样本,对260张不带标签图片的识别结果的准确率(IOU>0.7算有效成绩,最终结果以总mAP为准)作为最终评判标准。
  3. 决赛数据:…
  4. 比赛提交数据说明:提交结果为对给定的260张图片的识别结果,其中以提交一个txt文档的形式提交,txt文档中包含image_name class 4_point中间以空格隔开,4个点的顺序不做要求

预热赛和初赛的数据样本均为tif格式图片,label标签为txt文本,标注格式为a.tif对应a.txt,其中txt文档内包含图片的类别标签(1-5),目标对象的四个顶角坐标(无固定的顺序,但是99%的label是按顺时针连续点标注的),此时分析label数据可知其目标框Bounding Box为四边形(不一定是平行于图像边框的矩形)。
图像数据分析至此就产生了一个问题,目标对象的Bounding Box为四边形,而且边框可能为非Horizontal Bounding Box(HBB)且比赛中的有效成绩判定的IOU值要大于0.7,这样来看普通的目标检测是否能行吗?用HBB是否能解决问题?
普通目标检测的方法大多基于顶点+边宽、高(或左上顶点+右下顶点或中心点+边宽、高)的方式来处理,这就使得其检测的对象的Bounding Box固定为HBB,而卫星影像的船只方向是不确定,如果采用普通目标检测,IOU的值无论如何都不能保证大于0.7。上图可知不定向船只的IOU在HBB中很难达到0.7
此时本人转向与对有向物体的目标检测。

方法介绍

为了取得比赛比较好的成绩,本人先后尝试了有三种方法,本人将逐一对本人试过的方法进行介绍,在每个方法的结尾将对方法做评估,评估会以斜体字的方式展现给大家,但是遗憾的是本次比赛数据为保密数据,本人没办法将真实检测结果展示给大家(后续也不会再更新),但是本人会把本人尝试过的项目、论文地址附上。

基于图像分割(Instance segmentation)的方法(two-stage)

这个方法看名称就知道大概怎么做的了,方法很简单,利用现有的实例分割模型直接进行图像的实例分割;这个方法能够将大部分卫星图像的船只边缘分离出来,但是由于图像分割出来的实例为不止四个顶点,我们所提交的结果只需要四个顶点即可,所以采用opencv的内置方法cv2.minAreaRect()方法来求最小外接矩形。
本人直接采用facebook官方提供的detectron2方法来实现这种做法,该方法是采用很强大的MaskRCNN方式来做的,其具体实现细节我也没怎么搞清楚,因为这个需要读懂一系列的文章,本人才读到RCNN而已,后续有时间我会更新自己对这一些列方法的自我理解。
项目地址:detectron2
使用方法请参考他们的官方说明(本人写的肯定没他们团队写的好),这个方法中本人仅做了一些注册数据集的工作,然后就是改yaml配置文件内容,注册数据集的方式如果不会请参考文章:【detectron2】注册、训练、推断自己的数据集
方法评估:该方法能够将50%的目标完整的定位、分割出来,并且很神奇的是可以仅用四个顶点进行实例分割,可能是神奇的MaskRCNN的能力吧,但是这种方法存在严重的缺陷(由本人对识别结果的直观分析得到,我称它为形象标准),当目标过于密集且图像中的物体很小时,图像分割会把密集的物体当作一个物体来分割,也就是密集物体不能完全分割,这个是本人从最后图像中识别结果中分析得到的(图像由于保密原因不能提供),以下例图会展示本人所说明的缺陷,另外一张图为本人再训练集中分出的333张验证集的验证结果(其为量化标准)。
问题
问题如上述图像所示
这个是270000次迭代大概3天的检测结果,各项AP和总mAP的量化结果均在此处,IOU为比赛标准值0.7

这个是270000次迭代大概3天的检测结果,各项AP和总mAP的量化结果均在此处,IOU为比赛标准值0.7。yaml是基于他的configs/Base-RCNN-FPN.yaml文件的改写版,若需要请留言

基于中心点+半边长、宽+angle的方法(one-stage)

这个方法是基于一篇论文实现的,论文中作者将自己的代码开源,所以很我们能很方便的使用,而整篇博客的重点将落在此处,我将重点解释这种方法。
paper:Oriented Object Detection in Aerial Images with Box Boundary-Aware Vectors
code:BBAVectors-Oriented-Object-Detection
该方法以DOTA数据集为原始样本,最终的mAP为88.22,Paper是本年度很新的一篇文章(2020年8月发表)。
该方法采用强大的ResNet101方法作为特征提取层,将输入图片提取特征值,但是要注意的是该方法最终并不是输出batch_size*classes/others*1*1的结果,而是最终得到一个输入图像缩小四倍的一个“小图像”(且称之为图像),再在这个input.width/4*input.height/4的小图像层上提取多次极大值获得最终的结果,也即识别结果为裸回归得到的,这也就是one-stage方法。
首先大家可以先感受一下这个模型的网络层架构,如下图(作者论文中文字叙述和立体图像解释,本人将其2d的网络结构手工画了一份,本人大概估计了一下应该没毛病,如有人发现有问题请在评论区指出,本人万分感激)
网络层结构
首先作者将ResNet101的四个layer的输出(下采样down-sample)都记录下来,然后将每层的输出都送入到一个如上图所示的backblock块中再进行类似反卷积的操作(上采样up-sample),这时你会发现backblock1的两个输入的维度不一致对吧,但是这是能拼在一起卷积的(详细操作在code里有,我这里用backblock中的concat代替,但真实方法名不是concate),经过大概三个backblock以后得到一个公共输出层(就是上图颜色最多的输入箭头处),若按论文中给定的图像大小608*608计算,则公共输出层为batch_size*256*608/4*608/4=batch_size*256*152*152(按channel_first的来计算),但是对于本次比赛的图像则会输出一个batch_size*256*256*256大小的特征向量(记住此时的256*256,后面本人会讨论对这个模型的调整)。
到这里以后,作者再对这个公共输出层做四次卷积,而这次卷积得到的就是最终的结果。首先是计算得到Heatmap,这个是用来预测Bounding Box中物体的类别(即对每类标签的置信度),并且作者在论文中提到这个值会影响后面offset的值,这个输出也会预测到不准确的center point;box层得到的输出是预测的Bounding Box向量,这个向量用一组代表值来表示,其为 [top-point, right-point, bottom-point, left-point, width, height] 六个值构成的向量,作者称之为BBAVectors;offset层输出的是对heatmap层输出的中心点的偏置值(即heatmap粗略估计,后面offset补上误差);angle层输出偏置角。
到此网络层输出最终结果,看他输出的其实在box层就计算得到了四个顶角的位置,那为什么还要求angle和中心点呢?作者好像在方法论开篇说过一个问题就是这个方法在解决Bounding Box为HBB的时候存在很难准确定位的问题,本人估计是为了解决这个问题而设计的,但是作者没有明确给出为什么需要再测angle和中心点,这只是本人的猜测。
网络层架构完以后是他的loss计算方式,很容易从网络层的输出知道这个模型大概有四个loss,然后用叠加的方式计算整体的loss。loss值的计算方式是采用BECLOSS的计算方式,并不是单个的交叉熵,这个具体怎么计算的我没有仔细研究,只是有一个理性的了解,读者可以翻阅论文查看详细的loss求解。
接下来就来介绍本人怎么使用这个方法以及用这个方法在这次比赛中得到的结果:
本人得到的最后的训练结果如下,其AP、mAP的计算都是在训练样本上截取的333张验证集的计算结果。
在这里插入图片描述
貌似结果还不错对吧,实际上可能是因为这个挑出来的验证集有部分特殊原因,导致我们最终在260张上的mAP仅仅只有58.9,所以这个的计算值并不能完全相信。
说了这么多的理论和结果,到头来自己像是一行代码没写就拿别人的方法来做一样,这也不太好,但是本人确实就这么干的,因为本人暂时研一还没有能力自己完全实现一个这种大任务的代码。本人就把自己写的关于如何将比赛数据集labels、image读入程序的代码公布出来吧,其实大部分都是套用这个方法写好的内容然后修修补补。若采用其他的数据集请记住这个方法的标注格式为:a.tif–>a.txt, a.txt:class 4_point,四个点必须是顺时针/逆时针连续的点。

FILE_PATH: path/to/BBAVectors-Oriented-Object-Detection/datasets/dataset_rssj4.py 
from .base import BaseDataset
import os
import cv2
import numpy as np

from .DOTA_devkit import polyiou


class Rssj4(BaseDataset):

    def __init__(self, data_dir, phase, input_h=None, input_w=None, down_ratio=None):
        super(Rssj4, self).__init__(data_dir, phase, input_h, input_w, down_ratio)
        self.category = ['ship_0',
                         'ship_1',
                         'ship_2',
                         'ship_3',
                         'ship_4']
        self.color_pans = [(204, 78, 210),
                           (0, 192, 255),
                           (0, 131, 0),
                           (240, 176, 0),
                           (254, 100, 38)]
        self.num_classes = len(self.category)
        self.cat_ids = {
   cat: i for i, cat in enumerate(self.category)}
        self.img_ids = self.load_img_ids()
        self.image_path = os.path.join(data_dir, 'images')
        self.val_path = os.path.join(data_dir, 'test')
        self.label_path = os.path.join(data_dir, 'labels')

    def load_img_ids(self):
        image_set_index_file = os.path.join(self.data_dir, self.phase + '.txt')
        assert os.path.exists(image_set_index_file), 'Path does not exist: {}'.format(image_set_index_file)
        with open(image_set_index_file, 'r') as f:
            lines = f.readlines()
        image_lists = [line.strip() for line in lines]
        return image_lists

    def load_image(self, index):
        img_id = self.img_ids[index]
        imgFile = os.path.join(self.image_path, img_id + '.tif')
        assert os.path.exists(imgFile), 'image {} not existed'.format(imgFile)
        img = cv2.imread(imgFile)
        return img

    def load_annoFolder(self, img_id):
        return os.path.join(self.label_path, img_id + '.txt')

    def load_annotation(self, index):
        """
        我需要将所有的label文件合并为一个,格式为:
        ###################################
        x0 y0 x1 y1 x2 y2 x3 y3 category_id difficult(这个值默认设置为1;1表示图像复杂,0表示不复杂)
        (其中点的顺序为top-left,top-right,bottom-right,bottom-left)
        ###################################
        合并后的文件名命名为train.txt
        """
        image = self.load_image(index)
        h,w,c = image.shape
        valid_pts = []
        valid_cat = []
        valid_dif = []
        with open(self.load_annoFolder(self.img_ids[index]), 'r') as f:
            for i, line in enumerate(f.readlines()):
                obj = line.split(' ')  # list object
                if len(obj)>8:
                    x1 = min(max(float(obj[1]), 0), w - 1)
                    y1 = min(max(float(obj[2]), 0), h - 1)
                    x2 = min(max(float(obj[3]), 0), w - 1)
                    y2 = min(max(float(obj[4]), 0), h - 1)
                    x3 = min(max(float(obj[5]), 0), w - 1)
                    y3 = min(max(float(obj[6]), 0), h - 1)
                    x4 = min(max(float(obj[7]), 0), w - 1)
                    y4 = min(max(float(obj[8]), 0), h - 1)
                    # TODO: filter small instances
                    xmin = max(min(x1, x2, x3, x4), 0)
                    xmax = max(x1, x2, x3, x4)
                    ymin = max(min(y1, y2, y3, y4), 0)
                    ymax = max(y1, y2, y3, y4)

                    # 目标是找到bl, tl, tr, br
                    pst = [[x1, y1], [x2, y2], [x3, y3], [x4, y4]]
                    # pst经sort排序以后得到的只是左右确定的序列
                    valid_pts.append(pst)
                    valid_cat.append(int(obj[0]) - 1)
                    if ((xmax - xmin) > 10) and ((ymax - ymin) > 10):
                        valid_dif.append(0)
                    else:
                        valid_dif.append(1)
        f.close()
        annotation = {
   }
        annotation['pts'] = np.asarray(valid_pts, np.float32)
        annotation['cat'] = np.asarray(valid_cat, np.int32)
        annotation['dif'] = np.asarray(valid_dif, np.int32)

        return annotation

    def dec_evaluation(self, result_path):
        detpath = os.path.join(result_path, 'Task1_{}.txt')
        annopath = os.path.join(self.label_path, '{}.txt')
        imagesetfile = os.path.join(self.data_dir, 'test.txt')
        classaps = []
        map = 0
        for classname in self.category:
            if classname == 'background':
                continue
            print('classname:', classname)
            rec, prec, ap = self.voc_eval(detpath,
                                     annopath,
                                     imagesetfile,
                                     classname,
                                     ovthresh=0.5,
                                     use_07_metric=True)
            map = map + ap
            #print('rec: ', rec, 'prec: ', prec, 'ap: ', ap)
            print('{}:{} '.format(classname, ap*100))
            classaps.append(ap)
        map = map / len(self.category)
        print('map:', map * 100)
        
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值