跳轉到

10.2 影像分類與物件偵測#

概念說明#

影像分類 (image classification):給整張影像,輸出一個類別標籤。這是最基礎的視覺任務,也是其他任務的積木。

架構演化三個里程碑:

  • LeNet (1998):兩層卷積 + 池化 + 全連接,證明梯度下降能訓練 CNN(手寫數字)。
  • AlexNet (2012):GPU 訓練 + ReLU + Dropout,在 ImageNet 把錯誤率一口氣砍 10 個百分點,開啟深度學習時代。
  • ResNet (2015):殘差連接 (residual connection)。深度網路曾經「越深越差」(退化問題:更深反而訓練誤差更高,非過擬合)。ResNet 讓每個區塊學「增量」而非完整變換:

y = F(x) + x

恆等捷徑讓梯度可以原樣流回淺層(∇y/∇x = ∇F + I,至少有 1),百層網路得以訓練。殘差連接如今是所有深度架構(含 Transformer)的標配。另一個實務關鍵是遷移學習:用 ImageNet 預訓練的骨幹 (backbone) + 自己資料微調 (fine-tune),幾百張圖就能得到好分類器——特徵(第 9 章的夢想)由大資料一次學好、處處重用。

物件偵測 (object detection):不只說「有貓」,還要畫出每隻貓的邊界框 (bounding box) (x, y, w, h) 與類別。難點:物件數量未知、尺度各異、位置任意。兩條技術路線:

  • Two-stage(候選再精修):R-CNN → Fast/Faster R-CNN。先由 Region Proposal Network (RPN) 提出幾千個候選框,再對每個候選做分類與框回歸。精準但慢(早期 R-CNN 每張圖數十秒,Faster R-CNN 約 5~10 FPS)。
  • One-stage(一次算完):YOLO (You Only Look Once, 2016)、SSD。把影像切成網格,每格直接預測多個框的 (座標, 信心度, 類別),單次前向完成。快(數十~數百 FPS)但早期精度略遜;現代 YOLO 已兼顧兩者。

框的表示與損失:直接回歸絕對座標不穩定,實務用錨框 (anchor)——預設一組不同尺度與長寬比的框,網路只預測相對偏移;損失 = 分類交叉熵 + 框回歸損失(IoU 損失或 Smooth L1)。IoU (Intersection over Union) 是框的評測核心:

IoU = 兩框交集面積 / 聯集面積

預測框與真值 IoU > 0.5 算正確偵測;mAP (mean Average Precision) 對每類畫 precision-recall 曲線求面積再平均,是偵測的標準指標。後處理用 NMS(非極大值抑制,第 4 章同名技巧的框版本):同類重疊 IoU > 0.5 的候選框只留分數最高者,消掉重複偵測。

演算法步驟(IoU + 樸素 NMS,偵測後處理核心)#

  1. 輸入候選框清單 [(box, score, class)]。
  2. 依分數排序。
  3. 取最高分框加入結果;計算它與其餘框的 IoU,刪除同類且 IoU > 門檻者。
  4. 重複直到清單空。

Python 程式碼範例#

import numpy as np

def iou(a, b):
    ax1, ay1, ax2, ay2 = a; bx1, by1, bx2, by2 = b
    ix = max(0, min(ax2, bx2) - max(ax1, bx1))
    iy = max(0, min(ay2, by2) - max(ay1, by1))
    inter = ix * iy
    union = (ax2-ax1)*(ay2-ay1) + (bx2-bx1)*(by2-by1) - inter
    return inter / union

def nms(boxes, scores, thr=0.5):
    order = np.argsort(scores)[::-1]
    keep = []
    while order.size:
        i = order[0]; keep.append(i)
        rest = order[1:]
        order = rest[np.array([iou(boxes[i], boxes[j]) <= thr
                               for j in rest])] if rest.size else rest
    return keep

boxes = [(10,10,50,50), (12,12,52,52), (100,100,150,140), (15,15,55,55)]
scores = [0.9, 0.8, 0.7, 0.6]
print("兩個重疊框的 IoU:", iou(boxes[0], boxes[1]).round(3))
keep = nms(boxes, scores, 0.5)
print("NMS 保留的框:", keep, "(應保留 0 與 2)")

# 一個迷你的「錨框回歸」示範:預測偏移把錨框推向真值
anchor = np.array([20, 20, 60, 60])            # (x1,y1,x2,y2)
truth  = np.array([30, 22, 80, 66])
# 預測偏移 = 真值 - 錨框
pred = anchor + (truth - anchor) * 1.0
print("回歸後 IoU:", iou(pred, truth).round(3), "(=1 表示完美對齊)")

# 感受野與「分類→偵測」的橋:滑窗分類 vs 密集預測
print("滑窗法:100×100 影像、窗口 20×20、步長 10 →",
      ((100-20)//10+1)**2, "個分類呼叫")
print("YOLO 式:一次前向,網格 10×10 每格預測 →", 10*10, "組框")

執行後:兩個幾乎重疊的框 IoU 約 0.86,NMS 正確地只留高分者(索引 0 與不相干的 2);錨框回歸示範了「預測偏移」的幾何;最後的數量對比(81 次 vs 1 次)說明了 one-stage 偵測的效率來源——把滑窗的分類共享進一次卷積前向。

複雜度分析#

分類:一次前向 = 所有卷積層之和,ResNet-50 約 4 GFLOPs/張,GPU 上毫秒級。偵測 two-stage:骨幹前向 + RPN + 對 ~2000 個候選各自的 RoI 特徵與分類,約 2~4 倍骨幹成本;one-stage:一次密集預測 O(H·W·A·(4+1+C))(A 錨框數、C 類別數),與分類同階——這就是它能即時的原因。NMS:樸素 O(B²)(B 候選框),用按分數排序的堆與空間格網可近 O(B log B)。空間:特徵圖金字塔 (FPN) 進一步用多尺度特徵解決小物件問題——這正是 4.3 節「尺度空間」思想在深度學習中的再現。

練習#

  1. 實作 IoU 矩陣計算(B×B 向量化版本),對 1000 個隨機框測量與樸素雙迴圈的時間比。
  2. 修改 NMS 門檻 0.3 / 0.5 / 0.7,對「同一物件的多個高重疊候選 + 兩個相鄰物件」的測試案例,觀察哪個門檻開始把不同物件的框誤刪。
  3. 查一個預訓練模型的類別數與參數量(如 ResNet-18:11M),估算微調最後一層時需要更新的參數比例,解釋遷移學習為何省資料。