Pytorch機器學習(九)—— YOLO中錨框,預測框,産生候選區域及對候選區域進行标注詳解
目錄
Pytorch機器學習(九)—— YOLO中錨框,預測框,産生候選區域及對候選區域進行标注
前言
一、基本概念
1、邊界框(bounding box)
xyxy格式
xywh格式
2、錨框(anchor box)
3、預測框(prediction box)
中心目标生成
長寬生成
4、對候選區域進行标注
objectness标簽
location标簽
label标簽
二、代碼講解
總結
前言
YOLO中或者說one-stage的目标檢測中的第一步就是産生候選區域,如何産生候選區域是目标檢測領域的核心問題,而産生候選區域可以:分為以下兩步
- 按一定的規則在圖檔上生成一系列位置固定的錨框,将這些錨框看作是可能的候選區域。
- 對錨框是否包含目标物體進行預測,如果包含目标物體,還需要預測所包含物體的類别,以及預測框相對于錨框位置需要調整的幅度。
這篇文章主要就講解以上兩個步驟,下面的代碼借鑒了百度課程和YOLOV5的源碼進行編寫。
隻想了解概念的看前面就行,第二部分主要是代碼。
一、基本概念
在介紹下面内容之前,先簡單介紹一下幾個基本的概念。其中錨框和預測框在後續特别容易弄混,請多加留意。
1、邊界框(bounding box)
在目标檢測中,用邊界框來表示物體的位置,邊界框為能正好包含物體的矩形框。如下圖中包含人臉位置的矩形框即為邊界框。
邊界框主要有以下兩種表現形式。
xyxy格式
即邊界框由左上角坐标(x1,y1)和右下角坐标(x2,y2)來表示
xywh格式
即邊界框由中心坐标(x,y)和框的長寬(w,h)來表示,YOLO中采用的主要是這種形式。
我們在訓練的時候,也會把邊界框稱為ground true box,真實框
2、錨框(anchor box)
錨框與物體邊界框不同,是由人們假想出來的一種框。先設定好錨框的大小和形狀,再以圖像上某一個點為中心畫出矩形框。
而在YOLO中,生成規則是将圖檔分割成mxn個區域,然後在每個格子的中心,按照設定的長寬比等,生成一系列的錨框。
這裡說一下,設定的長寬,在YOLO中是采用聚類的方法産生的,而在YOLOV5中具體為采用k-means聚類+遺傳算法計算得到。具體看下面這篇文章
Pytorch機器學習(十)—— YOLO中k-means聚類方法生成錨框anchor
在目标檢測任務中,通常會以某種規則在圖檔上生成一系列錨框,将這些錨框當成可能的候選區域。模型對這些候選區域是否包含物體進行預測,如果包含目标物體,則還需要進一步預測出物體所屬的類别。還有更為重要的一點是,由于錨框位置是固定的,它不大可能剛好跟物體邊界框重合,是以需要在錨框的基礎上進行微調以形成能準确描述物體位置的預測框,模型需要預測出微調的幅度。
簡單的說,錨框對于預測框來說,就相當于網絡對其的初始化權重。
3、預測框(prediction box)
預測框即是錨框的微調結果,其結果是更加的貼近真實框,問題就在與如何微調錨框,才能得到較好的結果。在YOLO中,主要采用下面的方法對錨框進行微調。
中心目标生成
由錨框的生成方法可知,它是以某個劃分格子的中心坐标,以一定長寬比生成的,但在YOLO中預測框,則是以某個格子的左上角坐标為原點進行偏移的。
圖中格子的坐的左上角坐标為(cx,cy) ,則預測框中的中心坐标為(bx,by),其中
由sigmoid函數可知,其函數值在(0,1)之間,bx,by的中心坐标一定在這個劃分的格子中。
長寬生成
而對于預測框的長寬生成,則是基于錨框的初始長寬(pw,ph),以下面公式生成(bw,bh)
由上面的公式可以知道,我們的網絡其實就是在預測tx,ty,th,tw的值。由于我們是知道真實框的bx,by,bh,bw,由反函數,容易知道真實框tx*,ty*,th*,tw*,則我們網絡其中一個邊界框loss,為tx,ty,th,tw和tx*,ty*,th*,tw*計算得出,有關loss的東西下次在詳細說。
下面簡單說一下為什麼采用這種方法得出bx,by,bh,bw。其實主要就是采用sigmoid和e指數函數,對于網絡的輸出沒有限制。
下圖黃色框則為其中一個anchor box以tx = 3,ty = 0.3,tw = 0.3,th = 0.2生成的預測框。
4、對候選區域進行标注
這裡有很多關于錨框(anchor box)和預測框(prediction box)的概念,請注意區分!
每個錨框都可能是候選區域,但我們根據上面的内容可知,一張圖檔會生成很多錨框,那我們如何确定哪個是我們需要的,如果這個是需要的,我們需要得到它的掃描資訊,主要考慮以下三個方面。
- objectness标簽:錨框是否包含物體,這可以看成是一個二分類問題。當錨框包含了物體時,objectness=1,表示預測框屬于正類;當錨框不包含物體時,設定objectness=0,表示錨框屬于負類。
- location标簽:如果錨框包含了物體,那麼它對應的預測框的中心位置和大小應該是多少,或者說上面計算式中的tx,ty,tw,th應該是多少。
- label标簽:如果錨框包含了物體,那麼具體類别是什麼。
objectness标簽
對于objectness标簽,錨框是否包含物體,我們主要采用IOU或者GIOU等名額來判别,關于IOU,GIOU等,可以看我下面這篇文章。我下面的講解都以IOU為名額評價objectness标簽。
Pytorch機器學習(五)——目标檢測中的損失函數(l2,IOU,GIOU,DIOU, CIOU)
我們可以算出所有我們的錨框與真實框之間的IOU,然後根據IOU這個評價名額,選出n個錨框(n是真實框的個數)
我們将這n個錨框的objectness标簽都标記為1,其他都标注為0
但在YOLO中,對于一些IOU也挺大的錨框,是這麼處理的,設定一個門檻值,若其IOU大于這個門檻值,則将objectness标簽标注為-1,而不是0.
我們在預測中,也隻需要根據objectness标簽是1的錨框,進行預測location标簽和label标簽!!
location标簽
對于location标簽,即是對于objectness标簽為1,标注其真實框tx*,ty*,th*,tw*四個值。注意這裡标注的是真實框的tx*,ty*,th*,tw*
label标簽
對于label标簽,我們采用one-hot編碼,比如有4個類别,則标注為[0, 0, 0, 1](假設其為第4個類别)
由此可知,我們對于候選框的标注,一共需要标注,1(objectness标簽)+ 4(location标簽)+ n(類别數——label标簽)個參數。
而我們的網絡對于每個預測框也是這麼多個參數!!這對于後面了解網絡輸出很關鍵!!
二、代碼講解
對于如何生成錨框,與候選框标注的函數如下,代碼比較長,注釋我寫的比較全,一步步看完會對上面的内容了解更深。
# 标注預測框的objectness
def get_objectness_label(img, gt_boxes, gt_labels, iou_threshold = 0.7,
anchors = [116, 90, 156, 198, 373, 326],
num_classes=7, downsample=32):
"""
img 是輸入的圖像資料,形狀是[N, C, H, W]
gt_boxes,真實框,次元是[N, 50, 4],其中50是真實框數目的上限,當圖檔中真實框不足50個時,不足部分的坐标全為0
真實框坐标格式是xywh,這裡使用相對值
gt_labels,真實框所屬類别,次元是[N, 50]
iou_threshold,當預測框與真實框的iou大于iou_threshold時不将其看作是負樣本
anchors,錨框可選的尺寸
anchor_masks,通過與anchors一起确定本層級的特征圖應該選用多大尺寸的錨框
num_classes,類别數目
downsample,特征圖相對于輸入網絡的圖檔尺寸變化的比例
"""
img_shape = img.shape
batchsize = img_shape[0]
# 這裡的anchors兩兩一組,分别表示長寬
num_anchors = len(anchors) // 2
input_h = img_shape[2]
input_w = img_shape[3]
# 将輸入圖檔劃分成num_rows x num_cols個小方塊區域,每個小方塊的邊長是 downsample
# 計算一共有多少行小方塊
num_rows = input_h // downsample
# 計算一共有多少列小方塊
num_cols = input_w // downsample
# 對于傳回值進行全0初始化
label_objectness = np.zeros([batchsize, num_anchors, num_rows, num_cols])
label_location = np.zeros([batchsize, num_anchors, 4, num_rows, num_cols])
label_classification = np.zeros([batchsize, num_anchors, num_classes, num_rows, num_cols])
# 這裡有個scale_location,用來調節大目标和小目标的權重的
# 大目标和小目标對于相同的偏移,大目标更加敏感,故增強這個權重系數
scale_location = np.ones([batchsize, num_anchors, num_rows, num_cols])
# 對batchsize進行循環,依次處理每張圖檔
for n in range(batchsize):
# 對圖檔上的真實框進行循環,依次找出跟真實框形狀最比對的錨框
for n_gt in range(len(gt_boxes[n])):
gt = gt_boxes[n][n_gt]
# 真實框所屬類别
gt_cls = gt_labels[n][n_gt]
# 真實框的中心坐标和長寬(YOLO中采用xywh)
gt_center_x = gt[0]
gt_center_y = gt[1]
gt_width = gt[2]
gt_height = gt[3]
# 如果真實框過小,則認為标簽損壞
if (gt_width < 1e-3) or (gt_height < 1e-3):
continue
# 計算這個真實框的中心點落在哪個格子裡
i = int(gt_center_y * num_rows)
j = int(gt_center_x * num_cols)
ious = []
for ka in range(num_anchors):
# x,y為0,因為錨框和真實框的x,y相同
bbox1 = [0., 0., float(gt_width), float(gt_height)]
# 生成輸入(i,j)這個格子裡的錨框
anchor_w = anchors[ka * 2]
anchor_h = anchors[ka * 2 + 1]
bbox2 = [0., 0., anchor_w/float(input_w), anchor_h/float(input_h)]
# 計算iou
iou = box_iou_xywh(bbox1, bbox2)
ious.append(iou)
ious = np.array(ious)
# 算出最大iou的index
inds = np.argsort(ious)
k = inds[-1]
label_objectness[n, k, i, j] = 1
c = gt_cls
label_classification[n, k, c, i, j] = 1.
# for those prediction bbox with objectness =1, set label of location
# 反算出真實框的tx*,ty*,tw*,th*
dx_label = gt_center_x * num_cols - j
dy_label = gt_center_y * num_rows - i
dw_label = np.log(gt_width * input_w / anchors[k*2])
dh_label = np.log(gt_height * input_h / anchors[k*2 + 1])
label_location[n, k, 0, i, j] = dx_label
label_location[n, k, 1, i, j] = dy_label
label_location[n, k, 2, i, j] = dw_label
label_location[n, k, 3, i, j] = dh_label
# scale_location用來調節不同尺寸的錨框對損失函數的貢獻,作為權重系數和位置損失函數相乘
# 根據這個計算方法,可以得出真實框越大,權重越小
scale_location[n, k, i, j] = 2.0 - gt_width * gt_height
# 目前根據每張圖檔上所有出現過的gt box,都标注出了objectness為正的預測框,剩下的預測框則預設objectness為0
# 對于objectness為1的預測框,标出了他們所包含的物體類别,以及位置回歸的目标
return label_objectness.astype('float32'), label_location.astype('float32'), label_classification.astype('float32'), \
scale_location.astype('float32')
總結
涉及概念比較多,而且基于自己本人的了解,如果有出錯,歡迎讨論指正。