Skip to content

Commit d4c3765

Browse files
committed
Partial DIS implementation and OF benchmark
Basic interfaces and a partial implementation of the Dense Inverse Search (DIS) optical flow algorithm without variational refinement. Also added a python benchmarking script that can evaluate different optical flow algorithms on the MPI Sintel and Middlebury datasets and build overall comparative charts.
1 parent 76678bc commit d4c3765

File tree

5 files changed

+791
-2
lines changed

5 files changed

+791
-2
lines changed

modules/optflow/doc/optflow.bib

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,10 @@ @inproceedings{Weinzaepfel2013
3737
year={2013},
3838
organization={IEEE}
3939
}
40+
41+
@article{Kroeger2016,
42+
title={Fast Optical Flow using Dense Inverse Search},
43+
author={Kroeger, Till and Timofte, Radu and Dai, Dengxin and Van Gool, Luc},
44+
journal={arXiv preprint arXiv:1603.03590},
45+
year={2016}
46+
}

modules/optflow/include/opencv2/optflow.hpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,44 @@ CV_EXPORTS_W Ptr<DenseOpticalFlow> createOptFlow_Farneback();
191191
//! Additional interface to the SparseToDenseFlow algorithm - calcOpticalFlowSparseToDense()
192192
CV_EXPORTS_W Ptr<DenseOpticalFlow> createOptFlow_SparseToDense();
193193

194+
/** @brief DIS optical flow algorithm.
195+
196+
This class implements the Dense Inverse Search (DIS) optical flow algorithm. More
197+
details about the algorithm can be found at @cite Kroeger2016 .
198+
*/
199+
class CV_EXPORTS_W DISOpticalFlow : public DenseOpticalFlow
200+
{
201+
public:
202+
/** @brief Finest level of the gaussian pyramid on which the flow is computed (zero level
203+
corresponds to the original image resolution).The final flow is obtained by bilinear upscaling.
204+
@see setFinestScale */
205+
virtual int getFinestScale() const = 0;
206+
/** @copybrief getFinestScale @see getFinestScale */
207+
virtual void setFinestScale(int val) = 0;
208+
209+
/** @brief Size of an image patch for matching (in pixels)
210+
@see setPatchSize */
211+
virtual int getPatchSize() const = 0;
212+
/** @copybrief getPatchSize @see getPatchSize */
213+
virtual void setPatchSize(int val) = 0;
214+
215+
/** @brief Stride between neighbor patches. Must be less than patch size.
216+
@see setPatchStride */
217+
virtual int getPatchStride() const = 0;
218+
/** @copybrief getPatchStride @see getPatchStride */
219+
virtual void setPatchStride(int val) = 0;
220+
221+
/** @brief number of gradient descent iterations in the patch inverse search stage
222+
@see setGradientDescentIterations */
223+
virtual int getGradientDescentIterations() const = 0;
224+
/** @copybrief getGradientDescentIterations @see getGradientDescentIterations */
225+
virtual void setGradientDescentIterations(int val) = 0;
226+
};
227+
228+
/** @brief Creates an instance of DISOpticalFlow
229+
*/
230+
CV_EXPORTS_W Ptr<DISOpticalFlow> createOptFlow_DIS();
231+
194232
//! @}
195233

