AVA数据集以及SlowFast对该数据集的处理方法

发布时间 2023-06-07 19:51:19作者: Picassooo

本文内容全部摘自:知乎AVA Actions Dataset 详解 。推荐看原文。

1.1. 基本情况

  • 数据集类别:Spatio-Temporal Action Detection,即时空行为检测。
    • 举个例子,就是检测出视频中所有人的位置以及对应的行为类别。
  • 数据集形式(这里是简单介绍,后面会有更详细的说明):
    • 要标记的内容包括人物bbox,以及每个人的行为类别,同一时间同一人可能有多个行为。
      • 标记的内容还有还有每个实体编号,即相邻关键帧中的人物如果是同一个人,则拥有相同的实体编号。换句话说,“实体编号”其实就是目标跟踪的标签。
    • 并不是对视频中的每一帧进行标记,而只是对关键帧进行标记。
    • 所谓关键帧,按我的理解就是每秒取1帧作为关键帧,对该帧进行标记。
  • 行为类别:标签一共有80类,(但Evaluate时只用到其中的60类)。80类标签分为三类(person movement, object manipulation, person interaction)。

1.2. 略

1.3. 数据集构建过程

  • 这部分内容主要参考了 数据集论文
  • 第一步:Action vocabulary generation
    • 任务:确定要标注的行为类别以及继承关系。
    • 选择/设置“行为类别”时有三个准则:
      • generality,即通用性,而不是特定环境下的特定动作(如“在篮球场打篮球”)。
      • atomicity,即原子性。每个动作都有其特点,且与交互的物体无关(如行为 hold 且不要指定hold的物体)。
      • exhaustivity,即类别尽可能丰富。
  • 第二步:Movie and segment selection
    • 任务:构建原始视频数据集,为后续标注工作做准备。
    • AVA的数据源来自电影片段。
    • 每个电影只标注第15-30分钟内的视频
    • 每个长度为15分钟的视频都转换为897个长度为3s的视频片段(clip)。
      • 15分钟共900秒,窗口长度为3秒,stride为1秒,滑动897次得到897个clip。
      • 每个clip对对应一个keyframe(关键帧),关键帧是1.5秒位置。
    • 搞了一个各个国家顶尖演员列表,然后在Youtube中对每个演员进行搜索,寻找符合条件的电影。
      • 条件包括:有 file/television 标签,时长超过30分钟,发布时间超过1年,观看人数超过1000,且不包含黑白、低清晰度、卡通等类别的电影。
  • 第三步:Person bounding box annotation
    • 任务:针对每个keyframe标注人物bbox。
    • 使用了混合标注法:
      • 先用Faster-RCNN标注,(说是 set operating point 从而保证高精度,不知道啥意思,猜测就是提高了阈值吧)。
      • 之后在人工标注遗漏的bbox。
    • bbox对最终结果影响很大,所以这一步会比较注意。
  • 第四步:Person link annotation
    • 任务:对相邻keyframe中的任务bbox进行关联。
    • 方法:
      • 先机器标注一波:通过计算相邻两帧不同bbox之间的相似度,然后根据匈牙利算法进行匹配。
      • 再手动处理一波:手工删除FP样本。
  • 第五步:Action annotation
    • 任务:标注行为类别。
    • 通过众包实现。不可避免的,标注人会少标行为(因为行为太多了)。

