UCF101の10クラスを例に、動画フレームからアノテーションファイルを生成する手順を説明します。
1. フレーム抽出
フレーム抽出には主に2つのディレクトリ構成があります。
方法1:クラスごとに整理された構造(rawframes)
rawframes/
├── ApplyEyemakeup/
│ ├── v_ApplyEyemakeup_g01_c01/
│ │ ├── img_0001.jpg
│ │ └── ...
│ └── v_ApplyEyemakeup_g01_c02/
└── Basketball/
└── ...
この形式は mmaction2/tools/data/build_rawframes.py を使用して生成できます:
python build_rawframes.py ucf101/videos ucf101/rawframes \ --task rgb --num-gpu 1 --level 2 --ext avi \ --use-opencv --new-width 320 --new-height 240
ただし、このスクリプトは img_00001.jpg のようなゼロ埋め形式で出力するため、後述の処理のために 1.jpg のようなシンプルな連番形式に変更する必要があります。
方法2:フラットな構造(frames)
frames/ ├── v_ApplyEyemakeup_g01_c01/ │ ├── img_00001.jpg │ └── ... ├── v_BenchPress_g25_c07/ └── ...
この形式は外部ツール(例:vidtools)で実現可能です:
git clone https://github.com/liu-zhy/vidtools.git cd vidtools python extract_frames.py ../ucf101/ -o ../datasets/frames/ -j 16 --out_ext png
同様に、ファイル名を 1.jpg 形式に修正する必要があります。
2. クラスごとの再整理(three_f.py)
フレーム抽出後に、動画ファイル名(例:v_ApplyEyemakeup_g01_c01)からクラス名(ApplyEyemakeup)を抽出し、対応するクラスフォルダ下に移動します。
import os
import shutil
source = 'datasets/frames'
folders = [d for d in os.listdir(source) if os.path.isdir(os.path.join(source, d))]
mapping = {}
for name in folders:
if '_' in name:
cls = name.split('_')[1]
mapping.setdefault(cls, []).append(name)
for cls, items in mapping.items():
cls_dir = os.path.join(source, cls)
os.makedirs(cls_dir, exist_ok=True)
for item in items:
src = os.path.join(source, item)
dst = os.path.join(cls_dir, item)
shutil.move(src, dst)
3. アノテーションファイル生成(four_f.py)
各フレームに対し、パス・フレーム番号・クラスIDを含むリストを生成します。ここでは UCF101 の classInd.txt を読み込み、クラス名とIDのマッピングを作成します。
import os
frames_root = 'datasets/frames'
label_map_path = 'ucfTrainTestlist/classInd.txt'
# クラスIDマッピング作成
cls_to_id = {}
with open(label_map_path) as f:
for line in f:
idx, name = line.strip().split()
cls_to_id[name] = int(idx)
# 出力ファイル準備
train_f = open('train.txt', 'w')
val_f = open('val.txt', 'w')
test_f = open('test.txt', 'w')
for cls_name, cls_id in cls_to_id.items():
cls_path = os.path.join(frames_root, cls_name)
if not os.path.isdir(cls_path):
continue
videos = os.listdir(cls_path)
for vid in videos:
vid_path = os.path.join(cls_path, vid)
frames = sorted([f for f in os.listdir(vid_path) if f.endswith('.jpg')])
total = len(frames)
n_train = int(0.6 * total)
n_val = int(0.2 * total)
for i, frame in enumerate(frames, 1):
num = int(frame.split('.')[0]) # 1.jpg → 1
rel_path = os.path.join(cls_name, vid, frame).replace('\\', '/')
entry = f"{rel_path} {num} {cls_id}\n"
if i <= n_train:
train_f.write(entry)
elif i <= n_train + n_val:
val_f.write(entry)
else:
test_f.write(entry)
train_f.close()
val_f.close()
test_f.close()
4. パス整形(five_f.py)
上記で生成されたパス(例:/ApplyEyemakeup/v_ApplyEyemakeup_g01_c01/56.jpg)を、ApplyEyemakeup/v_ApplyEyemakeup_g01_c01 56 1 の形式に変換します。
with open('val.txt') as fin, open('val_final.txt', 'w') as fout:
for line in fin:
parts = line.strip().split()
img_path = parts[0]
frame_num = parts[1]
cls_id = parts[2]
# パスから先頭のスラッシュを除去し、最後のファイル名を削除
tokens = img_path.split('/')
new_path = '/'.join(tokens[1:3]) # クラス名/ビデオID
fout.write(f"{new_path} {frame_num} {cls_id}\n")
5. 実践例:UCF5サブセットでの処理フロー
実際のワークフローとして、以下のように実行します:
build_rawframes.pyを修正し、フレームを1.jpg形式で出力。- 抽出されたフレームを
three_f.pyでクラス別に再配置。 - 動画単位で6:2:2に分割(訓練/検証/テスト)。
- 各フレームのメタ情報を収集し、
four_f.pyで一時的なアノテーション生成。 five_f.pyで最終的な形式に整形。
これにより、MMAction2などの行動認識フレームワークで直接利用可能なアノテーションファイルが得られます。