您的位置:首頁(yè) > 軟件教程 > 教程 > yolov5 篩選正樣本流程 代碼多圖詳解

yolov5 篩選正樣本流程 代碼多圖詳解

來(lái)源:好特整理 | 時(shí)間:2024-07-10 18:50:04 | 閱讀:163 |  標(biāo)簽: Yolo v 代碼 OV   | 分享到:

正樣本全稱是anchor正樣本,正樣本所指的對(duì)象是anchor box,即先驗(yàn)框。 先驗(yàn)框:YOLO v2吸收了Faster RCNN的優(yōu)點(diǎn),設(shè)置了一定數(shù)量的預(yù)選框,使得模型不需要直接預(yù)測(cè)物體尺度與坐標(biāo),只需要預(yù)測(cè)先驗(yàn)框到真實(shí)物體的偏移,降低了預(yù)測(cè)難度。

yolov5  篩選正樣本流程 代碼多圖詳解

yolov5正樣本篩選原理

正樣本全稱是anchor正樣本,正樣本所指的對(duì)象是anchor box,即先驗(yàn)框。
先驗(yàn)框:從YOLO v2開始吸收了Faster RCNN的優(yōu)點(diǎn),設(shè)置了一定數(shù)量的預(yù)選框,使得模型不需要直接預(yù)測(cè)物體尺度與坐標(biāo),只需要預(yù)測(cè)先驗(yàn)框到真實(shí)物體的偏移,降低了預(yù)測(cè)難度。

正樣本獲取規(guī)則

Yolov5算法使用如下3種方式增加正樣本個(gè)數(shù):

一、跨anchor預(yù)測(cè)

假設(shè)一個(gè)GT框落在了某個(gè)預(yù)測(cè)分支的某個(gè)網(wǎng)格內(nèi),該網(wǎng)格具有3種不同大小anchor,若GT可以和這3種anchor中的多種anchor匹配,則這些匹配的anchor都可以來(lái)預(yù)測(cè)該GT框,即一個(gè)GT框可以使用多種anchor來(lái)預(yù)測(cè)。
具體方法:
不同于IOU匹配,yolov5采用基于寬高比例的匹配策略,GT的寬高與anchors的寬高對(duì)應(yīng)相除得到ratio1,anchors的寬高與GT的寬高對(duì)應(yīng)相除得到ratio2,取ratio1和ratio2的最大值作為最后的寬高比,該寬高比和設(shè)定閾值(默認(rèn)為4)比較,小于設(shè)定閾值的anchor則為匹配到的anchor。

anchor_boxes=torch.tensor([[1.25000, 1.62500],[2.00000, 3.75000],[4.12500, 2.87500]])
gt_box=torch.tensor([5,4])

ratio1=gt_box/anchor_boxes
ratio2=anchor_boxes/gt_box
ratio=torch.max(ratio1, ratio2).max(1)[0]
print(ratio)

anchor_t=4
res=ratio
tensor([4.0000, 2.5000, 1.3913])
tensor([False,  True,  True])

與 GT 相匹配的的 anchor 為 **anchor2 **和 anchor3 。
yolov5  篩選正樣本流程 代碼多圖詳解

二、跨grid預(yù)測(cè)

假設(shè)一個(gè)GT框落在了某個(gè)預(yù)測(cè)分支的某個(gè)網(wǎng)格內(nèi),則該網(wǎng)格有左、上、右、下4個(gè)鄰域網(wǎng)格,根據(jù)GT框的中心位置,將最近的2個(gè)鄰域網(wǎng)格也作為預(yù)測(cè)網(wǎng)格,也即一個(gè)GT框可以由3個(gè)網(wǎng)格來(lái)預(yù)測(cè)。
計(jì)算例子:
yolov5  篩選正樣本流程 代碼多圖詳解

GT box中心點(diǎn)處于grid1中,grid1被選中。為了增加增樣本,grid1的上下左右grid為候選網(wǎng)格,因?yàn)镚T中心點(diǎn)更靠近grid2和grid3,grid2和grid3也作為匹配到的網(wǎng)格。
根據(jù)上個(gè)步驟中的anchor匹配結(jié)果,GT與anchor2、anchor3相匹配,因此GT在當(dāng)前層匹配到的正樣本有6個(gè),分別為:

  • grid1_anchor2,grid1_anchor3
  • grid2_anchor2,grid2_anchor3
  • grid3_anchor2,grid3_anchor3

三、跨分支預(yù)測(cè)

假設(shè)一個(gè)GT框可以和2個(gè)甚至3個(gè)預(yù)測(cè)分支上的anchor匹配,則這2個(gè)或3個(gè)預(yù)測(cè)分支都可以預(yù)測(cè)該GT框。即一個(gè)GT框可以在3個(gè)預(yù)測(cè)分支上匹配正樣本,在每一個(gè)分支上重復(fù)anchor匹配和grid匹配的步驟,最終可以得到某個(gè)GT 匹配到的所有正樣本。
如下圖在Prediction的3個(gè)不同尺度的輸出中,gt都可以去匹配正樣本。

yolov5  篩選正樣本流程 代碼多圖詳解

正樣本篩選

正樣本篩選主要做了四件事情:

  1. 通過(guò)寬高比獲得合適的anchor
  2. 通過(guò)anchor所在的網(wǎng)格獲得上下左右擴(kuò)展網(wǎng)格
  3. 獲取標(biāo)注框相對(duì)網(wǎng)格左上角的偏移量
  4. 返回獲得的anchor,網(wǎng)格序號(hào),偏移量,類別等

yolov5中anchor值

anchors:
  - [10,13, 16,30, 33,23]  # P3/8
  - [30,61, 62,45, 59,119]  # P4/16
  - [116,90, 156,198, 373,326]  # P5/32

