估計與關(guān)鍵點檢測工程實戰(zhàn))
簡介面向人體姿態(tài)關(guān)鍵點檢測學習與實踐的完整項目包圍繞YOLOV8-pose模型展開適合計算機視覺初學者、課程設(shè)計與畢業(yè)設(shè)計開發(fā)者以及需要快速復(fù)現(xiàn)姿態(tài)估計實驗的研究者。包內(nèi)提供可直接跑通的訓練、推理與驗證源碼配套人體關(guān)鍵點標注數(shù)據(jù)集、預(yù)訓練權(quán)重和依賴配置省去從零搭建網(wǎng)絡(luò)、整理標注文件與調(diào)試運行環(huán)境的重復(fù)勞動。壓縮包共1369個文件約89.04MB以jpg圖像、md說明文檔、txt標注與配置、py腳本為主體輔以yaml訓練參數(shù)、pt權(quán)重、json記錄及ipynb、png可視化結(jié)果覆蓋數(shù)據(jù)準備、模型訓練、日志記錄與使用文檔等環(huán)節(jié)。已有6223人學習反映出姿態(tài)識別與關(guān)鍵點檢測方向具有較高關(guān)注度。讀者可據(jù)此完成關(guān)鍵點檢測模型的訓練、評估與推理理解數(shù)據(jù)增強、損失函數(shù)設(shè)計和預(yù)訓練遷移等流程README與腳本也有助于快速復(fù)現(xiàn)結(jié)果、排查環(huán)境配置和運行問題。1. 姿態(tài)關(guān)鍵點檢測落地為什么我選 YOLOV8-pose 而不是先啃 HRNet上個月幫朋友做一個健身動作糾偏的小工具需求很樸素攝像頭對著人能把肩、肘、腕、髖、膝、踝這些關(guān)鍵點標出來判斷深蹲有沒有塌腰、手肘有沒有外翻。第一反應(yīng)是去找姿態(tài)識別里口碑好的 HRNet 系列結(jié)果光是把預(yù)測熱圖轉(zhuǎn)回原圖坐標、再對齊翻轉(zhuǎn)和縮放這一套后處理就調(diào)了大半天玄學得很。后來換成 YOLOV8-pose一個模型文件丟進去predict出來直接是歸一化的關(guān)鍵點坐標十七個點帶置信度畫骨架、算角度、判姿態(tài)全都能接上。這份源碼包做的就是這件事——一套帶數(shù)據(jù)集、能直接跑通的姿態(tài)關(guān)鍵點檢測工程適合想快速把姿態(tài)識別接進自己業(yè)務(wù)、又不想在模型復(fù)現(xiàn)上耗掉一周的開發(fā)者。下面按“它能干什么、目錄長什么樣、怎么跑、怎么改”講透。2. 項目定位與技術(shù)選型單階段多人姿態(tài)估計的取舍2.1 它到底解決什么問題這個項目的核心任務(wù)是多人 2D 姿態(tài)估計Multi-Person Pose Estimation給一張圖或一幀視頻輸出畫面里每個人的 17 個 COCO 標準關(guān)鍵點鼻、左右眼、左右耳、左右肩、左右肘、左右腕、左右髖、左右膝、左右踝每個點帶 x、y 坐標和置信度。注意“多人”兩個字很關(guān)鍵——早期很多姿態(tài)方案是“先檢測人框、再對每個人單獨跑一遍關(guān)鍵點”兩階段串行人一多幀率就崩。YOLOV8-pose 走的是單階段路線一次前向同時回歸人框和該框內(nèi)的關(guān)鍵點N 個人就是 N 組輸出不額外多跑一遍網(wǎng)絡(luò)。從工程角度看這個取舍非常劃算。姿態(tài)估計在業(yè)務(wù)里很少是終點通常是上游特征提取健身糾偏要算關(guān)節(jié)角行為識別要喂骨架序列動畫驅(qū)動要做重定向。這些下游任務(wù)真正在意的是“關(guān)鍵點穩(wěn)不穩(wěn)、延遲夠不夠低”而不是“學術(shù)榜單上 PCKh 高 0.3 個點”。單階段模型在 640×640 輸入下中端顯卡能跑到實時甚至超實時CPU 上降分辨率也能湊合用這是它落地的最大底氣。2.2 為什么是 YOLOV8-pose 這套技術(shù)棧把技術(shù)棧拆開看其實是三層層次組成作用骨干與頸部CSPDarknet 系列 backbone PAN-FPN多尺度特征提取與融合檢測頭解耦頭分類/框回歸/關(guān)鍵點回歸分支輸出人框與 17 點熱圖式回歸訓練與推理框架Ultralytics 封裝PyTorch 后端數(shù)據(jù)加載、增強、損失、導出、預(yù)測一條龍關(guān)鍵點在檢測頭里是這么處理的對每個人框模型回歸 17 組(x, y, visibility)其中 x、y 是相對于該人框的歸一化偏移visibility 是該點是否可見的置信度。這種“框內(nèi)相對坐標”的設(shè)計讓模型不必在全圖尺度上直接回歸絕對坐標收斂更穩(wěn)也讓同一個模型天然支持多人——每個 anchor/query 只負責自己那個人。提示visibility不是“這個點在畫面里被遮擋的概率”這么簡單它同時承擔了“該點是否在圖像邊界內(nèi)”的語義。做下游角度計算時通常要把 visibility 低于閾值比如 0.5的點直接丟掉否則會出現(xiàn)用低置信點算出來的詭異角度。選型上還有兩個現(xiàn)實考量。一是生態(tài)Ultralytics 這套接口的訓練、驗證、導出ONNX、TensorRT、OpenVINO幾乎是模板化的換個部署環(huán)境不用重寫推理邏輯。二是數(shù)據(jù)集門檻低YOLO 格式的 pose 標注就是一個 txt 文件每行一個人類別 cx cy w h kx1 ky1 kv1 ... kx17 ky17 kv17全部歸一化。自己標幾十張圖就能微調(diào)比 COCO-WholeBody 那種 133 點的重型標注友好太多。2.3 源碼包的目錄骨架拿到這種工程我一般先tree一遍看結(jié)構(gòu)。這個包的組織方式基本是“訓練腳本 配置 數(shù)據(jù)集 推理示例”四塊典型布局如下yolov8-pose-project/ ├── ultralytics/ # 框架源碼含 cfg 與 nn 模塊 │ ├── cfg/models/v8/ # yolov8-pose.yaml 等模型結(jié)構(gòu)定義 │ └── nn/ # 骨干、頸部、檢測頭、損失實現(xiàn) ├── datasets/ │ └── pose-demo/ │ ├── images/{train,val}/ │ ├── labels/{train,val}/ │ └── data.yaml # 數(shù)據(jù)配置路徑、關(guān)鍵點形狀、類別名 ├── weights/ │ └── yolov8n-pose.pt # 預(yù)訓練權(quán)重用來做遷移學習起點 ├── train.py # 訓練入口 ├── val.py # 驗證入口 ├── predict.py # 圖片/視頻推理演示 └── requirements.txt這個結(jié)構(gòu)里最容易被忽視的是data.yaml它決定了模型頭部的輸出維度。文件大致長這樣# datasets/pose-demo/data.yaml path: ./datasets/pose-demo train: images/train val: images/val kpt_shape: [17, 3] # 17 個關(guān)鍵點每個點 3 個值 (x, y, visibility) flip_idx: [0, 2, 1, 4, 3, 6, 5, 8, 7, 10, 9, 12, 11, 14, 13, 16, 15] # 水平翻轉(zhuǎn)時左右點的索引交換 names: 0: personflip_idx是很多人第一次改數(shù)據(jù)集時會栽的地方——它用于數(shù)據(jù)增強里的水平翻轉(zhuǎn)。如果你自己定義了 5 個關(guān)鍵點卻忘了同步改這個數(shù)組訓練時翻轉(zhuǎn)增強會把“左肩”的標簽貼到“右肩”位置上模型學出來的骨架會左右亂竄loss 看著在降實際預(yù)測全是錯的。這個坑后面還會細說。3. 跑通流程從環(huán)境到第一張骨架圖3.1 環(huán)境準備與依賴安裝這套工程對環(huán)境的要求不算苛刻Python 3.8 以上、PyTorch 1.8 以上就能起步。GPU 訓練建議 CUDA 11.x 配對應(yīng)的 torch 版本CPU 也能跑但訓練會慢到讓人懷疑人生。我一般的做法是單獨開一個 conda 環(huán)境避免和機器上其他項目的 torch 版本打架# 創(chuàng)建并激活環(huán)境 conda create -n pose python3.10 -y conda activate pose # 先裝 torch按自己的 CUDA 版本選對應(yīng)命令這里以 CUDA 11.8 為例 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 再裝工程依賴 pip install -r requirements.txt # requirements 里通常包含 ultralytics、opencv-python、numpy、pyyaml、tqdm安裝完先做一次體檢確認 torch 能吃上 GPUpython -c import torch; print(torch.__version__, torch.cuda.is_available())輸出里True才算 GPU 就緒。如果顯示False多半是裝成了 CPU 版 torch或者驅(qū)動版本和 CUDA runtime 不匹配這時候別急著往下跑訓練先在nvidia-smi里核對驅(qū)動支持的 CUDA 上限。這個檢查看著多余但我見過太多次訓練跑了兩小時才發(fā)現(xiàn)一直在 CPU 上爬的情況。3.2 數(shù)據(jù)集組織與標注格式核對數(shù)據(jù)集這塊工程里自帶了一份演示數(shù)據(jù)目錄結(jié)構(gòu)在data.yaml里已經(jīng)聲明好了。跑通前必須核對兩件事圖片和標簽同文件名、同相對路徑標簽是 YOLO pose 格式的 txt。標注文件里每一行的格式是這樣的# labels/train/0001.txt # class cx cy w h x1 y1 v1 x2 y2 v2 ... x17 y17 v17 0 0.512 0.603 0.284 0.791 0.501 0.312 2 0.487 0.305 2 ... 0.402 0.881 1所有數(shù)值都是歸一化到 [0,1] 的相對于圖片寬高關(guān)鍵點坐標是相對于整張圖而不是人框——這點和推理階段的輸出不同推理輸出是相對框的后處理時框架會自動轉(zhuǎn)回去但如果自己寫標注轉(zhuǎn)換腳本這是最容易搞混的一處。v的取值一般是 0/1/20 表示未標注1 表示標注但被遮擋2 表示可見且標注。如果你的標注工具只輸出 0/1也問題不大但flip_idx和kpt_shape必須和實際點數(shù)嚴格一致。正確性校驗有個很實用的小腳本把標簽還原成骨架畫到圖上肉眼過一遍import cv2 import numpy as np IMG datasets/pose-demo/images/train/0001.jpg LBL datasets/pose-demo/labels/train/0001.txt # COCO 17 點的骨架連接對用來連線可視化 SKELETON [(5,7),(7,9),(6,8),(8,10),(5,6),(5,11),(6,12),(11,12), (11,13),(13,15),(12,14),(14,16),(0,1),(0,2),(1,3),(2,4)] img cv2.imread(IMG) h, w img.shape[:2] for line in open(LBL): parts np.array(line.split(), dtypefloat) kpts parts[5:].reshape(-1, 3) # 從第 6 個值起是 17*3 的關(guān)鍵點 pts (kpts[:, :2] * [w, h]).astype(int) # 反歸一化回像素坐標 for a, b in SKELETON: if kpts[a, 2] 0 and kpts[b, 2] 0: # 兩端都有效才連線 cv2.line(img, tuple(pts[a]), tuple(pts[b]), (0, 255, 0), 2) for (x, y), v in zip(pts, kpts[:, 2]): if v 0: cv2.circle(img, (x, y), 3, (0, 0, 255), -1) cv2.imwrite(check.jpg, img)這段代碼做了三件事按行解析每個目標的標注、把關(guān)鍵點反歸一化、按骨架連接對畫線畫點。參數(shù)上SKELETON是 COCO 標準連接關(guān)系kpts[a, 2] 0這個判斷是為了跳過未標注的點否則會在圖上的 (0,0) 附近畫出一堆亂線。跑完打開check.jpg如果骨架是貼在人體上的說明標注沒問題如果整個人是歪的或者骨架飄在別處那基本是歸一化基準錯了比如按人框而不是整圖歸一化。3.3 訓練命令與關(guān)鍵參數(shù)環(huán)境、數(shù)據(jù)都確認后訓練就是一條命令的事python train.py \ --model weights/yolov8n-pose.pt \ --data datasets/pose-demo/data.yaml \ --epochs 100 \ --imgsz 640 \ --batch 16 \ --device 0 \ --workers 4 \ --project runs/pose --name demo幾個參數(shù)我按重要性排一下--model預(yù)訓練權(quán)重起點。即使你的關(guān)鍵點數(shù)和 COCO 不同比如只標 5 個點也可以加載框架會自動裁掉/重建頭部但骨干權(quán)重仍然有用收斂比從零訓快得多。--imgsz輸入分辨率。640 是性能和精度的平衡點遠距離小目標多就上 960但顯存和延遲一起漲。--batch按顯存調(diào)OOM 就先減半別硬撐。--workersDataLoader 進程數(shù)Windows 上建議設(shè)小一點甚至設(shè) 0否則容易卡死在多進程上。--project/--name輸出目錄權(quán)重存runs/pose/demo/weights/best.pt訓練曲線、混淆矩陣、驗證集預(yù)測圖都在同目錄下。訓練腳本里真正干活的部分一般是對框架 API 的封裝早期版本寫法大致是這樣from ultralytics import YOLO # 加載帶 pose 頭的預(yù)訓練權(quán)重 model YOLO(weights/yolov8n-pose.pt) # 開始訓練內(nèi)部會讀取 data.yaml 里的 kpt_shape 構(gòu)建頭部 results model.train( datadatasets/pose-demo/data.yaml, epochs100, imgsz640, batch16, device0, workers4, projectruns/pose, namedemo, # 姿態(tài)任務(wù)常用的增強開關(guān) fliplr0.5, # 水平翻轉(zhuǎn)依賴 data.yaml 的 flip_idx degrees10.0, # 小幅旋轉(zhuǎn)應(yīng)對拍攝角度 scale0.5, # 隨機縮放提升尺度魯棒性 mosaic1.0, # 馬賽克增強前期提升明顯 )這里要點一下fliplr和flip_idx的聯(lián)動關(guān)系fliplr0.5意味著每張圖有一半概率被水平翻轉(zhuǎn)翻轉(zhuǎn)時標簽坐標要鏡像x - 1-x同時左右關(guān)鍵點的語義要對調(diào)左肩變右肩??蚣芸縟ata.yaml里的flip_idx完成后者。所以flip_idx不是可選裝飾它是增強正確性的前提。同理mosaic1.0把四張圖拼成一張對小目標友好但訓練末段通常要關(guān)掉框架默認最后 10 個 epoch 自動關(guān)閉否則真實場景的構(gòu)圖分布和訓練分布偏差會拉低最終指標。3.4 推理與骨架可視化訓練完最爽的一步是看效果。工程里的predict.py一般會做圖片、視頻、攝像頭三種輸入的分支核心調(diào)用只有幾行from ultralytics import YOLO model YOLO(runs/pose/demo/weights/best.pt) # 對單張圖片推理saveTrue 會把帶骨架的結(jié)果圖寫到 runs/pose/predict 下 results model.predict(test.jpg, imgsz640, conf0.4, saveTrue) for r in results: if r.keypoints is None: continue # keypoints.xy: (人數(shù), 17, 2) 像素坐標; keypoints.conf: (人數(shù), 17) 置信度 xy r.keypoints.xy.cpu().numpy() conf r.keypoints.conf.cpu().numpy() for person_xy, person_conf in zip(xy, conf): # 只保留置信度夠高的點避免下游算角度時用到垃圾數(shù)據(jù) valid person_conf 0.5 print(有效關(guān)鍵點數(shù):, valid.sum())conf0.4是人框的置信度閾值不是關(guān)鍵點的關(guān)鍵點置信度要單獨在keypoints.conf里過濾。這兩個閾值混用是新手高頻翻車點把人框閾值調(diào)得很高結(jié)果遠處的人整個被丟掉或者只看人框置信度就直接拿所有關(guān)鍵點去算角度被遮擋點的坐標其實是一團噪聲。推理速度上yolov8n-pose在 640 輸入、單張中端卡上通常是毫秒級到十幾毫秒換yolov8s/m精度漲一點、延遲也漲。做實時視頻建議先用 n 版跑通鏈路再按精度需求往上換型號別一上來就上大模型然后抱怨卡。4. 核心模塊拆解一個關(guān)鍵點是怎么被回歸出來的4.1 檢測頭里的關(guān)鍵點分支框架的檢測頭在ultralytics/nn/modules/head.py里pose 版本的頭繼承自檢測頭多出一條關(guān)鍵點回歸分支。結(jié)構(gòu)上的關(guān)鍵點是關(guān)鍵點不是獨立預(yù)測的而是綁定在每個人框上的。簡化后的前向邏輯大致是# 偽代碼保留關(guān)鍵結(jié)構(gòu)省略了大量廣播與形狀處理 class PoseHead(nn.Module): def __init__(self, nc, kpt_shape, ch()): super().__init__() self.nc nc # 類別數(shù)姿態(tài)任務(wù)通常就是 1人 self.nk kpt_shape[0] # 關(guān)鍵點數(shù)量COCO 是 17 self.no nc 4 self.nk * 3 # 每框輸出: 類別 框4 關(guān)鍵點(17*3) # 三條并行分支分類、框回歸、關(guān)鍵點回歸 self.cv2 nn.ModuleList(...) # 分類 框 self.cv3 nn.ModuleList(...) # 關(guān)鍵點 self.cv4 nn.ModuleList(...) # 關(guān)鍵點可見性/精修 def forward(self, x): for i in range(self.nl): # 遍歷 P3/P4/P5 三個尺度 cls_box self.cv2[i](x[i]) # (B, 4nc, H, W) kpt self.cv3[i](x[i]) # (B, nk*3, H, W) # 關(guān)鍵點坐標用 sigmoid 約束到 [0,1]表示相對網(wǎng)格/框的偏移 kpt[..., 0::3] kpt[..., 0::3].sigmoid() kpt[..., 1::3] kpt[..., 1::3].sigmoid() # 第三維visibility不套 sigmoid直接作為 logit 參與 BCE 損失這段里最值得記住的是self.no nc 4 self.nk * 3它決定了每個預(yù)測位置輸出多少維。你改關(guān)鍵點數(shù)量時kpt_shape一變這個維度自動跟著變所以永遠改data.yaml和模型 yaml 里的kpt_shape不要去手改no。另外x、y走 sigmoid 是有意的——把坐標壓進 [0,1]語義上是“相對該人框左上角的歸一化偏移”這讓不同大小的目標共享同一套回歸目標尺度訓練穩(wěn)定得多。后處理階段框架會把這些相對偏移乘上對應(yīng)人框的寬高、再加上框左上角坐標還原成原圖像素坐標。這條鏈路藏在ops.py的kpt_decode一類函數(shù)里平時不用碰但排查“預(yù)測點整體偏移”類問題時就得順著這條鏈一路看下來。4.2 姿態(tài)損失函數(shù)的三部分構(gòu)成訓練姿態(tài)模型loss 由三塊加起來框回歸損失、分類損失、關(guān)鍵點損失。前兩塊和普通檢測一致CIoU BCE姿態(tài)特有的第三塊是重點。關(guān)鍵點損失一般是OKS-based 的加權(quán) BCE或者直接的坐標回歸損失實現(xiàn)里大致是# 簡化示意關(guān)鍵點損失的核心思想 # 1) 對每個 GT 關(guān)鍵點找到負責它的預(yù)測點 # 2) 坐標用 L1/L2 回歸visibility 用 BCE # 3) 按 OKS 的 sigmoid 權(quán)重對不同關(guān)鍵點加權(quán) loss_kpt 0.0 for gt_kpt, pred_kpt, vis in zip(gt_kpts, pred_kpts, gt_vis): if vis 0: # 未標注的點不參與坐標損失 continue loss_kpt (pred_kpt - gt_kpt).abs().mean() loss_kpt loss_kpt / max(vis.sum(), 1) # 按有效點數(shù)歸一化 loss_vis BCEWithLogits(pred_vis, (gt_vis 0).float())兩個細節(jié)決定成敗。第一vis 0的點必須 mask 掉不然模型會被迫去擬合“未標注”的位置等價于教它胡說。第二歸一化要按有效點數(shù)而不是總點數(shù)否則一個大部分人只有半身可見的 batchloss 尺度會忽大忽小學習率都不好調(diào)。OKS 那套權(quán)重的作用是讓眼睛、手腕這類小且難的點在總 loss 里占比合理避免被肩髖這些大關(guān)節(jié)淹沒。4.3 數(shù)據(jù)增強對姿態(tài)任務(wù)的特殊處理檢測任務(wù)的增強搬到姿態(tài)上核心矛盾是任何幾何變換都必須同步作用到關(guān)鍵點上且左右翻轉(zhuǎn)要處理語義交換。馬賽克增強在姿態(tài)里尤其危險——四張圖拼接后原來的人可能被裁掉一半關(guān)鍵點坐標隨之越界框架內(nèi)部會做裁剪和過濾但如果你自己改增強流程一定要保證越界點被正確標記為不可見而不是硬留在 [0,1] 之外。常見的正確做法是先算好圖片的仿射矩陣同時對圖像做 warpAffine、對關(guān)鍵點做同一矩陣的坐標變換再根據(jù)變換后坐標是否落在圖內(nèi)更新 visibility。翻轉(zhuǎn)時按flip_idx交換點序并x - 1 - x。這兩步看似機械但只要漏一步訓練集會悄悄變臟loss 曲線照樣好看mAP 就是上不去——這類“黑匣子”式的指標異常十有八九要回到增強代碼里找。4.4 驗證指標怎么看姿態(tài)任務(wù)的驗證不能只看 mAP。工程val.py跑完會輸出幾類指標框的 mAP50-95、以及基于 OKS 的關(guān)鍵點 AP。OKSObject Keypoint Similarity是姿態(tài)版的 IoU按每個關(guān)鍵點的標注方差加權(quán)算相似度再取平均。實際看的時候我會重點關(guān)注三件事一是AP 隨 OKS 閾值的變化如果 AP50 很高但 AP75 掉得厲害說明點位普遍差幾個像素多半是輸入分辨率不夠或者小目標太多。二是分關(guān)鍵點看誤差手腕、腳踝通常是最差的如果差得離譜檢查這幾類點在訓練集里的可見比例。三是看驗證集的預(yù)測可視化圖框架會自動存一批肉眼看骨架有沒有系統(tǒng)性偏移比如整體偏右下有的話基本是坐標解碼環(huán)節(jié)或標注歸一化基準出了問題。5. 常見問題排查我踩過的五個坑5.1 訓練 loss 正常但預(yù)測骨架全亂現(xiàn)象訓練 loss 平穩(wěn)下降驗證指標看著也還行但拿best.pt預(yù)測骨架連到了錯誤的人身上或者所有點擠在一小塊區(qū)域。原因data.yaml里kpt_shape和模型 yaml 里的kpt_shape不一致或者flip_idx長度與點數(shù)不匹配??蚣茉谀承┌姹鞠虏粫苯訄箦e而是按較小維度截斷導致關(guān)鍵點順序錯位。解決訓練前打印一次模型頭的實際輸出維度和kpt_shape[0] * 3對一遍flip_idx用腳本自動生成COCO 17 點就用標準數(shù)組自定義點數(shù)時手寫并校驗長度。5.2 水平翻轉(zhuǎn)增強后左右肢體識別反了現(xiàn)象不翻轉(zhuǎn)的圖預(yù)測正常一遇到鏡像自拍或前置攝像頭畫面左右手腕、左右腳踝全部對調(diào)。原因fliplr開了但flip_idx是默認的 COCO 順序而你的數(shù)據(jù)集點數(shù)不同或者干脆沒配。翻轉(zhuǎn)時坐標鏡像了語義沒對調(diào)。解決把flip_idx改成和數(shù)據(jù)點定義嚴格對應(yīng)的交換數(shù)組如果暫時不想處理直接把fliplr設(shè) 0先保證正確性再談增強收益。5.3 顯存 OOM 或訓練中途進程被殺現(xiàn)象訓練跑幾十個 iteration 后報 CUDA out of memory或者 Linux 下進程被 OOM killer 干掉。原因batch與imgsz組合超出顯存或者workers開太大每個 worker 都緩存了一份數(shù)據(jù)內(nèi)存被吃光。解決先把batch減半、imgsz降到 512 試一輪確認能穩(wěn)定跑再往上加workers在內(nèi)存緊張的機器上設(shè) 2 到 4Windows 上設(shè) 0。真需要在有限顯存上跑大分辨率用梯度累積模擬大 batch。5.4 關(guān)鍵點置信度普遍偏低、下游角度算不準現(xiàn)象人明明站得端正預(yù)測出來的手腕、腳踝置信度只有 0.3 左右按 0.5 過濾后點數(shù)不夠算關(guān)節(jié)角直接崩。原因訓練集里這類點標注稀疏或被大量遮擋模型學不到高置信輸出也可能是驗證/推理時的imgsz與訓練不一致尺度變化讓本來就不穩(wěn)的小關(guān)鍵點更飄。解決補標一批包含完整四肢的樣本訓練時適當降低degrees和scale的強度讓模型先學好基本點位推理側(cè)把關(guān)鍵點閾值和“可見性”分開處理用 visibility 做軟加權(quán)而不是硬閾值角度計算時對低置信點做鄰近幀平滑。5.5 導出 ONNX 后推理結(jié)果和 PyTorch 對不上現(xiàn)象PyTorch 下骨架正常導出 ONNX 用 onnxruntime 跑關(guān)鍵點整體偏移或數(shù)量不對。原因?qū)С鰰r沒指定opset或者輸入尺寸動態(tài)化導致后處理里的坐標還原邏輯假設(shè)失效也有版本是導出時把 NMS 一起打包后處理重復(fù)做了一次。解決導出時固定imgsz、顯式指定opset12以上導出后先用同一張圖對比兩邊輸出的最大絕對誤差正常應(yīng)在 1e-3 量級NMS 是否內(nèi)置要確認清楚避免二次過濾。真對不上就退回導出去掉 NMS 的版本自己在外部做后處理鏈路反而清晰。6. 二次開發(fā)把關(guān)鍵點接進自己的業(yè)務(wù)邏輯跑通只是起點真正花時間的是把 17 個點變成業(yè)務(wù)結(jié)論。我一般會在工程里加一個獨立的后處理模塊和模型推理解耦這樣換模型、調(diào)閾值都不用動業(yè)務(wù)代碼。最常用的是關(guān)節(jié)角計算比如判斷手肘彎曲程度import numpy as np def angle_between(a, b, c): 計算 b 為頂點時a-b-c 的夾角角度制 ba np.array(a) - np.array(b) bc np.array(c) - np.array(b) # 加 eps 防止零向量導致除零 cos np.dot(ba, bc) / (np.linalg.norm(ba) * np.linalg.norm(bc) 1e-8) return np.degrees(np.arccos(np.clip(cos, -1.0, 1.0))) # COCO 索引: 5左肩 7左肘 9左腕 L_SHOULDER, L_ELBOW, L_WRIST 5, 7, 9 elbow_angle angle_between(kpts[L_SHOULDER], kpts[L_ELBOW], kpts[L_WRIST])參數(shù)上要守住兩條一是傳入前先確認這三個點的置信度都過閾值否則算出來的角度毫無意義二是np.clip不能省浮點誤差會讓arccos的輸入冒出 1.0000001 直接變 NaN。這就是典型的“算法沒問題、工程細節(jié)翻車”。改造思路上有三條路值得展開。第一條是換數(shù)據(jù)集適配自己的場景比如只關(guān)心上半身可以把kpt_shape改成[5, 3]甚至更少同步改flip_idx模型更輕、小樣本下更穩(wěn)。注意改點數(shù)等于換任務(wù)預(yù)訓練頭要重建但骨干權(quán)重照樣能加載這一點前面提過。第二條是做時序平滑單幀姿態(tài)抖動是常態(tài)視頻場景里對每個關(guān)鍵點維護一個滑動窗口做中值濾波或 One-Euro 濾波能顯著降低骨架“抽搐”代價是引入幾幀延遲實時糾偏類應(yīng)用要權(quán)衡。第三條是模型蒸餾或換小模型先用yolov8m-pose訓一個高精度版本再用它的輸出當軟標簽訓yolov8n-pose部署是端側(cè)性能不夠時最省事的路線。驗證改造是否有效我的習慣是固定一套“回歸樣本”挑 30 到 50 張覆蓋不同姿態(tài)、遮擋、光照的圖每次改完代碼都跑一遍比對關(guān)鍵點坐標的平均像素誤差和下游業(yè)務(wù)指標比如動作判定準確率。這套樣本不進訓練集專門當后悔藥用。從那以后我每次動kpt_shape、flip_idx或者增強參數(shù)都強制先跑一遍這套回歸樣本再提交省下來的返工時間遠多于維護它的成本。希望這些能幫你在接姿態(tài)識別時少繞幾個彎。本文還有配套的精品資源點擊獲取