主頁 >  其他 > 目標檢測模型的基礎

目標檢測模型的基礎

2022-12-02 06:57:45 其他

  • 前言
  • 一,anchor box
  • 二,IOU
  • 三,Focal Loss
    • 3.1,Cross Entropy
    • 3.2,Balanced Cross Entropy
    • 3.3,Focal Loss Definition
  • 四,NMS
    • 4.1,NMS 介紹
    • 4.2,NMS 演算法程序
  • 五,Soft NMS 演算法
  • 六,目標檢測的不平衡問題
    • 6.1,介紹
  • 參考資料

前言

邊界框:在?標檢測?,我們通常使?邊界框(bounding box,縮寫是 bbox)來描述?標位置,邊界框是?個矩形框,可以由矩形左上?的 xy 軸坐標與右下?的 xy 軸坐標確定,

檢測網路中的一些術語解釋:

  1. backbone:翻譯為主干網路,主要指用來做特征提取作用的網路,早期分類網路 VGGResNet 等去掉用于分類的全連接層的部分就是 backbone 網路,
  2. neck: 指放在 backbonehead 之間的網路,作用是更好的融合/利用 backbone 提取的 feature,可以理解為特征增強模塊,典型的 neck 是如 FPN 結構,
  3. head:檢測頭,輸出想要結果(分類+定位)的網路,放在模型最后,如 YOLO 使用特定維度的 conv 獲取目標的類別和 bbox 資訊,

一,anchor box

?標檢測演算法通常會在輸?影像中采樣?量的區域,然后判斷這些區域中是否包含我們感興趣的?標,并調整區域邊緣從而更準確地預測?標的真實邊界框(ground-truth bounding box),不同的模型使?的區域采樣?法可能不同,兩階段檢測模型常用的?種?法是:以每個像素為中??成多個?小和寬??(aspect ratio)不同的邊界框,這些邊界框被稱為錨框(anchor box),

Faster RCNN 模型中,每個像素都生成 9 個大小和寬高比都不同的 anchors,在代碼中,anchors 是一組由 generate_anchors.py 生成的矩形框串列,其中每行的 4 個值 (x1,y1,x2,y2) 表示矩形左上和右下角點坐標,9 個矩形共有 3 種形狀,長寬比為大約為 {1:1, 1:2, 2:1} 三種, 實際上通過 anchors 就引入了檢測中常用到的多尺度方法,generate_anchors.py 的代碼如下:

注意,這里生成的只是 base anchors,其中一個 框的左上角坐標為 (0,0) 坐標(特征圖左上角)的 9 個 anchor,后續還需網格化(meshgrid)生成其他 anchor,同一個 scale,但是不同的 anchor ratios 生成的 anchors 面積理論上是要一樣的,

import numpy as np
import six
from six import __init__  # 兼容python2和python3模塊


def generate_anchor_base(base_size=16, ratios=[0.5, 1, 2],
                         anchor_scales=[8, 16, 32]):
    """Generate base anchors by enumerating aspect ratio and scales.

    Args:
        base_size (number): The width and the height of the reference window.
        ratios (list of floats): anchor 的寬高比
        anchor_scales (list of numbers): anchor 的尺度

    Returns: Base anchors in a single-level feature maps.`(R, 4)`.
        bounding box is `(x_{min}, y_{min}, x_{max}, y_{max})`
    """
    import numpy as np
    py = base_size / 2.
    px = base_size / 2.

    anchor_base = np.zeros((len(ratios) * len(anchor_scales), 4),
                           dtype=np.float32)
    for i in six.moves.range(len(ratios)):
        for j in six.moves.range(len(anchor_scales)):
            // 乘以感受野值,得到縮放后的 anchor 大小
            h = base_size * anchor_scales[j] * np.sqrt(ratios[i])
            w = base_size * anchor_scales[j] * np.sqrt(1. / ratios[i])

            index = i * len(anchor_scales) + j
            anchor_base[index, 0] = px - w / 2.
            anchor_base[index, 1] = py - h / 2.

            anchor_base[index, 2] = px + h / 2.
            anchor_base[index, 3] = py + w / 2.
    return anchor_base


# test
if __name__ == "__main__":
    bbox_list = generate_anchor_base()
    print(bbox_list)

程式運行輸出如下:

[[ -82.50967 -37.254833 53.254833 98.50967 ]
[-173.01933 -82.50967 98.50967 189.01933 ]
[-354.03867 -173.01933 189.01933 370.03867 ]
[ -56. -56. 72. 72. ]
[-120. -120. 136. 136. ]
[-248. -248. 264. 264. ]
[ -37.254833 -82.50967 98.50967 53.254833]
[ -82.50967 -173.01933 189.01933 98.50967 ]
[-173.01933 -354.03867 370.03867 189.01933 ]]

二,IOU

交并比(Intersection-over-Union,IoU),目標檢測中使用的一個概念,是模型產生的候選框(candidate bound)與原標記框(ground truth bound)的交疊率,即它們的交集與并集的比值,最理想情況是完全重疊,即比值為 1,計算公式如下:

IOU計算公式

代碼實作如下:

# _*_ coding:utf-8 _*_
# 計算iou

"""
bbox的資料結構為(xmin,ymin,xmax,ymax)--(x1,y1,x2,y2),
每個bounding box的左上角和右下角的坐標
輸入:
    bbox1, bbox2: Single numpy bounding box, Shape: [4]
輸出:
    iou值
"""
import numpy as np
import cv2

def iou(bbox1, bbox2):
    """
    計算兩個bbox(兩框的交并比)的iou值
    :param bbox1: (x1,y1,x2,y2), type: ndarray or list
    :param bbox2: (x1,y1,x2,y2), type: ndarray or list
    :return: iou, type float
    """
    if type(bbox1) or type(bbox2) != 'ndarray':
        bbox1 = np.array(bbox1)
        bbox2 = np.array(bbox2)

    assert bbox1.size == 4 and bbox2.size == 4, "bounding box coordinate size must be 4"
    xx1 = np.max((bbox1[0], bbox2[0]))
    yy1 = np.min((bbox1[1], bbox2[1]))
    xx2 = np.max((bbox1[2], bbox2[2]))
    yy2 = np.min((bbox1[3], bbox2[3]))
    bwidth = xx2 - xx1
    bheight = yy2 - yy1
    area = bwidth * bheight  # 求兩個矩形框的交集
    union = (bbox1[2] - bbox1[0])*(bbox1[3] - bbox1[1]) + (bbox2[2] - bbox2[0])*(bbox2[3] - bbox2[1]) - area  # 求兩個矩形框的并集
    iou = area / union

    return iou


if __name__=='__main__':
    rect1 = (461, 97, 599, 237)
    # (top, left, bottom, right)
    rect2 = (522, 127, 702, 257)
    iou_ret = round(iou(rect1, rect2), 3) # 保留3位小數
    print(iou_ret)

    # Create a black image
    img=np.zeros((720,720,3), np.uint8)
    cv2.namedWindow('iou_rectangle')
    """
    cv2.rectangle 的 pt1 和 pt2 引數分別代表矩形的左上角和右下角兩個點,
    coordinates for the bounding box vertices need to be integers if they are in a tuple,
    and they need to be in the order of (left, top) and (right, bottom). 
    Or, equivalently, (xmin, ymin) and (xmax, ymax).
    """
    cv2.rectangle(img,(461, 97),(599, 237),(0,255,0),3)
    cv2.rectangle(img,(522, 127),(702, 257),(0,255,0),3)
    font  = cv2.FONT_HERSHEY_SIMPLEX
    cv2.putText(img, 'IoU is ' + str(iou_ret), (341,400), font, 1,(255,255,255),1)
    cv2.imshow('iou_rectangle', img)
    cv2.waitKey(0)

代碼輸出結果如下所示:

程式運行結果

三,Focal Loss

Focal Loss 是在二分類問題的交叉熵(CE)損失函式的基礎上引入的,所以需要先學習下交叉熵損失的定義,

3.1,Cross Entropy

在深度學習中我們常使用交叉熵來作為分類任務中訓練資料分布和模型預測結果分布間的代價函式,對于同一個離散型隨機變數 \(\textrm{x}\) 有兩個單獨的概率分布 \(P(x)\)\(Q(x)\),其交叉熵定義為:

P 表示真實分布, Q 表示預測分布,

\[H(P,Q) = \mathbb{E}_{\textrm{x}\sim P} log Q(x)= -\sum_{i}P(x_i)logQ(x_i) \tag{1} \]

但在實際計算中,我們通常不這樣寫,因為不直觀,在深度學習中,以二分類問題為例,其交叉熵損失(CE)函式如下:

\[Loss = L(y, p) = -ylog(p)-(1-y)log(1-p) \tag{2} \]

其中 \(p\) 表示當預測樣本等于 \(1\) 的概率,則 \(1-p\) 表示樣本等于 \(0\) 的預測概率,因為是二分類,所以樣本標簽 \(y\) 取值為 \(\{1,0\}\),上式可縮寫至如下:

\[CE = \left\{\begin{matrix} -log(p), & if \quad y=1 \\ -log(1-p), & if\quad y=0 \tag{3} \end{matrix}\right. \]

為了方便,用 \(p_t\) 代表 \(p\)\(p_t\) 定義如下:

\[p_t = \{\begin{matrix} p, & if \quad y=1\\ 1-p, & if\quad y=0 \end{matrix} \]

\((3)\)式可寫成:

\[CE(p, y) = CE(p_t) = -log(p_t) \tag{4} \]

前面的交叉熵損失計算都是針對單個樣本的,對于所有樣本,二分類的交叉熵損失計算如下:

\[L = \frac{1}{N}(\sum_{y_i = 1}^{m}-log(p)-\sum_{y_i = 0}^{n}log(1-p)) \]

其中 \(m\) 為正樣本個數,\(n\) 為負樣本個數,\(N\) 為樣本總數,\(m+n=N\),當樣本類別不平衡時,損失函式 \(L\) 的分布也會發生傾斜,如 \(m \ll n\) 時,負樣本的損失會在總損失占主導地位,又因為損失函式的傾斜,模型訓練程序中也會傾向于樣本多的類別,造成模型對少樣本類別的性能較差,

再衍生以下,對于所有樣本,多分類的交叉熵損失計算如下:

\[L = \frac{1}{N} \sum_i^N L_i = -\frac{1}{N}(\sum_i \sum_{c=1}^M y_{ic}log(p_{ic}) \]

其中,\(M\) 表示類別數量,\(y_{ic}\) 是符號函式,如果樣本 \(i\) 的真實類別等于 \(c\) 取值 1,否則取值 0; \(p_{ic}\) 表示樣本 \(i\) 預測為類別 \(c\) 的概率,

對于多分類問題,交叉熵損失一般會結合 softmax 激活一起實作,PyTorch 代碼如下,代碼出自這里,


import numpy as np

# 交叉熵損失
class CrossEntropyLoss():
    """
    對最后一層的神經元輸出計算交叉熵損失
    """
    def __init__(self):
        self.X = None
        self.labels = None
    
    def __call__(self, X, labels):
        """
        引數:
            X: 模型最后fc層輸出
            labels: one hot標注,shape=(batch_size, num_class)
        """
        self.X = X
        self.labels = labels

        return self.forward(self.X)
    
    def forward(self, X):
        """
        計算交叉熵損失
        引數:
            X:最后一層神經元輸出,shape=(batch_size, C)
            label:資料onr-hot標注,shape=(batch_size, C)
        return:
            交叉熵loss
        """
        self.softmax_x = self.softmax(X)
        log_softmax = self.log_softmax(self.softmax_x)
        cross_entropy_loss = np.sum(-(self.labels * log_softmax), axis=1).mean()
        return cross_entropy_loss
    
    def backward(self):
        grad_x =  (self.softmax_x - self.labels)  # 回傳的梯度需要除以batch_size
        return grad_x / self.X.shape[0]
        
    def log_softmax(self, softmax_x):
        """
        引數:
            softmax_x, 在經過softmax處理過的X
        return: 
            log_softmax處理后的結果shape = (m, C)
        """
        return np.log(softmax_x + 1e-5)
    
    def softmax(self, X):
        """
        根據輸入,回傳softmax
        代碼利用softmax函式的性質: softmax(x) = softmax(x + c)
        """
        batch_size = X.shape[0]
        # axis=1 表示在二維陣列中沿著橫軸進行取最大值的操作
        max_value = https://www.cnblogs.com/armcvai/p/X.max(axis=1)
        #每一行減去自己本行最大的數字,防止取指數后出現inf,性質:softmax(x) = softmax(x + c)
        # 一定要新定義變數,不要用-=,否則會改變輸入X,因為在呼叫計算損失時,多次用到了softmax,input不能改變
        tmp = X - max_value.reshape(batch_size, 1)
        # 對每個數取指數
        exp_input = np.exp(tmp)  # shape=(m, n)
        # 求出每一行的和
        exp_sum = exp_input.sum(axis=1, keepdims=True)  # shape=(m, 1)
        return exp_input / exp_sum

3.2,Balanced Cross Entropy

對于正負樣本不平衡的問題,較為普遍的做法是引入 \(\alpha \in(0,1)\) 引數來解決,上面公式重寫如下:

\[CE(p_t) = -\alpha log(p_t) = \left\{\begin{matrix} -\alpha log(p), & if \quad y=1\\ -(1-\alpha)log(1-p), & if\quad y=0 \end{matrix}\right. \]

對于所有樣本,二分類的平衡交叉熵損失函式如下:

\[L = \frac{1}{N}(\sum_{y_i = 1}^{m}-\alpha log(p)-\sum_{y_i = 0}^{n}(1 - \alpha) log(1-p)) \]

其中 \(\frac{\alpha}{1-\alpha} = \frac{n}{m}\),即 \(\alpha\) 引數的值是根據正負樣本分布比例來決定的,

3.3,Focal Loss Definition

雖然 \(\alpha\) 引數平衡了正負樣本(positive/negative examples),但是它并不能區分難易樣本(easy/hard examples),而實際上,目標檢測中大量的候選目標都是易分樣本,這些樣本的損失很低,但是由于難易樣本數量極不平衡,易分樣本的數量相對來講太多,最終主導了總的損失,而本文的作者認為,易分樣本(即,置信度高的樣本)對模型的提升效果非常小,模型應該主要關注與那些難分樣本(這個假設是有問題的,是 GHM 的主要改進物件)

Focal Loss 作者建議在交叉熵損失函式上加上一個調整因子(modulating factor\((1-p_t)^\gamma\),把高置信度 \(p\)(易分樣本)樣本的損失降低一些,Focal Loss 定義如下:

\[FL(p_t) = -(1-p_t)^\gamma log(p_t) = \{\begin{matrix} -(1-p)^\gamma log(p), & if \quad y=1\\ -p^\gamma log(1-p), & if\quad y=0 \end{matrix} \]

Focal Loss 有兩個性質:

  • 當樣本被錯誤分類且 \(p_t\) 值較小時,調制因子接近于 1loss 幾乎不受影響;當 \(p_t\) 接近于 1,調質因子(factor)也接近于 0容易分類樣本的損失被減少了權重,整體而言,相當于增加了分類不準確樣本在損失函式中的權重,
  • \(\gamma\) 引數平滑地調整容易樣本的權重下降率,當 \(\gamma = 0\) 時,Focal Loss 等同于 CE Loss\(\gamma\) 在增加,調制因子的作用也就增加,實驗證明 \(\gamma = 2\) 時,模型效果最好,

直觀地說,調制因子減少了簡單樣本的損失貢獻,并擴大了樣本獲得低損失的范圍,例如,當\(\gamma = 2\) 時,與 \(CE\) 相比,分類為 \(p_t = 0.9\) 的樣本的損耗將降低 100 倍,而當 \(p_t = 0.968\) 時,其損耗將降低 1000 倍,這反過來又增加了錯誤分類樣本的重要性(對于 \(pt≤0.5\)\(\gamma = 2\),其損失最多減少 4 倍),在訓練程序關注物件的排序為正難 > 負難 > 正易 > 負易,

難易正負樣本

在實踐中,我們常采用帶 \(\alpha\)Focal Loss

\[FL(p_t) = -\alpha (1-p_t)^\gamma log(p_t) \]

作者在實驗中采用這種形式,發現它比非 \(\alpha\) 平衡形式(non-\(\alpha\)-balanced)的精確度稍有提高,實驗表明 \(\gamma\) 取 2,\(\alpha\) 取 0.25 的時候效果最佳,

網上有各種版本的 Focal Loss 實作代碼,大多都是基于某個深度學習框架實作的,如 PytorchTensorFlow,我選取了一個較為清晰的代碼作為參考,代碼來自 這里,

后續有必要自己實作以下,有時間還要去看看 Caffe 的實作,

# -*- coding: utf-8 -*-
# @Author  : LG
from torch import nn
import torch
from torch.nn import functional as F

class focal_loss(nn.Module):
    def __init__(self, alpha=0.25, gamma=2, num_classes = 3, size_average=True):
        """
        focal_loss損失函式, -α(1-yi)**γ *ce_loss(xi,yi)
        步驟詳細的實作了 focal_loss損失函式.
        :param alpha:   阿爾法α,類別權重.      當α是串列時,為各類別權重,當α為常數時,類別權重為[α, 1-α, 1-α, ....],常用于 目標檢測演算法中抑制背景類 , retainnet中設定為0.25
        :param gamma:   伽馬γ,難易樣本調節引數. retainnet中設定為2
        :param num_classes:     類別數量
        :param size_average:    損失計算方式,默認取均值
        """
        super(focal_loss,self).__init__()
        self.size_average = size_average
        if isinstance(alpha,list):
            assert len(alpha)==num_classes   # α可以以list方式輸入,size:[num_classes] 用于對不同類別精細地賦予權重
            print(" --- Focal_loss alpha = {}, 將對每一類權重進行精細化賦值 --- ".format(alpha))
            self.alpha = torch.Tensor(alpha)
        else:
            assert alpha<1   #如果α為一個常數,則降低第一類的影響,在目標檢測中為第一類
            print(" --- Focal_loss alpha = {} ,將對背景類進行衰減,請在目標檢測任務中使用 --- ".format(alpha))
            self.alpha = torch.zeros(num_classes)
            self.alpha[0] += alpha
            self.alpha[1:] += (1-alpha) # α 最終為 [ α, 1-α, 1-α, 1-α, 1-α, ...] size:[num_classes]

        self.gamma = gamma

    def forward(self, preds, labels):
        """
        focal_loss損失計算
        :param preds:   預測類別. size:[B,N,C] or [B,C]    分別對應與檢測與分類任務, B 批次, N檢測框數, C類別數
        :param labels:  實際類別. size:[B,N] or [B],為 one-hot 編碼格式
        :return:
        """
        # assert preds.dim()==2 and labels.dim()==1
        preds = preds.view(-1,preds.size(-1))
        self.alpha = self.alpha.to(preds.device)
        preds_logsoft = F.log_softmax(preds, dim=1) # log_softmax
        preds_softmax = torch.exp(preds_logsoft)    # softmax

        preds_softmax = preds_softmax.gather(1,labels.view(-1,1))
        preds_logsoft = preds_logsoft.gather(1,labels.view(-1,1))
        self.alpha = self.alpha.gather(0,labels.view(-1))
        loss = -torch.mul(torch.pow((1-preds_softmax), self.gamma), preds_logsoft)  # torch.pow((1-preds_softmax), self.gamma) 為focal loss中 (1-pt)**γ

        loss = torch.mul(self.alpha, loss.t())
        if self.size_average:
            loss = loss.mean()
        else:
            loss = loss.sum()
        return loss

mmdetection 框架給出的 focal loss 代碼如下(有所刪減):

# This method is only for debugging
def py_sigmoid_focal_loss(pred,
                          target,
                          weight=None,
                          gamma=2.0,
                          alpha=0.25,
                          reduction='mean',
                          avg_factor=None):
    """PyTorch version of `Focal Loss <https://arxiv.org/abs/1708.02002>`_.
    Args:
        pred (torch.Tensor): The prediction with shape (N, C), C is the
            number of classes
        target (torch.Tensor): The learning label of the prediction.
        weight (torch.Tensor, optional): Sample-wise loss weight.
        gamma (float, optional): The gamma for calculating the modulating
            factor. Defaults to 2.0.
        alpha (float, optional): A balanced form for Focal Loss.
            Defaults to 0.25.
        reduction (str, optional): The method used to reduce the loss into
            a scalar. Defaults to 'mean'.
        avg_factor (int, optional): Average factor that is used to average
            the loss. Defaults to None.
    """
    pred_sigmoid = pred.sigmoid()
    target = target.type_as(pred)
    pt = (1 - pred_sigmoid) * target + pred_sigmoid * (1 - target)
    focal_weight = (alpha * target + (1 - alpha) *
                    (1 - target)) * pt.pow(gamma)
    loss = F.binary_cross_entropy_with_logits(
        pred, target, reduction='none') * focal_weigh
    return loss

四,NMS

4.1,NMS 介紹

在目標檢測中,常會利用非極大值抑制演算法(NMS,non maximum suppression)對生成的大量候選框進行后處理,去除冗余的候選框,得到最佳檢測框(bbox),以加快目標檢測的效,其本質思想搜素區域最大值,抑制非極大值,許多目標檢測模型都利用到了 NMS 演算法,如 DPM,YOLO,SSD,Faster R-CNN 等,NMS程序如下圖所示:

NMS程序

以上圖為例,每個選出來的 Bounding Box 檢測框(即 BBox)用(x,y,h,w, confidence score,Pdog,Pcat)表示,confidence score 表示 backgroundforeground 的置信度得分,取值范圍[0,1],Pdog, Pcat 分布代表類別是狗和貓的概率,如果是 100 類的目標檢測模型,BBox 輸出向量為 5+100=105

4.2,NMS 演算法程序

NMS 的目的就是除掉重復的邊界框,其主要是通過迭代的形式,不斷地以最大得分的框去與其他框做 IoU 操作,并過濾那些 IoU 較大的框,

其實作的思想主要是將各個框的置信度進行排序,然后選擇其中置信度最高的框 A,將其作為標準選擇其他框,同時設定一個閾值,當其他框 B 與 A 的重合程度超過閾值就將 B 舍棄掉,然后在剩余的框中選擇置信度最大的框,重復上述操作,多目標檢測的 NMS 演算法程序如下:

for object in all objects:
1. 將所有 bboxs 按照 confidence 排序,并標記當前 confidence 最大的 bbox,即要保留的 bbox;
2. 計算當前最大 confidence 對應的 bbox 和剩下所有 bboxIOU
3. 去除 IOU 大于設定閾值的 bbox,得到新的 bboxs
4. 對于新生下來的 bboxs,回圈執行步驟 2、3,直到所有的 bbox 都滿足要求(即無法再移除 bbox),

nms 的 python 代碼如下

import numpy as np

def py_nms(bboxs, thresh):
    """Pure Python NMS baseline.注意,這里的計算都是在矩陣層面上計算的
    greedily select boxes with high confidence and overlap with current maximum <= thresh
    rule out overlap >= thresh
    :param bboxs: [[x1, y1, x2, y2 score],] # ndarray, shape(-1,5)
    :param thresh: retain overlap < thresh
    :return: indexes to keep
    """
    if(bboxs) == 0:
        return [][]
    bboxs = npa.array(bboxs)
    # 計算 n 個候選框的面積大小
    x1 = bboxs[:,0]
    x2 = bboxs[:, 1]
    y1 = bboxs[:, 2]
    y2 = bboxs[:, 3]
    scores = bboxs[:, 4]
    areas = (x2 - x1 + 1)*(y2 - y1 + 1)

    # 1,對bboxs 按照置信度排序,獲取排序后的下標號,argsort 函式默認從小到大排序
    order = np.argsort(scores)  # order shape is (4,)
    picked_bboxs = []

    while order.size > 0:
        # 1, 保留當前 confidence 最大的 bbox加入到回傳框串列中
        index = order[-1]
        picked_bboxs.append(bboxs[index]]

        # 2,計算當前 confidence 最大的 bbox 和剩下 bbox 的 IOU
        xx1 = np.maximum(x1[-1], x1[order[:-1]])
        xx2 = np.maximum(x2[-1], x2[order[:-1]])
        yy1 = np.maximum(y1[-1], y1[order[:-1]])
        yy1 = np.maximum(y2[-1], y2[order[:-1]])

        # 計算相交框的面積,注意矩形框不相交時 w 或 h 算出來會是負數,用0代替
        w = np.maximum(0.0, xx2 - xx1 + 1)
        h = np.maximum(0.0, yy2 - yy1 + 1)
        overlap_area = w * h
        
        IOUs = overlap_area/(areas[index] + areas[order[:-1]] - overlap_area)

        # 3,只保留 `IOU` 小于設定閾值的 `bbox`,得到新的 `bboxs`,更新剩下來 bbox的索引
        remain_index = np.where(IOUs < thresh)  # np.where 來找到符合條件的 index
        order = order[remain_index]
    return picked_bboxs

# test
if __name__ == "__main__":
    bboxs = np.array([[30, 20, 230, 200, 1],
                     [50, 50, 260, 220, 0.9],
                     [210, 30, 420, 5, 0.8],
                     [430, 280, 460, 360, 0.7]])
    thresh = 0.35
    keep_bboxs = py_nms(bboxs, thresh)
    print(keep_bboxs)

程式輸出如下:

[0, 2, 3]
[[ 30. 20. 230. 200. 1. ]
[210. 30. 420. 5. 0.8]
[430. 280. 460. 360. 0.7]]

另一個版本的 nms 的 python 代碼如下:

from __future__ import print_function
import numpy as np
import time

def intersect(box_a, box_b):
    max_xy = np.minimum(box_a[:, 2:], box_b[2:])
    min_xy = np.maximum(box_a[:, :2], box_b[:2])
    inter = np.clip((max_xy - min_xy), a_min=0, a_max=np.inf)
    return inter[:, 0] * inter[:, 1]

def get_iou(box_a, box_b):
    """Compute the jaccard overlap of two sets of boxes.  The jaccard overlap
    is simply the intersection over union of two boxes.
    E.g.:
        A ∩ B / A ∪ B = A ∩ B / (area(A) + area(B) - A ∩ B)
        The box should be [x1,y1,x2,y2]
    Args:
        box_a: Single numpy bounding box, Shape: [4] or Multiple bounding boxes, Shape: [num_boxes,4]
        box_b: Single numpy bounding box, Shape: [4]
    Return:
        jaccard overlap: Shape: [box_a.shape[0], box_a.shape[1]]
    """
    if box_a.ndim==1:
        box_a=box_a.reshape([1,-1])
    inter = intersect(box_a, box_b)
    area_a = ((box_a[:, 2]-box_a[:, 0]) *
              (box_a[:, 3]-box_a[:, 1]))  # [A,B]
    area_b = ((box_b[2]-box_b[0]) *
              (box_b[3]-box_b[1]))  # [A,B]
    union = area_a + area_b - inter
    return inter / union  # [A,B]

def nms(bboxs,scores,thresh):
    """
    The box should be [x1,y1,x2,y2]
    :param bboxs: multiple bounding boxes, Shape: [num_boxes,4]
    :param scores: The score for the corresponding box
    :return: keep inds
    """
    if len(bboxs)==0:
        return []
    order=scores.argsort()[::-1]
    keep=[]
    while order.size>0:
        i=order[0]
        keep.append(i)
        ious=get_iou(bboxs[order],bboxs[i])
        order=order[ious<=thresh]
    return keep

五,Soft NMS 演算法

Soft NMS 演算法是對 NMS 演算法的改進,是發表在 ICCV2017 的論文 中提出的,NMS 演算法存在一個問題是可能會把一些相鄰檢測框框給過濾掉(即將 IOU 大于閾值的視窗的得分全部置為 0 ),從而導致目標的 recall 指標比較低,而 Soft NMS 演算法會為相鄰檢測框設定一個衰減函式而非徹底將其分數置為零,Soft NMS 演算法流程如下圖所示:

soft nms 演算法流程

原來的 NMS 演算法可以通過以下分數重置函式來描述:

硬NMS演算法

論文對 NMS 原有的分數重置函式的改進有兩種形式,一種是線性加權的,設 \(s_i\) 為第 \(i\) 個 bbox 的 score, 則在應用 Soft NMS 時各個 bbox score 的計算公式如下:

線性加權形式的soft NMS演算法

另一種是高斯加權形式的,其不需要設定 iou 閾值 \(N_t\),高斯懲罰系數(與上面的線性截斷懲罰不同的是, 高斯懲罰會對其他所有的 bbox 作用),計算公式圖如下:

高斯加權形式的soft NMS演算法

注意,這兩種形式,思想都是 \(M\) 為當前得分最高框,\(b_{i}\) 為待處理框, \(b_{i}\)\(M\) 的 IOU 越大,bbox 的得分 \(s_{i}\) 就下降的越厲害 ( \(N_{t}\) 為給定閾值),Soft NMS 在每輪迭代時,先選擇分數最高的預測框作為 \(M\),并對 \(B\) 中的每一個檢測框 \(b_i\) 進行 re-score,得到新的 score,當該框的新 score 低于某設定閾值時,則立即將該框洗掉,

soft nmspython 代碼如下:

def soft_nms(bboxes, Nt=0.3, sigma2=0.5, score_thresh=0.3, method=2):
    # 在 bboxes 之后添加對應的下標[0, 1, 2...], 最終 bboxes 的 shape 為 [n, 5], 前四個為坐標, 后一個為下標
    res_bboxes = deepcopy(bboxes)
    N = bboxes.shape[0]  # 總的 box 的數量
    indexes = np.array([np.arange(N)])  # 下標: 0, 1, 2, ..., n-1
    bboxes = np.concatenate((bboxes, indexes.T), axis=1)  # concatenate 之后, bboxes 的操作不會對外部變數產生影響

    # 計算每個 box 的面積
    x1 = bboxes[:, 0]
    y1 = bboxes[:, 1]
    x2 = bboxes[:, 2]
    y2 = bboxes[:, 3]
    scores = bboxes[:, 4]
    areas = (x2 - x1 + 1) * (y2 - y1 + 1)

    for i in range(N):
        # 找出 i 后面的最大 score 及其下標
        pos = i + 1
        if i != N - 1:
            maxscore = np.max(scores[pos:], axis=0)
            maxpos = np.argmax(scores[pos:], axis=0)
        else:
            maxscore = scores[-1]
            maxpos = 0

        # 如果當前 i 的得分小于后面的最大 score, 則與之交換, 確保 i 上的 score 最大
        if scores[i] < maxscore:
            bboxes[[i, maxpos + i + 1]] = bboxes[[maxpos + i + 1, i]]
            scores[[i, maxpos + i + 1]] = scores[[maxpos + i + 1, i]]
            areas[[i, maxpos + i + 1]] = areas[[maxpos + i + 1, i]]

        # IoU calculate
        xx1 = np.maximum(bboxes[i, 0], bboxes[pos:, 0])
        yy1 = np.maximum(bboxes[i, 1], bboxes[pos:, 1])
        xx2 = np.minimum(bboxes[i, 2], bboxes[pos:, 2])
        yy2 = np.minimum(bboxes[i, 3], bboxes[pos:, 3])
        w = np.maximum(0.0, xx2 - xx1 + 1)
        h = np.maximum(0.0, yy2 - yy1 + 1)
        intersection = w * h
        iou = intersection / (areas[i] + areas[pos:] - intersection)

        # Three methods: 1.linear 2.gaussian 3.original NMS
        if method == 1:  # linear
            weight = np.ones(iou.shape)
            weight[iou > Nt] = weight[iou > Nt] - iou[iou > Nt]
        elif method == 2:  # gaussian
            weight = np.exp(-(iou * iou) / sigma2)
        else:  # original NMS
            weight = np.ones(iou.shape)
            weight[iou > Nt] = 0

        scores[pos:] = weight * scores[pos:]

    # select the boxes and keep the corresponding indexes
    inds = bboxes[:, 5][scores > score_thresh]
    keep = inds.astype(int)

    return res_bboxes[keep]

六,目標檢測的不平衡問題

論文 Imbalance Problems in Object Detection 給出了詳細的綜述,這篇論文主要是系統的分析了目標檢測中的不平衡問題,并按照問題進行分類,提出了四類不平衡,并對每個問題現有的解決方案批判性的提出了觀點,且給出了一個實時跟蹤最新的不平衡問題研究的網頁,

6.1,介紹

文章指出當有關輸入屬性的分布影響性能時,就會出現與輸入屬性相關的不平衡問題,論文將不平衡問題歸為四類:

  • Class imbalance: 類別不平衡,不同類別的輸入邊界框的數量不同,包括前景/背景和前景/前景類別的不平衡,RPNFocal Loss 就是解決這類問題,
  • Scale imbalance: 尺度不平衡,主要是目標邊界框的尺度不平衡引起的,也包括將物體分配至 feature pyramid 時的不平衡,典型如 FPN 就是解決物體多尺度問題的,
  • Spatial imbalance: 空間不平衡,包括不同樣本對回歸損失貢獻的不平衡,IoU 分布的不平衡,和目標分布位置的不平衡,
  • Objective imbalance:不同任務(分類、回歸)對總損失貢獻的不平衡,

參考資料

  • Focal Loss for Dense Object Detection
  • NMS介紹
  • Faster RCNN 原始碼解讀(2) -- NMS(非極大抑制)
  • nms
  • Imbalance Problems in Object Detection: A Review
  • 5分鐘理解Focal Loss與GHM——解決樣本不平衡利器
  • focal loss 通俗講解
  • 損失函式|交叉熵損失函式

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/538961.html

標籤:其他

上一篇:高手系列!資料科學家私藏pandas高階用法大全 ?

下一篇:編程實作DBSCAN密度聚類演算法,并以西瓜資料集4.0為例進行聚類效果分析

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more