如何處理 SSD 神經網絡在小目標檢測數據集上 mAP 和置信度較低的問題


前言

SSD 的神經網絡結構很簡潔,可以較好的實現多尺度的目標檢測,但是對小目標物體的檢測效果並不是很好。雖然有很多 SSD 的魔改版本,比如 FSSD 和 DSSD,提高了 SSD 在小目標檢測上的表現,但是這里我們只討論怎么使用 SSD 來更好地檢測小目標,尤其是那些特征非常簡單的目標。

YOLO 的啟發

在 Yolo V3 中使用了先驗框聚類的方式來決定先驗框的尺寸,而在 SSD 的原始版本中是通過公式來決定先驗框的尺寸,最小的先驗框尺寸都有 30。如果我們的目標很小,比如只有十幾像素,那么使用這些先驗框訓練出來的 SSD 模型的表現大概率是差強人意的。所以我們可以在自己的數據集上對先驗框進行聚類,下面給出聚類的代碼:

# coding:utf-8
from pathlib import Path
from xml.etree import ElementTree as ET

import numpy as np



def iou(box: np.ndarray, boxes: np.ndarray):
    """ 計算一個邊界框和其他邊界框的交並比
    Parameters
    ----------
    box: `~np.ndarray` of shape `(4, )`
        邊界框
    boxes: `~np.ndarray` of shape `(n, 4)`
        其他邊界框

    Returns
    -------
    iou: `~np.ndarray` of shape `(n, )`
        交並比
    """
    # 計算交集
    xy_max = np.minimum(boxes[:, 2:], box[2:])
    xy_min = np.maximum(boxes[:, :2], box[:2])
    inter = np.clip(xy_max-xy_min, a_min=0, a_max=np.inf)
    inter = inter[:, 0]*inter[:, 1]

    # 計算並集
    area_boxes = (boxes[:, 2]-boxes[:, 0])*(boxes[:, 3]-boxes[:, 1])
    area_box = (box[2]-box[0])*(box[3]-box[1])

    # 計算 iou
    iou = inter/(area_box+area_boxes-inter)  # type: np.ndarray
    return iou


class AnchorKmeans:
    """ 先驗框聚類 """

    def __init__(self, annotation_dir: str):
        self.annotation_dir = Path(annotation_dir)
        if not self.annotation_dir.exists():
            raise ValueError(f'標簽文件夾 `{annotation_dir}` 不存在')

        self.bbox = self.get_bbox()

    def get_bbox(self) -> np.ndarray:
        """ 獲取所有的邊界框 """
        bbox = []

        for path in self.annotation_dir.glob('*.xml'):
            root = ET.parse(path).getroot()

            # 圖像的寬度和高度
            w = int(root.find('size/width').text)
            h = int(root.find('size/height').text)

            # 獲取所有邊界框
            for obj in root.iter('object'):
                box = obj.find('bndbox')

                # 歸一化坐標
                xmin = int(box.find('xmin').text)/w
                ymin = int(box.find('ymin').text)/h
                xmax = int(box.find('xmax').text)/w
                ymax = int(box.find('ymax').text)/h

                bbox.append([0, 0, xmax-xmin, ymax-ymin])

        return np.array(bbox)

    def get_cluster(self, n_clusters=9, metric=np.median):
        """ 獲取聚類結果

        Parameters
        ----------
        n_clusters: int
            聚類數

        metric: callable
            選取聚類中心點的方式
        """
        rows = self.bbox.shape[0]

        if rows < n_clusters:
            raise ValueError("n_clusters 不能大於邊界框樣本數")

        last_clusters = np.zeros(rows)
        clusters = np.ones((n_clusters, 2))
        distances = np.zeros((rows, n_clusters))  # type:np.ndarray

        # 隨機選取出幾個點作為聚類中心
        np.random.seed(1)
        clusters = self.bbox[np.random.choice(rows, n_clusters, replace=False)]

        # 開始聚類
        while True:
            # 計算距離
            distances = 1-self.iou(clusters)

            # 將每一個邊界框划到一個聚類中
            nearest_clusters = distances.argmin(axis=1)

            # 如果聚類中心不再變化就退出
            if np.array_equal(nearest_clusters, last_clusters):
                break

            # 重新選取聚類中心
            for i in range(n_clusters):
                clusters[i] = metric(self.bbox[nearest_clusters == i], axis=0)

            last_clusters = nearest_clusters

        return clusters[:, 2:]

    def average_iou(self, clusters: np.ndarray):
        """ 計算 IOU 均值

        Parameters
        ----------
        clusters: `~np.ndarray` of shape `(n_clusters, 2)`
            聚類中心
        """
        clusters = np.hstack((np.zeros((clusters.shape[0], 2)), clusters))
        return np.mean([np.max(iou(bbox, clusters)) for bbox in self.bbox])

    def iou(self, clusters: np.ndarray):
        """ 計算所有邊界框和所有聚類中心的交並比

        Parameters
        ----------
        clusters: `~np.ndarray` of shape `(n_clusters, 4)`
            聚類中心

        Returns
        -------
        iou: `~np.ndarray` of shape `(n_bbox, n_clusters)`
            交並比
        """
        bbox = self.bbox
        A = self.bbox.shape[0]
        B = clusters.shape[0]

        xy_max = np.minimum(bbox[:, np.newaxis, 2:].repeat(B, axis=1),
                            np.broadcast_to(clusters[:, 2:], (A, B, 2)))
        xy_min = np.maximum(bbox[:, np.newaxis, :2].repeat(B, axis=1),
                            np.broadcast_to(clusters[:, :2], (A, B, 2)))

        # 計算交集面積
        inter = np.clip(xy_max-xy_min, a_min=0, a_max=np.inf)
        inter = inter[:, :, 0]*inter[:, :, 1]

        # 計算每個矩陣的面積
        area_bbox = ((bbox[:, 2]-bbox[:, 0])*(bbox[:, 3] -
                     bbox[:, 1]))[:, np.newaxis].repeat(B, axis=1)
        area_clusters = ((clusters[:, 2] - clusters[:, 0])*(
            clusters[:, 3] - clusters[:, 1]))[np.newaxis, :].repeat(A, axis=0)

        return inter/(area_bbox+area_clusters-inter)


if __name__ == '__main__':
    # 標簽文件夾
    root = 'data/Hotspot/Annotations'
    model = AnchorKmeans(root)
    clusters = model.get_cluster(9)

    # 將先驗框還原為原本的大小
    print('聚類結果:\n', clusters*300)
    print('平均 IOU:', model.average_iou(clusters))

將代碼中的先驗框尺寸參照聚類的結果進行修改,不出意外的話是可以提升 mAP 和置信度的,以上~~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM