YOLO后處理

首發(fā)于個人博客

理論分析

system.png

YOLO從v2版本開始重新啟用anchor box,YOLOv2網(wǎng)絡(luò)的網(wǎng)絡(luò)輸出為尺寸為[b,125,13,13]的tensor,要將這個Tensor變?yōu)樽罱K的輸出結(jié)果,還需要以下的處理:

  • 解碼:從Tensor中解析出所有框的位置信息和類別信息
  • NMS:篩選最能表現(xiàn)物品的識別框

解碼過程

解碼之前渣慕,需要明確的是每個候選框需要5+class_num個數(shù)據(jù)竿奏,分別是相對位置x,y,相對寬度w,h鹏往,置信度c和class_num個分類結(jié)果,YOLOv2-voc中class_num=20,即每個格點對應(yīng)5個候選框伊履,每個候選框有5+20=25個參數(shù)韩容,這就是為什么輸出Tensor的最后一維為5*(20+5)=125。

tensor.png

上圖為一個框所需要的所有數(shù)據(jù)構(gòu)成唐瀑,假設(shè)這個框是位于格點X,Y的群凶,對應(yīng)的anchor box大小為W,H,位置相關(guān)參數(shù)的處理方法如下所示哄辣,其中请梢,T_x,T_y分別是輸出Tensor在長寬上的值,這里T_x = 13,T_y = 13力穗;P_x,P_y分別為原圖片的長和寬:
x_r = \cfrac{sigmoid(x) + X}{T_x} \times P_x\\ y_r = \cfrac{sigmoid(x) + Y}{T_y} \times P_y\\ w_r = e^{w} \times W \\ h_r = e^{h} \times H
置信度和類別信息處理方法如下所示:
c_{r} = sigmoid(c) \times max\{softmax(class)\} \\ class\_id = argmax(class)
當(dāng)格點置信度大于某個閾值時毅弧,認(rèn)為該格點有物體,物體類別為class_id對應(yīng)的類別

NMS

NMS為非最大值抑制当窗,用在YOLO系統(tǒng)中的含義指從多個候選框標(biāo)記同一個物品時够坐,從中選擇最合適的候選框。其基本思維很簡單:使用置信度最高的候選框標(biāo)記一個物體崖面,若其他候選框與該候選框的IOU超過一個閾值元咙,則認(rèn)為其他候選框與該候選框標(biāo)記的是同一個物體,丟棄其他候選框巫员。

具體實現(xiàn)時庶香,可以將所有候選框進(jìn)行排序,置信度高的在前简识,置信度低的在后赶掖。從置信度高的候選框開始遍歷所有候選框,對于某一個候選框七扰,將之后所有的候選框與其計算IOU奢赂,若IOU高于一個閾值,則丟棄置信度低的候選框戳寸。算法流程圖如下所示:

nms.png

代碼分析

這里選擇的是marvis開源的基于Pytorch的YOLOv2代碼呈驶,其優(yōu)勢在于所有的部分均使用Python實現(xiàn),沒有使用Cython疫鹊,無需編譯即可使用袖瞻,且依賴較少,文件管理比較扁平拆吆。

解碼部分

解碼部分在utils.py文件中聋迎,由get_region_boxes函數(shù)實現(xiàn)。首先是準(zhǔn)備部分枣耀,這里首先獲取了輸出的相關(guān)信息霉晕,yolo-voc網(wǎng)絡(luò)下有b為batch,預(yù)測模式下一般為1,h=w=13牺堰。隨后reshape了輸出拄轻,其維度變?yōu)?25,13*13*5),改變維度的目的是方便后面處理的索引伟葫。

def get_region_boxes(output, conf_thresh, num_classes, anchors, num_anchors, only_objectness=1, validation=False):    
    anchor_step = len(anchors) / num_anchors
    if output.dim() == 3:
        output = output.unsqueeze(0)
    batch = output.size(0)
    assert(output.size(1) == (5 + num_classes) * num_anchors)
    h = output.size(2)
    w = output.size(3)
    output = output.view(batch * num_anchors, 5 + num_classes, h * w).transpose(
        0, 1).contiguous().view(5 + num_classes, batch * num_anchors * h * w)
    all_boxes = []

隨后是處理x恨搓,y的部分,xs和ys就是處理后的候選框中心點相對坐標(biāo)筏养,grid_x和grid_y與output[0]shape相同斧抱,分別表示對應(yīng)output位置的候選框所屬的格點坐標(biāo)X與Y,這里的xs和ys實現(xiàn)了上述公式中的xs = sigmoid(x) + Xys = sigmoid(y) + Y

    grid_x = torch.linspace(0, w - 1, w).repeat(h, 1).repeat(batch *
                                                             num_anchors, 1, 1).view(batch * num_anchors * h * w).cuda()
    grid_y = torch.linspace(0, h - 1, h).repeat(w, 1).t().repeat(
        batch * num_anchors, 1, 1).view(batch * num_anchors * h * w).cuda()
    print("outputs shape", output.shape)
    xs = torch.sigmoid(output[0]) + grid_x
    ys = torch.sigmoid(output[1]) + grid_y

之后為處理w,h的部分渐溶,與處理x,y的部分類似辉浦,最終ws和hs為修正后的物品尺寸信息,實現(xiàn)了ws = e^w\cdot Whs = e^{h} \cdot H茎辐。其中W和H分別為當(dāng)前anchor box的建議尺寸宪郊。

    anchor_w = torch.Tensor(anchors).view(
        num_anchors, anchor_step).index_select(1, torch.LongTensor([0]))
    anchor_h = torch.Tensor(anchors).view(
        num_anchors, anchor_step).index_select(1, torch.LongTensor([1]))
    anchor_w = anchor_w.repeat(batch, 1).repeat(
        1, 1, h * w).view(batch * num_anchors * h * w).cuda()
    anchor_h = anchor_h.repeat(batch, 1).repeat(
        1, 1, h * w).view(batch * num_anchors * h * w).cuda()
    ws = torch.exp(output[2]) * anchor_w
    hs = torch.exp(output[3]) * anchor_h

接下來是獲取置信度的部分和類別部分,獲取該anchor box的置信度為det_confs=sigmoid(c)荔茬。隨后處理類別信息废膘,先對類別信息對應(yīng)的數(shù)據(jù)做softmax操作竹海,隨后獲取其最大值cls_max_confs和最大值所在的位置cls_max_ids慕蔚,其中位置cls_max_ids對應(yīng)每個anchor box框住的“物品”的類別。

    det_confs = torch.sigmoid(output[4])
    cls_confs = torch.nn.Softmax()(
        Variable(output[5:5 + num_classes].transpose(0, 1))).data
    cls_max_confs, cls_max_ids = torch.max(cls_confs, 1)
    cls_max_confs = cls_max_confs.view(-1)
    cls_max_ids = cls_max_ids.view(-1)

隨后是一些其他的處理過程斋配,例如獲取格點數(shù)量sz_hw孔飒,anchor box的數(shù)量sz_hwa等,函數(shù)convert2cpu是在CPU上復(fù)制一個該數(shù)據(jù)艰争,注意這里是拷貝坏瞄,并不是將數(shù)據(jù)從GPU轉(zhuǎn)移到CPU上。

    sz_hw = h * w
    sz_hwa = sz_hw * num_anchors
    det_confs = convert2cpu(det_confs)
    cls_max_confs = convert2cpu(cls_max_confs)
    cls_max_ids = convert2cpu_long(cls_max_ids)
    xs = convert2cpu(xs)
    ys = convert2cpu(ys)
    ws = convert2cpu(ws)
    hs = convert2cpu(hs)
    if validation:
        cls_confs = convert2cpu(cls_confs.view(-1, num_classes))

隨后是一個解碼的大循環(huán)甩卓,分析見下面的注釋

    for b in range(batch):
        boxes = []
        # boxes為容納所有候選框的list
        for cy in range(h):
            for cx in range(w):
                for i in range(num_anchors):
                    # 遍歷每一個anchor box鸠匀,這里訪問位于格點cx,cy的第i個anchor box
                    ind = b * sz_hwa + i * sz_hw + cy * w + cx
                    # 獲取該anchor box在det_conf中對應(yīng)的index
                    det_conf = det_confs[ind]
                    if only_objectness:
                        conf = det_confs[ind]
                    else:
                        conf = det_confs[ind] * cls_max_confs[ind]
                    # 處理置信度

                    if conf > conf_thresh:
                        # 若置信度大于閾值,則認(rèn)為該anchor box有效
                        bcx = xs[ind]
                        bcy = ys[ind]
                        bw = ws[ind]
                        bh = hs[ind]
                        cls_max_conf = cls_max_confs[ind]
                        cls_max_id = cls_max_ids[ind]
                        # 獲取所有相關(guān)信息逾柿,包括長缀棍,寬猜极,位置照卦,置信度和類別
                        box = [bcx / w, bcy / h, bw / w, bh / h,
                               det_conf, cls_max_conf, cls_max_id]
                        # 處理數(shù)據(jù),其中位置信息x,y,尺寸信息w,h均歸一化椅邓,使其與輸入圖片尺寸解耦
                        if (not only_objectness) and validation:
                            for c in range(num_classes):
                                tmp_conf = cls_confs[ind][c]
                                if c != cls_max_id and det_confs[ind] * tmp_conf > conf_thresh:
                                    box.append(tmp_conf)
                                    box.append(c)
                        boxes.append(box)
                        # 將處理好的anchor box信息保存在boxes中
        all_boxes.append(boxes)
        return all_boxes

