JOVANA
Explore Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

為偵測打分:IoU、NMS 與 mAP

學會判定一個預測方框算不算命中的幾何與計分規則。

兩個框重疊多少?交集比聯集(IoU)

上一篇我們說過,偵測器會輸出一串 邊界框,每個框都帶有類別與信心分數。但我們怎麼判斷一個預測框是真的「框在」物件上,還是只是飄在附近?我們需要一個單一數字,用來衡量兩個矩形有多吻合。這個數字就是 交集比聯集,幾乎都簡寫成 IoU。它是物件偵測的通用量尺:把預測對應到真實標註、去除重複框、為模型打分,全都建立在它之上。

兩個框、它們的重疊部分(交集),以及它們共同覆蓋的整片區域(聯集)。IoU 就是前者除以後者的比值。

兩個重疊矩形的示意圖,交集區域被填色,聯集的外框被描出。

先建立直覺,再談代數。想像兩個透明塑膠框疊在桌上。它們的交集是同時被兩個框蓋住的那塊桌面;它們的聯集是被至少一個框蓋住的那塊桌面。IoU 就是重疊面積除以總覆蓋面積。如果兩框不相碰,重疊為零,IoU 就是 0;如果兩框完全疊合,重疊等於聯集,IoU 就是 1;所有有用的情況都落在兩者之間。一個漂亮的特性順帶出現:因為它是兩個面積的比值,IoU 具有尺度不變性——把兩個框同時放大兩倍,數值完全不變,所以同一個門檻對一隻小鳥和一台大巴士都適用。

\mathrm{IoU} = \dfrac{|A \cap B|}{|A \cup B|}

兩個框 A 與 B 的交集比聯集。

逐個符號來讀:AB 是兩個框(比方說一個是預測框、一個是真實標註框)。A \cap B 是它們的交集——同時落在兩框內的區域——而 |A \cap B| 是它的面積(兩條豎線表示「面積」)。A \cup B 是它們的聯集——落在任一框內的區域——|A \cup B| 是它的面積。我們用重疊除以總覆蓋。要真正算出來就需要角點座標。假設每個框存成 (x_1, y_1, x_2, y_2):左上角 (x_1,y_1) 與右下角 (x_2,y_2),其中 x 向右增加、y 向下增加(影像的慣用座標)。於是:

\begin{aligned} x_{\text{left}} &= \max(x_1^{A}, x_1^{B}), & x_{\text{right}} &= \min(x_2^{A}, x_2^{B}) \\[2pt] y_{\text{top}} &= \max(y_1^{A}, y_1^{B}), & y_{\text{bottom}} &= \min(y_2^{A}, y_2^{B}) \\[2pt] \text{inter} &= \max(0,\, x_{\text{right}}-x_{\text{left}}) \cdot \max(0,\, y_{\text{bottom}}-y_{\text{top}}) \\[2pt] |A \cup B| &= \text{area}_A + \text{area}_B - \text{inter} \end{aligned}

從角點建出交集矩形,再求聯集。

逐行走一遍。重疊矩形的左邊是兩個左邊中較靠右的那一個,x_{\text{left}}=\max(x_1^A, x_1^B)——重疊只能在兩個框都已經開始的地方才開始。它的右邊是兩個右邊中較靠左的那一個,x_{\text{right}}=\min(x_2^A, x_2^B)——只要任一個框結束,重疊就必須結束。上邊與下邊在垂直方向遵循同樣邏輯。重疊寬度是 x_{\text{right}}-x_{\text{left}}、高度是 y_{\text{bottom}}-y_{\text{top}};兩者相乘就是交集面積 \text{inter}。關鍵的防護是 \max(0, \cdot):如果兩框在某個軸上沒有重疊,那個差會變成負數,而負寬度乘正高度會得到荒謬的負「面積」。夾到零後,不重疊的一對會剛好得到 \text{inter}=0、因而 \mathrm{IoU}=0,這正是它該有的值。最後,聯集是兩個框面積相加再減去交集——因為相加時,重疊那塊被算了兩次(每個框各算一次),所以要減掉一次,避免重複計算。

def iou(boxA, boxB):
    # boxes are (x1, y1, x2, y2): top-left and bottom-right corners
    x_left   = max(boxA[0], boxB[0])
    y_top    = max(boxA[1], boxB[1])
    x_right  = min(boxA[2], boxB[2])
    y_bottom = min(boxA[3], boxB[3])

    # clamp to 0: if the boxes do not overlap, width or height goes negative
    inter_w = max(0, x_right - x_left)
    inter_h = max(0, y_bottom - y_top)
    inter   = inter_w * inter_h

    areaA = (boxA[2] - boxA[0]) * (boxA[3] - boxA[1])
    areaB = (boxB[2] - boxB[0]) * (boxB[3] - boxB[1])
    union = areaA + areaB - inter        # subtract inter so it is not counted twice

    return inter / union if union > 0 else 0.0

# Worked example
A = (1, 1, 5, 4)   # areaA = 4 * 3 = 12
B = (3, 2, 7, 6)   # areaB = 4 * 4 = 16
# x_left=3, y_top=2, x_right=5, y_bottom=4 -> inter = 2 * 2 = 4
# union = 12 + 16 - 4 = 24  ->  IoU = 4 / 24 = 0.167
print(round(iou(A, B), 3))   # 0.167

C = (10, 10, 12, 12)         # far away from A: no overlap
# x_right - x_left = 5 - 10 = -5  ->  max(0, -5) = 0  ->  inter = 0
print(iou(A, C))             # 0.0
完整的 IoU 函式,含部分重疊(0.167)與空交集邊界情況(0.0)兩個例子。

我們把算過的數字再讀一次,因為這是你之後會重做上千次的運算。框 A=(1,1,5,4) 面積為 4\times3=12;框 B=(3,2,7,6) 面積為 4\times4=16。交集角點為 x_{\text{left}}=\max(1,3)=3y_{\text{top}}=\max(1,2)=2x_{\text{right}}=\min(5,7)=5y_{\text{bottom}}=\min(4,6)=4,重疊面積為 2\times2=4。聯集為 12+16-4=24,所以 \mathrm{IoU}=4/24\approx0.167——兩框只共享了合併足跡的六分之一。再看框 C=(10,10,12,12),它離 A 很遠:x_{\text{right}}-x_{\text{left}}=5-10=-5,夾到 0,所以交集為 0\mathrm{IoU}=0。就這一個數字——配對、抑制、評估全都從它流出。

判定命中或失誤:IoU 門檻

一個只稍微蓋到真實物件的框,確實完全沒對到的好;但要偵測器打分,我們終究得對每個預測下一個是/否的判決。IoU 把「這個框對不對?」這個模糊問題變成一條明確規則:選一個門檻(0.5 是經典選擇),只有當預測框與真實標註框的 IoU 越過這條線,才算幾何上夠好。再配上預測類別,就能把 物件偵測 中的每個預測,剛好分進以下三個桶之一。

我們熟悉的 TP/FP/FN 記帳法,套用到框上。(偵測中沒有有意義的「真陰性」——背景沒有框可數。)

混淆矩陣風格的方格,標示出真陽性、偽陽性與偽陰性。

這三個桶是:真陽性(TP)是類別正確、與某真實標註框的 IoU 超過門檻、而且是第一個認領那個物件的預測。偽陽性(FP)是沒對到任何東西的預測——它與每個真實框的 IoU 都很低、或類別錯誤、或它是某個已被更高信心框認領之物件的重複框偽陰性(FN)是沒有任何預測對到的真實物件——也就是偵測器漏掉的東西。(偵測沒有有用的「真陰性」:空白背景不是我們會去數的物件。)

  1. 把某一類別的所有預測依信心排序、最高在前,然後由上往下逐一處理。
  2. 對目前的預測,找出同類別中尚未被配對、且 IoU 最大的那個真實標註框。
  3. 如果這個最佳 IoU 超過門檻(比如 0.5),就把這個預測標為真陽性,並鎖定那個真實框為已被認領。
  4. 否則——沒有框越過門檻,或所有夠好的框都已被認領——就把這個預測標為偽陽性。
  5. 處理完所有預測後,每個仍未被配對的真實框就成為偽陰性。

用一個小例子就具體了。假設一張影像有兩隻真實的狗 G_1G_2,偵測器輸出三個框:P_1(信心 0.9,與 G_1 的 IoU 為 0.8)、P_2(信心 0.8,與 G_1 的 IoU 為 0.6)、P_3(信心 0.7,與所有東西的 IoU 都是 0.1)。依信心排序,先取 P_1:0.8 大於 0.5,所以 P_1 是 TP,G_1 現在被認領。接著 P_2:它最佳的重疊對象是 G_1,但 G_1 已被認領,所以 P_2 是重複的 FP。再來 P_3:沒有框越過 0.5,所以也是 FP。最後 G_2 從未被配對,所以是 FN。結算:1 個 TP、2 個 FP、1 個 FN——一個只找到兩隻狗之一、還吐出兩個垃圾框的偵測器。這四個計數,就是後面所有指標的原料。

去除重複框:非極大值抑制(NMS)

第一篇答應過:偵測器會吐出一團重疊的框,而我們有一個清理步驟。它就在這裡。現代偵測器很密集:它們測試成千上萬個候選位置,一個被自信偵測到的物件,會點亮的不是一個、而是一整叢相鄰的 邊界框,全都以略微不同的邊緣包住同一隻狗。上一節說明了這為什麼有害——第一個之後的每個框都變成偽陽性。我們想為每個物件只保留最好的那一個框、刪掉其餘。標準工具就是 非極大值抑制(NMS)。

之前:每個物件上一叢重疊的框。NMS 之後:每個物件只剩一個存活的框。

左圖顯示許多重疊的偵測框;右圖顯示抑制後每個物件只剩一個乾淨的框。

  1. 取某一類別的所有候選框,依信心分數排序、最高在前。
  2. 取出最頂端的框,稱為 M,加入保留清單——它是局部勝出者。
  3. 對每個剩餘的框 b 計算 IoU(M, b),丟棄任何 IoU 超過 NMS 門檻的 b——這些是 M 的重複框。
  4. 對存活下來的框重複上述步驟,直到一個不剩。保留清單就是最終的偵測集合。
\text{keep } M, \quad \text{then remove every } b_i \;\; \text{with} \;\; \mathrm{IoU}(M, b_i) \ge N_t

貪婪式 NMS 的核心抑制規則。

逐個符號來看:M 是這一步中仍在候選裡、分數最高的框——當前的「極大值」,這正是這方法叫非極大值抑制的原因:保留極大值、抑制它周圍的其他框。b_i 遍歷每一個尚未被保留或移除的其他候選框N_tNMS 的 IoU 門檻,當重疊超過這個水準,我們就判定 b_iM 的重複框並刪除它。規則讀作:接受 M,然後抹去任何與 M 重疊至少 N_tb_i。為什麼這個迴圈會停止、且每個物件剩一個框?每一輪都會永久地M(移進保留清單)連同它所有的近似孿生框一起從候選池移除,所以候選集合每輪都嚴格縮小、終究會清空。而且因為堆在同一物件上的框都與該物件的勝出者高度重疊,它們會在勝出者被選中的那一輪被抹掉——使每叢恰好留下一個存活者。

def nms(boxes, scores, iou_threshold):
    # boxes: list of (x1,y1,x2,y2); scores: matching confidence per box
    order = sorted(range(len(boxes)), key=lambda i: scores[i], reverse=True)
    keep = []
    while order:
        m = order.pop(0)          # M = highest-scoring box still in the running
        keep.append(m)
        # drop every remaining box that overlaps M too much (a duplicate)
        order = [i for i in order if iou(boxes[m], boxes[i]) < iou_threshold]
    return keep
幾行就寫完的貪婪式 NMS,重複使用第一節的 iou()。

N_t=0.5 走一個四框的例子。假設 B_1(分數 0.95)、B_2(0.90)、B_3(0.85)都緊抱同一隻狗,而 B_4(0.80)落在影像另一頭的第二隻狗上。重疊為 \mathrm{IoU}(B_1,B_2)=0.82\mathrm{IoU}(B_1,B_3)=0.74\mathrm{IoU}(B_1,B_4)=0.05。第一輪:M=B_1(極大值),接受它;B_2B_3 都超過 0.5,因此被當成重複框刪除,而 B_4 只有 0.05、遠低於 0.5,存活下來。第二輪:只剩 B_4,所以 M=B_4,接受它;沒有東西可比了。結果:\{B_1, B_4\}——每隻狗一個乾淨的框,去掉兩個偽陽性。這正是第一篇答應過的清理。

偵測器的精確率與召回率

一旦 NMS 清理好框、我們也數好了 TP、FP、FN,關於偵測器還剩兩個互補的問題。第一:在它預測的框裡,有多少是真的對的?第二:在外面所有真實物件裡,它找到了多少?這就是精確率召回率,是我們評判 物件偵測 品質的兩個面向。一個偵測器可能在其中一邊很強、另一邊很糟,所以我們永遠把兩者一起看。

掃動信心門檻會描出精確率–召回率曲線:降低門檻會提高召回率,但傾向於降低精確率。

一條精確率–召回率曲線,從低召回時的高精確,下降到高召回時的低精確。

\text{precision} = \dfrac{TP}{TP + FP}, \qquad \text{recall} = \dfrac{TP}{TP + FN}

由偵測計數算出的精確率與召回率。

用第二節的桶來讀這兩個分數。TP 是正確框的數量(類別對、IoU 超過門檻、非重複);FP 是錯誤或重複框的數量;FN 是完全漏掉的真實物件數量。精確率 =TP/(TP+FP) 是正確預測除以所有做出的預測——分母是偵測器吐出的一切,所以它回答「當它開口時,有多常是對的?」召回率 =TP/(TP+FN) 是正確預測除以所有真實物件——分母是全部真實標註物件,所以它回答「在所有存在的東西裡,它抓到了多少?」拿我們先前 1 個 TP、2 個 FP、1 個 FN 的結算:精確率 =1/(1+2)=0.33、召回率 =1/(1+1)=0.50——它抓到一半的狗,但它的框有三分之二是垃圾。

下一個指標就建立在這個關鍵動態上:精確率與召回率會隨著我們移動信心門檻而互相取捨。每個框都帶有一個信心值,而我們可以選擇一個框要多有信心才保留。調低門檻,偵測器會報出更多框——它找到更多真實物件(召回率上升),但也放進更多垃圾(精確率下降)。調高門檻,它只報出最有把握的框——那些通常是對的(精確率上升),但對較難的物件保持沉默(召回率下降)。把門檻從嚴格掃到寬鬆,會把工作點沿著一條曲線拖動,也就是圖中的精確率–召回率曲線。沒有單一門檻是「那個」答案;整條曲線才是誠實的全貌。

最關鍵的指標:平均精確率 AP 與 mAP

一整條精確率–召回率曲線雖然誠實,卻不好用——你無法靠盯著兩條曲線來排兩個模型的高下。所以我們把曲線壓成一個數字:某類別的平均精確率(AP)就是它精確率–召回率曲線下的面積。因為這條曲線已經把每個信心門檻下的精確/召回取捨都編碼進去了,它的面積就用一個介於 0 到 1 的數值,總結了偵測器在所有工作點上的表現。一個即使召回率爬向 1、精確率仍維持很高的模型,會貼近右上角、分數接近 1;一個必須犧牲精確率才換得一點召回率的模型,會塌向原點、分數很低。

