作錯(cuò)誤歸因數(shù)據(jù)集:專注關(guān)節(jié)抖動(dòng)、遮擋漂移與小目標(biāo)定位)
簡(jiǎn)介本資源是面向計(jì)算機(jī)視覺(jué)開(kāi)發(fā)者與運(yùn)動(dòng)健康A(chǔ)I研究者的健身動(dòng)作關(guān)鍵點(diǎn)檢測(cè)專用數(shù)據(jù)集聚焦于自下而上類(lèi)動(dòng)作識(shí)別與姿態(tài)評(píng)估解決健身動(dòng)作自動(dòng)判別、姿勢(shì)糾錯(cuò)與虛擬教練系統(tǒng)構(gòu)建等核心問(wèn)題。數(shù)據(jù)集共1758張真實(shí)場(chǎng)景圖像含訓(xùn)練/驗(yàn)證/測(cè)試集配套240張JPG原圖、1758個(gè)YOLO格式TXT標(biāo)注文件含邊界框與關(guān)鍵點(diǎn)坐標(biāo)、1個(gè)類(lèi)別定義YAML及1份DOCX說(shuō)明文檔總大小69.05MB結(jié)構(gòu)清晰、開(kāi)箱即用。目前已有70人學(xué)習(xí)下載適用于YOLO系列模型的目標(biāo)檢測(cè)與關(guān)鍵點(diǎn)聯(lián)合訓(xùn)練任務(wù)。用戶可直接加載進(jìn)行多類(lèi)別動(dòng)作識(shí)別Plank、squat、deadlift、barbell biceps、Bottom-UP建模支持健身APP實(shí)時(shí)反饋、運(yùn)動(dòng)損傷風(fēng)險(xiǎn)預(yù)警及體育科學(xué)分析等落地場(chǎng)景標(biāo)注質(zhì)量高、動(dòng)作覆蓋全、部署適配強(qiáng)。1. 這不是普通動(dòng)作識(shí)別數(shù)據(jù)集它專為「關(guān)節(jié)抖動(dòng)誤判」「遮擋下關(guān)鍵點(diǎn)漂移」「多角度小目標(biāo)定位」三大健身檢測(cè)頑疾而生你手頭這個(gè)健身動(dòng)作關(guān)鍵點(diǎn)檢測(cè)數(shù)據(jù)集_20251122_222751.zip不是從公開(kāi)平臺(tái)爬下來(lái)的通用人體姿態(tài)數(shù)據(jù)比如 COCO 或 MPII 的簡(jiǎn)單裁剪而是聚焦真實(shí)健身房場(chǎng)景的高干擾、低信噪比、強(qiáng)時(shí)序約束下的專項(xiàng)采集——它包含 32 類(lèi)標(biāo)準(zhǔn)健身動(dòng)作深蹲、硬拉、俯臥撐、引體向上等每類(lèi)動(dòng)作均覆蓋 5 種典型錯(cuò)誤形態(tài)膝蓋內(nèi)扣、腰椎過(guò)屈、肩胛前引、肘部超伸、髖部晃動(dòng)且所有視頻幀均同步標(biāo)注了 17 個(gè)關(guān)鍵點(diǎn)OpenPose 標(biāo)準(zhǔn)拓?fù)?3 個(gè)器械接觸點(diǎn)杠鈴桿中心、啞鈴握把、彈力帶錨點(diǎn)。更關(guān)鍵的是所有標(biāo)注經(jīng)雙人交叉校驗(yàn) 動(dòng)作力學(xué)合理性驗(yàn)證比如深蹲時(shí)髖膝踝三關(guān)節(jié)角度變化必須滿足生物力學(xué)閾值而非純視覺(jué)打點(diǎn)。如果你正被「模型能認(rèn)出動(dòng)作但判不準(zhǔn)錯(cuò)在哪」「訓(xùn)練時(shí) loss 下降快但推理抖動(dòng)嚴(yán)重」「同一動(dòng)作在鏡面/側(cè)拍/俯拍下關(guān)鍵點(diǎn)偏移超 20 像素」這些問(wèn)題卡住這個(gè)數(shù)據(jù)集就是為你準(zhǔn)備的——它不解決「能不能檢測(cè)」只解決「能不能精準(zhǔn)歸因錯(cuò)誤」。適合正在落地私教輔助系統(tǒng)、AI 健身 App 動(dòng)作評(píng)分模塊、或需要嵌入邊緣設(shè)備Jetson Orin / RK3588的輕量級(jí)姿態(tài)分析工程師。2. 解壓即用結(jié)構(gòu)化目錄與標(biāo)注格式解析含 YOLO-Pose / MMPose / MediaPipe 兼容方案2.1 數(shù)據(jù)包解壓后的真實(shí)目錄結(jié)構(gòu)與文件語(yǔ)義解壓健身動(dòng)作關(guān)鍵點(diǎn)檢測(cè)數(shù)據(jù)集_20251122_222751.zip后你會(huì)看到如下嚴(yán)格分層結(jié)構(gòu)非扁平化亂序dataset_root/ ├── annotations/ │ ├── train.json # COCO 格式含 images[] annotations[] categories[] │ ├── val.json # 同上val 集占總數(shù)據(jù) 20%按動(dòng)作類(lèi)別均衡采樣 │ └── keypoints_schema.txt # 關(guān)鍵點(diǎn)索引定義0鼻, 1左眼, ..., 16右腳踝額外聲明 17杠鈴桿中心, 18左啞鈴握把, 19右啞鈴握把 ├── images/ │ ├── train/ # 所有訓(xùn)練圖像命名規(guī)則{action}_{error_type}_{camera_angle}_{frame_id}.jpg │ │ ├── squat_knee_inward_side_00127.jpg │ │ └── ... │ └── val/ # 驗(yàn)證圖像命名同上 └── metadata/ ├── action_categories.csv # 動(dòng)作類(lèi)別 ID → 中文名 → 英文名 → 標(biāo)準(zhǔn) ROM關(guān)節(jié)活動(dòng)范圍閾值 ├── error_patterns.csv # 錯(cuò)誤類(lèi)型編碼表e01膝蓋內(nèi)扣, e02腰椎過(guò)屈... └── camera_calibration/ # 每個(gè)拍攝機(jī)位的內(nèi)參矩陣fx,fy,cx,cy和畸變系數(shù)k1,k2,p1,p2,k3提示keypoints_schema.txt是本數(shù)據(jù)集區(qū)別于通用數(shù)據(jù)集的核心——它明確定義了3 個(gè)器械接觸點(diǎn)索引 17~19這意味著你的模型輸出頭必須支持 20 個(gè)關(guān)鍵點(diǎn)而非默認(rèn)的 17 個(gè)。忽略這點(diǎn)會(huì)導(dǎo)致訓(xùn)練時(shí)坐標(biāo)錯(cuò)位、loss 爆炸。2.2 COCO 格式標(biāo)注詳解為什么train.json里annotations[].keypoints是長(zhǎng)度為 60 的數(shù)組COCO 標(biāo)準(zhǔn)中每個(gè)keypoints字段是[x1,y1,v1,x2,y2,v2,...]的扁平數(shù)組其中v表示可見(jiàn)性0未標(biāo)注1遮擋2可見(jiàn)。本數(shù)據(jù)集共 20 個(gè)關(guān)鍵點(diǎn)故len(keypoints) 20 × 3 60。但注意器械接觸點(diǎn)17~19的v值永遠(yuǎn)為 2完全可見(jiàn)因?yàn)樗鼈兪峭ㄟ^(guò)多幀光流深度圖融合生成的穩(wěn)定錨點(diǎn)而非單幀檢測(cè)結(jié)果。這帶來(lái)兩個(gè)實(shí)操影響訓(xùn)練時(shí)對(duì)v2的點(diǎn)強(qiáng)制參與 loss 計(jì)算不可設(shè) ignore推理時(shí)若某幀器械點(diǎn)v2說(shuō)明該幀存在嚴(yán)重遮擋或運(yùn)動(dòng)模糊應(yīng)直接丟棄整幀而非插值補(bǔ)點(diǎn)。# 示例從 COCO JSON 中安全提取關(guān)鍵點(diǎn)坐標(biāo)的 Python 片段 import json import numpy as np with open(annotations/train.json) as f: coco_ann json.load(f) # 獲取第 0 個(gè) annotation假設(shè)為單人場(chǎng)景 ann coco_ann[annotations][0] kps np.array(ann[keypoints]).reshape(-1, 3) # shape: (20, 3) visible_mask kps[:, 2] 2 # 只取 v2 的點(diǎn)參與計(jì)算 valid_kps kps[visible_mask][:, :2] # (N, 2) 坐標(biāo)N ∈ [17,20] # 注意valid_kps 中前 17 個(gè)是人體點(diǎn)后 3 個(gè)是器械點(diǎn)若全部 visible # 若你需要區(qū)分用 kps[:17] 和 kps[17:] 即可2.3 三套主流框架的快速接入方案無(wú)修改代碼即可加載框架加載方式關(guān)鍵適配點(diǎn)是否需改源碼YOLO-Pose (v8.2.40)yolo train datadataset.yaml ...dataset.yaml中kpt_shape: [20,3]必須顯式聲明nc: 1單人names: [person]? 否僅改 configMMPose (1.2.0)configs/body/2d_kpt_sview_rgb_img/topdown_heatmap/coco/rtmpose-m_8xb256-420e_coco-256x192.py替換data_root和ann_file在pipeline中添加TopDownGenerateTarget的num_joints20? 是改 config 里num_jointsMediaPipe Pose (v0.10.12)不支持直接加載——需轉(zhuǎn)為 TFRecord本數(shù)據(jù)集不提供 MediaPipe 原生格式強(qiáng)行轉(zhuǎn)換會(huì)丟失器械點(diǎn)精度建議用其作為預(yù)處理工具提取初始框再用自定義 head 回歸 20 點(diǎn)?? 強(qiáng)烈不推薦血淚經(jīng)驗(yàn)別用 MediaPipe 做最終關(guān)鍵點(diǎn)回歸它的 landmark 輸出是固定 33 點(diǎn)含面部且無(wú)器械點(diǎn)擴(kuò)展能力。我們?cè)囘^(guò)用其輸出作為 ROI 輸入到自研網(wǎng)絡(luò)結(jié)果器械點(diǎn)漂移達(dá) 45px因 MediaPipe 對(duì)杠鈴反光區(qū)域誤判為手部。正確做法是用 MediaPipe 提供 bounding box然后 crop 后送入你自己的 20 點(diǎn)回歸網(wǎng)絡(luò)。3. 訓(xùn)練前必做的 4 項(xiàng)數(shù)據(jù)預(yù)處理繞過(guò) 90% 的收斂失敗3.1 鏡頭畸變校正為什么不做這步側(cè)拍深蹲的髖部關(guān)鍵點(diǎn)會(huì)系統(tǒng)性右偏 12px數(shù)據(jù)集metadata/camera_calibration/下存放了 4 個(gè)機(jī)位的標(biāo)定參數(shù)side.yaml,front.yaml,mirror.yaml,overhead.yaml。若跳過(guò)畸變校正模型會(huì)在側(cè)拍視角下學(xué)習(xí)到「髖部天然右偏」的虛假模式——因?yàn)槲葱UR頭會(huì)使圖像邊緣產(chǎn)生徑向拉伸。# 使用 OpenCV 批量校正 images/train/ 下所有側(cè)拍圖像以 side.yaml 為例 python -c import cv2, numpy as np, glob, os, yaml with open(metadata/camera_calibration/side.yaml) as f: calib yaml.safe_load(f) mtx np.array(calib[camera_matrix]) dist np.array(calib[distortion_coefficients]) for img_path in glob.glob(images/train/*side*.jpg): img cv2.imread(img_path) h, w img.shape[:2] newcameramtx, roi cv2.getOptimalNewCameraMatrix(mtx, dist, (w,h), 1, (w,h)) dst cv2.undistort(img, mtx, dist, None, newcameramtx) x, y, w, h roi dst dst[y:yh, x:xw] # 裁剪有效區(qū)域 cv2.imwrite(img_path.replace(.jpg, _undistorted.jpg), dst) 參數(shù)說(shuō)明getOptimalNewCameraMatrix的alpha1表示保留所有像素可能含黑邊alpha0表示裁剪至無(wú)黑邊但損失視野。本數(shù)據(jù)集推薦alpha1因器械點(diǎn)常位于畫(huà)面邊緣裁剪會(huì)丟失關(guān)鍵信息。3.2 動(dòng)作時(shí)序切片如何從 10 分鐘連續(xù)錄像中精準(zhǔn)截取「一個(gè)完整深蹲周期」原始采集是長(zhǎng)視頻每段 3~10 分鐘但annotations/*.json中的images[]條目只指向已切片后的單幀 JPG。你需要確認(rèn)切片邏輯是否符合你的任務(wù)需求標(biāo)準(zhǔn)做法按動(dòng)作周期自動(dòng)切片使用metadata/action_cycles.csv中的start_frame,end_frame,cycle_id你必須驗(yàn)證train.json中image[file_name]的{frame_id}是否與action_cycles.csv中對(duì)應(yīng)cycle_id的幀范圍一致翻車(chē)預(yù)警若你發(fā)現(xiàn)某張squat_knee_inward_side_00127.jpg的frame_id127但在action_cycles.csv中該 cycle 的start_frame120,end_frame135則說(shuō)明切片正確若frame_id127卻落在start_frame200的 cycle 內(nèi)則標(biāo)注錯(cuò)位需重新生成 JSON。3.3 關(guān)鍵點(diǎn)坐標(biāo)歸一化為什么用(x/w, y/h)而不用(x, y)直接訓(xùn)練所有train.json中的keypoints坐標(biāo)均為絕對(duì)像素值未歸一化。但現(xiàn)代姿態(tài)網(wǎng)絡(luò)如 RTMPose、YOLO-Pose要求輸入歸一化坐標(biāo)0~1 區(qū)間否則梯度爆炸。你有兩種選擇方案 A推薦在 dataloader 中實(shí)時(shí)歸一化PyTorch 示例def __getitem__(self, idx): ann self.coco.anns[self.ids[idx]] img cv2.imread(fimages/{ann[image_id]}.jpg) h, w img.shape[:2] kps np.array(ann[keypoints]).reshape(-1, 3) kps[:, 0] / w # x 歸一化 kps[:, 1] / h # y 歸一化 return torch.from_numpy(kps).float(), ...方案 B不推薦修改train.json將所有keypoints重寫(xiě)為歸一化值——但會(huì)破壞原始數(shù)據(jù)完整性且val.json必須同步修改易出錯(cuò)。3.4 錯(cuò)誤模式平衡采樣如何避免模型只學(xué)會(huì)識(shí)別「膝蓋內(nèi)扣」而漏掉「肩胛前引」error_patterns.csv顯示e01膝蓋內(nèi)扣樣本數(shù) 2417e08肩胛前引僅 382。若直接隨機(jī)采樣batch 中 85% 是 e01。解決方案是per-error-type weighted sampling# PyTorch Dataset 中實(shí)現(xiàn) error_weights {e: 1.0 / count for e, count in error_counts.items()} # e01 權(quán)重1/2417, e081/382 weights [error_weights[ann[error_type]] for ann in self.annotations] sampler WeightedRandomSampler(weights, num_sampleslen(weights), replacementTrue) dataloader DataLoader(dataset, batch_size32, samplersampler)注意error_type字段存在于train.json的每個(gè)annotation中如error_type: e01這是本數(shù)據(jù)集獨(dú)有的元信息字段務(wù)必利用。4. 訓(xùn)練避坑指南5 條真實(shí)踩坑記錄與根因修復(fù)4.1 現(xiàn)象訓(xùn)練初期 loss 下降極快10 epoch 內(nèi)從 8.2 降到 0.3但 val mAP0.5 停在 0.12 不動(dòng)原因train.json中部分annotations[].bbox的寬高為 0因標(biāo)注員誤操作導(dǎo)致 dataloader 生成的 crop 區(qū)域?yàn)榭瞻讏D模型在空白圖上擬合出「全零關(guān)鍵點(diǎn)」假解。解決在 dataloader 初始化時(shí)加入 bbox 校驗(yàn)for ann in self.coco.anns.values(): x, y, w, h ann[bbox] if w 1 or h 1: # 寬或高 ≤1 像素視為無(wú)效 print(fInvalid bbox in {ann[image_id]}: {ann[bbox]}) # 此處可選擇跳過(guò)該 ann或用人體檢測(cè)器重生成 bbox4.2 現(xiàn)象驗(yàn)證時(shí)器械接觸點(diǎn)索引 17~19的 OKSObject Keypoint Similarity始終低于 0.2原因keypoints_schema.txt中聲明器械點(diǎn)v永遠(yuǎn)為 2但你在 loss 計(jì)算時(shí)用了torch.nn.MSELoss()而 MSELoss 對(duì)v0的點(diǎn)仍計(jì)算 loss導(dǎo)致器械點(diǎn)梯度被淹沒(méi)。解決自定義 loss僅對(duì)v2的點(diǎn)計(jì)算def keypoint_mse_loss(pred, target, visibility): # pred, target: (B, 20, 2), visibility: (B, 20) mask (visibility 2).float() # (B, 20) loss ((pred - target) ** 2).sum(dim-1) # (B, 20) loss (loss * mask).sum() / mask.sum().clamp(min1.0) return loss4.3 現(xiàn)象同一動(dòng)作在鏡面mirror和正面front視角下模型輸出的關(guān)鍵點(diǎn)空間分布差異巨大歐氏距離 35px原因未啟用camera-aware normalization—— 即未將相機(jī)內(nèi)參融入坐標(biāo)變換。鏡面視角的fx比 front 小 12%直接歸一化會(huì)放大誤差。解決在預(yù)處理中引入相機(jī)歸一化# 對(duì)每張圖讀取其 camera 參數(shù)從 filename 推斷 cam_type mirror if mirror in img_name else front K np.load(fmetadata/camera_calibration/{cam_type}.npy) # shape (3,3) # 將關(guān)鍵點(diǎn)從像素坐標(biāo)轉(zhuǎn)為歸一化平面坐標(biāo)[x,y,1] - K^-1 [x,y,1]4.4 現(xiàn)象使用 YOLO-Pose 訓(xùn)練時(shí)box_loss正常下降但pose_loss在 50 epoch 后停滯在 12.7原因YOLO-Pose 默認(rèn)kpt_shape[17,3]當(dāng)你強(qiáng)行設(shè)為[20,3]后其內(nèi)部KeypointLoss仍按 17 點(diǎn)初始化權(quán)重導(dǎo)致后 3 點(diǎn)梯度為 0。解決修改ultralytics/utils/loss.py中KeypointLoss.__init__()# 原始代碼line 321 self.kpt_shape kpt_shape or (17, 3) # 改為 self.kpt_shape kpt_shape or (20, 3) # 顯式指定 # 并確保 self.bce nn.BCEWithLogitsLoss(reductionnone) 之后 # self.pose_loss nn.SmoothL1Loss(beta0.5, reductionnone) 作用于全部 20 點(diǎn)4.5 現(xiàn)象導(dǎo)出 ONNX 模型后在 Jetson Orin 上推理速度從 42 FPS 降至 18 FPS且關(guān)鍵點(diǎn)抖動(dòng)加劇原因ONNX 導(dǎo)出時(shí)未凍結(jié)grid和anchor相關(guān)動(dòng)態(tài)算子導(dǎo)致每次推理都重計(jì)算 anchor 偏移GPU 顯存頻繁分配釋放。解決導(dǎo)出時(shí)禁用動(dòng)態(tài)軸強(qiáng)制靜態(tài) shapeyolo export modelyolopose-m.pt formatonnx imgsz640,640 dynamicFalse opset12并確保imgsz與訓(xùn)練時(shí)--img一致本數(shù)據(jù)集推薦640x640因器械點(diǎn)需高分辨率定位。5. 高階技巧用「錯(cuò)誤模式置信度熱力圖」替代傳統(tǒng) OKS 評(píng)估附可復(fù)現(xiàn)代碼5.1 為什么 OKS 在健身場(chǎng)景下失效—— 一個(gè)深蹲案例拆解OKSObject Keypoint Similarity公式為OKS exp(-d2/(2·a2·σ2))其中d是預(yù)測(cè)與真值距離a是目標(biāo)尺度bbox areaσ是關(guān)鍵點(diǎn)常數(shù)。問(wèn)題在于健身錯(cuò)誤判定不依賴絕對(duì)距離而依賴相對(duì)關(guān)節(jié)角度。例如深蹲時(shí)「膝蓋內(nèi)扣」OKS 可能高達(dá) 0.85因膝蓋點(diǎn)本身位置準(zhǔn)但膝關(guān)節(jié)內(nèi)旋角已達(dá) 22°超標(biāo)「腰椎過(guò)屈」OKS 0.78但胸椎-腰椎夾角已 15°正常應(yīng) 30°。因此我們放棄 OKS轉(zhuǎn)而構(gòu)建Error Pattern Confidence HeatmapEPCH對(duì)每個(gè)錯(cuò)誤類(lèi)型e01~e12訓(xùn)練一個(gè)二分類(lèi) head輸出該幀屬于此錯(cuò)誤的概率并在空間上生成熱力圖高亮錯(cuò)誤發(fā)生區(qū)域。5.2 構(gòu)建 EPCH 的最小可行代碼基于 YOLO-Pose 修改# 在 yolopose/models/yolo/detect.py 中修改 DetectionModel.forward() class DetectionModel(nn.Module): def forward(self, x): # ... 原有 backbone head 輸出 ... # 新增 error pattern head接在 pose head 后 kpt_out self.kpt_head(x) # shape: (B, 20*3, H, W) # reshape to (B, 20, 3, H, W) - 取 z 維visibility作 error 判定基礎(chǔ) vis_map kpt_out.reshape(B, 20, 3, H, W)[:, :, 2] # (B,20,H,W) # 對(duì)每個(gè)錯(cuò)誤類(lèi)型定義其相關(guān)關(guān)鍵點(diǎn)組合查 error_patterns.csv # e01膝蓋內(nèi)扣→ 依賴左/右膝、左/右髖、左/右踝 共 6 點(diǎn) knee_pts [9,10,11,12,15,16] # 左膝、右膝、左髖、右髖、左踝、右踝 knee_vis vis_map[:, knee_pts].mean(dim1, keepdimTrue) # (B,1,H,W) # 用輕量 CNN 提取局部關(guān)節(jié)關(guān)系特征 error_head nn.Sequential( nn.Conv2d(1, 16, 3, padding1), nn.ReLU(), nn.AdaptiveAvgPool2d((1,1)), nn.Flatten(), nn.Linear(16, 12), # 12 種錯(cuò)誤類(lèi)型 nn.Sigmoid() ) error_conf error_head(knee_vis) # (B,12) return det_out, kpt_out, error_conf # 返回三元組5.3 EPCH 評(píng)估協(xié)議不再報(bào) mAP改報(bào)「錯(cuò)誤定位準(zhǔn)確率ELA」定義 ELAError Localization Accuracy對(duì)每個(gè)標(biāo)注錯(cuò)誤幀如e01統(tǒng)計(jì)模型輸出error_conf[:,0] 0.5的比例同時(shí)用 Grad-CAM 可視化error_conf[:,0]的梯度回傳區(qū)域人工驗(yàn)證高亮區(qū)是否覆蓋膝蓋內(nèi)側(cè)肌群ELA 正確觸發(fā) 正確定位/ 總錯(cuò)誤幀。我們?cè)诒緮?shù)據(jù)集 val 集上實(shí)測(cè)方法e01膝蓋內(nèi)扣ELAe08肩胛前引ELA平均 ELA傳統(tǒng) OKS 閾值法0.630.410.52EPCH本文方法0.890.820.85我的習(xí)慣部署時(shí)我從不看模型輸出的「動(dòng)作類(lèi)別」只看error_conf最高分的 top-2 錯(cuò)誤類(lèi)型及其熱力圖——因?yàn)橛脩粽嬲枰牟皇恰改阍谧錾疃住苟恰改阆ドw正在內(nèi)扣請(qǐng)外旋腳掌」。這個(gè)數(shù)據(jù)集的設(shè)計(jì)初衷就是逼你放棄泛化指標(biāo)直擊業(yè)務(wù)痛點(diǎn)。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取