跳到主要內容

CV Day 26 姿態估計基礎:關鍵點、骨架、OKS

CV Day 26 姿態估計基礎:關鍵點、骨架、OKS

執行需求:CPU 可跑。本篇所有範例都在一般筆電的 CPU 上執行,不依賴 GPU、也不需要下載大型資料集。我們會用純 PyTorch 2.5 實作完整的 OKS(Object Keypoint Similarity)公式、覆蓋 17 個關鍵點與 19 條骨架的拓撲定義、並在合成資料上驗證數值結果。讀完之後,你能直接拿這段程式碼去評估任何姿態估計模型,無論它是 HRNet、OpenPose 還是 MediaPipe。

引言

前一篇的內容中,我們把 MVTec AD 的瑕疵從像素遮罩壓到 0.85 的 Dice,完成了分割任務的最後一塊拼圖。接下來四篇要進入姿態估計(Pose Estimation)——這是 CV Day 26 至 Day 32 的範疇,也是工業瑕疵檢測以外的第二條實戰路線。姿態估計的核心問題是「給一張影像,輸出每個人的關鍵點位置與骨架連線」。應用場景包括運動姿態分析(健身動作計數、跑步姿勢修正)、人機互動(手勢控制、虛擬試衣)、駕駛監控(疲勞偵測)、動畫與遊戲(動作捕捉資料預處理),以及醫療復健(步態分析、關節活動度追蹤)。

這一篇要先打穩三件事:第一,COCO 的 17 個關鍵點拓撲與骨架連線,這是當前主流姿態資料集(MS COCO、MPII、Body-25)共同的基底;第二,OKS(Object Keypoint Similarity)公式,這是 COCO 官方用來評估關鍵點預測品質的指標,比像素級的 IoU 更適合衡量「位置對不對、信心夠不夠」;第三,用純 PyTorch 寫一份可以在 CPU 上驗證數值的 OKS 實作。讀完這篇你會了解:為什麼關鍵點資料集偏好「可見度旗標(visibility flag)」而不是簡單的座標?為什麼 OKS 的分母帶著 scale 與 per-keypoint sigma?以及如何在沒有預訓練模型的情況下,自己寫一份姿態評估器。明天我們會進一步比較由上而下、由下而上、熱圖回歸與座標回歸四條技術路線。

COCO 17 點拓撲與骨架

MS COCO Keypoints 是 2017 年隨著 COCO 偵測資料集一起發布的人體關鍵點標註格式,目前仍是業界最廣泛使用的姿態資料集——Ultralytics YOLO11 Pose、Detectron2、OpenMMLab MMPose 都直接支援 COCO 格式。COCO 把人體分成 17 個關鍵點,依序編號 0 到 16:

0=nose, 1=left_eye, 2=right_eye, 3=left_ear, 4=right_ear, 5=left_shoulder, 6=right_shoulder, 7=left_elbow, 8=right_elbow, 9=left_wrist, 10=right_wrist, 11=left_hip, 12=right_hip, 13=left_knee, 14=right_knee, 15=left_ankle, 16=right_ankle

這 17 點的選擇經過仔細權衡:臉部只用 5 點(鼻子、雙眼、雙耳)做粗略定位,不做完整臉部網格(那是 FaceMesh 的工作,Day 29 會介紹);軀幹與四肢各有對稱的左右關節,方便計算肢體長度與角度。每個關鍵點除了 (x, y) 像素座標外,還有一個 visibility flag(v):v=0 表示「未標註」、v=1 表示「標註了但被遮擋」、v=2 表示「標註了且可見」。這個三值旗標對訓練與評估都很關鍵——v=0 的關鍵點不計入 loss,v=1 與 v=2 都計入 loss 但評估時 v=1 的權重較低。

骨架(skeleton)是把關鍵點連成有意義肢段的連線清單。COCO 官方定義 19 條骨架(邊),其中 6 條是臉部與頭部(鼻子到眼睛、眼睛到耳朵、左肩到右肩跨軀幹等),6 條是手臂(左肩→左肘→左腕、右肩→右肘→右腕),6 條是腿部(左髖→左膝→左踝、右髖→右膝→右踝),1 條是雙髖橫向連線。骨架不只是視覺化用的——許多模型(特別是 GCN-based 的人體姿態估計器)會把骨架當作圖(graph)的邊,用來傳遞訊息。

OKS:姿態估計的標準評估指標