\mathrm{AP} = \int_0^1 p(r)\, dr

平均精確率:單一類別精確率–召回率曲線下的面積。

輕輕拆開這個積分。當我們掃動信心門檻時,召回率 r 從 0 移到 1;在每個召回水準上,曲線給出一個精確率值,我們把它寫成函數 p(r)——「當偵測器的召回率為 r 時所達到的精確率」。積分 \int_0^1 p(r)\,drp(r) 在從 0 到 1 的每個召回上加總;在幾何上,這個總和就是曲線下的面積。為什麼一個面積能總結一切?因為它獎勵在盡可能寬的召回範圍內維持高精確率的偵測器——正是我們想要的全能表現。如果你還沒接觸過積分,就把它讀成「召回率跑完整個範圍時,精確率曲線的平均高度」:一條穩在精確率 0.8 的水平曲線,AP =0.8;一條完美的曲線(處處精確率為 1)AP =1

你會在論文裡看到一個實務上的細節:真實的精確率–召回率曲線是鋸齒狀的——隨著你一個一個加入預測,精確率會上下抖動。為了讓 AP 穩定且可比較,評估時會用內插 AP(interpolated AP):在每個召回水準上,把精確率換成在該召回或更高召回處所見到的最高精確率,把曲線抹平成一個只會往下走的階梯。你不需要手算這個;只要把「內插 AP」這個詞認得為「整理過、單調版本曲線下的面積」,這樣指標就不會去獎勵運氣好的局部抖動。

\mathrm{mAP} = \dfrac{1}{C} \sum_{c=1}^{C} \mathrm{AP}_c \qquad\Big(\text{COCO: also average over IoU thresholds } 0.50 : 0.05 : 0.95\Big)

對 C 個類別取平均的平均精確率,以及 COCO 的多門檻慣例。

AP 為一個類別打分;但偵測器要處理許多類別,所以我們取平均。\mathrm{AP}_c 是類別 c 的平均精確率;C 是類別總數(COCO 基準有 80 個);總和 \sum_{c=1}^{C}\mathrm{AP}_c 把各類別的分數加起來,而 \tfrac{1}{C} 把總和變成平均——這就是平均精確率均值(mAP),每篇偵測論文都會報的那個關鍵數字。對類別取平均,能阻止一個模型只靠把「人」做得很好、卻在「烤麵包機」上慘敗來看起來不錯;它必須全面表現。COCO 基準再加上第二層、更深的平均:它不把 IoU 門檻固定在 0.5,而是在十個門檻上計算 mAP——0.50, 0.55, 0.60, \dots, 0.95(記號 0.50\!:\!0.05\!:\!0.95 意思是「從 0.50 到 0.95、每步 0.05」)——再把這些也平均起來。

正是這第二層平均讓 mAP 如此有洞察力,因為它一次衡量了分類品質定位品質。要在 IoU 0.5 拿到 AP,框只需大致落在物件上——這主要測試偵測器有沒有叫對類別、有沒有靠近。要在 IoU 0.9 還能拿到 AP,框就必須緊緊包住物件、邊緣幾乎貼在真實標註上——這測試的是精準定位。藉由在整個 0.50–0.95 的階梯上取平均,COCO mAP 獎勵的是既「答對是什麼」(分類)、又「答對在哪、精準到像素」(定位)的偵測器。一個模型可能在寬鬆的框上拿滿分,卻在緊框上崩潰;多門檻平均正好把這一點暴露出來。

AP 是這條曲線下被填色的面積;mAP 把這塊面積在所有類別上取平均(COCO 還會在 IoU 門檻上再平均)。

精確率–召回率曲線,其下方面積被填色,代表平均精確率。