196234
} //optflow
Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
#!/usr/bin/env python
2+
from __future__ import print_function
3+
import os, sys, shutil
4+
import argparse
5+
import json, re
6+
from subprocess import check_output
7+
import datetime
8+
import matplotlib.pyplot as plt
9+
10+
11+
def load_json(path):
12+
f = open(path, "r")
13+
data = json.load(f)
14+
return data
15+
16+
17+
def save_json(obj, path):
18+
tmp_file = path + ".bak"
19+
f = open(tmp_file, "w")
20+
json.dump(obj, f, indent=2)
21+
f.flush()
22+
os.fsync(f.fileno())
23+
f.close()
24+
try:
25+
os.rename(tmp_file, path)
26+
except:
27+
os.remove(path)
28+
os.rename(tmp_file, path)
29+
30+
31+
def parse_evaluation_result(input_str, i):
32+
res = {}
33+
res['frame_number'] = i + 1
34+
res['error'] = {}
35+
regex = "([A-Za-z. \\[\\].0-9]+):[ ]*([0-9]*\.[0-9]+|[0-9]+)"
36+
for elem in re.findall(regex,input_str):
37+
if "Time" in elem[0]:
38+
res['time'] = float(elem[1])
39+
elif "Average" in elem[0]:
40+
res['error']['average'] = float(elem[1])
41+
elif "deviation" in elem[0]:
42+
res['error']['std'] = float(elem[1])
43+
else:
44+
res['error'][elem[0]] = float(elem[1])
45+
return res
46+
47+
48+
def evaluate_sequence(sequence, algorithm, dataset, executable, img_files, gt_files,
49+
state, state_path):
50+
if "eval_results" not in state[dataset][algorithm][-1].keys():
51+
state[dataset][algorithm][-1]["eval_results"] = {}
52+
elif sequence in state[dataset][algorithm][-1]["eval_results"].keys():
53+
return
54+
55+
res = []
56+
for i in range(len(img_files) - 1):
57+
sys.stdout.write("Algorithm: %-20s Sequence: %-10s Done: [%3d/%3d]\r" %
58+
(algorithm, sequence, i, len(img_files) - 1)),
59+
sys.stdout.flush()
60+
61+
res_string = check_output([executable, img_files[i], img_files[i + 1],
62+
algorithm, gt_files[i]])
63+
res.append(parse_evaluation_result(res_string, i))
64+
state[dataset][algorithm][-1]["eval_results"][sequence] = res
65+
save_json(state, state_path)
66+
67+
#############################DATSET DEFINITIONS################################
68+
69+
def evaluate_mpi_sintel(source_dir, algorithm, evaluation_executable, state, state_path):
70+
evaluation_result = {}
71+
img_dir = os.path.join(source_dir, 'mpi_sintel', 'training', 'final')
72+
gt_dir = os.path.join(source_dir, 'mpi_sintel', 'training', 'flow')
73+
sequences = [f for f in os.listdir(img_dir)
74+
if os.path.isdir(os.path.join(img_dir, f))]
75+
for seq in sequences:
76+
img_files = sorted([os.path.join(img_dir, seq, f)
77+
for f in os.listdir(os.path.join(img_dir, seq))
78+
if f.endswith(".png")])
79+
gt_files = sorted([os.path.join(gt_dir, seq, f)
80+
for f in os.listdir(os.path.join(gt_dir, seq))
81+
if f.endswith(".flo")])
82+
evaluation_result[seq] = evaluate_sequence(seq, algorithm, 'mpi_sintel',
83+
evaluation_executable, img_files, gt_files, state, state_path)
84+
return evaluation_result
85+
86+
87+
def evaluate_middlebury(source_dir, algorithm, evaluation_executable, state, state_path):
88+
evaluation_result = {}
89+
img_dir = os.path.join(source_dir, 'middlebury', 'other-data')
90+
gt_dir = os.path.join(source_dir, 'middlebury', 'other-gt-flow')
91+
sequences = [f for f in os.listdir(gt_dir)
92+
if os.path.isdir(os.path.join(gt_dir, f))]
93+
for seq in sequences:
94+
img_files = sorted([os.path.join(img_dir, seq, f)
95+
for f in os.listdir(os.path.join(img_dir, seq))
96+
if f.endswith(".png")])
97+
gt_files = sorted([os.path.join(gt_dir, seq, f)
98+
for f in os.listdir(os.path.join(gt_dir, seq))
99+
if f.endswith(".flo")])
100+
evaluation_result[seq] = evaluate_sequence(seq, algorithm, 'middlebury',
101+
evaluation_executable, img_files, gt_files, state, state_path)
102+
return evaluation_result
103+
104+
105+
dataset_eval_functions = {
106+
"mpi_sintel": evaluate_mpi_sintel,
107+
"middlebury": evaluate_middlebury
108+
}
109+
110+
###############################################################################
111+
112+
def create_dir(dir):
113+
if not os.path.exists(dir):
114+
os.makedirs(dir)
115+
116+
117+
def parse_sequence(input_str):
118+
if len(input_str) == 0:
119+
return []
120+
else:
121+
return [o.strip() for o in input_str.split(",") if o]
122+
123+
124+
def build_chart(dst_folder, state, dataset):
125+
fig = plt.figure(figsize=(16, 10))
126+
markers = ["o", "s", "h", "^", "D"]
127+
marker_idx = 0
128+
colors = ["b", "g", "r"]
129+
color_idx = 0
130+
for algo in state[dataset].keys():
131+
for eval_instance in state[dataset][algo]:
132+
name = algo + "--" + eval_instance["timestamp"]
133+
average_time = 0.0
134+
average_error = 0.0
135+
num_elem = 0
136+
for seq in eval_instance["eval_results"].keys():
137+
for frame in eval_instance["eval_results"][seq]:
138+
average_time += frame["time"]
139+
average_error += frame["error"]["average"]
140+
num_elem += 1
141+
average_time /= num_elem
142+
average_error /= num_elem
143+
144+
marker_style = colors[color_idx] + markers[marker_idx]
145+
color_idx += 1
146+
if color_idx >= len(colors):
147+
color_idx = 0
148+
marker_idx += 1
149+
if marker_idx >= len(markers):
150+
marker_idx = 0
151+
plt.gca().plot([average_time], [average_error],
152+
marker_style,
153+
markersize=14,
154+
label=name)
155+
156+
plt.gca().set_ylabel('Average Endpoint Error (EPE)', fontsize=20)
157+
plt.gca().set_xlabel('Average Runtime (seconds per frame)', fontsize=20)
158+
plt.gca().set_xscale("log")
159+
plt.gca().set_title('Evaluation on ' + dataset, fontsize=20)
160+
161+
plt.gca().legend()
162+
fig.savefig(os.path.join(dst_folder, "evaluation_results_" + dataset + ".png"),
163+
bbox_inches='tight')
164+
plt.close()
165+
166+
167+
if __name__ == '__main__':
168+
parser = argparse.ArgumentParser(
169+
description='Optical flow benchmarking script',
170+
formatter_class=argparse.RawDescriptionHelpFormatter)
171+
parser.add_argument(
172+
"bin_path",
173+
default="./optflow-example-optical_flow_evaluation",
174+
help="Path to the optical flow evaluation executable")
175+
parser.add_argument(
176+
"-a",
177+
"--algorithms",
178+
metavar="ALGORITHMS",
179+
default="",
180+
help=("Comma-separated list of optical-flow algorithms to evaluate "
181+
"(example: -a farneback,tvl1,deepflow). Note that previously "
182+
"evaluated algorithms are also included in the output charts"))
183+
parser.add_argument(
184+
"-d",
185+
"--datasets",
186+
metavar="DATASETS",
187+
default="mpi_sintel",
188+
help=("Comma-separated list of datasets for evaluation (currently only "
189+
"'mpi_sintel' and 'middlebury' are supported)"))
190+
parser.add_argument(
191+
"-f",
192+
"--dataset_folder",
193+
metavar="DATASET_FOLDER",
194+
default="./OF_datasets",
195+
help=("Path to a folder containing datasets. To enable evaluation on "
196+
"MPI Sintel dataset, please download it using the following links: "
197+
"http://files.is.tue.mpg.de/sintel/MPI-Sintel-training_images.zip and "
198+
"http://files.is.tue.mpg.de/sintel/MPI-Sintel-training_extras.zip and "
199+
"unzip these archives into the 'mpi_sintel' folder. To enable evaluation "
200+
"on the Middlebury dataset use the following links: "
201+
"http://vision.middlebury.edu/flow/data/comp/zip/other-color-twoframes.zip, "
202+
"http://vision.middlebury.edu/flow/data/comp/zip/other-gt-flow.zip. "
203+
"These should be unzipped into 'middlebury' folder"))
204+
parser.add_argument(
205+
"-o",
206+
"--out",
207+
metavar="OUT_DIR",
208+
default="./OF_evaluation_results",
209+
help="Output directory where to store benchmark results")
210+
parser.add_argument(
211+
"-s",
212+
"--state",
213+
metavar="STATE_JSON",
214+
default="./OF_evaluation_state.json",
215+
help=("Path to a json file that stores the current evaluation state and "
216+
"previous evaluation results"))
217+
args, other_args = parser.parse_known_args()
218+
219+
if not os.path.isfile(args.bin_path):
220+
print("Error: " + args.bin_path + " does not exist")
221+
sys.exit(1)
222+
223+
if not os.path.exists(args.dataset_folder):
224+
print("Error: " + args.dataset_folder + (" does not exist. Please, correctly "
225+
"specify the -f parameter"))
226+
sys.exit(1)
227+
228+
state = {}
229+
if os.path.isfile(args.state):
230+
state = load_json(args.state)
231+
232+
algorithm_list = parse_sequence(args.algorithms)
233+
dataset_list = parse_sequence(args.datasets)
234+
for dataset in dataset_list:
235+
if dataset not in dataset_eval_functions.keys():
236+
print("Error: unsupported dataset " + dataset)
237+
sys.exit(1)
238+
if dataset not in os.listdir(args.dataset_folder):
239+
print("Error: " + os.path.join(args.dataset_folder, dataset) + (" does not exist. "
240+
"Please, download the dataset and follow the naming conventions "
241+
"(use -h for more information)"))
242+
sys.exit(1)
243+
244+
for dataset in dataset_list:
245+
if dataset not in state.keys():
246+
state[dataset] = {}
247+
for algorithm in algorithm_list:
248+
if algorithm in state[dataset].keys():
249+
last_eval_instance = state[dataset][algorithm][-1]
250+
if "finished" not in last_eval_instance.keys():
251+
print(("Continuing an unfinished evaluation of " +
252+
algorithm + " started at " + last_eval_instance["timestamp"]))
253+
else:
254+
state[dataset][algorithm].append({"timestamp":
255+
datetime.datetime.now().strftime("%Y-%m-%d--%H-%M")})
256+
else:
257+
state[dataset][algorithm] = [{"timestamp":
258+
datetime.datetime.now().strftime("%Y-%m-%d--%H-%M")}]
259+
save_json(state, args.state)
260+
dataset_eval_functions[dataset](args.dataset_folder, algorithm, args.bin_path,
261+
state, args.state)
262+
state[dataset][algorithm][-1]["finished"] = True
263+
save_json(state, args.state)
264+
save_json(state, args.state)
265+
266+
create_dir(args.out)
267+
for dataset in dataset_list:
268+
build_chart(args.out, state, dataset)

