目标檢測算法評估
目錄
目标檢測的評估
引論
precision和recall
PR曲線和AP,mAP
VOC的mAP評價代碼
總結
引論
現在關于深度學習的算法層出不窮,那麼我們應該用什麼名額去評判一個算法的優缺點呢?
以目标檢測為例,要想準确地評價目标檢測算法,對目标檢測的評估工作肯定是少不了的。通常包括對速度的評價和對精度的評價,對速度的評價名額有FPS(Frame Per Second)即每秒處理的圖檔數,對精度的評價名額有精度(precision),召回率(recall),準确率(Accuracy),平均準确率(MAP)。
下面我來重點介紹幾個目标檢測的名額。
precision和recall
解釋精确率和召回率之前,先來看下混淆矩陣
把正例正确分類為正例,表示為TP(true positive),把正例錯誤分類為負例,表示為FN(false negative),
把負例正确分類為負例,表示為TN(true negative), 把負例錯誤分類為正例,表示為FP(false positive)
接着我們應該了解準确率(precision)和召回率(recall)的基本計算方式,參考下圖:
由圖可知:
- 左邊一整個矩形中(false negative和true positive)的數表示ground truth之中為1的(即為正确的)資料
- 右邊一整個矩形中的數表示ground truth之中為0的資料
- 精度precision的計算是用檢測正确的資料個數/總的檢測個數
-
召回率recall的計算是用檢測正确的資料個數/ground truth之中所有正資料個數
可得如下公式
對于目标檢測,我們通常設定一個
IOU的門檻值
來表示是否檢測正确,也就是一個檢測box和相應目标的ground truth的IOU超過一定的門檻值,并且分類正确則認為檢測到一個正确的目标。
PR曲線和AP,mAP
由上述定義可知,召回率和準确率受到了門檻值設定的影響,而且門檻值對于兩個名額的影響是相反的:門檻值增加則準确率增加,召回率降低,反之亦然。那麼我們就可以通過設定一系列的門檻值來得到一系列的(準确率,召回率)的名額對,然後利用這些名額對畫出坐标圖,這就是
PR曲線
,而AP(average precision)就是這個曲線下的面積。
mAP就是多個分類任務的AP的平均值。
VOC的mAP評價代碼
def voc_ap(self, rec, prec, use_07_metric=True):
if use_07_metric:
ap = 0.
# 2010年以前按recall等間隔取11個不同點處的精度值做平均(0., 0.1, 0.2, …, 0.9, 1.0)
for t in np.arange(0., 1.1, 0.1):
if np.sum(rec >= t) == 0:
p = 0
else:
# 取最大值等價于2010以後先計算包絡線的操作,保證precision非減
p = np.max(prec[rec >= t])
ap = ap + p / 11.
else:
# 2010年以後取所有不同的recall對應的點處的精度值做平均
# first append sentinel values at the end
mrec = np.concatenate(([0.], rec, [1.]))
mpre = np.concatenate(([0.], prec, [0.]))
# 計算包絡線,從後往前取最大保證precision非減
for i in range(mpre.size - 1, 0, -1):
mpre[i - 1] = np.maximum(mpre[i - 1], mpre[i])
# 找出所有檢測結果中recall不同的點
i = np.where(mrec[1:] != mrec[:-1])[0]
# and sum (\Delta recall) * prec
# 用recall的間隔對精度作權重平均
ap = np.sum((mrec[i + 1] - mrec[i]) * mpre[i + 1])
return ap
# 計算每個類别對應的AP,mAP是所有類别AP的平均值
def voc_eval(self, detpath,
classname,
ovthresh=0.5,
use_07_metric=True):
# 提取所有測試圖檔中目前類别所對應的所有ground_truth
class_recs = {}
npos = 0
# 周遊所有測試圖檔
for imagename in imagenames:
# 找出所有目前類别對應的object
R = [obj for obj in recs[imagename] if obj['name'] == classname]
# 該圖檔中該類别對應的所有bbox
bbox = np.array([x['bbox'] for x in R])
difficult = np.array([x['difficult'] for x in R]).astype(np.bool)
# 該圖檔中該類别對應的所有bbox的是否已被比對的标志位
det = [False] * len(R)
# 累計所有圖檔中的該類别目标的總數,不算diffcult
npos = npos + sum(~difficult)
class_recs[imagename] = {'bbox': bbox,
'difficult': difficult,
'det': det}
# 讀取相應類别的檢測結果檔案,每一行對應一個檢測目标
if any(lines) == 1:
# 某一行對應的檢測目标所屬的圖像名
image_ids = [x[0] for x in splitlines]
# 讀取該目标對應的置信度
confidence = np.array([float(x[1]) for x in splitlines])
# 讀取該目标對應的bbox
BB = np.array([[float(z) for z in x[2:]] for x in splitlines])
# 将該類别的檢測結果按照置信度大小降序排列
sorted_ind = np.argsort(-confidence)
sorted_scores = np.sort(-confidence)
BB = BB[sorted_ind, :]
image_ids = [image_ids[x] for x in sorted_ind]
# 該類别檢測結果的總數(所有檢測出的bbox的數目)
nd = len(image_ids)
# 用于标記每個檢測結果是tp還是fp
tp = np.zeros(nd)
fp = np.zeros(nd)
# 按置信度周遊每個檢測結果
for d in range(nd):
# 取出該條檢測結果所屬圖檔中的所有ground truth
R = class_recs[image_ids[d]]
bb = BB[d, :].astype(float)
ovmax = -np.inf
BBGT = R['bbox'].astype(float)
# 計算與該圖檔中所有ground truth的最大重疊度
if BBGT.size > 0:
......
overlaps = inters / uni
ovmax = np.max(overlaps)
jmax = np.argmax(overlaps)
# 如果最大的重疊度大于一定的門檻值
if ovmax > ovthresh:
# 如果最大重疊度對應的ground truth為difficult就忽略
if not R['difficult'][jmax]:
# 如果對應的最大重疊度的ground truth以前沒被比對過則比對成功,即tp
if not R['det'][jmax]:
tp[d] = 1.
R['det'][jmax] = 1
# 若之前有置信度更高的檢測結果比對過這個ground truth,則此次檢測結果為fp
else:
fp[d] = 1.
# 該圖檔中沒有對應類别的目标ground truth或者與所有ground truth重疊度都小于門檻值
else:
fp[d] = 1.
# 按置信度取不同數量檢測結果時的累計fp和tp
# np.cumsum([1, 2, 3, 4]) -> [1, 3, 6, 10]
fp = np.cumsum(fp)
tp = np.cumsum(tp)
# 召回率為占所有真實目标數量的比例,非減的,注意npos本身就排除了difficult,是以npos=tp+fn
rec = tp / float(npos)
# 精度為取的所有檢測結果中tp的比例
prec = tp / np.maximum(tp + fp, np.finfo(np.float64).eps)
# 計算recall-precise曲線下面積(嚴格來說并不是面積)
ap = self.voc_ap(rec, prec, use_07_metric)
# 如果這個類别對應的檢測結果為空,那麼都是-1
else:
rec = -1.
prec = -1.
ap = -1.
return rec, prec, ap
總結
mAP計算的總體思想總結如下,得到檢測結果dets之後:
- 将所有的det_box按det_score進行排序
- 計算每個det_box與所有gt_box(ground-truth)的IOU
- 取IOU最大(max_IOU)的gt_box作為這個det_box的預測結果是否正确的判斷依據,然後根據max_IOU的結果判斷預測結果是TP還是FP
針對上述的第3步,每個類别單獨處理:
- preTP:max_IOU大于ovp_thresh,同時分類結果與max_IOU對應的gt_box的類别是一緻的,則該det_box歸為preTP,同時将這個gt_box從候選gt_box中去除。
- preFP:det_box是A類,但是對應max_IOU的gt_box是B類。
- 如果一個det_box的max_IOU與一個已經被前面det_score較大的det_box對應并且從候選的gt_box中去除了,則這個det_box也是一個preFP。
- FN:沒有被檢測到的gt_box,也就是沒有det_box的max_IOU的目标是這個gt_box。
上述的計算過程可以簡化,也就是對每個det_box,我們計算與其預測類别一樣的gt_box的IOU就行,然後取max_IOU,如果max_IOU大于ovp_thresh,并且這個max_IOU對應的gt_box還沒有被别的det_box預測(設定一個found的标志位),則這個det_box就是preTP,并将該gt_box的found設定為true,否則就是preFP。周遊完之後就可以判斷,found為false的gt_box為FN。
注意到,上述的過程中,det_score是沒有用到的,隻是最初做了一個排序,是以求得的是preTP和preFP,還不是最終結果,然後在不同的det_score的門檻值下處理上述的結果,就得到了TP和FP,就可以計算不同門檻值下的recall和precision,畫出PR曲線,計算每個類别的ap,然後得到目标檢測算法的mAP。