1.4. annotations 解析

  • 以 v2.2 为例,解压 ava_v2.1.zip 得到的结果如下。
    • V2.1 和 V2.2 的区别:
      • 标签内容没细看,可能v2.2细化了吧。
      • 但视频源没有任何变化,即V2.1与V2.2的 train/val/test 的视频是完全相同的。
  • 行为类别文件:
    • ava_action_list_v2.1_for_activitynet_2018.pbtxt:60类行为,Evaluate时使用
    • ava_action_list_v2.1.pbtxt:80类行为
  • 行为标签文件:
    • ava_train_v2.1.csvava_val_v2.1.csvava_test_v2.1.txt
    • 其中,train/val有标签,test只是视频名称列表。
    • train/val 每行代表一个样本,共有5个部分
      • video_id:视频名称,不包括文件后缀,即Youtube对应url
      • middle_Frame_timestamp:关键帧所在位置(第几秒)
      • person_box:包括了四列,(x1, y1, x2, y2),分别代表左上、右下点的位置。
      • action_id:即ava_action_list_v2.1.pbtxt中对应的id。
      • person_id:bbox中人物的编号,即 person link 时产生的标签,每个人的id不同。
  • ava_included_timestamps_v2.2.txt:每个视频要检测的位置,即第902到1798秒。
  • 不需要进行检测的timestamp
    • ava_train_excluded_timestamps_v2.1.csv
    • ava_val_excluded_timestamps_v2.1.csv
    • ava_test_excluded_timestamps_v2.1.csv
    • 即 train/val/test 数据集中每个视频不需要进行检测的timestamp。

2. SlowFast对AVA数据集的处理

2.1. 构建 Ava 对象

  • 第一步:为每个视频进行编号,并保存对应的帧绝对路径的列表。
    • 从代码角度看保存了两个列表
      • _video_idx_to_name
        • 每个视频原来有个 video_name,即youtube中对应url后缀,如 1j20qq1JyX4
        • 在代码中,video_name用起来不方便,所以对视频进行编号,即每个 video_name 对应一个 video_id(从0开始编号)。
        • 本对象是 list,index就是 video_id,value就是video_name
      • _image_paths
        • 保存每个视频对应帧的绝对路径。
        • 本对象是list,index是 video_id,value是一个list(中每个元素是帧绝对路径,注意,这个list中帧文件的顺序必须是从小到大,不然后面代码有问题)。
    • 源码细节:主要输入数据就是 frame_lists 文件
      • 该文件不是AVA官方提供的,而是FAIR提供的,可以自己生成。
      • 该文件中保存有video_name以及对应所有帧的相对路径,且帧文件的顺序就是从小到大。
    • 主要就是 ava_helper.load_image_lists 实现。
  • 第二步:解析行为标签文件。
    • 从代码角度看,就是构建了一个list boxes_and_labels
      • 该数据类型是:boxes_and_labels[video_id][frame_sec_int] = list([box_i, box_i_labels])
        • boxes_and_labels[video_id].keys() 就是所有可用的时间点,即range(902, 1799)
        • len(boxes_and_labels[video_id][frame_sec_int]) 就是这个时间点 box 的数量。
      • boxes_and_labels 整体是一个列表,index是 video_id,value是一个字典。
      • 该字典的 key 是 frame_sec_int,即 [902, 1798],表示视频中的第几帧。
      • 该字典的 value 是列表,取名为 value_list
      • value_list通过列表形式保存 bbox(x1, y1, x2, y2形式) 以及对应的
      • labels(同一个box可能有多个标签)两部分信息。
      • 其中,box_i 的形式是 x1, y1, x2, y2
    • 源码细节:
      • 这一步的输入数据主要包括GT与Predict两部分。
      • GT指的就是AVA官方提供的标签文件,如ava_train_v2.1.csv
      • Predict指的是验证/测试时用的数据,只包括每个视频每一帧的人物bbox以及对应的score,不包括行为类别。包括 video_name, frame_sec, bbox_x1, bbox_y1, bbox_x2, bbox_y2, category, score。在使用Predict数据时会根据 score 筛选一部分数据。
    • 主要通过 ava_helper.load_boxes_and_labels 实现。
  • 第三步:构建关键帧数据。
    • 从源码上看,就是构建了 _keyframe_indices 和 _keyframe_boxes_and_labels 两个列表。
      • 这两个对象是配合使用的。两个列表中相同index的元素就是后续构建 clip 样本的输入数据。
      • 保存了所有可用关键帧相关信息。
    • _keyframe_indices
      • 是个list对象,index的作用就是与 _keyframe_boxes_and_labels 对应。
      • 主要保存四个数据 video_idx, sec_idx, sec, frame_idx
      • sec_idx 指的是当前关键帧在这个视频中所有关键帧的idx,从0开始取值。
      • sec 指的是当前关键帧在这个视频中的位置,从902开始取值。
      • frame_idx 指的是当前关键帧的具体编号,计算方法(sec-900)*FPS,其实就是每个clip的中心frame编号。
    • _keyframe_boxes_and_labels
      • 是个list对象,index的作用就是与 _keyframe_indices 对应。
      • 其实就是将上一步中的 boxes_and_labels[video_id][frame_sec_int] 直接保存下来。
    • 主要通过 ava_helper.get_keyframe_data 实现。