yolov5的網(wǎng)絡(luò)有三個(gè)尺寸的輸出,不同大小的輸出對(duì)應(yīng)不同尺寸:

  • 8倍下采樣: [10,13, 16,30, 33,23]
  • 16倍下采樣:[30,61, 62,45, 59,119]
  • 32倍下采樣:[116,90, 156,198, 373,326]

注釋代碼

yolov5/utils/loss.py

    def build_targets(self, p, targets):
        # Build targets for compute_loss(), input targets(image,class,x,y,w,h)

        """
        p: 預(yù)測(cè)值
        targets:gt
        (Pdb) pp p[0].shape
        torch.Size([1, 3, 80, 80, 7])
        (Pdb) pp p[1].shape
        torch.Size([1, 3, 40, 40, 7])
        (Pdb) pp p[2].shape
        torch.Size([1, 3, 20, 20, 7])
        (Pdb) pp targets.shape
        torch.Size([23, 6])
        """
        na, nt = self.na, targets.shape[0]  # number of anchors, targets
        tcls, tbox, indices, anch = [], [], [], []
        
        """
        tcls    保存類別id
        tbox    保存的是gt中心相對(duì)于所在grid cell左上角偏移量。也會(huì)計(jì)算出gt中心相對(duì)擴(kuò)展anchor的偏移量
        indices 保存的內(nèi)容是:image_id, anchor_id, grid x刻度  grid y刻度
        anch 保存anchor的具體寬高
        """
        
        gain = torch.ones(7, device=self.device)  # normalized to gridspace gain
        ai = torch.arange(na, device=self.device).float().view(na, 1).repeat(1, nt)  # same as .repeat_interleave(nt)
        """
        (Pdb) ai
        tensor([[0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
                [1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],
                [2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2., 2.]], device='cuda:0')
        (Pdb) ai.shape
        torch.Size([3, 23])
        """
        targets = torch.cat((targets.repeat(na, 1, 1), ai[..., None]), 2)  # append anchor indices

        g = 0.5  # bias
        off = torch.tensor(
            [
                [0, 0],
                [1, 0],
                [0, 1],
                [-1, 0],
                [0, -1],  # j,k,l,m
                # [1, 1], [1, -1], [-1, 1], [-1, -1],  # jk,jm,lk,lm
            ],
            device=self.device).float() * g  # offsets

        for i in range(self.nl):
            anchors, shape = self.anchors[i], p[i].shape
            """
            (Pdb) anchors
            tensor([[1.25000, 1.62500],
                    [2.00000, 3.75000],
                    [4.12500, 2.87500]], device='cuda:0')
            (Pdb) shape
            torch.Size([1, 3, 80, 80, 7])
            """
            gain[2:6] = torch.tensor(shape)[[3, 2, 3, 2]]  # xyxy gain
            """
            (Pdb) gain
            tensor([ 1.,  1., 80., 80., 80., 80.,  1.], device='cuda:0')
            """

            # Match targets to anchors
            t = targets * gain  # shape(3,n,7)  # 將grid cell還原到當(dāng)前feature map上
            """
            (Pdb) t.shape
            torch.Size([3, 23, 7])
            """

            if nt:
                # Matches
                r = t[..., 4:6] / anchors[:, None]  # wh ratio
                j = torch.max(r, 1 / r).max(2)[0] < self.hyp['anchor_t']  # compare
                # j = wh_iou(anchors, t[:, 4:6]) > model.hyp['iou_t']  # iou(3,n)=wh_iou(anchors(3,2), gwh(n,2))
                t = t[j]  # filter
                """
                (Pdb) t.shape
                torch.Size([3, 23, 7]) -> torch.Size([62, 7])
                """

                # Offsets
                gxy = t[:, 2:4]  # grid xy
                gxi = gain[[2, 3]] - gxy  # inverse
                j, k = ((gxy % 1 < g) & (gxy > 1)).T
                """
                (Pdb) ((gxy % 1 < g) & (gxy > 1)).shape
                torch.Size([186, 2])
                (Pdb) ((gxy % 1 < g) & (gxy > 1)).T.shape
                torch.Size([2, 186])
                """
                l, m = ((gxi % 1 < g) & (gxi > 1)).T

                j = torch.stack((torch.ones_like(j), j, k, l, m))
                """
                torch.ones_like(j) 代表gt中心所在grid cell
                j, k, l, m 代表擴(kuò)展的上下左右grid cell
                
                torch.Size([5, 51])
                """
                t = t.repeat((5, 1, 1))[j]
                """
                標(biāo)簽也重復(fù)5次,和上面的擴(kuò)展gird cell一起篩選出所有的,符合條件的grid cell
                (Pdb) pp t.shape
                torch.Size([153, 7])
                (Pdb) t.repeat((5, 1, 1)).shape
                torch.Size([5, 153, 7])
                (Pdb) pp t.shape
                torch.Size([232, 7])
                """
                offsets = (torch.zeros_like(gxy)[None] + off[:, None])[j]

                """
                計(jì)算出所有g(shù)rid cell的偏移量,作用在標(biāo)簽上之后就能得到最終的grid cell
                (Pdb) pp offsets.shape
                torch.Size([529, 2])
                """
            else:
                t = targets[0]
                offsets = 0


            # Define
            bc, gxy, gwh, a = t.chunk(4, 1)  # (image, class), grid xy, grid wh, anchors
            a, (b, c) = a.long().view(-1), bc.long().T  # anchors, image, class
            gij = (gxy - offsets).long()
            """
            用gt中心點(diǎn)的坐標(biāo)減去偏移量,得到最終的grid cell的坐標(biāo)。其中中心點(diǎn)也在。
            gxy 是在當(dāng)前feature map下的gt中心點(diǎn),如80*80下的 (55.09, 36.23),減去偏移量,再取整就能得到一個(gè)grid cell的坐標(biāo),如 (55,36)
            Pdb) pp gij.shape
            torch.Size([529, 2])
            (Pdb) pp gij
            tensor([[ 9, 22],
                [ 2, 23],
                [ 6, 23],
                ...,
                [ 5, 19],
                [ 5, 38],
                [15, 36]], device='cuda:0')
            """
            gi, gj = gij.T  # grid indices

            # Append
            # indices 保存的內(nèi)容是:image_id, anchor_id(0,1,2), grid x刻度  grid y刻度。這里的刻度就是正樣本
            indices.append((b, a, gj.clamp_(0, shape[2] - 1), gi.clamp_(0, shape[3] - 1)))  # image, anchor, grid

            # tbox保存的是gt中心相對(duì)于所在grid cell左上角偏移量。也會(huì)計(jì)算出gt中心相對(duì)擴(kuò)展anchor的偏移量
            tbox.append(torch.cat((gxy - gij, gwh), 1))  # box
            """
            (Pdb) pp tbox[0].shape
                torch.Size([312, 4])
            (Pdb) pp tbox[0]
                tensor([[ 0.70904,  0.50893,  4.81701,  5.14418],
                        [ 0.28421,  0.45330,  3.58872,  4.42822],
                        [ 0.44398,  0.60475,  3.79576,  4.98174],
                        ...,
                        [ 0.59653, -0.37711,  3.97289,  4.44963],
                        [ 0.32074, -0.05419,  5.19988,  5.59987],
                        [ 0.28691, -0.38742,  5.79986,  6.66651]], device='cuda:0')
            (Pdb) gxy
                tensor([[ 9.19086, 22.46842],
                        [ 2.50407, 23.72271],
                        [ 6.35452, 23.75447],
                        ...,
                        [ 5.91273, 18.75906],
                        [ 5.16037, 37.97290],
                        [15.64346, 35.80629]], device='cuda:0')
                (Pdb) gij
                tensor([[ 9, 22],
                        [ 2, 23],
                        [ 6, 23],
                        ...,
                        [ 5, 19],
                        [ 5, 38],
                        [15, 36]], device='cuda:0')
                (Pdb) gxy.shape
                torch.Size([529, 2])
                (Pdb) gij.shape
                torch.Size([529, 2])
            """
            anch.append(anchors[a])  # anchors # 保存anchor的具體寬高
            tcls.append(c)  # class 保存類別id
            
            """
            (Pdb) pp anch[0].shape
                torch.Size([312, 2])
                (Pdb) pp tcls[0].shape
                torch.Size([312])
            """

        return tcls, tbox, indices, anch