modules/optflow/samples/optical_flow_evaluation.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ using namespace optflow;
1111
const String keys = "{help h usage ? | | print this message }"
1212
"{@image1 | | image1 }"
1313
"{@image2 | | image2 }"
14-
"{@algorithm | | [farneback, simpleflow, tvl1, deepflow or sparsetodenseflow] }"
14+
"{@algorithm | | [farneback, simpleflow, tvl1, deepflow, sparsetodenseflow or DISflow] }"
1515
"{@groundtruth | | path to the .flo file (optional), Middlebury format }"
1616
"{m measure |endpoint| error measure - [endpoint or angular] }"
1717
"{r region |all | region to compute stats about [all, discontinuities, untextured] }"
@@ -229,7 +229,7 @@ int main( int argc, char** argv )
229229
if ( i2.depth() != CV_8U )
230230
i2.convertTo(i2, CV_8U);
231231

232-
if ( (method == "farneback" || method == "tvl1" || method == "deepflow") && i1.channels() == 3 )
232+
if ( (method == "farneback" || method == "tvl1" || method == "deepflow" || method == "DISflow") && i1.channels() == 3 )
233233
{ // 1-channel images are expected
234234
cvtColor(i1, i1, COLOR_BGR2GRAY);
235235
cvtColor(i2, i2, COLOR_BGR2GRAY);
@@ -252,6 +252,8 @@ int main( int argc, char** argv )
252252
algorithm = createOptFlow_DeepFlow();
253253
else if ( method == "sparsetodenseflow" )
254254
algorithm = createOptFlow_SparseToDense();
255+
else if ( method == "DISflow" )
256+
algorithm = createOptFlow_DIS();
255257
else
256258
{
257259
printf("Wrong method!\n");

0 commit comments

Comments
 (0)