写在前面

在前两个章节初步使用了SlowFast,使用的都是官方给出的数据集。

附上链接:

SlowFast使用指南(一)——demo运行-CSDN博客

SlowFast使用指南(二)——训练ava数据集-CSDN博客

本文尝试了使用自己的数据集进行训练,整个训练过程十分不易,整个过程最难的便在数据集的准备,下面附上整个过程。

数据集准备

视频准备

首先需要准备了训练使用到的两个视频,和 SlowFast使用指南(二)一样一个视频用于训练,一个视频用于验证。

视频裁剪

准备完成后,如果视频过长,需要对视频进行裁剪。本文使用到的两个视频均只有10秒左右,故没有进行裁剪。

视频抽帧

裁剪完成后,需要再对视频进行抽帧处理,抽帧分为两种方式

  • 1秒1帧:用来标注

#切割图片,每秒1帧
IN_DATA_DIR="./videos"
OUT_DATA_DIR="./labels"

if [[ ! -d "${OUT_DATA_DIR}" ]]; then
  echo "${OUT_DATA_DIR} doesn't exist. Creating it.";
  mkdir -p ${OUT_DATA_DIR}
fi

for video in $(ls -A1 -U ${IN_DATA_DIR}/*)
do
  video_name=${video##*/}

  if [[ $video_name = *".webm" ]]; then
    video_name=${video_name::-5}
  else
    video_name=${video_name::-4}
  fi

  out_video_dir=${OUT_DATA_DIR}/${video_name}/
  mkdir -p "${out_video_dir}"

  out_name="${out_video_dir}/${video_name}_%06d.jpg"

  ffmpeg -i "${video}" -r 1 -q:v 1 "${out_name}"
done

抽1帧完成的效果如下

  • 1秒30帧:用于训练(因为slowfast在slow流里1秒会采集到15帧,在fast流里1秒会采集到2帧)
#切割图片,每秒30帧
IN_DATA_DIR="./videos"
OUT_DATA_DIR="./frames"

if [[ ! -d "${OUT_DATA_DIR}" ]]; then
  echo "${OUT_DATA_DIR} doesn't exist. Creating it.";
  mkdir -p ${OUT_DATA_DIR}
fi

for video in $(ls -A1 -U ${IN_DATA_DIR}/*)
do
  video_name=${video##*/}

  if [[ $video_name = *".webm" ]]; then
    video_name=${video_name::-5}
  else
    video_name=${video_name::-4}
  fi

  out_video_dir=${OUT_DATA_DIR}/${video_name}/
  mkdir -p "${out_video_dir}"

  out_name="${out_video_dir}/${video_name}_%06d.jpg"

  ffmpeg -i "${video}" -r 30 -q:v 1 "${out_name}"
done

抽30帧完成的效果如下

图片标注

本文采用的是 VGG Image Annotator 进行图片标注的,点击链接进入后。

  1. 导入图片

  2. 创建Attributes
  3. 画框标注(这里行为的编号,建议从1开始)
  4. 导出标注csv文件

本文由于图片数量较少,采用的是手动标注。如果图片数量较多,建议自动标注。最终得到如下结果。

via转ava

导出的csv格式明显与slowfast所需要的csv格式不相匹配,需要对其进行转换。

import csv

# 输入输出文件
via_csv = "via_project_27Aug2025_14h55m_csv.csv"
ava_csv = "ava_val_v2.2.csv"

# 图像宽高(根据你的实际视频分辨率填写)
IMG_WIDTH = 1080
IMG_HEIGHT = 1920

def via_to_ava(via_csv, ava_csv, img_w=IMG_WIDTH, img_h=IMG_HEIGHT):
    with open(via_csv, newline='') as fin, open(ava_csv, 'w', newline='') as fout:
        reader = csv.DictReader(fin)
        writer = csv.writer(fout)

        for row in reader:
            filename = row['filename']
            video_id = filename.split('_')[0]
            frame_str = filename.split('_')[1].split('.')[0]
            frame_sec = int(frame_str.lstrip('0'))# 每30帧为1秒

            # 解析 region_shape_attributes
            shape = eval(row['region_shape_attributes'])
            x, y, w, h = shape['x'], shape['y'], shape['width'], shape['height']

            # 归一化坐标
            x1 = x / img_w
            y1 = y / img_h
            x2 = (x + w) / img_w
            y2 = (y + h) / img_h

            # 解析 action
            action = eval(row['region_attributes'])['action']
            action_id = int(action)
            person_id = eval(row['region_attributes'])['id']
            person_id = int(person_id)

            # AVA 格式:video_id, frame_sec, x1, y1, x2, y2, action_id, person_id
            writer.writerow([video_id, frame_sec, f"{x1:.3f}", f"{y1:.3f}",
                             f"{x2:.3f}", f"{y2:.3f}", action_id, person_id])