代碼基本思路

  1. 傳入預(yù)測(cè)值和標(biāo)注信息。預(yù)測(cè)值用于獲取當(dāng)前操作的下采樣倍數(shù)
  2. 遍歷每一種feature map,分別獲取正樣本數(shù)據(jù)
  3. 獲取當(dāng)前feature map的下采樣尺度,將歸一化的標(biāo)注坐標(biāo)還原到當(dāng)前feature map的大小上
  4. 計(jì)算gt和anchor的邊框長(zhǎng)寬比,符合條件置為True,不符合條件置為False。過(guò)濾掉為False的anchor
  5. 計(jì)算gt中心的xy和左上邊框距離和右下邊框距離,篩選出符合條件的grid cell,并計(jì)算出所有符合條件的anchor相對(duì)當(dāng)前gt所在anchor偏移量
  6. 通過(guò)上一步計(jì)算出來(lái)的偏移量和gt中心計(jì)算,得到所有anchor的坐標(biāo)信息
  7. 用gt所在偏移量減去grid cell的坐標(biāo)信息,得到gt相對(duì)于所屬anchor左上角的偏移量。包括gt中心anchor和擴(kuò)展anchor
  8. 收集所有信息,包括:
  • indices 保存的內(nèi)容是:image_id, anchor_id, grid x刻度 grid y刻度
  • tbox 保存的是gt中心相對(duì)于所在grid cell左上角偏移量。也會(huì)計(jì)算出gt中心相對(duì)擴(kuò)展anchor的偏移量
  • anchors 保存anchor的具體寬高
  • class 保存類別id

準(zhǔn)備工作

在進(jìn)入正樣本篩選之前,需要做一些準(zhǔn)備工作,主要是獲取必要的參數(shù)。

def build_targets(self, p, targets):
    pass 

輸入的參數(shù):
targets 是這一批圖片的標(biāo)注信息,每一行的內(nèi)容分別是: image, class, x, y, w, h。

(Pdb) pp targets.shape
torch.Size([63, 6])

