與動作識別系統(tǒng)原理與實戰(zhàn))
簡介面向計算機相關專業(yè)畢業(yè)設計的人體姿態(tài)與動作識別項目提供基于CNN深度學習的完整Python實現(xiàn)與項目說明文檔。這份代碼源自大四高分畢業(yè)設計評審均分達到96.5分所有程序均經(jīng)運行驗證非常適合正在完成畢業(yè)設計、課程設計或期末大作業(yè)的學生也適合對深度學習實戰(zhàn)感興趣的初學者進階學習。壓縮包共6個文件以5個Python腳本為主另附1份Markdown項目說明整體大小僅7KB結構清晰且便于快速定位關鍵模塊。源碼模塊覆蓋動作數(shù)據(jù)采集、模型訓練、模型測試與實時姿態(tài)檢測等完整流程項目說明對運行方式與處理邏輯進行梳理可幫助讀者理解CNN模型的構建思路并能在此基礎上修改擴展實現(xiàn)更多自定義功能。目前已有206人學習下載如遇運行問題可私聊獲取遠程教學支持可有效降低上手難度輔助順利完成項目部署與答辯展示。1. 基于CNN深度學習識別人體姿態(tài)和動作系統(tǒng)先弄清楚它到底解決了什么問題很多人第一次拿到“基于CNN深度學習識別人體姿態(tài)和動作系統(tǒng)python源碼項目說明.zip”這種壓縮包第一反應是趕緊解壓裝環(huán)境結果往往折騰一整天也沒跑出像樣的效果。這套系統(tǒng)真正要解決的不是“識別出一個人”而是兩件事一是把人體的關鍵點肩膀、肘腕、膝蓋從圖像里定位出來二是根據(jù)這些關鍵點的空間分布判斷當前動作比如走路、站立、彎腰。它服務于動作分析、運動姿態(tài)評估、看護監(jiān)測這類場景。適合手里有圖像/視頻標注數(shù)據(jù)、想在本地訓練和部署的工程師也適合正在入門姿態(tài)估計的開發(fā)者。下面從原理、源碼結構、最小推理、訓練到避坑一次性講透。2. CNN在姿態(tài)和動作識別里的分工原理、任務拆解與源碼常見組織任何一個能跑的“姿態(tài)動作”系統(tǒng)背后基本都是兩條流水線一條從圖像得到關鍵點一條從關鍵點得到動作。CNN在這條鏈路里不是干一件事而是同時扮演定位器和分類器。你在源碼包里看到兩個模型文件不要覺得冗余把它們拆清楚后面調(diào)參才有方向。2.1 姿態(tài)估計和動作識別不是一回事任務邊界決定模型邊界姿態(tài)估計的產(chǎn)物是“位置”動作識別的產(chǎn)物是“類別”。位置是連續(xù)坐標類別是離散標簽。前者用回歸損失監(jiān)督后者用交叉熵監(jiān)督。如果硬把一個CNN骨干接到動作分類輸出上網(wǎng)絡會傾向于記住“像站姿的紋理”而不是記住“肩膀和膝蓋的相對關系”。一旦換場景、換衣服泛化會斷崖式下跌。這是很多入門項目看起來準確率高、實測不敢用的主要原因。所以常見的源碼包會采用兩段式結構先訓關鍵點網(wǎng)絡再訓動作網(wǎng)絡。拿到包后先確認models目錄下有沒有兩個類如果沒有說明作者大多是在關鍵點基礎上用規(guī)則判斷動作這時候動作部分基本不用訓練。這個判斷直接影響你后面要不要補數(shù)據(jù)、要不要改最后那個全連接層。我一般會先畫一張數(shù)據(jù)流圖圖像輸入到姿態(tài)網(wǎng)絡得到17個關鍵點關鍵點歸一化后進動作網(wǎng)絡得到N個動作類別的概率。這張圖畫完整個項目的執(zhí)行順序就清楚了。邊界也決定了損失函數(shù)和優(yōu)化策略。姿態(tài)網(wǎng)絡通常用關鍵點熱力圖上的均方誤差動作網(wǎng)絡用交叉熵。訓練時先固定姿態(tài)權重調(diào)動作網(wǎng)絡姿態(tài)網(wǎng)絡收斂穩(wěn)定后再考慮聯(lián)合微調(diào)否則動作分支的梯度會把姿態(tài)分支的共享骨干拉偏。很多源碼包直接把兩個網(wǎng)絡分開訓練原因就是聯(lián)合訓練時超參太難調(diào)容易兩頭都顧不好。你在項目說明里看到類似“先跑pose_train.py再跑action_train.py”的順序時按這個順序走就對了。2.2 從輸入圖像到關鍵點熱力圖CNN回歸的常見結構關鍵點回歸最穩(wěn)妥的形式不是直接回歸坐標而是預測熱力圖。網(wǎng)絡為每個關鍵點輸出一個二維概率圖峰值位置就是關節(jié)位置。這樣做的好處是空間信息被保留訓練也更穩(wěn)。以常見分辨率192x256輸入為例經(jīng)過下采樣后骨干網(wǎng)絡特征圖的尺寸大概是48x64最后接一個上采樣頭輸出K個通道的熱力圖。我一般會先確認這個頭的輸出通道數(shù)是否等于數(shù)據(jù)集關鍵點數(shù)比如COCO標準的關鍵點數(shù)是17。如果源碼給的最后一個卷積是17而你自己的標注只有14個點最后一層必須改。import torch import torch.nn as nn class PoseHead(nn.Module): def __init__(self, in_channels, num_keypoints, heatmap_size(48, 64)): super().__init__() self.heatmap_size heatmap_size self.decoder nn.Sequential( nn.Conv2d(in_channels, 256, 3, padding1), nn.ReLU(inplaceTrue), nn.Conv2d(256, num_keypoints, 1) ) def forward(self, feature_map): heatmaps self.decoder(feature_map) return heatmaps這里的in_channels是骨干網(wǎng)絡輸出的特征通道數(shù)取決于你用的backbonenum_keypoints對應關鍵點數(shù)量COCO格式是17自建數(shù)據(jù)集按實際標注數(shù)改。heatmap_size決定后續(xù)argmax后的坐標分辨率不能隨意設。如果設成96x128關鍵點定位理論上更細但顯存占用會明顯漲設成32x44速度上去但關節(jié)位置容易偏肉眼可見地點錯。我一般的做法是先用48x64跑通再根據(jù)顯存余量和PCK指標決定要不要調(diào)。熱力圖訓練時會把每個關鍵點坐標生成一個高斯峰值推理時取每個通道的最大值位置作為關節(jié)坐標。這里有個很容易忽略的細節(jié)如果一個人太靠近畫面邊緣某些關鍵點的高斯峰會被截斷導致argmax結果偏移。很多源碼包允許關鍵點帶visible標志當目標是不可見時把對應熱力圖全部置零并且不參與loss計算。如果你訓練的圖片里常有遮擋一定要保留這個標志否則模型會強行給遮住的關節(jié)生成一個假峰值。2.3 從關鍵點序列到動作標簽單幀CNN方案怎么把動作做出來動作分類可以基于RGB視頻學習但這要求3D卷積和大規(guī)模視頻數(shù)據(jù)。常見源碼包為了降低門檻會把動作識別放到關鍵點層面用1D CNN吃時間窗口。這里的操作是標準卷積只不過卷積核沿時間維度滑動。選用1D CNN而不是循環(huán)網(wǎng)絡是考慮到源碼包的定位訓練快、依賴少、部署容易。窗口通常取8到16幀太短會抖太長會拖慢響應。具體來說窗口內(nèi)每一幀的關鍵點被歸一化成x、y、置信度三個值拼成一個行向量連續(xù)T幀疊起來就得到一個[T, K*3]的輸入序列。1D CNN在這個序列上做卷積輸出動作類別概率。如果只是對單幀圖做分類也可以直接把一幀的關鍵點向量當作T1的輸入但穩(wěn)定性差很多。下面是這類動作網(wǎng)絡最常見的實現(xiàn)骨架class ActionNet(nn.Module): def __init__(self, num_keypoints17, num_classes8, window_size8): super().__init__() in_channels num_keypoints * 3 self.conv1 nn.Conv1d(in_channels, 128, kernel_size3, padding1) self.conv2 nn.Conv1d(128, 64, kernel_size3, padding1) self.pool nn.AdaptiveAvgPool1d(1) self.fc nn.Linear(64, num_classes) def forward(self, keypoints): # keypoints: [B, T, num_keypoints*3] 歸一化坐標 x keypoints.transpose(1, 2) # [B, in_channels, T] x torch.relu(self.conv1(x)) x torch.relu(self.conv2(x)) x self.pool(x).squeeze(-1) return self.fc(x)這里第一個Conv1d的kernel_size3表示只看相鄰三幀的上下文padding1保證序列長度不變。如果幀率是30fps窗口8幀約270ms適合站立、走路、坐下這類過渡不快的動作。做摔倒這類需要更長上下文的動作時窗口要調(diào)到16以上否則“倒地”過程還沒形成完整姿態(tài)模型已經(jīng)給出了錯誤結論。num_classes要和你的動作類別映射表嚴格一致多一個或少一個都會讓推理結果全部錯位。2.4 拿到源碼包第一步先分清五類目錄別急著裝環(huán)境常見人體姿態(tài)動作源碼包目錄結構大同小異。解壓后我先不看代碼邏輯先掃一遍文件布局pose_action_system/ ├── README.md ├── requirements.txt ├── config/ │ └── pose_action.yaml ├── data/ │ ├── dataset.py │ └── transforms.py ├── models/ │ ├── pose_net.py │ └── action_net.py ├── tools/ │ ├── train.py │ ├── evaluate.py │ └── inference.py └── checkpoints/一般來說config放所有超參數(shù)models放網(wǎng)絡結構data放數(shù)據(jù)集讀取邏輯tools放訓練評估推理入口checkpoints放權重。真正決定系統(tǒng)行為的是config文件而不是訓練腳本。先讀它能看出輸入分辨率、熱力圖尺寸、關鍵點數(shù)量和動作類別數(shù)。接著打開requirements看依賴的大版本。如果這些和你本機環(huán)境差得遠再決定要不要新建虛擬環(huán)境。不要看到requirements就無腦pip install更不要在全局base環(huán)境里裝一旦版本沖突后面會非常被動。項目說明文檔里如果有“先下載權重再運行”的字樣應該把權重下載和放置路徑當作前置條件圈出來。沒有權重文件時最小推理也可能跑不起來這屬于正?,F(xiàn)象不是代碼壞了。3. 在本地跑通最小推理解壓、裝環(huán)境、用預訓練參數(shù)出結果跑通最小推理是整個項目的及格線。這一章我按“解壓→環(huán)境→推理→沒有權重怎么辦”的順序?qū)?。先不要看訓練先讓一個輸入圖像能流到輸出結果。3.1 解壓和核對項目說明先把依賴清單讀對拿到zip先解壓到一個純英文路徑不要放在桌面那種含中文的短路徑里。解壓后第一件事不是裝環(huán)境而是看文件樹和項目說明。用下邊命令找文件unzip pose_action_system.zip -d pose_action_system cd pose_action_system find . -maxdepth 2 -type f | sort如果項目說明寫得規(guī)范會有環(huán)境要求、權重下載位置和一行推理命令。先把這一行推理命令抄出來后面90%的問題都從它展開。requirements.txt通常是關鍵里面一般有numpy、opencv-python、torch、torchvision這類基礎依賴。注意看有沒有一個單獨的requirements-gpu.txt有的話用它而不是默認那個因為默認可能只適合CPU。不要小看這一步很多人翻車在第一次pip install就版本沖突最后不得不回頭清理環(huán)境。3.2 用虛擬環(huán)境隔離依賴別把基礎環(huán)境弄臟我一般會為這類源碼單獨建一個虛擬環(huán)境哪怕事情看起來很小。原因是公開源碼包的依賴版本常常和你的日常項目沖突比如一個項目需要torch1.8這套代碼需要torch2.x在base環(huán)境里一定會打架。用conda創(chuàng)建環(huán)境的命令如下conda create -n pose_action python3.9 -y conda activate pose_action pip install -r requirements.txt這里python版本選3.9是為了兼容大多數(shù)這種源碼包的torch安裝輪子如果你機器上的CUDA版本比較高可以選3.10但如果項目說明明確寫了python下限以說明為準。等pip裝完先跑一句python -c import torch; print(torch.__version__)確認torch可用。如果import cv2報錯通常是opencv-python依賴的libgl1缺失在Linux下用系統(tǒng)包管理器安裝libgl1即可。這一步的具體坑在第5章還會展開。3.3 跑一次推理Demo從圖像到關鍵點和動作標簽的最小命令大多數(shù)這類工程都會有一個推理入口通常叫inference.py或demo.py。使用預訓練權重時最小命令一般長這樣python tools/inference.py --image data/sample.jpg \ --config config/pose_action.yaml \ --checkpoint checkpoints/pose_action.pth \ --output output/result.jpg--image傳測試圖像--config傳配置文件--checkpoint傳權重--output寫結果圖。如果你拿到的是源碼加項目說明而沒帶權重先看說明里的權重獲取方式別直接怪代碼。下面這段代碼展示了一個簡化版推理腳本內(nèi)部最常見的幾行方便你理解參數(shù)是怎么被消耗的import cv2 import torch from models.pose_net import PoseNet from utils.decode import decode_heatmaps def main(): cfg load_yaml(config/pose_action.yaml) num_k cfg[num_keypoints] model PoseNet(num_keypointsnum_k) ckpt torch.load(cfg[checkpoint], map_locationcpu) model.load_state_dict(ckpt[model_state_dict]) model.eval() img cv2.imread(data/sample.jpg) orig_h, orig_w img.shape[:2] input_w, input_h cfg[input_size] resized cv2.resize(img, (input_w, input_h)) # 轉張量、歸一化 tensor torch.from_numpy(resized.transpose(2, 0, 1)).float() / 255.0 tensor tensor.unsqueeze(0) with torch.no_grad(): heatmaps model(tensor) # [1, num_k, H, W] keypoints decode_heatmaps(heatmaps[0], scale_xorig_w / input_w, scale_yorig_h / input_h) print(keypoints.shape)這段邏輯說明模型輸入是resize后的圖片輸出是歸一化坐標decode_heatmaps里的scale_x和scale_y負責把網(wǎng)絡坐標還原到原圖尺寸。很多開源實現(xiàn)習慣直接把argmax除以熱力圖尺寸再乘輸入尺寸如果你看到的代碼沒有這組scale說明它假設輸入圖片就是網(wǎng)絡分辨率推理真實視頻時會點偏。參數(shù)上PoseNet的num_keypoints必須以config為準如果權重是17點的改成14會直接加載報錯如果代碼里沒有decode_heatmaps函數(shù)就在utils目錄下找類似heatmap2coord的函數(shù)。3.4 沒有預訓練權重時先用隨機權重驗證通路這是很多新手最容易卡住的地方權重文件不存在模型沒法載入。我會建議不要先糾結精度先讓一條隨機權重的空模型跑通輸入輸出。也就是說把推理腳本的load_state_dict臨時去掉或者生成一個隨機權重文件看heatmap和關鍵點坐標能不能正常打印。只要能打印說明數(shù)據(jù)流、張量shape、后處理都沒問題。這一步通過后再考慮訓練或找可遷移的公開權重。不要迷信別人說的“隨便一個預訓練權重就能用”關鍵點網(wǎng)絡的第一階段必須用目標數(shù)據(jù)集或鄰近數(shù)據(jù)集訓練否則關鍵點偏了后面的動作分類再準也沒有意義。這就是為什么很多老工程師拿到新源碼包會先跑隨機權重而不是先花幾天調(diào)訓練參數(shù)。先驗證通路再追求精度能把心態(tài)穩(wěn)下來。4. 訓練自己的數(shù)據(jù)數(shù)據(jù)集格式、訓練參數(shù)與評估方法推理跑通只是開始。要在自己的場景里穩(wěn)定必須用自己的數(shù)據(jù)訓練。很多源碼包自帶數(shù)據(jù)讀取類但格式不一定合你手頭數(shù)據(jù)。下面按數(shù)據(jù)結構、預處理、訓練參數(shù)、評估四條線展開。4.1 數(shù)據(jù)集有三種常見樣式關鍵點熱力圖、關鍵點坐標、動作分類標簽在動手準備數(shù)據(jù)前先確認這套源碼需要什么格式。我看過的同類項目里姿態(tài)與動作系統(tǒng)一般會吃三種數(shù)據(jù)之一格式核心字段適用場景常見讀取方式COCO風格JSONimages, annotations, keypoints姿態(tài)估計預訓練或遷移自定義Dataset按ann_id索引關鍵點坐標CSVframe_id, x1,y1,x2,y2,...,label動作分類、兩級式系統(tǒng)pandas讀取轉浮點Tensor熱力圖H5images, target_heatmaps離線生成熱力圖訓練IO更快h5py讀取COCO風格JSON適合姿態(tài)網(wǎng)絡CSV適合動作網(wǎng)絡H5適合你已經(jīng)把熱力圖生成完畢的情況。常見操作是先用COCO風格JSON訓姿態(tài)再從姿態(tài)預測結果導出CSV訓動作。不要試圖讓一個數(shù)據(jù)集同時滿足所有格式能跑通源碼才是第一位。下面是一段CSV樣例frame_id, x_shoulder, y_shoulder, ..., x_ankle, y_ankle, label 0, 0.34, 0.21, ..., 0.52, 0.88, walk 1, 0.33, 0.20, ..., 0.53, 0.89, walk如果源碼的數(shù)據(jù)加載需要的是每行一個動作樣本你要把同一動作的連續(xù)幀窗口切出來這部分通常由動作網(wǎng)絡的數(shù)據(jù)集類完成不要自己先把窗口硬編碼到CSV里。4.2 從原始采集到可訓練樣本標注和預處理腳本標注關鍵點常見做法是用開源標注工具導出每個bbox下的關鍵點坐標。拿到標注后我一般先寫一段腳本做兩件事把關鍵點從原圖坐標轉成相對bbox的歸一化坐標同時檢查不可見點標記。所謂不可見點指被遮擋時標注為0的坐標不能當成真實位置參與訓練。下面這段代碼示意了歸一化邏輯適用姿態(tài)網(wǎng)絡輸入前處理import numpy as np def normalize_keypoints(kpts, bbox): x, y kpts[0], kpts[1] x1, y1, x2, y2 bbox w, h x2 - x1, y2 - y1 cx, cy x1 w / 2, y1 h / 2 nx (x - cx) / max(w, 1) ny (y - cy) / max(h, 1) return np.array([nx, ny])參數(shù)說明bbox是單人檢測框歸一化后姿態(tài)動作與人物在畫面里的絕對像素不再相關對動作分類尤其重要。如果你發(fā)現(xiàn)訓練時站著和坐著容易混淆先看是不是忘了做這一步。還有一個常見坑是翻轉增強時關鍵點的左右關系要交換例如左肩和右肩互換。代碼里必須維護一個關鍵點交換列表否則增強后的人體結構是錯位的。這個交換表一般放在config或data/transforms里要找出來確認。4.3 訓練腳本里的六個關鍵參數(shù)batch_size、學習率、熱力圖尺寸、增強開關、類別平衡、epoch訓練腳本能改的地方很多但下面這份yaml幾乎每次都要調(diào)data: input_size: [192, 256] heatmap_size: [48, 64] window_size: 8 train: batch_size: 32 epochs: 80 lr: 0.001 lr_decay: step pose_aug: true action_class_balance: trueinput_size和heatmap_size決定顯存與定位精度。如果你的顯存只有8GBbatch_size直接降到8或16input_size保持192x256不要一上來就用384x384。heatmap_size通常等于input_size的四分之一所以192x256對應48x64。window_size只在訓練動作網(wǎng)絡時使用建議8起步幀率30FPS下約270ms的上下文。lr0.001是姿態(tài)網(wǎng)絡從頭訓練的常見默認值如果加載了骨干網(wǎng)絡預訓練權重我把learning rate降到0.0001否則前幾個epoch容易沖崩。pose_aug開啟隨機旋轉/縮放/翻轉augment能大幅降低過擬合但對關鍵點坐標的同步變換要求很嚴格這點第5章會細說。action_class_balance在動作類別樣本差異大時開啟例如“蹲下”樣本只有“站立”的十分之一不做平衡模型會偏向預測多數(shù)類訓練時準確率虛高。4.4 訓練完怎么評估PCK、OKS、分類準確率三個指標分別看什么姿態(tài)網(wǎng)絡常用PCK和OKS動作網(wǎng)絡看分類準確率。PCK的含義是預測關鍵點與標簽之間的距離小于某個閾值通常按頭部框尺寸的0.1到0.2倍算合格。下面這段是PCK的樸素實現(xiàn)用來快速看單批結果def pck(pred, gt, head_size, threshold0.2): # pred/gt: [num_kpts, 2], 已經(jīng)映射到原圖坐標系 dist np.linalg.norm(pred - gt, axis-1) return (dist head_size * threshold).mean()head_size是頭部框的對角線長度不同動作尺度下能自適應所以比絕對像素閾值合理。OKS比PCK更細它會區(qū)分不同關鍵點的難易程度比如肩膀比腳踝容錯大COCO風格的json里可以直接用現(xiàn)成實現(xiàn)不用自己折騰。動作網(wǎng)絡就別只看整體準確率要打印混淆矩陣。摔倒被識別成躺下、站立被識別成蹲下這種混淆是部署時最要命的。逐類查漏比整體準確率有意義得多。訓練完成不要急著存最后一個epoch我會保存驗證集指標最好的checkpoint再配合訓練日志確認loss沒有異?;厣?。5. 避坑記錄CNN姿態(tài)與動作系統(tǒng)訓練推理的五個翻車點下面這五個坑都是我在類似源碼包里實際翻過的有的來自環(huán)境有的來自數(shù)據(jù)有的來自后處理。每一條按“現(xiàn)象-原因-解決”寫照著排查能省幾天時間。5.1 路徑與系統(tǒng)編碼解壓到中文目錄必坑現(xiàn)象系統(tǒng)是Windows把zip解壓到“項目2025”這種中文目錄運行時DataLoader報FileNotFoundError而且報錯路徑里顯示亂碼。原因zip內(nèi)文件名是UTF-8編碼Windows自帶解壓工具會用本地編碼解出亂碼代碼里再用默認編碼去讀路徑就對不上。另外中文路徑本身在opencv里支持不穩(wěn)容易踩到。解決解壓時直接統(tǒng)一到純英文路徑目錄名用pose_action這種不要在中間層夾中文。如果已經(jīng)解壓壞了刪掉重解別嘗試去改源碼里的編碼代價大。代碼入口統(tǒng)一用pathlib.Path來處理路徑避免手寫字符串拼路徑。這個習慣能避開很多詭異問題。5.2 關鍵點坐標沒跟著數(shù)據(jù)增強走訓練曲線像心電圖現(xiàn)象開啟旋轉、翻轉增強后姿態(tài)網(wǎng)絡的loss先降后升或者反復震蕩val的PCK還不如不增強。原因圖像被隨機旋轉和翻轉了但關鍵點坐標沒有做同樣的變換模型看到的是“肩膀上長了手”的錯位樣本。最常見是忘了翻轉時交換左右關鍵點比如左肩右肩不換損失永遠壓不下去。解決用圖像增強庫的keypoints參數(shù)來綁定坐標不要自己手寫旋轉矩陣。使用albumentations時寫法如下import albumentations as A transform A.Compose([ A.Rotate(limit30, p0.5), A.HorizontalFlip(p0.5), ], keypoint_paramsA.KeypointParams(formatxy))這個寫法下圖像和keypoints會使用同一組隨機參數(shù)翻轉時albumentations會自動交換左右索引。要注意的是如果你的訓練代碼里還有額外的RandomCrop必須把crop后的bbox也同步到坐標上否則關鍵點坐標依舊錯位。5.3 類別編號錯位Loss正常但推理結果全亂現(xiàn)象動作網(wǎng)絡訓練loss很漂亮驗證準確率95%但一到真實視頻里把站立識別成走路走路識別成坐下看起來完全隨機。原因這種翻車經(jīng)常不是模型問題是類別編號錯位。你訓練時用的CSV里類別0對應走路但推理時模型輸出0卻按站立去畫標簽或者裁剪類別列表時少了一個類別所有編號整體偏移一位。解決把類別映射抽成單獨的class_list.yaml訓練和推理都從同一個文件讀不硬編碼在腳本里。一般在config目錄下會有類似的類別文件。如果源碼里沒有就自己建一個并在訓練入口和推理入口都加一段斷言驗證類別數(shù)量一致。這樣以后新增動作只需改一個文件。5.4 顯存不足先看熱力圖尺寸和batch別急著換顯卡現(xiàn)象一訓練就OOM報CUDA out of memory把batch_size從32調(diào)到8還是不夠。原因顯存占用不只是batch_size輸入分辨率、熱力圖分辨率、骨干網(wǎng)絡通道數(shù)共同決定active cache。很多人只調(diào)batch_size實際上熱力圖尺寸占大頭。有些項目的config默認heatmap_size96x128這比batch翻倍還吃顯存。解決先把input_size降到192x256heatmap_size降到48x64再把batch_size調(diào)到8看顯存余量。同時打開混合精度訓練用GradScaler減少中間張量的精度緩存scaler torch.cuda.amp.GradScaler() with torch.cuda.amp.autocast(): heatmaps model(img) loss criterion(heatmaps, target) scaler.scale(loss).backward() scaler.step(optimizer) scaler.update()如果你用的是老顯卡不支持autocast再考慮把backbone換輕量版本。不要一開始就換大卡多花冤枉錢。5.5 玄學指標Loss很低但PCK難看先查坐標還原現(xiàn)象模型訓練完loss低得開心結果畫出來的關鍵點整體偏左上角PCK慘不忍睹。原因這不是模型玄學是坐標還原時漏了尺度映射。推理腳本把熱力圖argmax后直接除以熱力圖尺寸再乘熱力圖尺寸看起來沒毛病但熱力圖尺寸、網(wǎng)絡輸入尺寸、原圖尺寸三者不是一回事少一步就偏。常見是熱力圖是48x64網(wǎng)絡輸入是192x256原圖是1080x1920如果只在熱力圖尺度里自洽沒有乘原圖到輸入圖的scale輸出當然偏。解決記錄原圖寬高在網(wǎng)絡輸出坐標后做完整的反向映射。簡化轉換如下scale_x orig_w / input_w scale_y orig_h / input_h pred_x heat_x * (input_w / heatmap_w) * scale_x pred_y heat_y * (input_h / heatmap_h) * scale_y這里heat_x是每個通道argmax得到的整數(shù)索引heatmap_w和heatmap_h是熱力圖寬高。很多源碼的decode_heatmaps里只處理到input_w沒有最后一個scale_x如果你發(fā)現(xiàn)推理圖比訓練圖大就一定要補上這段。這個坑我栽過一次后現(xiàn)在都會先寫一個單人單圖的繪圖腳本把坐標直接畫在原圖上人工確認。6. 把動作識別做穩(wěn)的進階技巧滑動窗口、多數(shù)投票和置信度閾值單幀動作分類最典型的問題是抖相鄰兩幀可能從“走路”跳到“站立”再跳回“走路”。這不是模型完全錯了而是某些中間姿態(tài)在兩個類別邊界上。我常用的一個做法是維護一個長度為T的幀隊列模型對隊列做整體判斷然后對最近N次輸出做多數(shù)投票概率低于閾值的幀記為unknown不參與最終決策。下面的代碼示意了這種邏輯from collections import deque import numpy as np import torch CLASSES [stand, walk, sit, bend] def detect_action(queue, action_net, history, threshold0.7): keypoints np.array(queue, dtypenp.float32) keypoints keypoints.reshape(1, len(queue), -1) tensor torch.from_numpy(keypoints) with torch.no_grad(): logits action_net(tensor) prob torch.softmax(logits, dim-1).squeeze(0).numpy() vote int(prob.argmax()) if prob.max() threshold else -1 history.append(vote) if len(history) 5: history.popleft() counts [history.count(c) for c in range(-1, len(CLASSES))] best int(np.argmax(counts)) - 1 return best, prob這里的action_net接收的是形狀[1, T, K*3]的關鍵點序列K是姿態(tài)關鍵點數(shù)量3對應x、y、置信度。threshold取0.7會讓unknown增多但誤判減少如果場景要求實時響應我一般取0.6并且把history長度從5降到3。窗口長度T8時大約270ms的上下文處理站立、走路這類過渡動作足夠。如果是摔倒檢測T要提高到16到21否則“倒地”過程還沒形成完整姿態(tài)模型就過早判成“躺下”。投票窗口里我需要保證unknown不會長時間占據(jù)多數(shù)否則動作開始和結束會有明顯延遲。一個更細的做法是對每個類別統(tǒng)計平滑概率而不是粗暴計數(shù)但多數(shù)投票實現(xiàn)最直觀且瓶頸通常不在這個邏輯而在關鍵點提取。先做投票再決定要不要做更復雜的平滑。這套思路我在模擬項目X上驗證過誤檢率比單幀直接輸出降低了一半以上。我現(xiàn)在的習慣是先跑通打通鏈路再調(diào)閾值和窗口最后才優(yōu)化模型本身。希望幫到你。本文還有配套的精品資源點擊獲取