境搭建與數(shù)據(jù)加載實戰(zhàn):從零跑通3D姿態(tài)估計)
簡介這份資源面向計算機視覺與機器學(xué)習(xí)方向的開發(fā)者尤其是正在入門或?qū)嵺`3D人體姿態(tài)估計的研究者與學(xué)生圍繞Human3.6M這一標(biāo)準(zhǔn)數(shù)據(jù)集提供用Python獲取與處理數(shù)據(jù)的完整工程代碼。壓縮包共14個文件約34KB以4個Python腳本為核心覆蓋數(shù)據(jù)下載、處理與提取等環(huán)節(jié)另含txt校驗與依賴說明、Dockerfile與docker-compose.yml容器配置、yml、xml元數(shù)據(jù)、md說明文檔及l(fā)icense等便于快速搭建可復(fù)現(xiàn)的運行環(huán)境。資源已有3634人學(xué)習(xí)下載熱度較高。讀者可借助腳本完成HDF5數(shù)據(jù)讀取、3D關(guān)節(jié)位置提取、格式轉(zhuǎn)換與標(biāo)準(zhǔn)化并參考說明進行訓(xùn)練集、驗證集、測試集劃分為CNN、RNN或seq2seq等姿態(tài)估計模型的構(gòu)建、訓(xùn)練與評估打好數(shù)據(jù)基礎(chǔ)同時理解多視角、多動作數(shù)據(jù)的組織方式與工程化處理思路。1. 從一次數(shù)據(jù)加載失敗說起Human3.6M 環(huán)境搭建到底卡在哪第一次在實驗室服務(wù)器上跑 Human3.6M 的姿態(tài)估計訓(xùn)練我信心滿滿地裝完 PyTorch結(jié)果卡在了數(shù)據(jù)加載這一步。報錯信息翻來覆去就那幾句找不到h36m目錄、.npz文件解析失敗、torch.utils.data的collate_fn直接拋異常。折騰了大半天才反應(yīng)過來——問題根本不在模型代碼而是這套數(shù)據(jù)集的環(huán)境搭建本身就有一堆前置條件目錄結(jié)構(gòu)要對、標(biāo)注文件要轉(zhuǎn)格式、Python 版本和依賴庫版本還得對得上。Human3.6M 是 3D 人體姿態(tài)估計領(lǐng)域最常用的基準(zhǔn)數(shù)據(jù)集之一做機器學(xué)習(xí)方向的同學(xué)幾乎繞不開它但它的原始數(shù)據(jù)格式和主流框架之間隔著一層不薄的適配工作。這篇筆記記錄的就是我從零搭好這套環(huán)境、跑通第一個數(shù)據(jù)加載腳本的完整過程適合剛接觸 3D 姿態(tài)估計、正準(zhǔn)備在本地或服務(wù)器上部署 Human3.6M 的開發(fā)者。2. 環(huán)境準(zhǔn)備Python 版本、依賴庫與目錄結(jié)構(gòu)2.1 版本選擇與依賴清單Human3.6M 本身是 MATLAB 格式的標(biāo)注文件Python 這邊要讀它核心依賴是h5py和numpy。但真正決定你能不能跑通的是深度學(xué)習(xí)框架的版本。我試過三種組合最后穩(wěn)定下來的是 Python 3.8 PyTorch 1.10 CUDA 11.3。Python 3.9 以上在裝h5py時偶爾會遇到 wheel 不匹配的問題Python 3.7 又太老部分新版的numpy已經(jīng)不支持了。下面是我實際用的依賴清單直接寫進requirements.txt# requirements.txt numpy1.21.6 h5py3.6.0 torch1.10.0 torchvision0.11.1 matplotlib3.5.3 tqdm4.64.1numpy鎖在 1.21.6 是因為 1.22 之后np.float被徹底移除而 Human3.6M 的一些舊版處理腳本里還在用這個別名。h5py3.6.0 是我測下來讀寫.h5標(biāo)注文件最穩(wěn)的版本再新的版本在讀取某些舊格式的 Human3.6M 標(biāo)注時會報OSError: Unable to open file。torch和torchvision的版本要對應(yīng)1.10 配 0.11.1 是官方驗證過的組合。安裝命令# 創(chuàng)建虛擬環(huán)境避免污染系統(tǒng) Python python3.8 -m venv h36m_env source h36m_env/bin/activate # 安裝 PyTorchCUDA 11.3 版本 pip install torch1.10.0cu113 torchvision0.11.1cu113 -f https://download.pytorch.org/whl/torch_stable.html # 安裝其余依賴 pip install -r requirements.txt這里有個細節(jié)PyTorch 的 CUDA 版本要和服務(wù)器驅(qū)動匹配。我用的服務(wù)器驅(qū)動是 465.19支持 CUDA 11.3所以選了cu113的 wheel。如果你的驅(qū)動版本更低比如 450 系列那就得降到 CUDA 11.0 對應(yīng)的 PyTorch 1.7.1。查驅(qū)動版本用nvidia-smi右上角那行CUDA Version就是上限。2.2 目錄結(jié)構(gòu)規(guī)劃Human3.6M 原始數(shù)據(jù)集下載下來是一堆壓縮包解壓后目錄很亂。我一般會先規(guī)劃好一個固定的目錄結(jié)構(gòu)后面寫 DataLoader 的時候直接按這個路徑來省得每次改代碼# 我用的目錄結(jié)構(gòu) data/ ├── h36m/ │ ├── annotations/ │ │ ├── h36m_train.npz │ │ └── h36m_test.npz │ ├── images/ │ │ ├── s_01_act_02_subact_01_ca_01/ │ │ ├── s_01_act_02_subact_01_ca_02/ │ │ └── ... │ └── metadata.xml └── scripts/ ├── prepare_data.py └── dataset.pyannotations放轉(zhuǎn)換后的.npz文件images放原始圖像序列metadata.xml是數(shù)據(jù)集自帶的相機參數(shù)和動作標(biāo)簽。這個結(jié)構(gòu)不是官方強制的但社區(qū)里大部分開源項目的 DataLoader 都默認按這個來比如VideoPose3D和MotionBERT的預(yù)處理腳本。提前對齊能省掉后面改路徑的麻煩。創(chuàng)建目錄的命令mkdir -p data/h36m/annotations data/h36m/images data/scripts提示如果你的數(shù)據(jù)盤空間緊張images目錄可以放到掛載的大容量盤上然后用軟鏈接鏈回來。Human3.6M 的原始圖像有幾十 GB全量下載前先確認磁盤余量。2.3 驗證 Python 環(huán)境是否就緒裝完依賴別急著跑數(shù)據(jù)先驗證一下核心庫能不能正常導(dǎo)入# check_env.py import numpy as np import h5py import torch print(fnumpy version: {np.__version__}) print(fh5py version: {h5py.__version__}) print(ftorch version: {torch.__version__}) print(fCUDA available: {torch.cuda.is_available()}) print(fCUDA device: {torch.cuda.get_device_name(0) if torch.cuda.is_available() else CPU only})跑python check_env.py如果CUDA available輸出True說明 PyTorch 和顯卡驅(qū)動對上了。如果輸出False先別往下走回去檢查 CUDA 版本和驅(qū)動版本是否匹配。這一步偷懶后面訓(xùn)練時會在model.cuda()那里直接崩掉。3. 數(shù)據(jù)準(zhǔn)備從原始標(biāo)注到可加載的 npz 文件3.1 原始數(shù)據(jù)下載與解壓Human3.6M 的原始數(shù)據(jù)需要從官方渠道獲取下載下來是多個壓縮包按受試者subject和動作action分卷。我一般會先下載標(biāo)注文件和幾個受試者的圖像數(shù)據(jù)做測試不用一上來就拉全量。標(biāo)注文件是h36m_annotations.rar或類似的壓縮包解壓后得到.h5文件和metadata.xml。解壓命令# 假設(shè)下載的壓縮包放在 ~/Downloads 下 cd ~/Downloads unzip h36m_annotations.zip -d ./h36m_raw # 如果壓縮包是分卷的先合并再解壓 cat h36m_images_part_* h36m_images_full.zip unzip h36m_images_full.zip -d ./h36m_raw/images解壓后檢查一下文件完整性標(biāo)注文件應(yīng)該有h36m_train.h5和h36m_test.h5兩個圖像目錄下每個動作序列對應(yīng)一個文件夾里面是.jpg格式的幀圖像。3.2 標(biāo)注文件格式轉(zhuǎn)換原始.h5文件的結(jié)構(gòu)比較繞直接讀也能讀但寫 DataLoader 的時候不方便。我一般會先轉(zhuǎn)成.npz格式把關(guān)鍵字段提取出來# prepare_data.py import h5py import numpy as np import os def convert_h36m_annotations(h5_path, output_path): 將 Human3.6M 的 h5 標(biāo)注文件轉(zhuǎn)換為 npz 格式 提取 3D 關(guān)節(jié)點坐標(biāo)、2D 投影坐標(biāo)和相機參數(shù) with h5py.File(h5_path, r) as f: # 遍歷所有受試者和動作 for subject in f.keys(): for action in f[subject].keys(): # 3D 關(guān)節(jié)點坐標(biāo)形狀為 (幀數(shù), 32, 3) poses_3d f[subject][action][3d_joints][:] # 2D 投影坐標(biāo)形狀為 (幀數(shù), 32, 2) poses_2d f[subject][action][2d_joints][:] # 相機外參 camera_param f[subject][action][camera_param][:] # 保存為 npz每個動作序列一個文件 save_name f{subject}_{action}.npz np.savez_compressed( os.path.join(output_path, save_name), poses_3dposes_3d, poses_2dposes_2d, camera_paramcamera_param ) print(fSaved {save_name}, 3D shape: {poses_3d.shape}) if __name__ __main__: convert_h36m_annotations( h5_pathdata/h36m/annotations/h36m_train.h5, output_pathdata/h36m/annotations/ )這段代碼的核心邏輯是遍歷.h5文件里的層級結(jié)構(gòu)把每個動作序列的 3D 坐標(biāo)、2D 坐標(biāo)和相機參數(shù)分別提取出來存成獨立的.npz文件。poses_3d的形狀是(幀數(shù), 32, 3)32 是關(guān)節(jié)點數(shù)量3 是 XYZ 坐標(biāo)。poses_2d是投影到圖像平面的坐標(biāo)訓(xùn)練 2D 到 3D 的提升網(wǎng)絡(luò)時會用到。camera_param包含相機的內(nèi)外參數(shù)做多視角融合或者坐標(biāo)變換時要用。注意不同版本的 Human3.6M 標(biāo)注文件里字段名可能不一樣有的用3d_joints有的用S。跑之前先用h5py打開文件看一眼結(jié)構(gòu)f[subject][action].keys()打印出來確認字段名。3.3 寫一個可復(fù)用的 Dataset 類轉(zhuǎn)換完數(shù)據(jù)下一步是寫 PyTorch 的Dataset類。這個類要能按索引讀取單個樣本返回 3D 坐標(biāo)和對應(yīng)的 2D 坐標(biāo)# dataset.py import numpy as np import torch from torch.utils.data import Dataset import os class Human36MDataset(Dataset): def __init__(self, data_dir, splittrain, seq_len1): data_dir: npz 文件所在目錄 split: train 或 test seq_len: 每個樣本包含的連續(xù)幀數(shù) self.data_dir data_dir self.seq_len seq_len self.file_list sorted([ f for f in os.listdir(data_dir) if f.endswith(.npz) and split in f ]) # 預(yù)加載所有數(shù)據(jù)到內(nèi)存數(shù)據(jù)量不大時可行 self.data [] for fname in self.file_list: npz np.load(os.path.join(data_dir, fname)) self.data.append({ poses_3d: npz[poses_3d], poses_2d: npz[poses_2d], camera_param: npz[camera_param] }) def __len__(self): return len(self.data) def __getitem__(self, idx): sample self.data[idx] poses_3d torch.from_numpy(sample[poses_3d]).float() poses_2d torch.from_numpy(sample[poses_2d]).float() return { poses_3d: poses_3d, poses_2d: poses_2d }__init__里把每個.npz文件加載成一個字典存到self.data列表里。__getitem__返回單個樣本的 3D 和 2D 坐標(biāo)轉(zhuǎn)成torch.float32張量。這里沒有做序列切分如果要做時序模型可以在__getitem__里按seq_len截取連續(xù)幀。測試 Dataset 是否能正常加載from dataset import Human36MDataset ds Human36MDataset(data_dirdata/h36m/annotations/, splittrain) print(fDataset size: {len(ds)}) sample ds[0] print(f3D shape: {sample[poses_3d].shape}) print(f2D shape: {sample[poses_2d].shape})如果輸出3D shape: torch.Size([幀數(shù), 32, 3])說明數(shù)據(jù)加載鏈路已經(jīng)通了。4. 避坑記錄Human3.6M 環(huán)境搭建中最容易翻車的五個點4.1 h5py 讀取報 OSError: Unable to open file現(xiàn)象用h5py.File(h36m_train.h5, r)打開標(biāo)注文件時直接拋OSError文件路徑?jīng)]問題文件大小也正常。原因h5py3.7 及以上版本對某些舊版 HDF5 格式的兼容性變差了Human3.6M 的標(biāo)注文件恰好是用舊版 HDF5 庫生成的。解決降級到h5py3.6.0命令是pip install h5py3.6.0。如果已經(jīng)裝了新版先pip uninstall h5py再裝。裝完用python -c import h5py; print(h5py.__version__)確認版本。4.2 numpy 版本過高導(dǎo)致 np.float 報錯現(xiàn)象跑數(shù)據(jù)預(yù)處理腳本時報AttributeError: module numpy has no attribute float。原因numpy1.22 開始移除了np.float、np.int這些別名但 Human3.6M 的一些社區(qū)處理腳本還在用。解決把numpy鎖在 1.21.6或者手動把腳本里的np.float改成np.float64。我一般選前者因為改腳本容易漏。命令是pip install numpy1.21.6。4.3 CUDA 版本和 PyTorch 不匹配現(xiàn)象torch.cuda.is_available()返回False但nvidia-smi能看到顯卡。原因裝的 PyTorch wheel 是 CPU 版本或者 CUDA 版本和驅(qū)動不兼容。解決先用nvidia-smi查驅(qū)動支持的 CUDA 上限然后去 PyTorch 官網(wǎng)找對應(yīng)版本的安裝命令。比如驅(qū)動支持 CUDA 11.3就裝torch1.10.0cu113。裝完在 Python 里跑import torch; print(torch.version.cuda)確認。4.4 圖像路徑和標(biāo)注文件對不上現(xiàn)象DataLoader 能加載標(biāo)注但讀圖像時報FileNotFoundError。原因Human3.6M 的圖像目錄命名規(guī)則和標(biāo)注文件里的動作名稱不是一一對應(yīng)的中間需要做一次映射。解決檢查metadata.xml里的動作標(biāo)簽和圖像目錄名做對照。我一般會寫一個映射字典把標(biāo)注里的action名稱轉(zhuǎn)成圖像目錄的實際名稱。這個映射關(guān)系在社區(qū)腳本里能找到參考但不同下載渠道的文件命名可能有差異以實際目錄為準(zhǔn)。4.5 內(nèi)存不夠?qū)е录虞d中斷現(xiàn)象__init__里預(yù)加載所有數(shù)據(jù)時進程被系統(tǒng) kill 掉終端顯示Killed。原因Human3.6M 全量數(shù)據(jù)加載到內(nèi)存后占用幾個 GB如果服務(wù)器內(nèi)存緊張就會被 OOM killer 干掉。解決改成懶加載__getitem__里再讀文件而不是在__init__里全讀進來?;蛘哂胣p.load的mmap_moder參數(shù)做內(nèi)存映射不實際加載數(shù)據(jù)到內(nèi)存。我一般會先估算一下數(shù)據(jù)量超過 2GB 就用懶加載。5. 裝完怎么驗證跑通一個最小訓(xùn)練循環(huán)環(huán)境搭好、數(shù)據(jù)加載通了最后一步是驗證整條鏈路能不能跑起來。我一般會寫一個最小訓(xùn)練循環(huán)用幾行代碼確認前向傳播和反向傳播都沒問題# verify_pipeline.py import torch import torch.nn as nn from torch.utils.data import DataLoader from dataset import Human36MDataset # 加載數(shù)據(jù)集 ds Human36MDataset(data_dirdata/h36m/annotations/, splittrain) loader DataLoader(ds, batch_size4, shuffleTrue, num_workers0) # 定義一個極簡的 2D 到 3D 提升網(wǎng)絡(luò) class SimpleLiftingNet(nn.Module): def __init__(self): super().__init__() self.fc nn.Sequential( nn.Linear(32 * 2, 256), nn.ReLU(), nn.Linear(256, 32 * 3) ) def forward(self, x): # x: (batch, 幀數(shù), 32, 2) batch, frames, joints, _ x.shape x x.view(batch * frames, joints * 2) out self.fc(x) return out.view(batch, frames, joints, 3) # 初始化模型和優(yōu)化器 device torch.device(cuda if torch.cuda.is_available() else cpu) model SimpleLiftingNet().to(device) optimizer torch.optim.Adam(model.parameters(), lr1e-3) criterion nn.MSELoss() # 跑一個 epoch model.train() for batch_idx, batch in enumerate(loader): poses_2d batch[poses_2d].to(device) poses_3d batch[poses_3d].to(device) optimizer.zero_grad() pred_3d model(poses_2d) loss criterion(pred_3d, poses_3d) loss.backward() optimizer.step() if batch_idx % 5 0: print(fBatch {batch_idx}, Loss: {loss.item():.4f}) # 只跑幾個 batch 驗證流程 if batch_idx 20: break print(Pipeline verification passed.)這段代碼的關(guān)鍵驗證點有三個第一DataLoader能正常迭代說明數(shù)據(jù)加載鏈路通了第二model(poses_2d)能跑通前向傳播說明輸入輸出維度對上了第三loss.backward()和optimizer.step()不報錯說明反向傳播和參數(shù)更新正常。如果這三步都過了環(huán)境就算搭好了。跑的時候注意num_workers先設(shè)成 0避免多進程加載數(shù)據(jù)時出一些玄學(xué)問題。等確認流程通了再改成 4 或 8 加速。另外batch_size設(shè)小一點4 就行驗證階段不需要跑全量數(shù)據(jù)。我一般還會額外檢查一下poses_3d和pred_3d的數(shù)值范圍如果 loss 一開始就是nan多半是數(shù)據(jù)里有異常值或者輸入沒歸一化。Human3.6M 的 3D 坐標(biāo)單位是毫米數(shù)值范圍在幾百到幾千之間如果看到poses_3d里有inf或nan回去檢查標(biāo)注文件轉(zhuǎn)換那一步是不是漏了字段。從那以后我每次搭新環(huán)境都會先跑一遍這個最小驗證腳本確認數(shù)據(jù)加載、模型前向、反向傳播三條鏈路都通了再開始寫正式的訓(xùn)練代碼。這個習(xí)慣幫我省掉了無數(shù)次「訓(xùn)練跑了一晚上才發(fā)現(xiàn)數(shù)據(jù)沒加載對」的后悔藥。希望幫到你。本文還有配套的精品資源點擊獲取