tensor([[0.00000, 1.00000, 0.22977, 0.56171, 0.08636, 0.09367],
        [0.00000, 0.00000, 0.06260, 0.59307, 0.07843, 0.08812],
        [0.00000, 0.00000, 0.15886, 0.59386, 0.06021, 0.06430],
        [0.00000, 0.00000, 0.31930, 0.58910, 0.06576, 0.09129],
        [0.00000, 0.00000, 0.80959, 0.70458, 0.23025, 0.26275],
        [1.00000, 1.00000, 0.85008, 0.07597, 0.09781, 0.11827],
        [1.00000, 0.00000, 0.22484, 0.09267, 0.14065, 0.18534]

p 模型預(yù)測(cè)數(shù)據(jù)。主要用于獲取每一層的尺度

(Pdb) pp p[0].shape
torch.Size([1, 3, 80, 80, 7])
(Pdb) pp p[1].shape
torch.Size([1, 3, 40, 40, 7])
(Pdb) pp p[2].shape
torch.Size([1, 3, 20, 20, 7])

獲取anchor的數(shù)量和標(biāo)注的數(shù)據(jù)的個(gè)數(shù)。設(shè)置一批讀入的數(shù)據(jù)為6張圖片,產(chǎn)生了66個(gè)標(biāo)注框。

na, nt = self.na, targets.shape[0]  # number of anchors, targets
tcls, tbox, indices, anch = [], [], [], []
pp na
3
(Pdb) pp nt
66
(Pd

targets保存的標(biāo)注信息,首先將標(biāo)注信息復(fù)制成三份,同時(shí)給每一份標(biāo)注信息分配一個(gè)不同大小的anchor。 相當(dāng)于同一個(gè)標(biāo)注框就擁有三個(gè)不同的anchor 。

在targets張量最后增加一個(gè)數(shù)據(jù)用于保存anchor的index。后續(xù)的篩選都是以單個(gè)anchor為顆粒度。targets 每一行內(nèi)容: image, class, x, y, w, h,anchor_id

targets = torch.cat((targets.repeat(na, 1, 1), ai[..., None]), 2)
>>>
(Pdb) pp targets.shape
torch.Size([3, 63, 7])

定義長(zhǎng)寬比的比例g=0.5和擴(kuò)展網(wǎng)格的選擇范圍off

g = 0.5  # bias
off = torch.tensor(
    [
        [0, 0],
        [1, 0],
        [0, 1],
        [-1, 0],
        [0, -1],  # j,k,l,m
        # [1, 1], [1, -1], [-1, 1], [-1, -1],  # jk,jm,lk,lm
    ],
    device=self.device).float() * g  # offsets

獲取正樣本anchor

遍歷三種尺度,在每一種尺度上獲取正樣本anchor和擴(kuò)展網(wǎng)格
首先將標(biāo)注框還原到當(dāng)前尺度上。從傳入的預(yù)測(cè)數(shù)據(jù)中獲取尺度,如80 * 80,那么就是將中心點(diǎn)和寬高還原到80*80的尺度上,還原之前的尺度都是0-1之間歸一化處理的,還原之后范圍就是在0-80。

anchors, shape = self.anchors[i], p[i].shape
"""
(Pdb) anchors
tensor([[1.25000, 1.62500],
        [2.00000, 3.75000],
        [4.12500, 2.87500]], device='cuda:0')
(Pdb) shape
torch.Size([1, 3, 80, 80, 7])
"""
gain[2:6] = torch.tensor(shape)[[3, 2, 3, 2]]  # xyxy gain
"""
(Pdb) gain
tensor([ 1.,  1., 80., 80., 80., 80.,  1.], device='cuda:0')
"""

# Match targets to anchors
t = targets * gain  # shape(3,n,7)  # 將grid cell還原到當(dāng)前feature map上

targets此時(shí)一行數(shù)據(jù)分別是:image_id, clss_id, 當(dāng)前尺度下的x,當(dāng)前尺度下的y,當(dāng)前尺度下的寬,當(dāng)前尺度下的高,當(dāng)前尺度下的anchor_id。

(Pdb) pp t.shape
torch.Size([3, 63, 7])
(Pdb) pp t[0,0]
tensor([ 0.00000,  1.00000, 18.38171, 44.93684,  6.90862,  7.49398,  0.00000], device='cuda:0')
(Pdb) pp t
tensor([[[ 0.00000,  1.00000, 18.38171,  ...,  6.90862,  7.49398,  0.00000],
         [ 0.00000,  0.00000,  5.00814,  ...,  6.27480,  7.04943,  0.00000],
         [ 0.00000,  0.00000, 12.70904,  ...,  4.81701,  5.14418,  0.00000],
         ...,
         [ 5.00000,  0.00000, 10.32074,  ...,  5.19988,  5.59987,  0.00000],
         [ 5.00000,  0.00000, 31.28691,  ...,  5.79986,  6.66651,  0.00000],
         [ 5.00000,  0.00000, 51.81977,  ...,  5.66653,  5.93320,  0.00000]],

        [[ 0.00000,  1.00000, 18.38171,  ...,  6.90862,  7.49398,  1.00000],
         [ 0.00000,  0.00000,  5.00814,  ...,  6.27480,  7.04943,  1.00000],
         [ 0.00000,  0.00000, 12.70904,  ...,  4.81701,  5.14418,  1.00000],
         ...,
         [ 5.00000,  0.00000, 10.32074,  ...,  5.19988,  5.59987,  1.00000],
         [ 5.00000,  0.00000, 31.28691,  ...,  5.79986,  6.66651,  1.00000],
         [ 5.00000,  0.00000, 51.81977,  ...,  5.66653,  5.93320,  1.00000]],

        [[ 0.00000,  1.00000, 18.38171,  ...,  6.90862,  7.49398,  2.00000],
         [ 0.00000,  0.00000,  5.00814,  ...,  6.27480,  7.04943,  2.00000],
         [ 0.00000,  0.00000, 12.70904,  ...,  4.81701,  5.14418,  2.00000],
         ...,
         [ 5.00000,  0.00000, 10.32074,  ...,  5.19988,  5.59987,  2.00000],
         [ 5.00000,  0.00000, 31.28691,  ...,  5.79986,  6.66651,  2.00000],
         [ 5.00000,  0.00000, 51.81977,  ...,  5.66653,  5.93320,  2.00000]]], device='cuda:0')

yolov5 正樣本選取規(guī)則
yolov5中正負(fù)樣本的計(jì)算規(guī)則是:比較標(biāo)注框和anchor的寬高,比例在0.25-4以內(nèi)就是正樣本。如下圖所示:
gt的原本面積為藍(lán)色,虛線標(biāo)注了0.25倍和4倍。只要anchor在0.25-4之間,就是匹配成功。

yolov5  篩選正樣本流程 代碼多圖詳解

如果存在標(biāo)注框,則計(jì)算anchor和標(biāo)注框的寬高比

if nt:
    # 獲取寬高比
    r = t[..., 4:6] / anchors[:, None]  

    # 獲取 寬高比或?qū)捀弑鹊箶?shù) 中最大的一個(gè),和4比較。self.hyp['anchor_t'] = 4
    j = torch.max(r, 1 / r).max(2)[0] < self.hyp['anchor_t']  # compare

    # 將正樣本過(guò)濾出來(lái)
    t = t[j]  # filter

此時(shí)t保存的就是所有符合條件的標(biāo)注框,后續(xù)用于計(jì)算anchor和網(wǎng)格信息。這一階段的結(jié)束之后,輸出的是所有符合條件的anchor。t保存的是 image, class, x, y, w, h,anchor_id, 同一個(gè)圖片會(huì)對(duì)應(yīng)多個(gè)標(biāo)注框,多個(gè)標(biāo)注框可能會(huì)對(duì)應(yīng)多個(gè)anchor。

跨anchor匹配
r計(jì)算的過(guò)程中包含了跨anchor匹配。在準(zhǔn)備工作中已經(jīng)介紹過(guò)將標(biāo)注框復(fù)制了三份,每一份都分配了一個(gè)anchor,相當(dāng)于一個(gè)標(biāo)注框擁有三種不同大小的anchor,F(xiàn)在計(jì)算寬高比獲得的結(jié)果只要符合條件的都會(huì)認(rèn)為是正樣本,3種anchor之間互不干擾,所以會(huì)出現(xiàn)一個(gè)標(biāo)注框匹配多個(gè)anchor。

(Pdb) pp t.shape
torch.Size([3, 63, 7])
(Pdb) pp t
tensor([[[ 0.00000,  1.00000, 18.38171,  ...,  6.90862,  7.49398,  0.00000],
         [ 0.00000,  0.00000,  5.00814,  ...,  6.27480,  7.04943,  0.00000],
         [ 0.00000,  0.00000, 12.70904,  ...,  4.81701,  5.14418,  0.00000],
         ...,
         [ 5.00000,  0.00000, 10.32074,  ...,  5.19988,  5.59987,  0.00000],
         [ 5.00000,  0.00000, 31.28691,  ...,  5.79986,  6.66651,  0.00000],
         [ 5.00000,  0.00000, 51.81977,  ...,  5.66653,  5.93320,  0.00000]],

        [[ 0.00000,  1.00000, 18.38171,  ...,  6.90862,  7.49398,  1.00000],
         [ 0.00000,  0.00000,  5.00814,  ...,  6.27480,  7.04943,  1.00000],
         [ 0.00000,  0.00000, 12.70904,  ...,  4.81701,  5.14418,  1.00000],
         ...,
         [ 5.00000,  0.00000, 10.32074,  ...,  5.19988,  5.59987,  1.00000],
         [ 5.00000,  0.00000, 31.28691,  ...,  5.79986,  6.66651,  1.00000],
         [ 5.00000,  0.00000, 51.81977,  ...,  5.66653,  5.93320,  1.00000]],

        [[ 0.00000,  1.00000, 18.38171,  ...,  6.90862,  7.49398,  2.00000],
         [ 0.00000,  0.00000,  5.00814,  ...,  6.27480,  7.04943,  2.00000],
         [ 0.00000,  0.00000, 12.70904,  ...,  4.81701,  5.14418,  2.00000],
         ...,
         [ 5.00000,  0.00000, 10.32074,  ...,  5.19988,  5.59987,  2.00000],
         [ 5.00000,  0.00000, 31.28691,  ...,  5.79986,  6.66651,  2.00000],
         [ 5.00000,  0.00000, 51.81977,  ...,  5.66653,  5.93320,  2.00000]]], device='cuda:0')
(Pdb) pp t[0,0]
tensor([ 0.00000,  1.00000, 18.38171, 44.93684,  6.90862,  7.49398,  0.00000], device='cuda:0')

獲取擴(kuò)展網(wǎng)格

在yolov5中除了將gt中心點(diǎn)所在網(wǎng)格的anchor匹配為正樣本之外,還會(huì)將網(wǎng)格相鄰的上下左右四個(gè)網(wǎng)格中的對(duì)應(yīng)anchor作為正樣本。獲取擴(kuò)展網(wǎng)格的規(guī)則就是根據(jù)中心點(diǎn)距離上下左右哪個(gè)更近來(lái)確定擴(kuò)展的網(wǎng)格。如下圖中心點(diǎn)更靠近上和右,那么上和右網(wǎng)格中對(duì)應(yīng)的anchor就會(huì)成為正樣本。

yolov5  篩選正樣本流程 代碼多圖詳解

獲取擴(kuò)展網(wǎng)格主要分為幾步走:

  1. 獲取所有g(shù)t的中心點(diǎn)坐標(biāo)gxy
  2. 獲取中心點(diǎn)坐標(biāo)相對(duì)于右下邊界的距離
  3. 計(jì)算中心點(diǎn)距離上下左右哪兩個(gè)邊界更近
  4. 獲取所有anchor所在的網(wǎng)格,包括gt中心點(diǎn)所在網(wǎng)格和擴(kuò)展網(wǎng)格
gxy = t[:, 2:4]  # grid xy
gxi = gain[[2, 3]] - gxy  # inverse
j, k = ((gxy % 1 < g) & (gxy > 1)).T
"""
(Pdb) ((gxy % 1 < g) & (gxy > 1)).shape
torch.Size([186, 2])
(Pdb) ((gxy % 1 < g) & (gxy > 1)).T.shape
torch.Size([2, 186])
"""
l, m = ((gxi % 1 < g) & (gxi > 1)).T

j = torch.stack((torch.ones_like(j), j, k, l, m))
"""
torch.ones_like(j) 代表gt中心所在grid cell
j, k, l, m 代表擴(kuò)展的上下左右grid cell

torch.Size([5, 51])
"""
t = t.repeat((5, 1, 1))[j]
"""
標(biāo)簽也重復(fù)5次,和上面的擴(kuò)展gird cell一起篩選出所有的,符合條件的grid cell
(Pdb) pp t.shape
torch.Size([153, 7])
(Pdb) t.repeat((5, 1, 1)).shape
torch.Size([5, 153, 7])
(Pdb) pp t.shape
torch.Size([232, 7])
"""
offsets = (torch.zeros_like(gxy)[None] + off[:, None])[j]
"""
計(jì)算出所有g(shù)rid cell的偏移量,作用在標(biāo)簽上之后就能得到最終的grid cell
(Pdb) pp offsets.shape
torch.Size([529, 2])
"""

gxy 是中心點(diǎn)的坐標(biāo),中心點(diǎn)坐標(biāo)是相對(duì)于整個(gè)80*80網(wǎng)格的左上角(0,0)的距離,而gxi是80減去中心點(diǎn)坐標(biāo),得到的結(jié)果相當(dāng)于是中心點(diǎn)距離(80,80)的距離。將中心點(diǎn)取余1之后相當(dāng)于縮放到一個(gè)網(wǎng)格中,如上圖所示。

yolov5  篩選正樣本流程 代碼多圖詳解

gxy = t[:, 2:4]  # grid xy
gxi = gain[[2, 3]] - gxy  # inverse
j, k = ((gxy % 1 < g) & (gxy > 1)).T

模擬以上操作,j,k得到的是一組布爾值

>>> import torch
>>> 
>>> arr = torch.tensor([[1,2,3], [4,5,6]])
>>> one = arr % 2 < 2 
>>> two = arr > 3
>>> one
tensor([[True, True, True],
        [True, True, True]])
>>> two
tensor([[False, False, False],
        [ True,  True,  True]])
>>> one & two
tensor([[False, False, False],
        [ True,  True,  True]])

距離的計(jì)算過(guò)程:

j, k = ((gxy % 1 < g) & (gxy > 1)).T
"""
(Pdb) ((gxy % 1 < g) & (gxy > 1)).shape
torch.Size([186, 2])
(Pdb) ((gxy % 1 < g) & (gxy > 1)).T.shape
torch.Size([2, 186])
"""
l, m = ((gxi % 1 < g) & (gxi > 1)).T

gxy % 1 < g 代表x或y離左上角距離小于0.5,小于0.5也就意味著靠的更近
gxy > 1 代表x或y必須大于1,x必須大于1也就是說(shuō)第一行的網(wǎng)格不能向上擴(kuò)展;y必須大于1就是說(shuō)第一列的網(wǎng)格不能向左擴(kuò)展。

yolov5  篩選正樣本流程 代碼多圖詳解

同理gxi是相對(duì)下邊和右邊的距離,得到布爾張量。

l, m = ((gxi % 1 < g) & (gxi > 1)).T

獲取所有的正樣本網(wǎng)格結(jié)果

j = torch.stack((torch.ones_like(j), j, k, l, m))
t = t.repeat((5, 1, 1))[j]

j 保存上面擴(kuò)展網(wǎng)格和中心點(diǎn)網(wǎng)格的匹配結(jié)果,是bool數(shù)組。torch.ones_like(j) 表示中心點(diǎn)匹配到的網(wǎng)格,jklm中保存的上下左右匹配的網(wǎng)格。
t是將gt中心點(diǎn)的網(wǎng)格復(fù)制出來(lái)5份,用于計(jì)算所有網(wǎng)格。第一份是中心點(diǎn)匹配結(jié)果,剩余四份是上下左右網(wǎng)格匹配結(jié)果。
用j來(lái)篩選t,最終留下所有選中的網(wǎng)格。

計(jì)算出從中心點(diǎn)網(wǎng)格出發(fā)到擴(kuò)展網(wǎng)格的需要的偏移量。后續(xù)使用使用該偏移量即可獲取所有網(wǎng)格,包括中心點(diǎn)網(wǎng)格和擴(kuò)展網(wǎng)格。計(jì)算的過(guò)程中涉及到了廣播機(jī)制。

offsets = (torch.zeros_like(gxy)[None] + off[:, None])[j]

示例如下:

>>> off
tensor([[ 0,  0],
        [ 1,  0],
        [ 0,  1],
        [-1,  0],
        [ 0, -1]])
>>> arr = torch.tensor([10])
>>> 
>>> 
>>> arr + off
tensor([[10, 10],
        [11, 10],
        [10, 11],
        [ 9, 10],
        [10,  9]])

以下圖為例,可視化正樣本anchor。
經(jīng)過(guò)mosaic處理的圖片,藍(lán)色為標(biāo)注框
yolov5  篩選正樣本流程 代碼多圖詳解

三種尺度下的正樣本網(wǎng)格
yolov5  篩選正樣本流程 代碼多圖詳解

yolov5  篩選正樣本流程 代碼多圖詳解

yolov5  篩選正樣本流程 代碼多圖詳解

三種尺度下的正樣本anchor

yolov5  篩選正樣本流程 代碼多圖詳解

yolov5  篩選正樣本流程 代碼多圖詳解

yolov5  篩選正樣本流程 代碼多圖詳解

三種尺度下原圖的正樣本網(wǎng)格
yolov5  篩選正樣本流程 代碼多圖詳解
yolov5  篩選正樣本流程 代碼多圖詳解
yolov5  篩選正樣本流程 代碼多圖詳解

三種尺度下原圖的anchor

yolov5  篩選正樣本流程 代碼多圖詳解

yolov5  篩選正樣本流程 代碼多圖詳解

yolov5  篩選正樣本流程 代碼多圖詳解

保存結(jié)果

從t中獲取相關(guān)數(shù)據(jù),包括:

  • bc:image_id, class_id
  • gxy: gt中心點(diǎn)坐標(biāo)
  • gwh: gt寬高
  • a: anchor_id
bc, gxy, gwh, a = t.chunk(4, 1)  # (image, class), grid xy, grid wh, anchors
a, (b, c) = a.long().view(-1), bc.long().T  # anchors, image, class
gij = (gxy - offsets).long()

獲取所有正樣本網(wǎng)格:

gij = (gxy - offsets).long()
gi, gj = gij.T  # grid indices

gxy是gt中心點(diǎn)的坐標(biāo),減去對(duì)應(yīng)偏移量再取整, 得到所有正樣本所在網(wǎng)格。然后將xy拆分出來(lái)得到gi,gj。

(Pdb) pp gij
tensor([[74, 24],
        [37, 28],
        [72,  9],
        [75, 11],
        [67,  5],
        [73,  5],
        [70,  5],
        [75,  1],
        ...)

indices: 保存圖片,anchor,網(wǎng)格等信息

# indices 保存的內(nèi)容是:image_id, anchor_id(0,1,2), grid x刻度  grid y刻度。這里的刻度就是正樣本
indices.append((b, a, gj.clamp_(0, shape[2] - 1), gi.clamp_(0, shape[3] - 1)))  # image, anchor, grid
(Pdb) pp a.shape
torch.Size([367])
(Pdb) pp gij.shape
torch.Size([367, 2])

保存中心點(diǎn)偏移量

# tbox保存的是gt中心相對(duì)于所在grid cell左上角偏移量。也會(huì)計(jì)算出gt中心相對(duì)擴(kuò)展anchor的偏移量
tbox.append(torch.cat((gxy - gij, gwh), 1))  # box

gij是網(wǎng)格起始坐標(biāo),gxy是gt中心點(diǎn)坐標(biāo)。gxy-gij就是獲取gt中心點(diǎn)相對(duì)于網(wǎng)格左上角坐標(biāo)的偏移量。
yolov5  篩選正樣本流程 代碼多圖詳解

在后續(xù)的損失函數(shù)計(jì)算中,用這個(gè)偏移量和網(wǎng)絡(luò)預(yù)測(cè)出來(lái)的偏移量計(jì)算損失函數(shù)。

保存anchor具體的寬高和類別id

anch.append(anchors[a])  # anchors # 保存anchor的具體寬高
tcls.append(c)  # class 保存類別id

自此正樣本篩選的流程就結(jié)束了,最終返回了4個(gè)張量:

  1. indices 保存的內(nèi)容是:image_id, anchor_id, grid x刻度 grid y刻度
  2. tbox 保存的是gt中心相對(duì)于所在grid cell左上角偏移量。也會(huì)計(jì)算出gt中心相對(duì)擴(kuò)展anchor的偏移量
  3. anchors 保存anchor的具體寬高
  4. class 保存類別id

返回的正樣本anchor會(huì)在后續(xù)損失函數(shù)的計(jì)算中使用。用 indices 保存的網(wǎng)格篩選出模型輸出的中對(duì)應(yīng)的網(wǎng)格里的內(nèi)容,用 tbox中中心點(diǎn)相對(duì)網(wǎng)格的偏移 模型輸出的預(yù)測(cè)中心點(diǎn)相對(duì)于網(wǎng)格左上角偏移量 計(jì)算偏差,并不斷修正。

Q&A

一、正樣本指的是anchor,anchor匹配如何體現(xiàn)在過(guò)程?
targets 是這一批圖片的標(biāo)注信息,每一行的內(nèi)容分別是: image, class, x, y, w, h。

(Pdb) pp targets.shape
torch.Size([63, 6])

tensor([[0.00000, 1.00000, 0.22977, 0.56171, 0.08636, 0.09367],
        [0.00000, 0.00000, 0.06260, 0.59307, 0.07843, 0.08812],
        [0.00000, 0.00000, 0.15886, 0.59386, 0.06021, 0.06430],
        [0.00000, 0.00000, 0.31930, 0.58910, 0.06576, 0.09129],
        [0.00000, 0.00000, 0.80959, 0.70458, 0.23025, 0.26275],
        [1.00000, 1.00000, 0.85008, 0.07597, 0.09781, 0.11827],
        [1.00000, 0.00000, 0.22484, 0.09267, 0.14065, 0.18534]

targets = torch.cat((targets.repeat(na, 1, 1), ai[..., None]), 2)
>>>
(Pdb) pp targets.shape
torch.Size([3, 63, 7])

targets保存的標(biāo)注信息,首先將標(biāo)注信息復(fù)制成三份,因?yàn)槊恳粋(gè)尺度每一個(gè)網(wǎng)格上有三個(gè)anchor, 相當(dāng)于給一份標(biāo)注框分配了一個(gè)anchor
在后續(xù)的操作中,先通過(guò)先將標(biāo)注框還原到對(duì)應(yīng)的尺度上,通過(guò)寬高比篩選anchor,獲得符合正樣本的anchor。到這里就獲得所有正樣本的anchor。
然后再通過(guò)中心點(diǎn)的坐標(biāo)獲得擴(kuò)展網(wǎng)格。

j = torch.stack((torch.ones_like(j), j, k, l, m))
t = t.repeat((5, 1, 1))[j]

此時(shí)將t復(fù)制5份,每一份的每一行內(nèi)容代表: image, class, x, y, w, h,anchor_id。
復(fù)制的過(guò)程中就攜帶了anchor_id的信息,最終通過(guò)擴(kuò)展獲取上下左右兩個(gè)網(wǎng)格,相當(dāng)于獲得了兩個(gè)網(wǎng)格中的anchor。
最后將所有的anchor保存起來(lái),在計(jì)算損失函數(shù)時(shí)使用到anchor的兩個(gè)功能:

  1. 使用這些anchor的寬高作為基準(zhǔn),模型輸出的結(jié)果是anchor寬高的比例
  2. anchor所在的網(wǎng)格為定位參數(shù)提供范圍。網(wǎng)絡(luò)輸出的xy是相對(duì)于網(wǎng)格左上角的偏移

二、 跨anchor匹配體現(xiàn)在哪里?

targets保存的標(biāo)注信息,首先將標(biāo)注信息復(fù)制成三份,因?yàn)槊恳粋(gè)尺度每一個(gè)網(wǎng)格上有三個(gè)anchor, 相當(dāng)于給一份標(biāo)注框分配了一個(gè)anchor 。

r = t[..., 4:6] / anchors[:, None]  

# 獲取 寬高比或?qū)捀弑鹊箶?shù) 中最大的一個(gè),和0.5比較
j = torch.max(r, 1 / r).max(2)[0] < self.hyp['anchor_t']  # compare

# 將正樣本過(guò)濾出來(lái)
t = t[j]  # filter

r計(jì)算的過(guò)程中包含了跨anchor匹配。t是將原有的標(biāo)注信息復(fù)制了三份,而每一個(gè)網(wǎng)格也有三個(gè)anchor,也就是說(shuō)一份標(biāo)注信息對(duì)應(yīng)一個(gè)anchor。現(xiàn)在計(jì)算寬高比獲得的結(jié)果只要符合條件的都會(huì)認(rèn)為是正樣本,3種anchor之間互不干擾。
那么有可能存在的情況是三種anchor和gt的寬高比都符合條件,那么這3個(gè)標(biāo)注數(shù)據(jù)都會(huì)保存下來(lái),相應(yīng)的anchor都會(huì)成為正樣本。

三、 跨網(wǎng)格匹配體現(xiàn)在哪里?

所謂跨網(wǎng)格匹配就是除了gt中心點(diǎn)所在網(wǎng)格,還會(huì)選擇擴(kuò)展網(wǎng)格。
yolov5  篩選正樣本流程 代碼多圖詳解

擴(kuò)展網(wǎng)格的篩選過(guò)程就是跨網(wǎng)格匹配的過(guò)程

gxy = t[:, 2:4]  # grid xy
gxi = gain[[2, 3]] - gxy  # inverse
j, k = ((gxy % 1 < g) & (gxy > 1)).T
l, m = ((gxi % 1 < g) & (gxi > 1)).T
j = torch.stack((torch.ones_like(j), j, k, l, m))
t = t.repeat((5, 1, 1))[j]

四、 跨尺度匹配體現(xiàn)在哪里?

一個(gè)標(biāo)注框可以在不同的預(yù)測(cè)分支上匹配上anchor。anchor的匹配在不同的尺度上分開單獨(dú)處理,三個(gè)尺度互相不干擾,所以一個(gè)標(biāo)注框最多能在三個(gè)尺度上都匹配上anchor。

for i in range(self.nl):
    anchors, shape = self.anchors[i], p[i].shape
    ...
    indices.append((b, a, gj.clamp_(0, shape[2] - 1), gi.clamp_(0, shape[3] - 1)))  # image, anchor, grid

    # tbox保存的是gt中心相對(duì)于所在grid cell左上角偏移量。也會(huì)計(jì)算出gt中心相對(duì)擴(kuò)展anchor的偏移量
    tbox.append(torch.cat((gxy - gij, gwh), 1))  # box

可以看到以下三個(gè)不同尺度的anchor匹配中,右上角目標(biāo)都匹配上了。
yolov5  篩選正樣本流程 代碼多圖詳解

五、擴(kuò)展的網(wǎng)格中用哪一個(gè)anchor?
通過(guò)寬高比篩選出來(lái)的正樣本才會(huì)被復(fù)制,也就是說(shuō)一個(gè)網(wǎng)格中的anchor匹配上gt之后,然后才有可能被擴(kuò)展網(wǎng)格選中。
在擴(kuò)展網(wǎng)格之前,就已經(jīng)篩選出正樣本,有一個(gè)確定大小的anchor。擴(kuò)展網(wǎng)格的獲得過(guò)程是將正樣本復(fù)制5份。復(fù)制的過(guò)程就將中心點(diǎn)匹配的anchor_id攜帶過(guò)去。

j = torch.stack((torch.ones_like(j), j, k, l, m))
t = t.repeat((5, 1, 1))[j]

復(fù)制的是正樣本,那么擴(kuò)展網(wǎng)格最終獲得的也是中心點(diǎn)所在網(wǎng)格上匹配好的anchor
一個(gè)網(wǎng)格中有兩個(gè)anchor成為正樣本,那么擴(kuò)展網(wǎng)格中就有兩個(gè)anchor為正樣本。擴(kuò)展網(wǎng)格的anchor_id 和中心點(diǎn)網(wǎng)格保持一致。

六、擴(kuò)展網(wǎng)格中g(shù)t的偏移量如何計(jì)算?
計(jì)算gt中心點(diǎn)相對(duì)于網(wǎng)格左上角的偏移量中有幾個(gè)變量:

  1. gxy: 中心點(diǎn)的坐標(biāo)
  2. gij:網(wǎng)格的起始坐標(biāo)
gij = (gxy - offsets).long()

gij 是通過(guò)中心點(diǎn)減去偏移量再取整獲得的

# tbox保存的是gt中心相對(duì)于所在grid cell左上角偏移量。也會(huì)計(jì)算出gt中心相對(duì)擴(kuò)展anchor的偏移量
tbox.append(torch.cat((gxy - gij, gwh), 1))  # box

gxy - gij 的計(jì)算過(guò)程中,對(duì)于那些擴(kuò)展的網(wǎng)格,也會(huì)同樣計(jì)算偏移量。所以擴(kuò)展網(wǎng)格的偏移量就是網(wǎng)格的左上角到gt中心點(diǎn)的距離。
yolov5  篩選正樣本流程 代碼多圖詳解

小編推薦閱讀

好特網(wǎng)發(fā)布此文僅為傳遞信息,不代表好特網(wǎng)認(rèn)同期限觀點(diǎn)或證實(shí)其描述。

相關(guān)視頻攻略

更多

掃二維碼進(jìn)入好特網(wǎng)手機(jī)版本!

掃二維碼進(jìn)入好特網(wǎng)微信公眾號(hào)!

本站所有軟件,都由網(wǎng)友上傳,如有侵犯你的版權(quán),請(qǐng)發(fā)郵件[email protected]

湘ICP備2022002427號(hào)-10 湘公網(wǎng)安備:43070202000427號(hào)© 2013~2025 haote.com 好特網(wǎng)