dense_flow安装教程可见:安装dense_flow
dense_flow提取过程可见:dense_flow代码理解
过程很简单,首先就是一些参数的输入,数据集、光流存放和光流提取工具的路径,读取帧的宽、高,线程个数,GPU个数,提取出的光流格式(dir、zip),视频文件格式(avi、mp4)。读取数据集中所有视频文件存在vid_list,并打印视频数量。将视频路径和视频类别打包成一个元组,输入到光流提取函数run_optical_flow中,调用dense_flow工具,完成rgb图片和光流提取。具体见代码注释。
from __future__ import print_function
import os
import sys
import glob
import argparse
from pipes import quote
from multiprocessing import Pool, current_process
def run_optical_flow(vid_item):
#文件名
vid_path = vid_item[0]
#文件索引
vid_id = vid_item[1]
#得到视频文件的名字
vid_name = vid_path.split('/')[-1].split('.')[0]
#为视频文件创建一个新文件夹
out_full_path = os.path.join(out_path, vid_name)
try:
os.mkdir(out_full_path)
except OSError:
pass
current = current_process()
#获取GPU的id
dev_id = (int(current._identity[0]) - 1) % NUM_GPU
#构建图像、光流路径
image_path = '{}/img'.format(out_full_path)
flow_x_path = '{}/flow_x'.format(out_full_path)
flow_y_path = '{}/flow_y'.format(out_full_path)
#quote将字符串转化为ASSIC码
#-f 视频路径 -x x光流 -y y光流 -i rgb图片 -d GPU编号 -o 文件格式 -w 新宽度 -h 新高度
cmd = os.path.join(df_path + 'build/extract_gpu')+' -f {} -x {} -y {} -i {} -b 20 -t 1 -d {} -s 1 -o {} -w {} -h {}'.format(
quote(vid_path), quote(flow_x_path), quote(flow_y_path), quote(image_path), dev_id, out_format, new_size[0], new_size[1])
print(cmd);
os.system(cmd)
print('{} {} done'.format(vid_id, vid_name))
sys.stdout.flush()
return True
if __name__ == '__main__':
parser = argparse.ArgumentParser(description="extract optical flows")
#src_dir :数据集存放路径
parser.add_argument("--src_dir", type=str, default='./UCF-101',
help='path to the video data')
#out_dir :rgb图像和光流存放路径
parser.add_argument("--out_dir", type=str, default='./ucf101_frames',
help='path to store frames and optical flow')
#df_path :密集光流工具路径
parser.add_argument("--df_path", type=str, default='./dense_flow/',
help='path to the dense_flow toolbox')
#new_width :图像resize新宽度
parser.add_argument("--new_width", type=int, default=0, help='resize image width')
#new_height :图像resize新高度
parser.add_argument("--new_height", type=int, default=0, help='resize image height')
#num_worker 线程个数
parser.add_argument("--num_worker", type=int, default=8)
#num_gpu :gpu数量
parser.add_argument("--num_gpu", type=int, default=2, help='number of GPU')
#out_format :光流文件格式
parser.add_argument("--out_format", type=str, default='dir', choices=['dir','zip'],
help='path to the dense_flow toolbox')
#ext :视频文件后缀
parser.add_argument("--ext", type=str, default='avi', choices=['avi','mp4'],
help='video file extensions')
args = parser.parse_args()
out_path = args.out_dir
src_path = args.src_dir
num_worker = args.num_worker
df_path = args.df_path
out_format = args.out_format
ext = args.ext
new_size = (args.new_width, args.new_height)
NUM_GPU = args.num_gpu
if not os.path.isdir(out_path):
print("creating folder: "+out_path)
os.makedirs(out_path)
#获取数据集所有的文件
vid_list = glob.glob(src_path+'/*/*.'+ext)
#打印文件个数
print(len(vid_list))
pool = Pool(num_worker)
#zip将列表打包成元组,此处是将文件名和文件索引即视频所属类别打包成元组
pool.map(run_optical_flow, zip(vid_list, range(len(vid_list))))
在UCF-101数据集中,官方附赠了一个数据集列表文件,将ucf101数据集用三种划分方法分成训练集和测试集,得到六个数据列表文件testlist01.txt、testlist02.txt、testlist03.txt、trainlist01.txt、trainlist02.txt、trainlist03.txt,文件内容为类别/视频名,再加上一个ClassInd(描述类别对应的标签1开始)。
由于网络的输入是rgb帧和flow光流,因此需要将上述六个视频列表文件转化成rgb和flow列表文件。build_file_list.py就是用来生成rgb和flow列表文件的。对每种划分方案中的每个训练集和测试集列表文件,计算其中每个视频中含有的rgb数量和flow数量,并将其与视频所属类别一起写进文件中。也就是说上述6个文件是三种不同划分形式的测试集和训练集文件,再将其分别分为rgb和flow,对应生成了12个文件。
实现过程:
1、parse_ucf101_splits()
读取自带的6个文件(类别/视频名),根据ClassInd找出每个视频的类别,
先读取第一种划分方案下的训练集和测试集,元组形式返回训练集中的(视频名,标签)和测试集中的(视频名,标签),然后将其加到split中,并按照此顺序读取其他两种划分方案。返回的列表Split,第一维表示哪种划分方案,第二维表示数据集类别(测试集or训练集),第三维代表(视频名,标签)。
2、parse_directory(path, rgb_prefix=‘img_’, flow_x_prefix=‘flow_x_’, flow_y_prefix=‘flow_y_’)
根据输入的rgb和flow路径,计算每个视频下rgb和flow的数量,返回二维列表分别表示rgb和flow数量
3、build_split_list(split_tuple, frame_info, split_idx, shuffle=False)
split_tuple为第一步解析得到的列表,首先得到第idx中划分方案的数据集即split =split_tuple[idx],分别对其中训练集split[0]和测试集split[1]中的视频求其rgb和flow数量,并按照视频名 rgb/flow数量 标签的格式以字符串存储。
4、对第三步中得到的,按行分别写进对应文件,最终形成12个帧列表文件。
####
#前提是已经将视频分帧以及光流计算处理,存放在每个视频文件下
#建立描述性文件,描述每个视频文件中含有的帧的数量、光流的数量以及视频所属类别
####
import argparse
import os
import glob
import random
import fnmatch
#函数主要作用是对path路径下的文件进行分析rgb图和flow的数量,其中每个文件夹表示一个视频
def parse_directory(path, rgb_prefix='img_', flow_x_prefix='flow_x_', flow_y_prefix='flow_y_'):
#将path路径输出
print('parse frames under folder {}'.format(path))
#h获取path路径下所有文件路径于frame_folders中
frame_folders = glob.glob(os.path.join(path, '*'))
#函数作用:计算某个视频下rgb,flow_x,flow_y文件的个数
def count_files(directory, prefix_list):
#返回directory目录下的所有文件或文件夹的名字
lst = os.listdir(directory)
#找出所有文件中满足img_,flow_x,flow_y的文件的个数存在cnt_list中
cnt_list = [len(fnmatch.filter(lst, x+'*')) for x in prefix_list]
return cnt_list
rgb_counts = {}
flow_counts = {}
#i为文件在path下的索引,f为文件路径
for i,f in enumerate(frame_folders):
#查找在f文件夹中,分别有图片、光流x、光流y文件的个数
all_cnt = count_files(f, (rgb_prefix, flow_x_prefix, flow_y_prefix))
#截断f的最后一块,即f文件的名字
k = f.split('/')[-1]
rgb_counts[k] = all_cnt[0]
#x、y分别为光流的x、y方向的两个通道
x_cnt = all_cnt[1]
y_cnt = all_cnt[2]
if x_cnt != y_cnt:
raise ValueError('x and y direction have different number of flow images. video: '+f)
flow_counts[k] = x_cnt
if i % 200 == 0:
print('{} videos parsed'.format(i))
print('frame folder analysis done')
return rgb_counts, flow_counts
#split_tuple的长度为训练集文件位置描述的文件数量,即为3
#frame_info指每个视频中含有的rgb数量和flow数量
#返回每个视频文件中rgb图像数量和flow数量以及视频对应类别
def build_split_list(split_tuple, frame_info, split_idx, shuffle=False):
#找到第split_idx训练集文件和测试集文件
split = split_tuple[split_idx]
#set_list描述文件名和文件类别
def build_set_list(set_list):
rgb_list, flow_list = list(), list()
for item in set_list:
#item[0]为文件名,item[1]为文件类别
rgb_cnt = frame_info[0][item[0]]
flow_cnt = frame_info[1][item[0]]
rgb_list.append('{} {} {}\n'.format(item[0], rgb_cnt, item[1]))
flow_list.append('{} {} {}\n'.format(item[0], flow_cnt, item[1]))
if shuffle:
random.shuffle(rgb_list)
random.shuffle(flow_list)
return rgb_list, flow_list
#split[0]表示训练集中文件名+类别,split[1]表示测试集文件名+类别
#根据split构建训练集rgb和flow以及测试集rgb和flow
train_rgb_list, train_flow_list = build_set_list(split[0])
test_rgb_list, test_flow_list = build_set_list(split[1])
return (train_rgb_list, test_rgb_list), (train_flow_list, test_flow_list)
#解析UCF101数据集,将训练集和测试集每个文件的名字id和类别label返回
def parse_ucf101_splits():
#class_ind 序号+视频类别
class_ind = [x.strip().split() for x in open('ucf101_splits/classInd.txt')]
#将视频类别映射为数字即标签,用元胞数组表示(序号从0开始),
class_mapping = {x[1]:int(x[0])-1 for x in class_ind}
#将训练集、测试集文件解析,返回文件名和类别
def line2rec(line):
#去掉line前后空白格用/分开,动作描述+文件名称
items = line.strip().split('/')
#得到文件的标签
label = class_mapping[items[0]]
#得到文件的名字,split('.')是将文件后缀名去掉
vid = items[1].split('.')[0]
return vid, label
splits = []
for i in xrange(1, 4):
train_list = [line2rec(x) for x in open('ucf101_splits/trainlist{:02d}.txt'.format(i))]
test_list = [line2rec(x) for x in open('ucf101_splits/testlist{:02d}.txt'.format(i))]
splits.append((train_list, test_list))
return splits
if __name__ == '__main__':
parser = argparse.ArgumentParser()
#添加参数,数据集默认为ucf101
parser.add_argument('--dataset', type=str, default='ucf101', choices=['ucf101', 'hmdb51'])
#rgb和flow的路径,默认为./ucf101_frame
parser.add_argument('--frame_path', type=str, default='./ucf101_frames',
help="root directory holding the frames")
#外部列表路径
parser.add_argument('--out_list_path', type=str, default='./settings')
#rgb文件的前缀
parser.add_argument('--rgb_prefix', type=str, default='img_',
help="prefix of RGB frames")
#x方向flow文件前缀
parser.add_argument('--flow_x_prefix', type=str, default='flow_x',
help="prefix of x direction flow images")
#y方向flow文件前缀
parser.add_argument('--flow_y_prefix', type=str, default='flow_y',
help="prefix of y direction flow images", )
#数据集划分段个数
parser.add_argument('--num_split', type=int, default=3,
help="number of split building file list")
#数据是否打乱
parser.add_argument('--shuffle', action='store_true', default=False)
args = parser.parse_args()
dataset = args.dataset
frame_path = args.frame_path
rgb_p = args.rgb_prefix
flow_x_p = args.flow_x_prefix
flow_y_p = args.flow_y_prefix
num_split = args.num_split
out_path = args.out_list_path
shuffle = args.shuffle
#得到数据集路径
out_path = os.path.join(out_path,dataset)
#数据集路径不存在的话,创建一个这样的路径
if not os.path.isdir(out_path):
print("creating folder: "+out_path)
os.makedirs(out_path)
# operation
print('processing dataset {}'.format(dataset))
#解析数据集,得到数据集每个文件的名字和类别
if dataset=='ucf101':
split_tp = parse_ucf101_splits()
else:
split_tp = parse_hmdb51_splits()
#得到frame_path下每个视频中单帧图片和光流图片的数量
f_info = parse_directory(frame_path, rgb_p, flow_x_p, flow_y_p)
print('writing list files for training/testing')
#xrange(m)-----[0,1...m-1]
for i in xrange(max(num_split, len(split_tp))):
lists = build_split_list(split_tp, f_info, i, shuffle)
open(os.path.join(out_path, 'train_rgb_split{}.txt'.format(i + 1)), 'w').writelines(lists[0][0])
open(os.path.join(out_path, 'val_rgb_split{}.txt'.format(i + 1)), 'w').writelines(lists[0][1])
open(os.path.join(out_path, 'train_flow_split{}.txt'.format(i + 1)), 'w').writelines(lists[1][0])
open(os.path.join(out_path, 'val_flow_split{}.txt'.format(i + 1)), 'w').writelines(lists[1][1])
实现:
1、首先将数据集解析为一个视频类别列表和一个(视频,标签)的元组。接着根据输入指定的帧列表文件得到其-----> List[视频路径,帧数量,视频类别]。
2、getitem()函数:segment为一个视频中rgb或者flow取segment个。首先或者该视频的(视频路径,数量duration,视频类别),接着根据segment,每隔duration/segment取一个rgb图或者flow,如果是训练集则随机取,如果是测试集则每次取中间。对取到的图片进行预处理(增强特征),返回取到的segment个流,以及对应的标签。
附:训练集随机取,这样保证每次的图片不一样,从而可以使训练的模型更加准确,而测试集每次取的需要保证一样,因为它是用来测试的,相同的图片测试不同的模型才会有比较性。
这个文件不能单独运行,是用来在主函数中,训练模型时进行数据导入的。
import torch.utils.data as data
import os
import sys
import random
import numpy as np
import cv2
#给定数据集路径,返回视频类别和一个视频类别与标签的元组
def find_classes(dir):
classes = [d for d in os.listdir(dir) if os.path.isdir(os.path.join(dir, d))]
classes.sort()
class_to_idx = {classes[i]: i for i in range(len(classes))}
return classes, class_to_idx
def make_dataset(root, source):
if not os.path.exists(source):
print("Setting file %s for ucf101 dataset doesn't exist." % (source))
sys.exit()
else:
clips = []
with open(source) as split_f:
data = split_f.readlines()
for line in data:
line_info = line.split()
#视频路径
clip_path = os.path.join(root, line_info[0])
#视频下帧数量
duration = int(line_info[1])
#视频类别
target = int(line_info[2])
item = (clip_path, duration, target)
clips.append(item)
return clips
#path 视频路径
def ReadSegmentRGB(path, offsets, new_height, new_width, new_length, is_color, name_pattern):
if is_color:
cv_read_flag = cv2.IMREAD_COLOR # > 0
else:
cv_read_flag = cv2.IMREAD_GRAYSCALE # = 0
interpolation = cv2.INTER_LINEAR
sampled_list = []
for offset_id in range(len(offsets)):
offset = offsets[offset_id]
for length_id in range(1, new_length+1):
frame_name = name_pattern % (length_id + offset)
frame_path = path + "/" + frame_name
cv_img_origin = cv2.imread(frame_path, cv_read_flag)
if cv_img_origin is None:
print("Could not load file %s" % (frame_path))
sys.exit()
# TODO: error handling here
if new_width > 0 and new_height > 0:
# use OpenCV3, use OpenCV2.4.13 may have error
cv_img = cv2.resize(cv_img_origin, (new_width, new_height), interpolation)
else:
cv_img = cv_img_origin
cv_img = cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB)
sampled_list.append(cv_img)
clip_input = np.concatenate(sampled_list, axis=2)
return clip_input
def ReadSegmentFlow(path, offsets, new_height, new_width, new_length, is_color, name_pattern):
if is_color:
cv_read_flag = cv2.IMREAD_COLOR # > 0
else:
cv_read_flag = cv2.IMREAD_GRAYSCALE # = 0
interpolation = cv2.INTER_LINEAR
sampled_list = []
for offset_id in range(len(offsets)):
offset = offsets[offset_id]
for length_id in range(1, new_length+1):
frame_name_x = name_pattern % ("x", length_id + offset)
frame_path_x = path + "/" + frame_name_x
cv_img_origin_x = cv2.imread(frame_path_x, cv_read_flag)
frame_name_y = name_pattern % ("y", length_id + offset)
frame_path_y = path + "/" + frame_name_y
cv_img_origin_y = cv2.imread(frame_path_y, cv_read_flag)
if cv_img_origin_x is None or cv_img_origin_y is None:
print("Could not load file %s or %s" % (frame_path_x, frame_path_y))
sys.exit()
# TODO: error handling here
if new_width > 0 and new_height > 0:
cv_img_x = cv2.resize(cv_img_origin_x, (new_width, new_height), interpolation)
cv_img_y = cv2.resize(cv_img_origin_y, (new_width, new_height), interpolation)
else:
cv_img_x = cv_img_origin_x
cv_img_y = cv_img_origin_y
sampled_list.append(np.expand_dims(cv_img_x, 2))
sampled_list.append(np.expand_dims(cv_img_y, 2))
clip_input = np.concatenate(sampled_list, axis=2)
return clip_input
class ucf101(data.Dataset):
def __init__(self,
root, #root 数据集路径
source, #source 数据集设置文件
phase, #phase 设置文件中的关键词(train val)
modality,#modality 数据形式(rgb、flow)
name_pattern=None, #name_pattern 文件格式
is_color=True,
num_segments=1, #num_segments 文件个数
new_length=1, #new_length 帧的个数
new_width=0,
new_height=0,
transform=None,
target_transform=None,
video_transform=None):
#class 视频名称 class_to_index 字典 名称:编号
classes, class_to_idx = find_classes(root)
#得到设置文件中每个视频 -----> List[视频路径,帧数量,视频类别]
clips = make_dataset(root, source)
if len(clips) == 0:
raise(RuntimeError("Found 0 video clips in subfolders of: " + root + "\n"
"Check your data directory."))
self.root = root
self.source = source
self.phase = phase
self.modality = modality
self.classes = classes
self.class_to_idx = class_to_idx
self.clips = clips
if name_pattern:
self.name_pattern = name_pattern
else:
if self.modality == "rgb":
self.name_pattern = "img_%05d.jpg"
elif self.modality == "flow":
self.name_pattern = "flow_%s_%05d.jpg"
self.is_color = is_color
self.num_segments = num_segments
self.new_length = new_length
self.new_width = new_width
self.new_height = new_height
self.transform = transform
self.target_transform = target_transform
self.video_transform = video_transform
#返回视频中的一帧
def __getitem__(self, index):
#某个视频文件中所有数据:路径、个数、类别
path, duration, target = self.clips[index]
average_duration = int(duration / self.num_segments)
offsets = []
#num_segment 将帧分成num_segment块
#Train中从每块中随机取一个,Val中取中间帧 offset记录帧的编号
for seg_id in range(self.num_segments):
if self.phase == "train":
if average_duration >= self.new_length:
offset = random.randint(0, average_duration - self.new_length)
# No +1 because randint(a,b) return a random integer N such that a <= N <= b.
offsets.append(offset + seg_id * average_duration)
else:
offsets.append(0)
elif self.phase == "val":
if average_duration >= self.new_length:
offsets.append(int((average_duration - self.new_length + 1)/2 + seg_id * average_duration))
else:
offsets.append(0)
else:
print("Only phase train and val are supported.")
if self.modality == "rgb":
clip_input = ReadSegmentRGB(path,
offsets,
self.new_height,
self.new_width,
self.new_length,
self.is_color,
self.name_pattern
)
elif self.modality == "flow":
clip_input = ReadSegmentFlow(path,
offsets,
self.new_height,
self.new_width,
self.new_length,
self.is_color,
self.name_pattern
)
else:
print("No such modality %s" % (self.modality))
#clip_input 对视频下的帧采样、resize、灰度化
#再对图片预处理
if self.transform is not None:
clip_input = self.transform(clip_input)
#target是标签处理,因为此处target是一个数值,可能需要将其转化为向量
if self.target_transform is not None:
target = self.target_transform(target)
if self.video_transform is not None:
clip_input = self.video_transform(clip_input)
#返回网络输入,类别
return clip_input, target
def __len__(self):
return len(self.clips)