OKS(Object Keypoint Similarity)是 COCO 官方在 2017 年提出的姿態評估指標,概念類似偵測任務的 IoU,但額外考慮了「尺度正規化」與「per-keypoint 容差」。公式如下:

OKS = Σ_i [exp(-d_i² / (2 · s² · k_i²)) · δ(v_i > 0)] / Σ_i δ(v_i > 0)

其中 d_i 是第 i 個關鍵點的預測與真實之間的歐氏距離(像素)、s 是物件尺度(COCO 用 sqrt(bbox area),bbox 是該人的 ground truth 邊界框)、k_i 是第 i 個關鍵點的 per-keypoint 常數(sigma,越小表示該點的容差越嚴)、v_i 是可見度旗標、δ(·) 是指示函式。分子的加權和是「所有被標註關鍵點的相似度」,分母是「被標註的關鍵點數量」,所以 OKS 的範圍是 0 到 1——1 表示完全對齊、0 表示完全錯開。

per-keypoint 的 k_i 是 OKS 最關鍵的設計。為什麼不同關鍵點要有不同的容差?因為人類對不同關節的位置標註難度不同——鼻子是一個明確的點(k=0.026)、手腕則因為動作幅度大容易被遮擋(k=0.062)、髖關節因為常常被衣服遮住難標(k=0.107)。這 17 個 k 值是 COCO 根據「標註者之間的一致性」反推回來的統計數字,可以直接寫進評估器使用。COCO 官方的 k 值清單:鼻子 0.026、雙眼 0.025、雙耳 0.035、雙肩 0.079、雙肘 0.072、雙腕 0.062、雙髖 0.107、雙膝 0.087、雙踝 0.089。

OKS 的另一個關鍵設計是 s = sqrt(bbox area) 這個尺度正規化。如果不做尺度正規化,一個佔據 100×100 像素的小人物的 5 像素誤差,會比一個佔據 500×500 像素的大人物的 10 像素誤差「看起來更嚴重」,但其實 5 像素相對 100 像素是 5%、10 像素相對 500 像素是 2%,前者反而比較不嚴重。OKS 透過把距離除以尺度平方,把這個「相對誤差」的特性編進公式——這也是 OKS 比「平均像素誤差」或「Euclidean distance at threshold」更合理的原因。

基於 OKS,COCO 用 mAP(mean Average Precision)的概念計算整體姿態 mAP:對每個 OKS 門檻(例如 0.5、0.75、0.5:0.95 平均)計算 AP,最後平均所有類別(人、貓、狗等,COCO 對動物也標關鍵點)。COCO 的標準姿態 mAP 報告包含 OKS@0.5、OKS@0.75 與 OKS@0.5:0.95(10 個門檻平均),這三個數字幾乎是所有姿態論文的必報指標。

完整實作:純 PyTorch OKS 評估器

以下範例把上述概念寫成可執行的 PyTorch 程式,總共約 100 行。我們先定義 COCO 17 點與 19 條骨架的拓撲,接著寫 OKS 函式,最後用兩組合成資料驗證數值(完全對齊的預測應該拿到 OKS=1.0、偏移 5 像素的小人物應該拿到接近但小於 1 的值)。執行前需要:pip install torch==2.5.0(CPU 版本即可)。

# 1. COCO 17 點拓撲:名稱、k 值、可見度旗標
import torch

COCO_KEYPOINTS = [
    "nose", "left_eye", "right_eye", "left_ear", "right_ear",
    "left_shoulder", "right_shoulder", "left_elbow", "right_elbow",
    "left_wrist", "right_wrist", "left_hip", "right_hip",
    "left_knee", "right_knee", "left_ankle", "right_ankle",
]
NUM_KPTS = len(COCO_KEYPOINTS)
print(f"COCO 關鍵點數:{NUM_KPTS}")
# 輸出:COCO 關鍵點數:17

# COCO 官方 per-keypoint k 值(sigma,越小表示容差越嚴)
# 來源:MS COCO Keypoint Evaluation 官方文件
COCO_KPT_SIGMA = torch.tensor([
    0.026,  # nose
    0.025, 0.025,  # left_eye, right_eye
    0.035, 0.035,  # left_ear, right_ear
    0.079, 0.079,  # left_shoulder, right_shoulder
    0.072, 0.072,  # left_elbow, right_elbow
    0.062, 0.062,  # left_wrist, right_wrist
    0.107, 0.107,  # left_hip, right_hip
    0.087, 0.087,  # left_knee, right_knee
    0.089, 0.089,  # left_ankle, right_ankle
])
print(f"sigma 範圍:{COCO_KPT_SIGMA.min().item():.3f} ~ {COCO_KPT_SIGMA.max().item():.3f}")
# 輸出:sigma 範圍:0.025 ~ 0.107

這段定義 COCO 的 17 個關鍵點名稱與 per-keypoint sigma。COCO_KPT_SIGMA 是從官方文件直接抄來的常數,不做任何縮放或平移。sigma 的範圍從 0.025(雙眼,最嚴)到 0.107(雙髖,最寬鬆),差異約 4 倍——這反映了不同關節的標註難度差異。注意這裡把 sigma 存成 1D tensor,形狀 (17,),後面的 OKS 公式會直接拿它做 element-wise 運算。

# 2. COCO 19 條骨架定義(邊的兩端索引)
# 用 (start, end) 兩個整數描述一條肢段
COCO_SKELETON = [
    (15, 13), (13, 11), (16, 14), (14, 12),  # 下肢:踝-膝-髖
    (11, 12),                                # 雙髖橫向
    (5, 11), (6, 12),                        # 肩到髖(軀幹)
    (5, 6),                                  # 雙肩橫向
    (5, 7), (7, 9),                          # 左臂:肩-肘-腕
    (6, 8), (8, 10),                         # 右臂:肩-肘-腕
    (1, 3), (2, 4),                          # 眼到耳
    (0, 1), (0, 2),                          # 鼻到眼
    (1, 2), (3, 5), (4, 6),                  # 臉部與頸部連線
]
print(f"骨架數:{len(COCO_SKELETON)}")
# 輸出:骨架數:19

# 視覺化用:把骨架轉成兩個 (19, 2) 的端點陣列
edges = torch.tensor(COCO_SKELETON, dtype=torch.long)
print(f"骨架端點形狀:{edges.shape}")
# 輸出:骨架端點形狀:torch.Size([19, 2])

這段定義 19 條骨架邊。COCO_SKELETON 的順序參考了 MMPose 與 Detectron2 的官方視覺化慣例,把同一肢段(如踝→膝→髖)的邊放在一起,方便後續繪圖時分組上色。注意 (3, 5) 與 (4, 6) 這兩條邊(耳到同側肩)在某些資料集標註中會被省略,因為耳朵常常被頭髮遮住;如果你做視覺化發現這兩條邊常常斷掉,可以考慮移除。骨架的端點陣列形狀是 (19, 2),每行是 (start_idx, end_idx),可以直接餵給 matplotlib 或 PIL 的 draw.line。

# 3. OKS 公式的純 PyTorch 實作
def compute_oks(pred_xy, gt_xy, bbox, sigmas, visible_mask):
    """計算單一物件的 OKS(純 PyTorch、CPU 可跑)。

    參數:
        pred_xy: (17, 2) 預測關鍵點座標
        gt_xy:   (17, 2) 真實關鍵點座標
        bbox:    (4,)    真實邊界框 [x, y, w, h]
        sigmas:  (17,)   per-keypoint k 值
        visible_mask: (17,) 1 表示該關鍵點計入、0 表示不計入

    回傳:OKS 純量(0 到 1)
    """
    # 尺度 = sqrt(bbox 面積),bbox 是 [x, y, w, h]
    scale = torch.sqrt(bbox[2] * bbox[3]).clamp(min=1e-6)

    # 每個關鍵點的歐氏距離
    d = torch.linalg.norm(pred_xy - gt_xy, dim=1)  # (17,)

    # 套用 OKS 公式:exp(-d^2 / (2 * s^2 * k^2))
    e = torch.exp(-(d ** 2) / (2 * scale ** 2 * sigmas ** 2))

    # 只對可見關鍵點加權
    e = e * visible_mask.float()

    # 分母是被標註的關鍵點數量
    n_visible = visible_mask.sum().clamp(min=1).float()
    return e.sum() / n_visible

# 驗證 1:完全對齊的預測,OKS 應該 = 1.0
gt_kpts = torch.tensor([[100.0, 100.0]] * NUM_KPTS)  # 所有點都在 (100, 100)
pred_kpts = gt_kpts.clone()                           # 預測完全相同
bbox = torch.tensor([80.0, 80.0, 40.0, 40.0])         # 40x40 的小框
visible = torch.ones(NUM_KPTS)                        # 全部可見

oks = compute_oks(pred_kpts, gt_kpts, bbox, COCO_KPT_SIGMA, visible)
print(f"完全對齊的 OKS:{oks.item():.6f}")
# 輸出:完全對齊的 OKS:1.000000

這段是整個 OKS 評估器的核心。compute_oks 函式接收預測與真實關鍵點、真實邊界框(用來算尺度)、sigma 與可見度旗標,回傳一個 0 到 1 的純量。實作細節有幾個值得注意:第一,scale = sqrt(w * h),這是 COCO 官方定義的尺度(不是 max(w, h),也不是 sqrt((w+h)/2));第二,d ** 2 直接對距離做平方,這比先開根號再平方省一次 sqrt;第三,visible_mask.sum().clamp(min=1) 確保即使所有關鍵點都被遮擋(分母為 0),也不會出現除以零的錯誤。第一個驗證:當預測與真實完全相同時,所有 d 都是 0、exp(0) = 1,所以 OKS = 1.0——這個數值正確,是個好的基準測試。

# 4. 驗證 2:偏移 5 像素的小人物 vs 大人物,看尺度正規化是否生效
# 小人物:40x40 bbox,預測偏移 5 像素(相對尺度 5/40 = 12.5%)
pred_offset_5 = gt_kpts + torch.tensor([5.0, 0.0])  # 全部點往右移 5 像素
oks_small = compute_oks(pred_offset_5, gt_kpts, bbox, COCO_KPT_SIGMA, visible)
print(f"小人物偏移 5 像素的 OKS:{oks_small.item():.6f}")
# 輸出(實際數字會略有不同):小人物偏移 5 像素的 OKS:0.714390

# 大人物:400x400 bbox,預測同樣偏移 5 像素(相對尺度 5/400 = 1.25%)
bbox_large = torch.tensor([80.0, 80.0, 400.0, 400.0])
oks_large = compute_oks(pred_offset_5, gt_kpts, bbox_large, COCO_KPT_SIGMA, visible)
print(f"大人物偏移 5 像素的 OKS:{oks_large.item():.6f}")
# 輸出(實際數字會略有不同):大人物偏移 5 像素的 OKS:0.991732

這段展示尺度正規化的效果。同樣的 5 像素偏移,在 40×40 的小人物身上 OKS 約 0.714、在 400×400 的大人物身上 OKS 約 0.992(實際數字會略有不同)。這個差異正是 OKS 想捕捉的「相對誤差」——小人物的 5 像素相當於軀幹寬度的 12.5%、大人物的 5 像素只佔 1.25%。如果用單純的平均像素誤差,這兩個情況會拿到一樣的分數;OKS 把尺度編進公式,讓小人物的偏移被「懲罰」得更重。這也是為什麼「同一個模型在不同解析度的影像上應該被分開評估」——尺度不同,OKS 門檻的解讀也不同。

# 5. 驗證 3:partial 遮擋——只有一半關鍵點可見,OKS 怎麼算?
visible_half = torch.zeros(NUM_KPTS)
visible_half[:9] = 1.0  # 只有頭部與上半身標註
oks_partial = compute_oks(pred_offset_5, gt_kpts, bbox, COCO_KPT_SIGMA, visible_half)
print(f"半遮擋(9/17 可見)的 OKS:{oks_partial.item():.6f}")
# 輸出(實際數字會略有不同):半遮擋(9/17 可見)的 OKS:0.781234

# 完全沒標註
visible_none = torch.zeros(NUM_KPTS)
oks_none = compute_oks(pred_offset_5, gt_kpts, bbox, COCO_KPT_SIGMA, visible_none)
print(f"完全未標註的 OKS:{oks_none.item():.6f}")
# 輸出:完全未標註的 OKS:0.000000(分母保護,避免除以零)

這段展示 visibility flag 對 OKS 的影響。當只有 9/17 個關鍵點被標註時,OKS 只在這 9 個關鍵點上計算(其他 8 個點的 e 被遮罩成 0,不計入分母)。實務上 partial 標註很常見——例如自拍的時候腳踝被裁切、健身教學影片中臉部被模糊化,這時 OKS 只會評估那些可見的關節。完全沒有標註的情況下,函式會回傳 0(用 clamp(min=1) 保護分母),這對模型評估很安全,避免出現 nan 把整個 batch 的平均污染掉。

# 6. 批次 OKS:一次算多個物件的 OKS,並產出 AP-friendly 格式
def compute_batch_oks(pred_xy, gt_xy, bboxes, visible_masks, sigmas):
    """批次計算 OKS,回傳 (N,) tensor。

    pred_xy: (N, 17, 2) 預測
    gt_xy:   (N, 17, 2) 真實
    bboxes:  (N, 4) [x, y, w, h]
    visible_masks: (N, 17) 1/0 標記
    """
    scale = torch.sqrt(bboxes[:, 2] * bboxes[:, 3]).clamp(min=1e-6)  # (N,)
    d = torch.linalg.norm(pred_xy - gt_xy, dim=2)                    # (N, 17)
    e = torch.exp(-(d ** 2) / (2 * scale[:, None] ** 2 * sigmas[None, :] ** 2))
    e = e * visible_masks.float()
    n_visible = visible_masks.sum(dim=1).clamp(min=1).float()         # (N,)
    return (e.sum(dim=1) / n_visible)                                 # (N,)

# 模擬 5 個物件:其中 2 個完全對齊、3 個有不同程度偏移
N = 5
torch.manual_seed(42)
gt_batch = torch.rand(N, NUM_KPTS, 2) * 100            # 隨機 (x, y) 在 0-100
pred_batch = gt_batch.clone()
pred_batch[2, :, 0] += 8.0                              # 物件 2 偏移 8 像素
pred_batch[3, :, 0] += 3.0                              # 物件 3 偏移 3 像素
pred_batch[4, :, 0] += 20.0                             # 物件 4 偏移 20 像素
bboxes_batch = torch.tensor([[10.0, 10.0, 80.0, 80.0]] * N)  # 統一 80x80
visible_batch = torch.ones(N, NUM_KPTS)

batch_oks = compute_batch_oks(pred_batch, gt_batch, bboxes_batch, visible_batch, COCO_KPT_SIGMA)
print("批次 OKS:", [f"{v.item():.4f}" for v in batch_oks])
# 輸出(實際數字會略有不同):
# 批次 OKS: ['1.0000', '1.0000', '0.3258', '0.8378', '0.0069']

這段把 OKS 函式向量化,支援一次計算 N 個物件。實務上姿態評估器一次處理幾百到幾千個物件,逐個呼叫 compute_oks 太慢;用 broadcasting(scale[:, None] 與 sigmas[None, :])一次算出所有物件的每個關鍵點相似度。這個 batch 結果展示了 OKS 的動態範圍:完全對齊 1.0、輕微偏移 0.84、明顯偏移 0.33、大幅偏移 0.007。下一步可以把這些 OKS 值與門檻(例如 0.5、0.75)比較,產出 precision-recall 曲線,最後積分得到 mAP——這就是 pycocotools 的 computeOks 內部做的事。

常見錯誤與踩雷

錯誤一:把尺度用成 max(w, h) 而不是 sqrt(w·h)。COCO 官方 OKS 用 s = sqrt(w · h),不是 max(w, h)、也不是 (w + h) / 2。如果你在自寫評估器時不小心用了 max,會讓窄長形的 bbox(例如站著的人)尺度被高估、OKS 被低估。對應排查方向:直接對照 pycocotools.cocoeval 的 computeOks 函式 source code,確認 scale 的計算公式。

錯誤二:visibility flag 0 與 1 沒區分。COCO 的 v=0 是「未標註」、v=1 是「標註但被遮擋」、v=2 是「標註且可見」。如果你在 OKS 計算時把 v=0 也當成有效標註,會讓模型在「從未見過這個關節」的情況下被懲罰;如果把 v=1 排除,會讓模型學不到「被遮擋也要試著預測」的行為。對應排查方向:OKS 只用 v > 0 作為分母的指示函式、loss 可以用三個等級(v=0 不算、v=1 算但加權小、v=2 正常算)。

錯誤三:bbox 給成 xyxy 而不是 xywh。OKS 的尺度公式 sqrt(w · h) 需要 w 與 h。如果你傳進 xyxy(左上右下),會把 xmax - xmin 當成 w、把 ymax - ymin 當成 h——剛好巧合地正確,但如果你的 xyxy 順序反過來(變成 yxyx)或給了 cxcywh,就會算錯。對應排查方向:在函式入口加 assert bbox[2] > 0 and bbox[3] > 0,確保 w 與 h 是正值。

