Skip to content

Commit

Permalink
Fix anet video downloader (#601)
Browse files Browse the repository at this point in the history
* add anet video downloader

* pass the test
  • Loading branch information
dreamerlin authored Feb 21, 2021
1 parent d3d646a commit c329052
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 85 deletions.
46 changes: 33 additions & 13 deletions tools/data/activitynet/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,16 @@ For action detection, you can either use the ActivityNet rescaled feature provid
We release both pipeline.
Before we start, please make sure that current working directory is `$MMACTION2/tools/data/activitynet/`.

## Step 1. Download Annotations
## Option 1: Use the ActivityNet rescaled feature provided in this [repo](https://github.com/wzmsltw/BSN-boundary-sensitive-network#code-and-data-preparation)

### Step 1. Download Annotations

First of all, you can run the following script to download annotation files.

```shell
bash download_annotations.sh
bash download_feature_annotations.sh
```

## Option 1: Use the ActivityNet rescaled feature provided in this [repo](https://github.com/wzmsltw/BSN-boundary-sensitive-network#code-and-data-preparation)

### Step 2. Prepare Videos Features

Then, you can run the following script to download activitynet features.
Expand All @@ -40,24 +40,42 @@ bash download_features.sh
### Step 3. Process Annotation Files

Next, you can run the following script to process the downloaded annotation files for training and testing.
It first merges the two annotation files together and then seperates the annoations by `train`, `val` and `test`.
It first merges the two annotation files together and then separates the annoations by `train`, `val` and `test`.

```shell
python process_annotations.py
```

## Option 2: Extract ActivityNet feature using MMAction2
## Option 2: Extract ActivityNet feature using MMAction2 with all videos provided in official [website](http://activity-net.org/)

### Step 1. Download Annotations

First of all, you can run the following script to download annotation files.

```shell
bash download_annotations.sh
```

### Step 2. Prepare Videos

Then, you can run the following script to prepare videos.
The codes are adapted from the [official crawler](https://github.com/activitynet/ActivityNet/tree/master/Crawler/Kinetics). Note that this might take a long time.
Some videos in the ActivityNet dataset might be no longer available on YouTube, so that after video downloading, the downloading scripts update the annotation file to make sure every video in it exists.

```shell
bash download_videos.sh
```

Since some videos in the ActivityNet dataset might be no longer available on YouTube, official [website](http://activity-net.org/) has made the full dataset available on Google and Baidu drives.
To accommodate missing data requests, you can fill in this [request form](https://docs.google.com/forms/d/e/1FAIpQLSeKaFq9ZfcmZ7W0B0PbEhfbTHY41GeEgwsa7WobJgGUhn4DTQ/viewform) provided in official [download page](http://activity-net.org/download.html) to have a 7-day-access to download the videos from the drive folders.

We also provide download steps for annotations from [BSN repo](https://github.com/wzmsltw/BSN-boundary-sensitive-network#code-and-data-preparation)

```shell
bash download_bsn_videos.sh
```

For this case, the downloading scripts update the annotation file after downloading to make sure every video in it exists.

### Step 3. Extract RGB and Flow

Before extracting, please refer to [install.md](/docs/install.md) for installing [denseflow](https://github.com/open-mmlab/denseflow).
Expand Down Expand Up @@ -119,18 +137,23 @@ mmaction2
├── configs
├── data
│ ├── ActivityNet
(if Option 1 used)
│ │ ├── anet_anno_{train,val,test,full}.json
│ │ ├── anet_anno_action.json
│ │ ├── video_info_new.csv
(if Option 1 used)
│ │ ├── activitynet_feature_cuhk
│ │ │ ├── csv_mean_100
│ │ │ │ ├── v___c8enCfzqw.csv
│ │ │ │ ├── v___dXUJsj3yo.csv
│ │ │ | ├── ..
(if Option 2 used)
│ │ ├── anet_train_video.txt
│ │ ├── anet_val_video.txt
│ │ ├── anet_train_clip.txt
│ │ ├── anet_val_clip.txt
│ │ ├── activity_net.v1-3.min.json
│ │ ├── mmaction_feat
│ │ │ ├── v___c8enCfzqw.csv
│ │ │ ├── v___dXUJsj3yo.csv
Expand All @@ -142,10 +165,7 @@ mmaction2
│ │ │ │ ├── flow_y_00000.jpg
│ │ │ │ ├── ..
│ │ │ ├── ..
│ │ ├── anet_train_video.txt
│ │ ├── anet_val_video.txt
│ │ ├── anet_train_clip.txt
│ │ ├── anet_val_clip.txt
```

For training and evaluating on ActivityNet, please refer to [getting_started.md](/docs/getting_started.md).
50 changes: 37 additions & 13 deletions tools/data/activitynet/download.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# This scripts is copied from
# https://github.com/activitynet/ActivityNet/blob/master/Crawler/Kinetics/download.py # noqa: E501
# The code is licensed under the MIT licence.
import argparse
import os
import ssl
import subprocess
Expand All @@ -10,11 +11,19 @@

ssl._create_default_https_context = ssl._create_unverified_context
data_file = '../../../data/ActivityNet'
video_list = f'{data_file}/video_info_new.csv'
anno_file = f'{data_file}/anet_anno_action.json'
output_dir = f'{data_file}/videos'


def parse_args():
parser = argparse.ArgumentParser(description='ActivityNet downloader')
parser.add_argument(
'--bsn',
action='store_true',
help='download for BSN annotation or official one')
args = parser.parse_args()
return args


def download_clip(video_identifier,
output_filename,
num_attempts=5,
Expand Down Expand Up @@ -72,7 +81,7 @@ def download_clip_wrapper(youtube_id, output_dir):
return status


def parse_activitynet_annotations(input_csv):
def parse_activitynet_annotations(input_csv, is_bsn_case=False):
"""Returns a list of YoutubeID.
arguments:
---------
Expand All @@ -85,16 +94,21 @@ def parse_activitynet_annotations(input_csv):
List of all YoutubeIDs in ActivityNet.
"""
lines = open(input_csv).readlines()
lines = lines[1:]
# YoutubeIDs do not have prefix `v_`
youtube_ids = [x.split(',')[0][2:] for x in lines]
if is_bsn_case:
lines = open(input_csv).readlines()
lines = lines[1:]
# YoutubeIDs do not have prefix `v_`
youtube_ids = [x.split(',')[0][2:] for x in lines]
else:
data = mmcv.load(anno_file)['database']
youtube_ids = list(data.keys())

return youtube_ids


def main(input_csv, output_dir, anno_file, num_jobs=24):
def main(input_csv, output_dir, anno_file, num_jobs=24, is_bsn_case=False):
# Reading and parsing ActivityNet.
youtube_ids = parse_activitynet_annotations(input_csv)
youtube_ids = parse_activitynet_annotations(input_csv, is_bsn_case)

# Creates folders where videos will be saved later.
if not os.path.exists(output_dir):
Expand All @@ -114,10 +128,20 @@ def main(input_csv, output_dir, anno_file, num_jobs=24):
annotation = mmcv.load(anno_file)
downloaded = {status[0]: status[1] for status in status_list}
annotation = {k: v for k, v in annotation.items() if downloaded[k]}
anno_file_bak = anno_file.replace('.json', '_bak.json')
os.system(f'mv {anno_file} {anno_file_bak}')
mmcv.dump(annotation, anno_file)

if is_bsn_case:
anno_file_bak = anno_file.replace('.json', '_bak.json')
os.system(f'mv {anno_file} {anno_file_bak}')
mmcv.dump(annotation, anno_file)


if __name__ == '__main__':
main(video_list, output_dir, anno_file, 24)
args = parse_args()
is_bsn_case = args.bsn
if is_bsn_case:
video_list = f'{data_file}/video_info_new.csv'
anno_file = f'{data_file}/anet_anno_action.json'
else:
video_list = f'{data_file}/activity_net.v1-3.min.json'
anno_file = video_list
main(video_list, output_dir, anno_file, 24, is_bsn_case)
6 changes: 1 addition & 5 deletions tools/data/activitynet/download_annotations.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,6 @@ fi

cd ${DATA_DIR}

wget https://raw.githubusercontent.com/wzmsltw/BSN-boundary-sensitive-network/master/data/activitynet_annotations/anet_anno_action.json

wget https://raw.githubusercontent.com/wzmsltw/BSN-boundary-sensitive-network/master/data/activitynet_annotations/video_info_new.csv

wget https://download.openmmlab.com/mmaction/localization/anet_activity_indexes_val.txt
wget http://ec2-52-25-205-214.us-west-2.compute.amazonaws.com/files/activity_net.v1-3.min.json

cd -
13 changes: 13 additions & 0 deletions tools/data/activitynet/download_bsn_videos.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/usr/bin/env bash

# set up environment
conda env create -f environment.yml
source activate activitynet
pip install --upgrade youtube-dl
pip install mmcv

DATA_DIR="../../../data/ActivityNet"
python download.py --bsn

source deactivate activitynet
conda remove -n activitynet --all
16 changes: 16 additions & 0 deletions tools/data/activitynet/download_feature_annotations.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
DATA_DIR="../../../data/ActivityNet/"

if [[ ! -d "${DATA_DIR}" ]]; then
echo "${DATA_DIR} does not exist. Creating";
mkdir -p ${DATA_DIR}
fi

cd ${DATA_DIR}

wget https://raw.githubusercontent.com/wzmsltw/BSN-boundary-sensitive-network/master/data/activitynet_annotations/anet_anno_action.json

wget https://raw.githubusercontent.com/wzmsltw/BSN-boundary-sensitive-network/master/data/activitynet_annotations/video_info_new.csv

wget https://download.openmmlab.com/mmaction/localization/anet_activity_indexes_val.txt

cd -
115 changes: 61 additions & 54 deletions tools/data/activitynet/generate_rawframes_filelist.py
Original file line number Diff line number Diff line change
@@ -1,81 +1,88 @@
import json
import os
import os.path as osp

import mmcv

data_file = '../../../data/ActivityNet'
video_list = f'{data_file}/video_info_new.csv'
anno_file = f'{data_file}/anet_anno_action.json'
rawframe_dir = f'{data_file}/rawframes'
action_name_list = 'action_name.csv'

train_rawframe_dir = rawframe_dir
val_rawframe_dir = rawframe_dir

json_file = f'{data_file}/activity_net.v1-3.min.json'


def generate_rawframes_filelist():
anet_annotations = mmcv.load(anno_file)

videos = open(video_list).readlines()
videos = [x.strip().split(',') for x in videos]
attr_names = videos[0][1:]
# the first line is 'video,numFrame,seconds,fps,rfps,subset,featureFrame'
attr_names = [x.lower() for x in attr_names]
attr_types = [int, float, float, float, str, int]

video_annos = {}
for line in videos[1:]:
name = line[0]
data = {}
for attr_name, attr_type, attr_val in zip(attr_names, attr_types,
line[1:]):
data[attr_name] = attr_type(attr_val)
video_annos[name] = data

# only keep downloaded videos
video_annos = {
k: v
for k, v in video_annos.items() if k in anet_annotations
}
# update numframe
for video in video_annos:
pth = osp.join(rawframe_dir, video)
num_imgs = len(os.listdir(pth))
# one more rgb img than flow
assert (num_imgs - 1) % 3 == 0
num_frames = (num_imgs - 1) // 3
video_annos[video]['numframe'] = num_frames
load_dict = json.load(open(json_file))

anet_labels = open(action_name_list).readlines()
anet_labels = [x.strip() for x in anet_labels[1:]]

train_videos, val_videos = {}, {}
for k, video in video_annos.items():
if video['subset'] == 'training':
train_videos[k] = video
elif video['subset'] == 'validation':
val_videos[k] = video
train_dir_list = [
osp.join(train_rawframe_dir, x) for x in os.listdir(train_rawframe_dir)
]
val_dir_list = [
osp.join(val_rawframe_dir, x) for x in os.listdir(val_rawframe_dir)
]

def simple_label(video_idx):
anno = anet_annotations[video_idx]
label = anno['annotations'][0]['label']
def simple_label(anno):
label = anno[0]['label']
return anet_labels.index(label)

def count_frames(dir_list, video):
for dir_name in dir_list:
if video in dir_name:
return osp.basename(dir_name), len(os.listdir(dir_name))
return None, None

database = load_dict['database']
training = {}
validation = {}
key_dict = {}

for k in database:
data = database[k]
subset = data['subset']

if subset in ['training', 'validation']:
annotations = data['annotations']
label = simple_label(annotations)
if subset == 'training':
dir_list = train_dir_list
data_dict = training
else:
dir_list = val_dir_list
data_dict = validation

else:
continue

gt_dir_name, num_frames = count_frames(dir_list, k)
if gt_dir_name is None:
continue
data_dict[gt_dir_name] = [num_frames, label]
key_dict[k] = gt_dir_name

train_lines = [
k + ' ' + str(train_videos[k]['numframe']) + ' ' +
str(simple_label(k)) for k in train_videos
k + ' ' + str(training[k][0]) + ' ' + str(training[k][1])
for k in training
]
val_lines = [
k + ' ' + str(val_videos[k]['numframe']) + ' ' + str(simple_label(k))
for k in val_videos
k + ' ' + str(validation[k][0]) + ' ' + str(validation[k][1])
for k in validation
]

with open(osp.join(data_file, 'anet_train_video.txt'), 'w') as fout:
fout.write('\n'.join(train_lines))
with open(osp.join(data_file, 'anet_val_video.txt'), 'w') as fout:
fout.write('\n'.join(val_lines))

def clip_list(k, anno, vidanno):
num_seconds = anno['duration_second']
num_frames = vidanno['numframe']
fps = num_frames / num_seconds
def clip_list(k, anno, video_anno):
duration = anno['duration']
num_frames = video_anno[0]
fps = num_frames / duration
segs = anno['annotations']
lines = []
for seg in segs:
Expand All @@ -90,10 +97,10 @@ def clip_list(k, anno, vidanno):
return lines

train_clips, val_clips = [], []
for k in train_videos:
train_clips.extend(clip_list(k, anet_annotations[k], train_videos[k]))
for k in val_videos:
val_clips.extend(clip_list(k, anet_annotations[k], val_videos[k]))
for k in training:
train_clips.extend(clip_list(k, database[key_dict[k]], training[k]))
for k in validation:
val_clips.extend(clip_list(k, database[key_dict[k]], validation[k]))

with open(osp.join(data_file, 'anet_train_clip.txt'), 'w') as fout:
fout.write('\n'.join(train_clips))
Expand Down

0 comments on commit c329052

Please sign in to comment.