via_to_ava(via_csv, ava_csv)

其中via_csv、ava_csv表示输入路径和输出路径,需要根据实际位置进行修改。IMG_WIDTH 和IMG_HEIGHT 表示图片分辨率,也需要根据实际情况修改。最后得到如下结果。

生成其他配置文件

ava_action_list_v2.2_for_activitynet_2019.pbtxt

item {
  name: "normal"
  id: 1
}
item {
  name: "abnormal"
  id: 2
}

此为你要识别的行为类别,本文识别了两种类别正常和不正常

frame_lists

frame_lists下的文件使用1秒抽30帧的图片。

import os
from typing import List, Dict

def build_vid_map(frames_root: str) -> Dict[str, int]:
    """
    返回 目录名 → video_id 的字典序映射
    """
    dirs = sorted([d for d in os.listdir(frames_root)
                   if os.path.isdir(os.path.join(frames_root, d))])
    return {d: idx for idx, d in enumerate(dirs)}

def generate_val_csv(dirs: List[str],
                     frames_root: str = '../frames',
                     out_csv: str = 'frame_lists/val.csv') -> None:
    """
    仅针对给定目录列表生成 val.csv
    :param dirs:        目录名列表,顺序即 video_id
    :param frames_root: 帧根目录
    :param out_csv:     输出文件
    """
    vid_map = build_vid_map(frames_root)
    os.makedirs(os.path.dirname(out_csv), exist_ok=True)
    with open(out_csv, 'w') as f:
        f.write('original_vido_id video_id frame_id path labels\n')
        for orig_vid in dirs:
            vid_id = vid_map[orig_vid]
            vid_dir = os.path.join(frames_root, orig_vid)
            if not os.path.isdir(vid_dir):
                continue
            for fname in sorted(os.listdir(vid_dir)):
                if fname.endswith('.jpg'):
                    frame_id = int(fname.split('_')[-1].split('.')[0])
                    f.write(f'{orig_vid} {vid_id} {frame_id} {orig_vid}/{fname} ""\n')

    print(f'✅ 已生成 {out_csv},目录 {dirs} 已处理')

# ===== 用法 =====
if __name__ == '__main__':
    generate_val_csv(['e68a9872cfd45cc79867fc6a22117eda'])

由于本文是直接指定哪个视频为训练集哪个训练为验证集,故上方代码传递相对应的目录名,即可生成train.csv和val.csv。

最终生成的结果如下

ava_detection_train_boxes_and_labels_include_negative_v2.2.csv

此文件直接对ava_train_v2.2.csv的前七列进行复制,最后一列使用置信度进行填充,置信度设置为0.996382.

ava_detection_val_boxes_and_labels.csv

此文件对ava_val_v2.2.csv的前六列进行复制,第七列是空值,第八列用置信度填充,然后同样设置置信度。

SLOWFAST_32x2_R50_SHORT5.yaml

在 configs/AVA 目录下创建 SLOWFAST_32x2_R50_SHORT5.yaml 配置文件

TRAIN:
  ENABLE: True
  DATASET: ava
  BATCH_SIZE: 1 
  EVAL_PERIOD: 1
  CHECKPOINT_PERIOD: 1
  AUTO_RESUME: True
  CHECKPOINT_FILE_PATH: ''  #path to pretrain model
  CHECKPOINT_TYPE: caffe2
  AUTO_RESUME: True
  CHECKPOINT_EPOCH_RESET: True  # 重置epoch计数