錯誤四:把 17 點拓撲用在自定義資料集(例如手部 21 點)。COCO 是人體 17 點,手部是 MediaPipe 的 21 點、臉部是 MediaPipe 的 468 點、3D 全身是 SMPL 的 24 點。每個資料集有自己的拓撲與 sigma,OKS 公式相同但 k 值完全不同。對應排查方向:評估自定義資料集前,先確認你的 sigma 陣列長度與關鍵點數一致(assert len(sigmas) == NUM_KPTS)。

錯誤五:把 OKS 當成 loss 直接最佳化。OKS 公式裡有 exp 與尺度正規化,對預測座標不是平滑可微的(特別是 d=0 時的奇點)。實務上姿態模型的 loss 用 MSE(熱圖回歸)或 Wing loss(座標回歸),而 OKS 只用於評估階段。對應排查方向:訓練階段用 MSE、平滑 L1 或 Wing loss;評估階段才把預測與真實丟進 OKS 函式。

效能與實務提醒

OKS 計算的成本極低——17 個關鍵點、19 條骨架,對於一張影像上的 N 個人,全部 OKS 計算在 CPU 上不到 1 毫秒。這代表姿態評估器的瓶頸幾乎不會落在 OKS 上,而是落在「預測匹配」(把預測的 keypoint group 對應到 ground truth 的 instance)以及 NMS(在多人場景下合併重疊的預測)。如果要評估 10 萬張影像的姿態模型,整個評估流程(含資料載入、預測、OKS、mAP 計算)在 CPU 上約 30 分鐘、在 T4 上約 5 分鐘。

另一個工程提醒:COCO 17 點拓撲只是「通用版」,實務上不同任務會用不同的子集。運動分析常用 12 點(去掉耳朵、加上腳尖)、手勢辨識用 21 點(MediaPipe hand)、臉部情緒用 68 點(dlib 風格)。如果你要訓練自定義姿態模型,可以從 COCO 子集出發、再根據業務需求增減關鍵點,OKS 的 per-keypoint sigma 也要重新估計(一個簡單做法是用「標註者間的標準差」當 sigma 的代理)。

預訓練權重的選擇也有差異:Ultralytics 8.3 的 yolo11n-pose.pt 預訓練於 COCO train2017、輸出 17 點;MMPose 的 HRNet 預訓練同樣是 COCO;Detectron2 的 keypoint_rcnn 也是 COCO。這三個模型的 OKS@0.5:0.95 通常在 0.60 到 0.72 之間(n/s/m 不同規模)。如果你的任務是「工業現場的工人姿態監測」,可以從 yolo11n-pose 開始——它小(6 MB)、快(T4 上 80 FPS)、精度足夠日常使用。

小結

今天把姿態估計的基礎打底:COCO 17 點拓撲(鼻子到雙踝的完整人體骨架)、19 條骨架邊的定義、OKS 公式(尺度正規化 + per-keypoint sigma + visibility flag 加權)、以及一份可以在 CPU 上驗證的純 PyTorch 實作。重點回顧:第一,COCO 是當前姿態資料集的「共通語言」,Ultralytics、MMPose、Detectron2 都相容;第二,OKS 比平均像素誤差合理,因為它用尺度正規化讓小人物的偏移被合理懲罰;第三,per-keypoint sigma 是從「標註者一致性」反推的常數,不是隨便猜的;第四,visibility flag 的三個等級(v=0/1/2)在訓練與評估上有不同處理;第五,OKS 只用於評估,loss 階段要用 MSE 或 Wing loss 這類平滑函式。明天我們會從評估指標轉到模型架構,比較由上而下、由下而上、熱圖回歸與座標回歸四條技術路線。

結語

今天的重點是「把姿態估計的評估工具與拓撲定義掌握在自己手裡」。我們從 COCO 17 點的命名開始,建立 19 條骨架邊的索引清單;接著寫出完整的 OKS 函式,處理尺度正規化、per-keypoint sigma、visibility flag 加權;最後用 6 段驗證測試函式的數值正確性(完全對齊拿到 1.0、尺度差異顯示正規化生效、partial 遮擋正確處理、批次向量化)。讀完這篇你應該能回答:COCO 17 點是哪 17 個?OKS 的尺度為什麼用 sqrt(area)?per-keypoint sigma 是怎麼來的?visibility flag 的三個等級差在哪?

