Pyskl实战避坑指南:从零构建自定义数据集训练流程
如果你正在尝试用Pyskl训练自己的动作识别模型,大概率已经踩过不少坑了。从数据准备到最终模型训练,这个看似简单的流程里藏着无数个可能让你卡住几天的陷阱。我花了整整两周时间,在多个数据集上反复试验,才把整个流程彻底跑通。这篇文章不会重复那些官方文档里已有的基础步骤,而是聚焦于那些真正让你头疼的实际问题——路径配置、分布式训练失败、PKL文件生成错误、环境依赖冲突等等。
Pyskl作为基于骨骼点的动作识别工具箱,确实为研究者提供了强大的算法支持,包括ST-GCN、ST-GCN++、PoseC3D等多种模型。但当你真正开始用自定义数据集训练时,会发现官方示例和实际应用之间存在巨大的鸿沟。下面我将按照实际操作的顺序,逐一拆解每个环节的常见问题和解决方案。
1. 环境配置:版本兼容性这个隐形杀手
环境配置是Pyskl训练的第一个拦路虎。官方文档给出的安装命令看似简单,但当你真正执行时,各种版本冲突会让你怀疑人生。
1.1 关键依赖版本锁定
Pyskl对各个依赖包的版本极其敏感。我测试过多个组合,最终确定了一套稳定的版本配置:
# 核心依赖版本
torch==1.10.0+cu113
torchvision==0.11.1+cu113
mmcv-full==1.3.18
mmdet==2.23.0
mmpose==0.24.0
mmcv==1.3.18
注意:这里有个关键细节——
mmcv和mmcv-full需要同时安装,但版本必须完全一致。很多教程只提mmcv-full,实际上mmcv也是必需的。
如果你使用conda环境,建议按以下顺序安装:
# 1. 创建并激活环境
conda create -n pyskl python=3.8
conda activate pyskl
# 2. 安装PyTorch(注意CUDA版本)
pip install torch==1.10.0+cu113 torchvision==0.11.1+cu113 -f https://download.pytorch.org/whl/torch_stable.html
# 3. 安装MMCV系列
pip install mmcv==1.3.18
pip install mmcv-full==1.3.18 -f https://download.openmmlab.com/mmcv/dist/cu113/torch1.10.0/index.html
# 4. 安装其他MM系列
pip install mmdet==2.23.0
pip install mmpose==0.24.0
# 5. 安装Pyskl
git clone https://github.com/kennymckormick/pyskl.git
cd pyskl
pip install -r requirements.txt
pip install -e .
1.2 验证安装是否成功
安装完成后不要急着跑训练,先验证几个关键功能:
# 测试脚本:check_installation.py
import torch
import mmcv
import mmdet
import mmpose
import pyskl
print(f"PyTorch版本: {torch.__version__}")
print(f"CUDA可用: {torch.cuda.is_available()}")
print(f"GPU数量: {torch.cuda.device_count()}")
# 测试MMDetection
from mmdet.apis import init_detector
print("MMDetection导入成功")
# 测试MMPose
from mmpose.apis import init_pose_model
print("MMPose导入成功")
# 测试Pyskl
from pyskl.datasets import PoseDataset
print("Pyskl导入成功")
运行这个脚本,确保所有导入都不报错。如果出现ImportError,通常是版本不匹配或安装不完整。
1.3 常见环境问题及解决方案
我在不同机器上部署时遇到过各种奇怪的问题,这里整理了几个典型的:
问题1:libcudart.so.11.0: cannot open shared object file
# 解决方案:检查CUDA版本并设置环境变量
echo $CUDA_HOME # 应该显示CUDA安装路径
export LD_LIBRARY_PATH=/usr/local/cuda-11.3/lib64:$LD_LIBRARY_PATH
问题2:ModuleNotFoundError: No module named 'mmcv._ext'
这是mmcv-full编译失败导致的。重新安装时指定正确的版本:
pip uninstall mmcv mmcv-full -y
pip install mmcv==1.3.18
pip install mmcv-full==1.3.18 --no-cache-dir
问题3:分布式训练相关错误
如果你不打算用多GPU训练,可以在代码中禁用分布式相关功能。但如果你需要多GPU训练,必须确保NCCL正确安装:
# 检查NCCL
python -c "import torch.distributed as dist; print('NCCL可用:', dist.is_nccl_available())"
2. 数据集准备:结构决定成败
数据集的组织方式直接影响后续所有流程。Pyskl对数据格式有严格的要求,但文档中的说明往往不够详细。
2.1 推荐的文件结构
经过多次试验,我推荐以下目录结构:
custom_dataset/
├── videos/
│ ├── train/
│ │ ├── class_0/
│ │ │ ├── video_0001.mp4
│ │ │ ├── video_0002.mp4
│ │ │ └── ...
│ │ ├── class_1/
│ │ │ ├── video_0001.mp4
│ │ │ └── ...
│ │ └── ...
│ └── val/
│ ├── class_0/
│ │ ├── video_0001.mp4
│ │ └── ...
│ └── ...
├── annotations/
│ ├── train.json
│ ├── val.json
│ └── video_list.txt
└── processed/
├── train.pkl
└── custom_xsub.pkl
这种结构的好处是清晰分离原始视频、标注文件和中间处理结果。当某个环节出错时,你可以快速定位问题所在。
2.2 标注文件生成脚本
官方提供的custom_2d_skeleton.py需要输入一个视频列表文件。我写了一个更通用的生成脚本:
# generate_annotation_files.py
import os
import json
from pathlib import Path
def generate_annotations(video_root, output_dir):
"""
生成Pyskl所需的标注文件
Args:
video_root: 视频根目录,包含train/和val/子目录
output_dir: 输出目录
"""
video_root = Path(video_root)
output_dir = Path(output_dir)
output_dir.mkdir(parents=True, exist_ok=True)
# 生成train.json和val.json
for split in ['train', 'val']:
split_dir = video_root / split
annotations = []
# 遍历所有类别
for class_idx, class_dir in enumerate(sorted(split_dir.iterdir())):
if not class_dir.is_dir():
continue
# 遍历该类别的所有视频
for video_file in sorted(class_dir.glob('*.mp4')):
annotation = {
'frame_dir': video_file.stem, # 视频文件名(不含扩展名)
'label': class_idx,
'vid_name': str(video_file.relative_to(video_root.parent)),
'total_frames': 0, # 稍后填充
'img_shape': (0, 0), # 稍后填充
'original_shape': (0, 0),
'num_person_raw': 0,
'keypoint': [],
'keypoint_score': []
}
annotations.append(annotation)
# 保存为JSON
output_file = output_dir / f'{split}.json'
with open(output_file, 'w') as f:
json.dump(annotations, f, indent=2)
print(f"生成 {split}.json: {len(annotations)} 个样本")
# 生成video_list.txt(用于custom_2d_skeleton.py)
video_list = []
for split in ['train', 'val']:
split_dir = video_root / split
for class_dir in sorted(split_dir.iterdir()):
if not class_dir.is_dir():
continue
for video_file in sorted(class_dir.glob('*.mp4')):
# 格式: 视频路径 标签
video_list.append(f"{video_file} {class_dir.name}")
list_file = output_dir / 'video_list.txt'
with open(list_file, 'w') as f:
f.write('\n'.join(video_list))
print(f"生成 video_list.txt: {len(video_list)} 个视频")
if __name__ == '__main__':
# 使用示例
generate_annotations(
video_root='/path/to/your/videos',
output_dir='/path/to/output/annotations'
)
这个脚本会自动处理类别标签,并生成符合Pyskl要求的格式。
2.3 视频预处理注意事项
在准备视频数据时,有几个细节需要特别注意:
- 视频格式统一:尽量使用MP4格式,编码为H.264。其他格式可能导致
decord库读取失败。 - 分辨率一致:建议将所有视频调整为相同分辨率,如256x256或320x240。不一致的分辨率会导致骨架提取时出现问题。
- 帧率处理:如果视频帧率差异较大,建议统一采样到30fps。
- 视频长度:过短的视频(少于16帧)可能无法有效训练,建议过滤掉。
我写了一个简单的视频检查脚本:
# check_videos.py
import cv2
from pathlib import Path
def check_video_properties(video_path):
"""检查视频属性"""
cap = cv2.VideoCapture(str(video_path))
if not cap.isOpened():
return False, "无法打开视频"
fps = cap.get(cv2.CAP_PROP_FPS)
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
cap.release()
if fps < 10:
return False, f"帧率过低: {fps}"
if frame_count < 16:
return False, f"帧数过少: {frame_count}"
return True, {
'fps': fps,
'width': width,
'height': height,
'frame_count': frame_count
}
def batch_check_videos(video_dir):
"""批量检查视频"""
video_dir = Path(video_dir)
issues = []
for video_file in video_dir.rglob('*.mp4'):
ok, result = check_video_properties(video_file)
if not ok:
issues.append(f"{video_file}: {result}")
else:
print(f"✓ {video_file}: {result}")
if issues:
print("\n发现以下问题:")
for issue in issues:
print(f" - {issue}")
else:
print("\n所有视频检查通过")
3. 骨架提取:custom_2d_skeleton.py的陷阱与修复
custom_2d_skeleton.py是Pyskl中用于从视频提取2D骨架的关键脚本,但原始版本存在多个问题。
3.1 分布式训练问题的根本解决
原始脚本默认启用分布式处理,但在单GPU或CPU环境下会报错。以下是修改后的关键部分:
# 修改custom_2d_skeleton.py中的main函数
def main():
args = parse_args()
assert args.out.endswith('.pkl')
lines = mrlines(args.video_list)
lines = [x.split() for x in lines]
# 解析视频列表
if len(lines[0]) == 1:
annos = [dict(frame_dir=osp.basename(x[0]).split('.')[0],
filename=x[0]) for x in lines]
else:
annos = [dict(frame_dir=osp.basename(x[0]).split('.')[0],
filename=x[0],
label=int(x[1])) for x in lines]
# 关键修改:禁用分布式
rank = 0
world_size = 1
# 注释掉所有分布式相关代码
# if 'RANK' not in os.environ:
# os.environ['RANK'] = str(args.local_rank)
# os.environ['WORLD_SIZE'] = str(1)
# os.environ['MASTER_ADDR'] = 'localhost'
# os.environ['MASTER_PORT'] = '12345'
# init_dist('pytorch', backend='nccl')
# rank, world_size = get_dist_info()
# 创建临时目录
if rank == 0:
os.makedirs(args.tmpdir, exist_ok=True)
# 处理所有数据(单进程)
my_part = annos
# 初始化检测和姿态估计模型
print("初始化检测模型...")
det_model = init_detector(args.det_config, args.det_ckpt, 'cuda')
assert det_model.CLASSES[0] == 'person', '需要COCO预训练的人体检测器'
print("初始化姿态估计模型...")
pose_model = init_pose_model(args.pose_config, args.pose_ckpt, 'cuda')
#

3万+

被折叠的 条评论
为什么被折叠?