2.3. 读取某个keyframe信息

  • 这里,每个keyframe的信息就是对应一个clip数据,主要就是通过 __getitem__ 实现。
    • 输入的idx其实就是 2.2. 中第三步 _keyframe_indices 中的下标。
  • 第一步:获取 _keyframe_indices 对应 idx 下标信息。
    • video_idx, sec_idx, sec, center_idx,sec_idx 从0开始取值,sec从902开始取值。
  • 第二步:根据输入数据,进行数据采样。
    • 以 center_idx 为中心,根据输入数据 _seq_len 和 _sample_rate 进行采样。
    • 采样细节:
      • center_idx - _seq_len // 2开始,在 [center_idx - _seq_len // 2, center_idx + _seq_len // 2) 范围内,根据 _sample_rate 进行采样。
      • _seq_len 的取值其实是 _sample_rate * _sample_frames_length 得到的。
      • 刚开始在想,为什么这个采样刚好就能用。列了不等式算了算,刚刚好,这个样子采样其实刚好能得到 _sample_frames_length 个帧下标。
  • 第三步:获取 _keyframe_boxes_and_labels 获取该关键帧的所有boxes与对应label信息。
  • 第四步:根据采样结果以及 _image_paths 读取帧文件绝对路径,并读取对应图片。
    • _image_paths[video_idx][frame] for frame in seq,其中 seq 就是上面采样得到的结果。
  • 第五步:图片数据预处理。
    • 有pytorch与cv2两种模式,预处理的过程是一样的,只是调用的库不同。
    • 得到的结果都是 C, T, H, W 结构。
    • 预处理过程包括:
      • 数据类型/范围转换:[0, 255] -> [0, 1]
      • resize/crop操作:训练集 随机短边resize -> random crop -> random flip;验证集 短边resize -> center crop;测试集 短边resize。
        • 随机短边resize通过 transform.random_short_side_scale_jitter 实现,根据输入参数 TRAIN_JITTER_SCALES 指定短边范围,随机获取其中数值作为短边的size,然后进行resize。
      • 随机色彩变换,主要通过 transform.color_jitter 与 transform.lighting_jitter 实现。
      • 图像标准化,减去平均数除以标准差。
  • 第六步:构建行为识别 one-hot 形式label。
    • label的shape为 [num_boxes, num_classes]
    • 注意,每行可能不止一个类别为1。
  • 第七步:分别为不同分支构建输入数据。
    • 对于I3D模型,这一步其实也没做什么。
    • 对于SlowFast模型,这一步会分别对 Slow 分支与 Fast 分支构建对应的输入图片。
      • Fast分支就是之前输入的。
      • Slow分支就是在T纬度上进行sample rate为SLOWFAST.ALPHA的采样。
  • 第八步:构建输出数据。
    • 输出数据包括四部分 imgs, labels, idx, extra_data
    • imgs 是第七步图像预处理的结果,是个list,分别对应每个分支的结果,每个分支的shape为 [C, T, H, W]
    • labels 就是第六步的结果,shape为 [num_boxes, num_classes]
    • idx 即 __getitem__ 的输入数据。
    • extra_data 是个字典,包括三个数据:
      • boxes 经过数据预处理后的bbox。
      • ori_boxes 原始 boxes,即在resize/crop等操作前的bbox。
      • metadata 元数据列表,列表长度与 boxes 相同,每个元素都是 [video_id, sec]。sec从902开始取值。