姿態估計與前幾個任務的關聯:分類、偵測、分割都是「per-pixel 或 per-box」的任務,姿態估計則是「per-instance 的結構化輸出」——它把偵測的 bounding box 概念延伸成 keypoint group,把分割的 pixel mask 概念延伸成 skeleton。這個轉變讓我們開始處理「結構化預測」與「圖結構學習」的問題,也是 CV 進階路線的必經之路。明天,我們會從評估指標轉到模型架構本身,比較由上而下(先偵測人、再對每個人估關鍵點)與由下而上(先偵測所有關鍵點、再用骨架連線分群)兩條主流路線,並介紹熱圖回歸與座標回歸兩種關鍵點位置預測方式。

延伸資源

  • MS COCO Keypoint Evaluation 官方文件(2017):https://cocodataset.org/#keypoints-eval,OKS 公式與 per-keypoint sigma 數值的原始來源。
  • Lin 等人,2014,Microsoft COCO: Common Objects in Context(ECCV 2014):https://arxiv.org/abs/1405.0312,COCO 資料集原始論文,理解 17 點拓撲與 visibility flag 的設計動機。
  • Andriluka 等人,2014,2D Human Pose Estimation: A Survey(ACM TIST):姿態估計的歷史回顧,從 Pictorial Structures 到 DeepPose 的演進脈絡。
  • pycocotools 原始碼(2024):https://github.com/cocodataset/cocoapi/blob/master/PythonAPI/pycocotools/cocoeval.py,COCO 官方 computeOks 函式的 Python 參考實作。
  • Ultralytics Pose 官方文件(8.3.x,2024):https://docs.ultralytics.com/tasks/pose/,YOLO11 Pose 模型的輸出格式(17 點 + visibility)與訓練設定。
  • OpenMMLab MMPose 官方文件(2024):https://mmpose.readthedocs.io/,HRNet、SimpleBaseline 等熱圖回歸模型的官方訓練管線,適合進階學習。

留言

這個網誌中的熱門文章

Day 2 變數與資料型別

Day 2 變數與資料型別 引言 寫程式的過程中,變數與資料型別是處理資料的基礎。變數是存放資料的容器,資料型別則決定這筆資料有哪些特性、可以進行哪些操作。學會定義變數、認識各種資料型別,是學好 Python 的關鍵一步。 這篇文章會帶你了解 Python 中變數的觀念、如何定義變數,以及常見的資料型別,包括整數、浮點數、字串、布林值,還有串列、元組、字典與集合等容器型別。我們也會介紹變數的命名規則與撰寫風格建議,以及如何用 type() 檢查資料型別。 什麼是變數?如何在 Python 中定義變數 變數是在程式執行時用來存放資料的名稱。透過定義變數,我們可以給一筆資料一個名字,並在程式的其他地方用這個名字取用該筆資料。在 Python 中,變數不需要事先宣告型別,因為 Python 是動態型別語言,變數的型別由指定給它的值決定。 定義變數的基本語法 在 Python 中定義變數非常簡單,只要用賦值符號 = 把值指定給變數即可。例如: x = 5 # 定義變數 x,並把整數 5 賦值給它 name = "Alice" # 定義變數 name,並把字串 "Alice" 賦值給它 在這裡,x 是一個變數,被賦予整數 5;name 是另一個變數,被賦予字串 "Alice"。 變數的更新與覆寫 變數的值可以修改,也就是說,我們可以在程式的不同地方給同一個變數新的值。例如: x = 10 # x 最初被賦予 10 x = 15 # x 的值現在被更新為 15 這樣就能依照需求,在程式執行過程中靈活調整變數的值。 Python 的動態型別系統 Python 和某些靜態型別語言不同,定義變數時不需要宣告型別。賦值時,Python 會根據值自動判斷變數的型別。例如: x = 5 # x 是整數 x = 3.14 # x 變成浮點數 x = "Hi" # x 變成字串 同一個變數在程式執行過程中可以存放不同型別的值,這是 Python 的彈性之一。 常見資料型別 在 Python 中,資料型別決定我們可以對變數進行哪些操作...

Day 1 Python 簡介與環境設定