NMS部分

NMS也在utils.py中弱匪,函數(shù)名為nms青瀑。該函數(shù)中,首先實現(xiàn)對所有候選框的排序。這里使用det_confs獲取了置信度從大到小的anchor box的坐標(biāo)位置sortIds斥难。

def nms(boxes, nms_thresh):
    if len(boxes) == 0:
        return boxes

    det_confs = torch.zeros(len(boxes))
    for i in range(len(boxes)):
        det_confs[i] = 1 - boxes[i][4]

    _, sortIds = torch.sort(det_confs)

隨后實現(xiàn)候選框的篩選枝嘶,從高置信度的候選框開始遍歷,對于每個候選框boxes[sortIds[i]]哑诊,遍歷所有置信度低于該候選框且置信度不為0(置信度為0表示該候選框被拋棄)的候選框躬络,若低置信度候選框與高置信度候選框的IOU大于閾值,則拋棄低置信度候選框搭儒。

    out_boxes = []
    for i in range(len(boxes)):
        # 按置信度從高到低遍歷
        box_i = boxes[sortIds[i]]
        if box_i[4] > 0:
            # 置信度大于0表示該候選框沒有在之前的篩選中被拋棄
            out_boxes.append(box_i)
            for j in range(i + 1, len(boxes)):
                # 遍歷所有置信度低于該候選框的候選框
                box_j = boxes[sortIds[j]]
                if bbox_iou(box_i, box_j, x1y1x2y2=False) > nms_thresh:
                    # 若置信度低的候選框與該候選框IOU大于一定值穷当,拋棄低置信度候選框
                    box_j[4] = 0
    return out_boxes
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市淹禾,隨后出現(xiàn)的幾起案子馁菜,更是在濱河造成了極大的恐慌,老刑警劉巖铃岔,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件汪疮,死亡現(xiàn)場離奇詭異,居然都是意外死亡毁习,警方通過查閱死者的電腦和手機(jī)智嚷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來纺且,“玉大人盏道,你說我怎么就攤上這事≡芈担” “怎么了猜嘱?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長嫁艇。 經(jīng)常有香客問我朗伶,道長,這世上最難降的妖魔是什么步咪? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任论皆,我火速辦了婚禮,結(jié)果婚禮上猾漫,老公的妹妹穿的比我還像新娘点晴。我一直安慰自己,他們只是感情好静袖,可當(dāng)我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布觉鼻。 她就那樣靜靜地躺著,像睡著了一般队橙。 火紅的嫁衣襯著肌膚如雪坠陈。 梳的紋絲不亂的頭發(fā)上萨惑,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天,我揣著相機(jī)與錄音仇矾,去河邊找鬼庸蔼。 笑死,一個胖子當(dāng)著我的面吹牛贮匕,可吹牛的內(nèi)容都是我干的姐仅。 我是一名探鬼主播,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼刻盐,長吁一口氣:“原來是場噩夢啊……” “哼掏膏!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起敦锌,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤馒疹,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后乙墙,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體颖变,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年听想,在試婚紗的時候發(fā)現(xiàn)自己被綠了腥刹。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡汉买,死狀恐怖衔峰,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情录别,我是刑警寧澤朽色,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布邻吞,位于F島的核電站组题,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏抱冷。R本人自食惡果不足惜崔列,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望旺遮。 院中可真熱鬧赵讯,春花似錦、人聲如沸耿眉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鸣剪。三九已至组底,卻和暖如春丈积,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背债鸡。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工江滨, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人厌均。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓唬滑,卻偏偏與公主長得像,于是被迫代替她去往敵國和親棺弊。 傳聞我的和親對象是個殘疾皇子晶密,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,435評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 1.監(jiān)聽輸入法的返回事件 關(guān)鍵是EditText的public boolean onKeyPreIme(int k...
    小山包閱讀 485評論 0 0
  • 總有一些傳奇的社群 若不走進(jìn) 也許永遠(yuǎn)都不會接觸 因為超級猩猩的緣起 開始接觸到背后這個叫做萊美的組織 來自新西蘭...
    RayofLight閱讀 757評論 1 0