DATA:
  NUM_FRAMES: 32
  SAMPLING_RATE: 2
  TRAIN_JITTER_SCALES: [256, 320]
  TRAIN_CROP_SIZE: 224
  TEST_CROP_SIZE: 224
  INPUT_CHANNEL_NUM: [3, 3]
  PATH_TO_DATA_DIR: '/mnt/workspace/slowfast_project/cowdemo'
DETECTION:
  ENABLE: True
  ALIGNED: True
AVA:
  FULL_TEST_ON_VAL: True
  FRAME_DIR: "/mnt/workspace/slowfast_project/cowdemo/frames"
  FRAME_LIST_DIR: "/mnt/workspace/slowfast_project/cowdemo/frame_lists"
  ANNOTATION_DIR: "/mnt/workspace/slowfast_project/cowdemo/annotations"
  DETECTION_SCORE_THRESH: 0.8
  TRAIN_PREDICT_BOX_LISTS: [
    "ava_train_v2.2.csv",
    "person_box_67091280_iou90/ava_detection_train_boxes_and_labels_include_negative_v2.2.csv",
  ]
  TEST_PREDICT_BOX_LISTS: ["person_box_67091280_iou90/ava_detection_val_boxes_and_labels.csv"]

SLOWFAST:
  ALPHA: 4
  BETA_INV: 8
  FUSION_CONV_CHANNEL_RATIO: 2
  FUSION_KERNEL_SZ: 5
RESNET:
  ZERO_INIT_FINAL_BN: True
  WIDTH_PER_GROUP: 64
  NUM_GROUPS: 1
  DEPTH: 101
  TRANS_FUNC: bottleneck_transform
  STRIDE_1X1: False
  NUM_BLOCK_TEMP_KERNEL: [[3, 3], [4, 4], [6, 6], [3, 3]]
  SPATIAL_DILATIONS: [[1, 1], [1, 1], [1, 1], [2, 2]]
  SPATIAL_STRIDES: [[1, 1], [2, 2], [2, 2], [1, 1]]
NONLOCAL:
  LOCATION: [[[], []], [[], []], [[6, 13, 20], []], [[], []]]
  GROUP: [[1, 1], [1, 1], [1, 1], [1, 1]]
  INSTANTIATION: dot_product
  POOL: [[[2, 2, 2], [2, 2, 2]], [[2, 2, 2], [2, 2, 2]], [[2, 2, 2], [2, 2, 2]], [[2, 2, 2], [2, 2, 2]]]
BN:
  USE_PRECISE_STATS: False
  NUM_BATCHES_PRECISE: 200
SOLVER:
  BASE_LR: 0.1
  LR_POLICY: steps_with_relative_lrs
  STEPS: [0, 10, 15, 20]
  LRS: [1, 0.1, 0.01, 0.001]
  MAX_EPOCH: 20
  MOMENTUM: 0.9
  WEIGHT_DECAY: 1e-7
  WARMUP_EPOCHS: 5.0
  WARMUP_START_LR: 0.000125
  OPTIMIZING_METHOD: sgd
MODEL:
  NUM_CLASSES: 2
  ARCH: slowfast
  MODEL_NAME: SlowFast
  LOSS_FUNC: bce
  DROPOUT_RATE: 0.5
  HEAD_ACT: sigmoid
TEST:
  ENABLE: False
  DATASET: ava
  BATCH_SIZE: 8
DATA_LOADER:
  NUM_WORKERS: 2
  PIN_MEMORY: True
NUM_GPUS: 1
NUM_SHARDS: 1
RNG_SEED: 0
OUTPUT_DIR: .

运行

python tools/run_net.py --cfg configs/AVA/SLOWFAST_32x2_R50_SHORT5.yaml

最终训练效果如下图所示

问题解决

问题一

原csv文件是从902秒开始的,但是自己的视频是从第一秒开始的

问题二

使用自制的数据集,使用demo模式测试训练好的模型的效果,得到的输出视频并无检测框。通过查看源代码发现

代码只保留类别为 0 的检测框(人类),但我需要识别的行为在COCO数据集中的类别并不为0,所以全部被过滤掉了!将此id修改成需要检测的类别即可(见下图)。

写在后面

当前数据量较少,训练效果不理想。本文仅展示训练自己数据集的过程,后续还需要提升模型效果。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