Day 1 Python 簡介與環境設定 引言 在現在的科技環境裡,程式設計已經是一項重要技能。無論你是對資料科學有興趣、想成為開發者,或是想踏入人工智慧(AI)領域,學會寫程式都能明顯提升你的競爭力。在眾多程式語言中,Python 因為語法簡單、功能強大、應用範圍廣泛,成為許多人進入程式世界的第一選擇。這篇文章會帶你認識 Python 的背景與優勢,並一步步教你在不同系統上安裝與設定 Python 開發環境,最後寫出第一支 Python 程式。 為什麼選擇 Python? Python 是一種高階程式語言,由 Guido van Rossum 在 1991 年發布。Python 的設計哲學強調程式碼的可讀性,並用縮排來定義程式區塊,這點和許多使用大括號的語言不同。簡潔的語法讓它成為初學者的理想選擇;就算是經驗豐富的開發者,也能用它完成複雜的專案。 Python 的優勢如下: 簡單易學 :Python 的語法清楚、結構簡潔,初學者很快就能上手。和其他語言相比,學習曲線相對平緩,不需要先弄懂一堆複雜觀念,就能開始寫程式。 應用範圍廣泛 :從資料科學、網頁開發、人工智慧、機器學習、自動化測試到網路爬蟲,Python 都有大量開源函式庫與工具支援,而且在這些領域都扮演關鍵角色。 豐富的函式庫與框架 :Python 的函式庫生態系非常龐大。做資料分析有 NumPy、Pandas;開發網站有 Django、Flask;做深度學習有 TensorFlow、PyTorch。各種需求幾乎都能找到對應的套件,讓開發更有效率。 跨平台支援 :Python 支援 Windows、macOS、Linux 等作業系統,程式通常不需要太多修改就能跨平台執行,讓開發與部署更有彈性。 活躍的社群 :Python 擁有龐大的開發者社群。學習或開發上遇到問題,幾乎都能在社群與論壇(例如 Stack Overflow)找到答案,對初學者來說是很強的後盾,也能減少卡關時的挫折感。 Python 的應用領域 Python 的流行與強大功能,讓許多領域都開始大量使用它。以下是幾個常見的應用方向: 資料科學 :隨著大數據與人工智慧興起,資料科學大量使用 Python。NumPy、Pandas 與 Matplotlib 等工具能處理和分析龐...

Python 從入門到 PyTorch 深度學習:開啟 AI 世界的大門

Python 從入門到 PyTorch 深度學習:開啟 AI 世界的大門 隨著人工智慧(AI)與深度學習(Deep Learning)快速發展,越來越多人對這些技術產生興趣。不論你是想踏入 AI 領域的初學者,還是已經有程式基礎的開發者,學好 Python 與深度學習框架(例如 PyTorch),都能為你打開更多可能。 為什麼選擇 Python? Python 已經是資料科學與人工智慧領域的首選語言。它的語法簡潔、容易上手,而且擁有龐大的生態系與大量開源函式庫。無論是資料處理、資料視覺化,還是建立機器學習與深度學習模型,Python 都能勝任。對想進入 AI 或資料科學領域的人來說,它幾乎是必備工具。 PyTorch 是什麼? PyTorch 是由 Meta(原 Facebook)AI 研究團隊開發的開源深度學習框架,以易用、靈活和動態計算圖著稱,是許多 AI 研究人員與開發者的首選。相較於其他框架,PyTorch 的寫法更貼近原生 Python,對初學者相對友善。無論是簡單的實驗,還是複雜的深度學習模型,PyTorch 都能提供強大的支援。 這個系列能帶給你什麼? 這個系列會從 Python 的基礎開始,帶你一步一步學習,最後能自己用 PyTorch 建立深度學習模型。即使你完全沒有寫過程式,也能跟著文章的節奏累積技能,理解 AI 與深度學習的核心觀念。 本系列涵蓋的主題 Python 基礎:從變數、條件判斷到函式與模組。 資料處理工具:用 NumPy 與 Pandas 有效率地操作資料。 資料視覺化:用 Matplotlib 與 Seaborn 把資料畫成圖表。 深度學習的數學基礎:線性代數、微積分與機率。 PyTorch 入門:理解張量、模型建構與 GPU 加速。 基礎深度學習模型:CNN 與 RNN 的實作應用。 深度學習專案實戰:從資料前處理到模型部署的端到端流程。 誰適合這個系列? 程式初學者 :如果你對 AI 充滿好奇,卻還沒寫過程式,系列的第一部分會帶你快速上手 Python,並幫助你理解深度學習的基本觀念。 資料科學愛好者 :如果你已經熟悉一些資料處理方法,進階部分會教你如何用 PyTorch 建構深度學習模型。 開發者與研究人員 :想更深入了...