vendor: OpenCV 5.0.0 snapshot at 755e50675d97db9b7d449d8bd6b09888646f6c6e

This commit is contained in:
Gitea Mirror Bot
2026-08-22 00:11:13 +08:00
commit 12022378a3
3872 changed files with 2513409 additions and 0 deletions
+174
View File
@@ -0,0 +1,174 @@
#include "opencv2/core/ocl.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/optflow.hpp"
#include <fstream>
#include <iostream>
#include <stdio.h>
/* This tool finds correspondences between two images using Global Patch Collider
* and calculates error using provided ground truth flow.
*
* It will look for the file named "forest.yml.gz" with a learned forest.
* You can obtain the "forest.yml.gz" either by manually training it using another tool with *_train suffix
* or by downloading one of the files trained on some publicly available dataset from here:
*
* https://drive.google.com/open?id=0B7Hb8cfuzrIIZDFscXVYd0NBNFU
*/
using namespace cv;
const String keys = "{help h ? | | print this message}"
"{@image1 |<none> | image1}"
"{@image2 |<none> | image2}"
"{@groundtruth |<none> | path to the .flo file}"
"{@output | | output to a file instead of displaying, output image path}"
"{g gpu | | use OpenCL}"
"{f forest |forest.yml.gz| path to the forest.yml.gz}";
const int nTrees = 5;
static double normL2( const Point2f &v ) { return sqrt( v.x * v.x + v.y * v.y ); }
static Vec3d getFlowColor( const Point2f &f, const bool logScale = true, const double scaleDown = 5 )
{
if ( f.x == 0 && f.y == 0 )
return Vec3d( 0, 0, 1 );
double radius = normL2( f );
if ( logScale )
radius = log( radius + 1 );
radius /= scaleDown;
radius = std::min( 1.0, radius );
double angle = ( atan2( -f.y, -f.x ) + CV_PI ) * 180 / CV_PI;
return Vec3d( angle, radius, 1 );
}
static void displayFlow( InputArray _flow, OutputArray _img )
{
const Size sz = _flow.size();
Mat flow = _flow.getMat();
_img.create( sz, CV_32FC3 );
Mat img = _img.getMat();
for ( int i = 0; i < sz.height; ++i )
for ( int j = 0; j < sz.width; ++j )
img.at< Vec3f >( i, j ) = getFlowColor( flow.at< Point2f >( i, j ) );
cvtColor( img, img, COLOR_HSV2BGR );
}
static bool fileProbe( const char *name ) { return std::ifstream( name ).good(); }
int main( int argc, const char **argv )
{
CommandLineParser parser( argc, argv, keys );
parser.about( "Global Patch Collider evaluation tool" );
if ( parser.has( "help" ) )
{
parser.printMessage();
return 0;
}
String fromPath = parser.get< String >( 0 );
String toPath = parser.get< String >( 1 );
String gtPath = parser.get< String >( 2 );
String outPath = parser.get< String >( 3 );
const bool useOpenCL = parser.has( "gpu" );
String forestDumpPath = parser.get< String >( "forest" );
if ( !parser.check() )
{
parser.printErrors();
return 1;
}
if ( !fileProbe( forestDumpPath.c_str() ) )
{
std::cerr << "Can't open the file with a trained model: `" << forestDumpPath
<< "`.\nYou can obtain this file either by manually training the model using another tool with *_train suffix or by "
"downloading one of the files trained on some publicly available dataset from "
"here:\nhttps://drive.google.com/open?id=0B7Hb8cfuzrIIZDFscXVYd0NBNFU"
<< std::endl;
return 1;
}
ocl::setUseOpenCL( useOpenCL );
Ptr< optflow::GPCForest< nTrees > > forest = Algorithm::load< optflow::GPCForest< nTrees > >( forestDumpPath );
Mat from = imread( fromPath );
Mat to = imread( toPath );
Mat gt = readOpticalFlow( gtPath );
std::vector< std::pair< Point2i, Point2i > > corr;
TickMeter meter;
meter.start();
forest->findCorrespondences( from, to, corr, optflow::GPCMatchingParams( useOpenCL ) );
meter.stop();
std::cout << "Found " << corr.size() << " matches." << std::endl;
std::cout << "Time: " << meter.getTimeSec() << " sec." << std::endl;
double error = 0;
int totalCorrectFlowVectors = 0;
Mat dispErr = Mat::zeros( from.size(), CV_32FC3 );
dispErr = Scalar( 0, 0, 1 );
Mat disp = Mat::zeros( from.size(), CV_32FC3 );
disp = Scalar( 0, 0, 1 );
for ( size_t i = 0; i < corr.size(); ++i )
{
const Point2f a = corr[i].first;
const Point2f b = corr[i].second;
const Point2f gtDisplacement = gt.at< Point2f >( corr[i].first.y, corr[i].first.x );
// Check that flow vector is correct
if (!cvIsNaN(gtDisplacement.x) && !cvIsNaN(gtDisplacement.y) && gtDisplacement.x < 1e9 && gtDisplacement.y < 1e9)
{
const Point2f c = a + gtDisplacement;
error += normL2( b - c );
circle( dispErr, a, 3, getFlowColor( b - c, false, 32 ), -1 );
++totalCorrectFlowVectors;
}
circle( disp, a, 3, getFlowColor( b - a ), -1 );
}
if (totalCorrectFlowVectors)
error /= totalCorrectFlowVectors;
std::cout << "Average endpoint error: " << error << " px." << std::endl;
cvtColor( disp, disp, COLOR_HSV2BGR );
cvtColor( dispErr, dispErr, COLOR_HSV2BGR );
Mat dispGroundTruth;
displayFlow( gt, dispGroundTruth );
if ( outPath.length() )
{
putText( disp, "Sparse matching: Global Patch Collider", Point2i( 24, 40 ), FONT_HERSHEY_DUPLEX, 1, Vec3b( 1, 0, 0 ), 2, LINE_AA );
char buf[256];
sprintf( buf, "Average EPE: %.2f", error );
putText( disp, buf, Point2i( 24, 80 ), FONT_HERSHEY_DUPLEX, 1, Vec3b( 1, 0, 0 ), 2, LINE_AA );
sprintf( buf, "Number of matches: %u", (unsigned)corr.size() );
putText( disp, buf, Point2i( 24, 120 ), FONT_HERSHEY_DUPLEX, 1, Vec3b( 1, 0, 0 ), 2, LINE_AA );
disp *= 255;
imwrite( outPath, disp );
return 0;
}
namedWindow( "Correspondences", WINDOW_AUTOSIZE );
imshow( "Correspondences", disp );
namedWindow( "Error", WINDOW_AUTOSIZE );
imshow( "Error", dispErr );
namedWindow( "Ground truth", WINDOW_AUTOSIZE );
imshow( "Ground truth", dispGroundTruth );
waitKey( 0 );
return 0;
}
+66
View File
@@ -0,0 +1,66 @@
#include "opencv2/optflow.hpp"
#include <iostream>
/* This tool trains the forest for the Global Patch Collider and stores output to the "forest.yml.gz".
*/
using namespace cv;
const String keys = "{help h ? | | print this message}"
"{max-tree-depth | | Maximum tree depth to stop partitioning}"
"{min-samples | | Minimum number of samples in the node to stop partitioning}"
"{descriptor-type|0 | Descriptor type. Set to 0 for quality, 1 for speed.}"
"{print-progress | | Set to 0 to enable quiet mode, set to 1 to print progress}"
"{f forest |forest.yml.gz| Path where to store resulting forest. It is recommended to use .yml.gz extension.}";
const int nTrees = 5;
static void fillInputImagesFromCommandLine( std::vector< String > &img1, std::vector< String > &img2, std::vector< String > &gt, int argc,
const char **argv )
{
for ( int i = 1, j = 0; i < argc; ++i )
{
if ( argv[i][0] == '-' )
continue;
if ( j % 3 == 0 )
img1.push_back( argv[i] );
if ( j % 3 == 1 )
img2.push_back( argv[i] );
if ( j % 3 == 2 )
gt.push_back( argv[i] );
++j;
}
}
int main( int argc, const char **argv )
{
CommandLineParser parser( argc, argv, keys );
parser.about( "Global Patch Collider training tool" );
std::vector< String > img1, img2, gt;
optflow::GPCTrainingParams params;
if ( parser.has( "max-tree-depth" ) )
params.maxTreeDepth = parser.get< unsigned >( "max-tree-depth" );
if ( parser.has( "min-samples" ) )
params.minNumberOfSamples = parser.get< unsigned >( "min-samples" );
if ( parser.has( "descriptor-type" ) )
params.descriptorType = parser.get< int >( "descriptor-type" );
if ( parser.has( "print-progress" ) )
params.printProgress = parser.get< unsigned >( "print-progress" ) != 0;
fillInputImagesFromCommandLine( img1, img2, gt, argc, argv );
if ( parser.has( "help" ) || img1.size() != img2.size() || img1.size() != gt.size() || img1.size() == 0 )
{
std::cerr << "\nUsage: " << argv[0] << " [params] ImageFrom1 ImageTo1 GroundTruth1 ... ImageFromN ImageToN GroundTruthN\n" << std::endl;
parser.printMessage();
return 1;
}
Ptr< optflow::GPCForest< nTrees > > forest = optflow::GPCForest< nTrees >::create();
forest->train( img1, img2, gt, params );
forest->save( parser.get< String >( "forest" ) );
return 0;
}
@@ -0,0 +1,58 @@
import argparse
import glob
import os
import subprocess
def execute(cmd):
popen = subprocess.Popen(cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
for stdout_line in iter(popen.stdout.readline, ''):
print(stdout_line.rstrip())
for stderr_line in iter(popen.stderr.readline, ''):
print(stderr_line.rstrip())
popen.stdout.close()
popen.stderr.close()
return_code = popen.wait()
if return_code != 0:
raise subprocess.CalledProcessError(return_code, cmd)
def main():
parser = argparse.ArgumentParser(
description='Train Global Patch Collider using Middlebury dataset')
parser.add_argument(
'--bin_path',
help='Path to the training executable (example_optflow_gpc_train)',
required=True)
parser.add_argument('--dataset_path',
help='Path to the directory with frames',
required=True)
parser.add_argument('--gt_path',
help='Path to the directory with ground truth flow',
required=True)
parser.add_argument('--descriptor_type',
help='Descriptor type',
type=int,
default=0)
args = parser.parse_args()
seq = glob.glob(os.path.join(args.dataset_path, '*'))
seq.sort()
input_files = []
for s in seq:
if os.path.isdir(s):
seq_name = os.path.basename(s)
frames = glob.glob(os.path.join(s, 'frame*.png'))
frames.sort()
assert (len(frames) == 2)
assert (os.path.basename(frames[0]) == 'frame10.png')
assert (os.path.basename(frames[1]) == 'frame11.png')
gt_flow = os.path.join(args.gt_path, seq_name, 'flow10.flo')
if os.path.isfile(gt_flow):
input_files += [frames[0], frames[1], gt_flow]
execute([args.bin_path, '--descriptor-type=%d' % args.descriptor_type] + input_files)
if __name__ == '__main__':
main()
@@ -0,0 +1,60 @@
import argparse
import glob
import os
import subprocess
FRAME_DIST = 2
assert (FRAME_DIST >= 1)
def execute(cmd):
popen = subprocess.Popen(cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
for stdout_line in iter(popen.stdout.readline, ''):
print(stdout_line.rstrip())
for stderr_line in iter(popen.stderr.readline, ''):
print(stderr_line.rstrip())
popen.stdout.close()
popen.stderr.close()
return_code = popen.wait()
if return_code != 0:
raise subprocess.CalledProcessError(return_code, cmd)
def main():
parser = argparse.ArgumentParser(
description='Train Global Patch Collider using MPI Sintel dataset')
parser.add_argument(
'--bin_path',
help='Path to the training executable (example_optflow_gpc_train)',
required=True)
parser.add_argument('--dataset_path',
help='Path to the directory with frames',
required=True)
parser.add_argument('--gt_path',
help='Path to the directory with ground truth flow',
required=True)
parser.add_argument('--descriptor_type',
help='Descriptor type',
type=int,
default=0)
args = parser.parse_args()
seq = glob.glob(os.path.join(args.dataset_path, '*'))
seq.sort()
input_files = []
for s in seq:
seq_name = os.path.basename(s)
frames = glob.glob(os.path.join(s, 'frame*.png'))
frames.sort()
for i in range(0, len(frames) - 1, FRAME_DIST):
gt_flow = os.path.join(args.gt_path, seq_name,
os.path.basename(frames[i])[0:-4] + '.flo')
assert (os.path.isfile(gt_flow))
input_files += [frames[i], frames[i + 1], gt_flow]
execute([args.bin_path, '--descriptor-type=%d' % args.descriptor_type] + input_files)
if __name__ == '__main__':
main()
+168
View File
@@ -0,0 +1,168 @@
#include "opencv2/optflow.hpp"
#include "opencv2/imgproc.hpp"
#include "opencv2/videoio.hpp"
#include "opencv2/highgui.hpp"
#include <time.h>
#include <stdio.h>
#include <ctype.h>
using namespace cv;
using namespace std;
using namespace cv::motempl;
static void help(void)
{
printf(
"\nThis program demonstrated the use of motion templates -- basically using the gradients\n"
"of thresholded layers of decaying frame differencing. New movements are stamped on top with floating system\n"
"time code and motions too old are thresholded away. This is the 'motion history file'. The program reads from the camera of your choice or from\n"
"a file. Gradients of motion history are used to detect direction of motion etc\n"
"Usage :\n"
"./motempl [camera number 0-n or file name, default is camera 0]\n"
);
}
// various tracking parameters (in seconds)
const double MHI_DURATION = 5;
const double MAX_TIME_DELTA = 0.5;
const double MIN_TIME_DELTA = 0.05;
// number of cyclic frame buffer used for motion detection
// (should, probably, depend on FPS)
// ring image buffer
vector<Mat> buf;
int last = 0;
// temporary images
Mat mhi, orient, mask, segmask, zplane;
vector<Rect> regions;
// parameters:
// img - input video frame
// dst - resultant motion picture
// args - optional parameters
static void update_mhi(const Mat& img, Mat& dst, int diff_threshold)
{
double timestamp = (double)clock() / CLOCKS_PER_SEC; // get current time in seconds
Size size = img.size();
int i, idx1 = last;
Rect comp_rect;
double count;
double angle;
Point center;
double magnitude;
Scalar color;
// allocate images at the beginning or
// reallocate them if the frame size is changed
if (mhi.size() != size)
{
mhi = Mat::zeros(size, CV_32F);
zplane = Mat::zeros(size, CV_8U);
buf[0] = Mat::zeros(size, CV_8U);
buf[1] = Mat::zeros(size, CV_8U);
}
cvtColor(img, buf[last], COLOR_BGR2GRAY); // convert frame to grayscale
int idx2 = (last + 1) % 2; // index of (last - (N-1))th frame
last = idx2;
Mat silh = buf[idx2];
absdiff(buf[idx1], buf[idx2], silh); // get difference between frames
threshold(silh, silh, diff_threshold, 1, THRESH_BINARY); // and threshold it
updateMotionHistory(silh, mhi, timestamp, MHI_DURATION); // update MHI
// convert MHI to blue 8u image
mhi.convertTo(mask, CV_8U, 255. / MHI_DURATION, (MHI_DURATION - timestamp)*255. / MHI_DURATION);
Mat planes[] = { mask, zplane, zplane };
merge(planes, 3, dst);
// calculate motion gradient orientation and valid orientation mask
calcMotionGradient(mhi, mask, orient, MAX_TIME_DELTA, MIN_TIME_DELTA, 3);
// segment motion: get sequence of motion components
// segmask is marked motion components map. It is not used further
regions.clear();
segmentMotion(mhi, segmask, regions, timestamp, MAX_TIME_DELTA);
// iterate through the motion components,
// One more iteration (i == -1) corresponds to the whole image (global motion)
for (i = -1; i < (int)regions.size(); i++) {
if (i < 0) { // case of the whole image
comp_rect = Rect(0, 0, size.width, size.height);
color = Scalar(255, 255, 255);
magnitude = 100;
}
else { // i-th motion component
comp_rect = regions[i];
if (comp_rect.width + comp_rect.height < 100) // reject very small components
continue;
color = Scalar(0, 0, 255);
magnitude = 30;
}
// select component ROI
Mat silh_roi = silh(comp_rect);
Mat mhi_roi = mhi(comp_rect);
Mat orient_roi = orient(comp_rect);
Mat mask_roi = mask(comp_rect);
// calculate orientation
angle = calcGlobalOrientation(orient_roi, mask_roi, mhi_roi, timestamp, MHI_DURATION);
angle = 360.0 - angle; // adjust for images with top-left origin
count = norm(silh_roi, NORM_L1);; // calculate number of points within silhouette ROI
// check for the case of little motion
if (count < comp_rect.width*comp_rect.height * 0.05)
continue;
// draw a clock with arrow indicating the direction
center = Point((comp_rect.x + comp_rect.width / 2),
(comp_rect.y + comp_rect.height / 2));
circle(img, center, cvRound(magnitude*1.2), color, 3, 16, 0);
line(img, center, Point(cvRound(center.x + magnitude*cos(angle*CV_PI / 180)),
cvRound(center.y - magnitude*sin(angle*CV_PI / 180))), color, 3, 16, 0);
}
}
int main(int argc, char** argv)
{
VideoCapture cap;
help();
if (argc == 1 || (argc == 2 && strlen(argv[1]) == 1 && isdigit(argv[1][0])))
cap.open(argc == 2 ? argv[1][0] - '0' : 0);
else if (argc == 2)
cap.open(argv[1]);
if (!cap.isOpened())
{
printf("Could not initialize video capture\n");
return 0;
}
buf.resize(2);
Mat image, motion;
for (;;)
{
cap >> image;
if (image.empty())
break;
update_mhi(image, motion, 30);
imshow("Image", image);
imshow("Motion", motion);
if (waitKey(10) >= 0)
break;
}
return 0;
}
+97
View File
@@ -0,0 +1,97 @@
#!/usr/bin/env python
import numpy as np
import cv2 as cv
MHI_DURATION = 0.5
DEFAULT_THRESHOLD = 32
MAX_TIME_DELTA = 0.25
MIN_TIME_DELTA = 0.05
# (empty) trackbar callback
def nothing(dummy):
pass
def draw_motion_comp(vis, rect, angle, color):
x, y, w, h = rect
cv.rectangle(vis, (x, y), (x+w, y+h), (0, 255, 0))
r = min(w//2, h//2)
cx, cy = x+w//2, y+h//2
angle = angle*np.pi/180
cv.circle(vis, (cx, cy), r, color, 3)
cv.line(vis, (cx, cy), (int(cx+np.cos(angle)*r), int(cy+np.sin(angle)*r)), color, 3)
if __name__ == '__main__':
import sys
try:
video_src = sys.argv[1]
except:
video_src = 0
cv.namedWindow('motempl')
visuals = ['input', 'frame_diff', 'motion_hist', 'grad_orient']
cv.createTrackbar('visual', 'motempl', 2, len(visuals)-1, nothing)
cv.createTrackbar('threshold', 'motempl', DEFAULT_THRESHOLD, 255, nothing)
cam = cv.VideoCapture(video_src)
if not cam.isOpened():
print("could not open video_src " + str(video_src) + " !\n")
sys.exit(1)
ret, frame = cam.read()
if ret == False:
print("could not read from " + str(video_src) + " !\n")
sys.exit(1)
h, w = frame.shape[:2]
prev_frame = frame.copy()
motion_history = np.zeros((h, w), np.float32)
hsv = np.zeros((h, w, 3), np.uint8)
hsv[:,:,1] = 255
while True:
ret, frame = cam.read()
if ret == False:
break
frame_diff = cv.absdiff(frame, prev_frame)
gray_diff = cv.cvtColor(frame_diff, cv.COLOR_BGR2GRAY)
thrs = cv.getTrackbarPos('threshold', 'motempl')
ret, motion_mask = cv.threshold(gray_diff, thrs, 1, cv.THRESH_BINARY)
timestamp = cv.getTickCount() / cv.getTickFrequency()
cv.motempl.updateMotionHistory(motion_mask, motion_history, timestamp, MHI_DURATION)
mg_mask, mg_orient = cv.motempl.calcMotionGradient( motion_history, MAX_TIME_DELTA, MIN_TIME_DELTA, apertureSize=5 )
seg_mask, seg_bounds = cv.motempl.segmentMotion(motion_history, timestamp, MAX_TIME_DELTA)
visual_name = visuals[cv.getTrackbarPos('visual', 'motempl')]
if visual_name == 'input':
vis = frame.copy()
elif visual_name == 'frame_diff':
vis = frame_diff.copy()
elif visual_name == 'motion_hist':
vis = np.uint8(np.clip((motion_history-(timestamp-MHI_DURATION)) / MHI_DURATION, 0, 1)*255)
vis = cv.cvtColor(vis, cv.COLOR_GRAY2BGR)
elif visual_name == 'grad_orient':
hsv[:,:,0] = mg_orient/2
hsv[:,:,2] = mg_mask*255
vis = cv.cvtColor(hsv, cv.COLOR_HSV2BGR)
for i, rect in enumerate([(0, 0, w, h)] + list(seg_bounds)):
x, y, rw, rh = rect
area = rw*rh
if area < 64**2:
continue
silh_roi = motion_mask [y:y+rh,x:x+rw]
orient_roi = mg_orient [y:y+rh,x:x+rw]
mask_roi = mg_mask [y:y+rh,x:x+rw]
mhi_roi = motion_history[y:y+rh,x:x+rw]
if cv.norm(silh_roi, cv.NORM_L1) < area*0.05:
continue
angle = cv.motempl.calcGlobalOrientation(orient_roi, mask_roi, mhi_roi, timestamp, MHI_DURATION)
color = ((255, 0, 0), (0, 0, 255))[i == 0]
draw_motion_comp(vis, rect, angle, color)
cv.putText(vis, visual_name, (20, 20), cv.FONT_HERSHEY_PLAIN, 1.0, (200,0,0))
cv.imshow('motempl', vis)
prev_frame = frame.copy()
if 0xFF & cv.waitKey(5) == 27:
break
# cleanup the camera and close any open windows
cam.release()
cv.destroyAllWindows()
@@ -0,0 +1,268 @@
#!/usr/bin/env python
from __future__ import print_function
import os, sys, shutil
import argparse
import json, re
from subprocess import check_output
import datetime
import matplotlib.pyplot as plt
def load_json(path):
f = open(path, "r")
data = json.load(f)
return data
def save_json(obj, path):
tmp_file = path + ".bak"
f = open(tmp_file, "w")
json.dump(obj, f, indent=2)
f.flush()
os.fsync(f.fileno())
f.close()
try:
os.rename(tmp_file, path)
except:
os.remove(path)
os.rename(tmp_file, path)
def parse_evaluation_result(input_str, i):
res = {}
res['frame_number'] = i + 1
res['error'] = {}
regex = "([A-Za-z. \\[\\].0-9]+):[ ]*([0-9]*\.[0-9]+|[0-9]+)"
for elem in re.findall(regex,input_str):
if "Time" in elem[0]:
res['time'] = float(elem[1])
elif "Average" in elem[0]:
res['error']['average'] = float(elem[1])
elif "deviation" in elem[0]:
res['error']['std'] = float(elem[1])
else:
res['error'][elem[0]] = float(elem[1])
return res
def evaluate_sequence(sequence, algorithm, dataset, executable, img_files, gt_files,
state, state_path):
if "eval_results" not in state[dataset][algorithm][-1].keys():
state[dataset][algorithm][-1]["eval_results"] = {}
elif sequence in state[dataset][algorithm][-1]["eval_results"].keys():
return
res = []
for i in range(len(img_files) - 1):
sys.stdout.write("Algorithm: %-20s Sequence: %-10s Done: [%3d/%3d]\r" %
(algorithm, sequence, i, len(img_files) - 1)),
sys.stdout.flush()
res_string = check_output([executable, img_files[i], img_files[i + 1],
algorithm, gt_files[i]])
res.append(parse_evaluation_result(res_string, i))
state[dataset][algorithm][-1]["eval_results"][sequence] = res
save_json(state, state_path)
#############################DATSET DEFINITIONS################################
def evaluate_mpi_sintel(source_dir, algorithm, evaluation_executable, state, state_path):
evaluation_result = {}
img_dir = os.path.join(source_dir, 'mpi_sintel', 'training', 'final')
gt_dir = os.path.join(source_dir, 'mpi_sintel', 'training', 'flow')
sequences = [f for f in os.listdir(img_dir)
if os.path.isdir(os.path.join(img_dir, f))]
for seq in sequences:
img_files = sorted([os.path.join(img_dir, seq, f)
for f in os.listdir(os.path.join(img_dir, seq))
if f.endswith(".png")])
gt_files = sorted([os.path.join(gt_dir, seq, f)
for f in os.listdir(os.path.join(gt_dir, seq))
if f.endswith(".flo")])
evaluation_result[seq] = evaluate_sequence(seq, algorithm, 'mpi_sintel',
evaluation_executable, img_files, gt_files, state, state_path)
return evaluation_result
def evaluate_middlebury(source_dir, algorithm, evaluation_executable, state, state_path):
evaluation_result = {}
img_dir = os.path.join(source_dir, 'middlebury', 'other-data')
gt_dir = os.path.join(source_dir, 'middlebury', 'other-gt-flow')
sequences = [f for f in os.listdir(gt_dir)
if os.path.isdir(os.path.join(gt_dir, f))]
for seq in sequences:
img_files = sorted([os.path.join(img_dir, seq, f)
for f in os.listdir(os.path.join(img_dir, seq))
if f.endswith(".png")])
gt_files = sorted([os.path.join(gt_dir, seq, f)
for f in os.listdir(os.path.join(gt_dir, seq))
if f.endswith(".flo")])
evaluation_result[seq] = evaluate_sequence(seq, algorithm, 'middlebury',
evaluation_executable, img_files, gt_files, state, state_path)
return evaluation_result
dataset_eval_functions = {
"mpi_sintel": evaluate_mpi_sintel,
"middlebury": evaluate_middlebury
}
###############################################################################
def create_dir(dir):
if not os.path.exists(dir):
os.makedirs(dir)
def parse_sequence(input_str):
if len(input_str) == 0:
return []
else:
return [o.strip() for o in input_str.split(",") if o]
def build_chart(dst_folder, state, dataset):
fig = plt.figure(figsize=(16, 10))
markers = ["o", "s", "h", "^", "D"]
marker_idx = 0
colors = ["b", "g", "r"]
color_idx = 0
for algo in state[dataset].keys():
for eval_instance in state[dataset][algo]:
name = algo + "--" + eval_instance["timestamp"]
average_time = 0.0
average_error = 0.0
num_elem = 0
for seq in eval_instance["eval_results"].keys():
for frame in eval_instance["eval_results"][seq]:
average_time += frame["time"]
average_error += frame["error"]["average"]
num_elem += 1
average_time /= num_elem
average_error /= num_elem
marker_style = colors[color_idx] + markers[marker_idx]
color_idx += 1
if color_idx >= len(colors):
color_idx = 0
marker_idx += 1
if marker_idx >= len(markers):
marker_idx = 0
plt.gca().plot([average_time], [average_error],
marker_style,
markersize=14,
label=name)
plt.gca().set_ylabel('Average Endpoint Error (EPE)', fontsize=20)
plt.gca().set_xlabel('Average Runtime (seconds per frame)', fontsize=20)
plt.gca().set_xscale("log")
plt.gca().set_title('Evaluation on ' + dataset, fontsize=20)
plt.gca().legend()
fig.savefig(os.path.join(dst_folder, "evaluation_results_" + dataset + ".png"),
bbox_inches='tight')
plt.close()
if __name__ == '__main__':
parser = argparse.ArgumentParser(
description='Optical flow benchmarking script',
formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument(
"bin_path",
default="./optflow-example-optical_flow_evaluation",
help="Path to the optical flow evaluation executable")
parser.add_argument(
"-a",
"--algorithms",
metavar="ALGORITHMS",
default="",
help=("Comma-separated list of optical-flow algorithms to evaluate "
"(example: -a farneback,tvl1,deepflow). Note that previously "
"evaluated algorithms are also included in the output charts"))
parser.add_argument(
"-d",
"--datasets",
metavar="DATASETS",
default="mpi_sintel",
help=("Comma-separated list of datasets for evaluation (currently only "
"'mpi_sintel' and 'middlebury' are supported)"))
parser.add_argument(
"-f",
"--dataset_folder",
metavar="DATASET_FOLDER",
default="./OF_datasets",
help=("Path to a folder containing datasets. To enable evaluation on "
"MPI Sintel dataset, please download it using the following links: "
"http://files.is.tue.mpg.de/sintel/MPI-Sintel-training_images.zip and "
"http://files.is.tue.mpg.de/sintel/MPI-Sintel-training_extras.zip and "
"unzip these archives into the 'mpi_sintel' folder. To enable evaluation "
"on the Middlebury dataset use the following links: "
"http://vision.middlebury.edu/flow/data/comp/zip/other-color-twoframes.zip, "
"http://vision.middlebury.edu/flow/data/comp/zip/other-gt-flow.zip. "
"These should be unzipped into 'middlebury' folder"))
parser.add_argument(
"-o",
"--out",
metavar="OUT_DIR",
default="./OF_evaluation_results",
help="Output directory where to store benchmark results")
parser.add_argument(
"-s",
"--state",
metavar="STATE_JSON",
default="./OF_evaluation_state.json",
help=("Path to a json file that stores the current evaluation state and "
"previous evaluation results"))
args, other_args = parser.parse_known_args()
if not os.path.isfile(args.bin_path):
print("Error: " + args.bin_path + " does not exist")
sys.exit(1)
if not os.path.exists(args.dataset_folder):
print("Error: " + args.dataset_folder + (" does not exist. Please, correctly "
"specify the -f parameter"))
sys.exit(1)
state = {}
if os.path.isfile(args.state):
state = load_json(args.state)
algorithm_list = parse_sequence(args.algorithms)
dataset_list = parse_sequence(args.datasets)
for dataset in dataset_list:
if dataset not in dataset_eval_functions.keys():
print("Error: unsupported dataset " + dataset)
sys.exit(1)
if dataset not in os.listdir(args.dataset_folder):
print("Error: " + os.path.join(args.dataset_folder, dataset) + (" does not exist. "
"Please, download the dataset and follow the naming conventions "
"(use -h for more information)"))
sys.exit(1)
for dataset in dataset_list:
if dataset not in state.keys():
state[dataset] = {}
for algorithm in algorithm_list:
if algorithm in state[dataset].keys():
last_eval_instance = state[dataset][algorithm][-1]
if "finished" not in last_eval_instance.keys():
print(("Continuing an unfinished evaluation of " +
algorithm + " started at " + last_eval_instance["timestamp"]))
else:
state[dataset][algorithm].append({"timestamp":
datetime.datetime.now().strftime("%Y-%m-%d--%H-%M")})
else:
state[dataset][algorithm] = [{"timestamp":
datetime.datetime.now().strftime("%Y-%m-%d--%H-%M")}]
save_json(state, args.state)
dataset_eval_functions[dataset](args.dataset_folder, algorithm, args.bin_path,
state, args.state)
state[dataset][algorithm][-1]["finished"] = True
save_json(state, args.state)
save_json(state, args.state)
create_dir(args.out)
for dataset in dataset_list:
build_chart(args.out, state, dataset)
@@ -0,0 +1,411 @@
#include "opencv2/highgui.hpp"
#include "opencv2/video.hpp"
#include "opencv2/optflow.hpp"
#include "opencv2/core/ocl.hpp"
#include <fstream>
#include <limits>
using namespace std;
using namespace cv;
using namespace optflow;
const String keys = "{help h usage ? | | print this message }"
"{@image1 | | image1 }"
"{@image2 | | image2 }"
"{@algorithm | | [farneback, simpleflow, tvl1, deepflow, sparsetodenseflow, RLOF_EPIC, RLOF_RIC, pcaflow, DISflow_ultrafast, DISflow_fast, DISflow_medium] }"
"{@groundtruth | | path to the .flo file (optional), Middlebury format }"
"{m measure |endpoint| error measure - [endpoint or angular] }"
"{r region |all | region to compute stats about [all, discontinuities, untextured] }"
"{d display | | display additional info images (pauses program execution) }"
"{g gpu | | use OpenCL}"
"{prior | | path to a prior file for PCAFlow}";
inline bool isFlowCorrect( const Point2f u )
{
return !cvIsNaN(u.x) && !cvIsNaN(u.y) && (fabs(u.x) < 1e9) && (fabs(u.y) < 1e9);
}
inline bool isFlowCorrect( const Point3f u )
{
return !cvIsNaN(u.x) && !cvIsNaN(u.y) && !cvIsNaN(u.z) && (fabs(u.x) < 1e9) && (fabs(u.y) < 1e9)
&& (fabs(u.z) < 1e9);
}
static Mat endpointError( const Mat_<Point2f>& flow1, const Mat_<Point2f>& flow2 )
{
Mat result(flow1.size(), CV_32FC1);
for ( int i = 0; i < flow1.rows; ++i )
{
for ( int j = 0; j < flow1.cols; ++j )
{
const Point2f u1 = flow1(i, j);
const Point2f u2 = flow2(i, j);
if ( isFlowCorrect(u1) && isFlowCorrect(u2) )
{
const Point2f diff = u1 - u2;
result.at<float>(i, j) = sqrt((float)diff.ddot(diff)); //distance
} else
result.at<float>(i, j) = std::numeric_limits<float>::quiet_NaN();
}
}
return result;
}
static Mat angularError( const Mat_<Point2f>& flow1, const Mat_<Point2f>& flow2 )
{
Mat result(flow1.size(), CV_32FC1);
for ( int i = 0; i < flow1.rows; ++i )
{
for ( int j = 0; j < flow1.cols; ++j )
{
const Point2f u1_2d = flow1(i, j);
const Point2f u2_2d = flow2(i, j);
const Point3f u1(u1_2d.x, u1_2d.y, 1);
const Point3f u2(u2_2d.x, u2_2d.y, 1);
if ( isFlowCorrect(u1) && isFlowCorrect(u2) )
result.at<float>(i, j) = acos((float)(u1.ddot(u2) / norm(u1) * norm(u2)));
else
result.at<float>(i, j) = std::numeric_limits<float>::quiet_NaN();
}
}
return result;
}
// what fraction of pixels have errors higher than given threshold?
static float stat_RX( Mat errors, float threshold, Mat mask )
{
CV_Assert(errors.size() == mask.size());
CV_Assert(mask.depth() == CV_8U);
int count = 0, all = 0;
for ( int i = 0; i < errors.rows; ++i )
{
for ( int j = 0; j < errors.cols; ++j )
{
if ( mask.at<char>(i, j) != 0 )
{
++all;
if ( errors.at<float>(i, j) > threshold )
++count;
}
}
}
return (float)count / all;
}
static float stat_AX( Mat hist, int cutoff_count, float max_value )
{
int counter = 0;
int bin = 0;
int bin_count = hist.rows;
while ( bin < bin_count && counter < cutoff_count )
{
counter += (int) hist.at<float>(bin, 0);
++bin;
}
return (float) bin / bin_count * max_value;
}
static void calculateStats( Mat errors, Mat mask = Mat(), bool display_images = false )
{
float R_thresholds[] = { 0.5f, 1.f, 2.f, 5.f, 10.f };
float A_thresholds[] = { 0.5f, 0.75f, 0.95f };
if ( mask.empty() )
mask = Mat::ones(errors.size(), CV_8U);
CV_Assert(errors.size() == mask.size());
CV_Assert(mask.depth() == CV_8U);
//displaying the mask
if(display_images)
{
namedWindow( "Region mask", WINDOW_AUTOSIZE );
imshow( "Region mask", mask );
}
//mean and std computation
Scalar s_mean, s_std;
float mean, std;
meanStdDev(errors, s_mean, s_std, mask);
mean = (float)s_mean[0];
std = (float)s_std[0];
printf("Average: %.2f\nStandard deviation: %.2f\n", mean, std);
//RX stats - displayed in percent
float R;
int R_thresholds_count = sizeof(R_thresholds) / sizeof(float);
for ( int i = 0; i < R_thresholds_count; ++i )
{
R = stat_RX(errors, R_thresholds[i], mask);
printf("R%.1f: %.2f%%\n", R_thresholds[i], R * 100);
}
//AX stats
double max_value;
minMaxLoc(errors, NULL, &max_value, NULL, NULL, mask);
Mat hist;
const int n_images = 1;
const int channels[] = { 0 };
const int n_dimensions = 1;
const int hist_bins[] = { 1024 };
const float iranges[] = { 0, (float) max_value };
const float* ranges[] = { iranges };
const bool uniform = true;
const bool accumulate = false;
calcHist(&errors, n_images, channels, mask, hist, n_dimensions, hist_bins, ranges, uniform,
accumulate);
int all_pixels = countNonZero(mask);
int cutoff_count;
float A;
int A_thresholds_count = sizeof(A_thresholds) / sizeof(float);
for ( int i = 0; i < A_thresholds_count; ++i )
{
cutoff_count = (int) (floor(A_thresholds[i] * all_pixels + 0.5f));
A = stat_AX(hist, cutoff_count, (float) max_value);
printf("A%.2f: %.2f\n", A_thresholds[i], A);
}
}
static Mat flowToDisplay(const Mat flow)
{
Mat flow_split[2];
Mat magnitude, angle;
Mat hsv_split[3], hsv, rgb;
split(flow, flow_split);
cartToPolar(flow_split[0], flow_split[1], magnitude, angle, true);
normalize(magnitude, magnitude, 0, 1, NORM_MINMAX);
hsv_split[0] = angle; // already in degrees - no normalization needed
hsv_split[1] = Mat::ones(angle.size(), angle.type());
hsv_split[2] = magnitude;
merge(hsv_split, 3, hsv);
cvtColor(hsv, rgb, COLOR_HSV2BGR);
return rgb;
}
int main( int argc, char** argv )
{
CommandLineParser parser(argc, argv, keys);
parser.about("OpenCV optical flow evaluation app");
if ( parser.has("help") || argc < 4 )
{
parser.printMessage();
printf("EXAMPLES:\n");
printf("./example_optflow_optical_flow_evaluation im1.png im2.png farneback -d \n");
printf("\t - compute flow field between im1 and im2 with farneback's method and display it");
printf("./example_optflow_optical_flow_evaluation im1.png im2.png simpleflow groundtruth.flo \n");
printf("\t - compute error statistics given the groundtruth; all pixels, endpoint error measure");
printf("./example_optflow_optical_flow_evaluation im1.png im2.png farneback groundtruth.flo -m=angular -r=untextured \n");
printf("\t - as before, but with changed error measure and stats computed only about \"untextured\" areas");
printf("\n\n Flow file format description: http://vision.middlebury.edu/flow/code/flow-code/README.txt\n\n");
return 0;
}
String i1_path = parser.get<String>(0);
String i2_path = parser.get<String>(1);
String method = parser.get<String>(2);
String groundtruth_path = parser.get<String>(3);
String error_measure = parser.get<String>("measure");
String region = parser.get<String>("region");
bool display_images = parser.has("display");
const bool useGpu = parser.has("gpu");
if ( !parser.check() )
{
parser.printErrors();
return 0;
}
cv::ocl::setUseOpenCL(useGpu);
printf("OpenCL Enabled: %u\n", useGpu && cv::ocl::haveOpenCL());
Mat i1, i2;
Mat_<Point2f> flow, ground_truth;
Mat computed_errors;
i1 = imread(i1_path, 1);
i2 = imread(i2_path, 1);
if ( !i1.data || !i2.data )
{
printf("No image data \n");
return -1;
}
if ( i1.size() != i2.size() || i1.channels() != i2.channels() )
{
printf("Dimension mismatch between input images\n");
return -1;
}
// 8-bit images expected by all algorithms
if ( i1.depth() != CV_8U )
i1.convertTo(i1, CV_8U);
if ( i2.depth() != CV_8U )
i2.convertTo(i2, CV_8U);
if ( (method == "farneback" || method == "tvl1" || method == "deepflow" || method == "DISflow_ultrafast" || method == "DISflow_fast" || method == "DISflow_medium") && i1.channels() == 3 )
{ // 1-channel images are expected
cvtColor(i1, i1, COLOR_BGR2GRAY);
cvtColor(i2, i2, COLOR_BGR2GRAY);
} else if ( method == "simpleflow" && i1.channels() == 1 )
{ // 3-channel images expected
cvtColor(i1, i1, COLOR_GRAY2BGR);
cvtColor(i2, i2, COLOR_GRAY2BGR);
}
flow = Mat(i1.size[0], i1.size[1], CV_32FC2);
Ptr<DenseOpticalFlow> algorithm;
if ( method == "farneback" )
algorithm = createOptFlow_Farneback();
else if ( method == "simpleflow" )
algorithm = createOptFlow_SimpleFlow();
else if ( method == "tvl1" )
algorithm = createOptFlow_DualTVL1();
else if ( method == "deepflow" )
algorithm = createOptFlow_DeepFlow();
else if ( method == "sparsetodenseflow" )
algorithm = createOptFlow_SparseToDense();
else if (method == "RLOF_EPIC")
{
algorithm = createOptFlow_DenseRLOF();
Ptr<DenseRLOFOpticalFlow> rlof = algorithm.dynamicCast< DenseRLOFOpticalFlow>();
rlof->setInterpolation(INTERP_EPIC);
rlof->setForwardBackward(1.f);
}
else if (method == "RLOF_RIC")
{
algorithm = createOptFlow_DenseRLOF();
Ptr<DenseRLOFOpticalFlow> rlof = algorithm.dynamicCast< DenseRLOFOpticalFlow>();;
rlof->setInterpolation(INTERP_RIC);
rlof->setForwardBackward(1.f);
}
else if ( method == "pcaflow" ) {
if ( parser.has("prior") ) {
String prior = parser.get<String>("prior");
printf("Using prior file: %s\n", prior.c_str());
algorithm = makePtr<OpticalFlowPCAFlow>(makePtr<PCAPrior>(prior.c_str()));
}
else
algorithm = createOptFlow_PCAFlow();
}
else if ( method == "DISflow_ultrafast" )
algorithm = DISOpticalFlow::create(DISOpticalFlow::PRESET_ULTRAFAST);
else if (method == "DISflow_fast")
algorithm = DISOpticalFlow::create(DISOpticalFlow::PRESET_FAST);
else if (method == "DISflow_medium")
algorithm = DISOpticalFlow::create(DISOpticalFlow::PRESET_MEDIUM);
else
{
printf("Wrong method!\n");
parser.printMessage();
return -1;
}
double startTick, time;
startTick = (double) getTickCount(); // measure time
if (useGpu)
algorithm->calc(i1, i2, flow.getUMat(ACCESS_RW));
else
algorithm->calc(i1, i2, flow);
time = ((double) getTickCount() - startTick) / getTickFrequency();
printf("\nTime [s]: %.3f\n", time);
if(display_images)
{
Mat flow_image = flowToDisplay(flow);
namedWindow( "Computed flow", WINDOW_AUTOSIZE );
imshow( "Computed flow", flow_image );
}
if ( !groundtruth_path.empty() )
{ // compare to ground truth
ground_truth = readOpticalFlow(groundtruth_path);
if ( flow.size() != ground_truth.size() || flow.channels() != 2
|| ground_truth.channels() != 2 )
{
printf("Dimension mismatch between the computed flow and the provided ground truth\n");
return -1;
}
if ( error_measure == "endpoint" )
computed_errors = endpointError(flow, ground_truth);
else if ( error_measure == "angular" )
computed_errors = angularError(flow, ground_truth);
else
{
printf("Invalid error measure! Available options: endpoint, angular\n");
return -1;
}
Mat mask;
if( region == "all" )
mask = Mat::ones(ground_truth.size(), CV_8U) * 255;
else if ( region == "discontinuities" )
{
Mat truth_merged, grad_x, grad_y, gradient;
vector<Mat> truth_split;
split(ground_truth, truth_split);
truth_merged = truth_split[0] + truth_split[1];
Sobel( truth_merged, grad_x, CV_16S, 1, 0, -1, 1, 0, BORDER_REPLICATE );
grad_x = abs(grad_x);
Sobel( truth_merged, grad_y, CV_16S, 0, 1, 1, 1, 0, BORDER_REPLICATE );
grad_y = abs(grad_y);
addWeighted(grad_x, 0.5, grad_y, 0.5, 0, gradient); //approximation!
Scalar s_mean;
s_mean = mean(gradient);
double threshold = s_mean[0]; // threshold value arbitrary
mask = gradient > threshold;
dilate(mask, mask, Mat::ones(9, 9, CV_8U));
}
else if ( region == "untextured" )
{
Mat i1_grayscale, grad_x, grad_y, gradient;
if( i1.channels() == 3 )
cvtColor(i1, i1_grayscale, COLOR_BGR2GRAY);
else
i1_grayscale = i1;
Sobel( i1_grayscale, grad_x, CV_16S, 1, 0, 7 );
grad_x = abs(grad_x);
Sobel( i1_grayscale, grad_y, CV_16S, 0, 1, 7 );
grad_y = abs(grad_y);
addWeighted(grad_x, 0.5, grad_y, 0.5, 0, gradient); //approximation!
GaussianBlur(gradient, gradient, Size(5,5), 1, 1);
Scalar s_mean;
s_mean = mean(gradient);
// arbitrary threshold value used - could be determined statistically from the image?
double threshold = 1000;
mask = gradient < threshold;
dilate(mask, mask, Mat::ones(3, 3, CV_8U));
}
else
{
printf("Invalid region selected! Available options: all, discontinuities, untextured");
return -1;
}
//masking out NaNs and incorrect GT values
Mat truth_split[2];
split(ground_truth, truth_split);
Mat abs_mask = Mat((abs(truth_split[0]) < 1e9) & (abs(truth_split[1]) < 1e9));
Mat nan_mask = Mat((truth_split[0]==truth_split[0]) & (truth_split[1] == truth_split[1]));
bitwise_and(abs_mask, nan_mask, nan_mask);
bitwise_and(nan_mask, mask, mask); //including the selected region
if(display_images) // display difference between computed and GT flow
{
Mat difference = ground_truth - flow;
Mat masked_difference;
difference.copyTo(masked_difference, mask);
Mat flow_image = flowToDisplay(masked_difference);
namedWindow( "Error map", WINDOW_AUTOSIZE );
imshow( "Error map", flow_image );
}
printf("Using %s error measure\n", error_measure.c_str());
calculateStats(computed_errors, mask, display_images);
}
if(display_images) // wait for the user to see all the images
waitKey(0);
return 0;
}
+172
View File
@@ -0,0 +1,172 @@
#include "opencv2/core/ocl.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/optflow.hpp"
#include <fstream>
#include <iostream>
#include <stdio.h>
using namespace cv;
using optflow::OpticalFlowPCAFlow;
using optflow::PCAPrior;
const String keys = "{help h ? | | print this message}"
"{@image1 |<none>| image1}"
"{@image2 |<none>| image2}"
"{@groundtruth |<none>| path to the .flo file}"
"{@prior |<none>| path to a prior file for PCAFlow}"
"{@output |<none>| output image path}"
"{g gpu | | use OpenCL}";
static double normL2( const Point2f &v ) { return sqrt( v.x * v.x + v.y * v.y ); }
static bool fileProbe( const char *name ) { return std::ifstream( name ).good(); }
static Vec3d getFlowColor( const Point2f &f, const bool logScale = true, const double scaleDown = 5 )
{
if ( f.x == 0 && f.y == 0 )
return Vec3d( 0, 0, 1 );
double radius = normL2( f );
if ( logScale )
radius = log( radius + 1 );
radius /= scaleDown;
radius = std::min( 1.0, radius );
double angle = ( atan2( -f.y, -f.x ) + CV_PI ) * 180 / CV_PI;
return Vec3d( angle, radius, 1 );
}
static void displayFlow( InputArray _flow, OutputArray _img )
{
const Size sz = _flow.size();
Mat flow = _flow.getMat();
_img.create( sz, CV_32FC3 );
Mat img = _img.getMat();
for ( int i = 0; i < sz.height; ++i )
for ( int j = 0; j < sz.width; ++j )
img.at< Vec3f >( i, j ) = getFlowColor( flow.at< Point2f >( i, j ) );
cvtColor( img, img, COLOR_HSV2BGR );
}
static bool isFlowCorrect( const Point2f &u )
{
return !cvIsNaN( u.x ) && !cvIsNaN( u.y ) && ( fabs( u.x ) < 1e9 ) && ( fabs( u.y ) < 1e9 );
}
static double calcEPE( const Mat &f1, const Mat &f2 )
{
double sum = 0;
Size sz = f1.size();
size_t cnt = 0;
for ( int i = 0; i < sz.height; ++i )
for ( int j = 0; j < sz.width; ++j )
if ( isFlowCorrect( f1.at< Point2f >( i, j ) ) && isFlowCorrect( f2.at< Point2f >( i, j ) ) )
{
sum += normL2( f1.at< Point2f >( i, j ) - f2.at< Point2f >( i, j ) );
++cnt;
}
return sum / cnt;
}
static void displayResult( Mat &i1, Mat &i2, Mat &gt, Ptr< DenseOpticalFlow > &algo, OutputArray _img, const char *descr,
const bool useGpu = false )
{
Mat flow( i1.size[0], i1.size[1], CV_32FC2 );
TickMeter meter;
meter.start();
if ( useGpu )
algo->calc( i1, i2, flow.getUMat( ACCESS_RW ) );
else
algo->calc( i1, i2, flow );
meter.stop();
displayFlow( flow, _img );
Mat img = _img.getMat();
putText( img, descr, Point2i( 24, 40 ), FONT_HERSHEY_DUPLEX, 1, Vec3b( 1, 0, 0 ), 2, LINE_AA );
char buf[256];
sprintf( buf, "Average EPE: %.2f", calcEPE( flow, gt ) );
putText( img, buf, Point2i( 24, 80 ), FONT_HERSHEY_DUPLEX, 1, Vec3b( 1, 0, 0 ), 2, LINE_AA );
sprintf( buf, "Time: %.2fs", meter.getTimeSec() );
putText( img, buf, Point2i( 24, 120 ), FONT_HERSHEY_DUPLEX, 1, Vec3b( 1, 0, 0 ), 2, LINE_AA );
}
static void displayGT( InputArray _flow, OutputArray _img, const char *descr )
{
displayFlow( _flow, _img );
Mat img = _img.getMat();
putText( img, descr, Point2i( 24, 40 ), FONT_HERSHEY_DUPLEX, 1, Vec3b( 1, 0, 0 ), 2, LINE_AA );
}
int main( int argc, const char **argv )
{
CommandLineParser parser( argc, argv, keys );
parser.about( "PCAFlow demonstration" );
if ( parser.has( "help" ) )
{
parser.printMessage();
return 0;
}
String img1 = parser.get< String >( 0 );
String img2 = parser.get< String >( 1 );
String groundtruth = parser.get< String >( 2 );
String prior = parser.get< String >( 3 );
String outimg = parser.get< String >( 4 );
const bool useGpu = parser.has( "gpu" );
if ( !parser.check() )
{
parser.printErrors();
return 1;
}
if ( !fileProbe( prior.c_str() ) )
{
std::cerr << "Can't open the file with prior! Check the provided path: " << prior << std::endl;
return 1;
}
cv::ocl::setUseOpenCL( useGpu );
Mat i1 = imread( img1 );
Mat i2 = imread( img2 );
Mat gt = readOpticalFlow( groundtruth );
Mat i1g, i2g;
cvtColor( i1, i1g, COLOR_BGR2GRAY );
cvtColor( i2, i2g, COLOR_BGR2GRAY );
Mat pcaflowDisp, pcaflowpriDisp, farnebackDisp, gtDisp;
{
Ptr< DenseOpticalFlow > pcaflow = makePtr< OpticalFlowPCAFlow >( makePtr< PCAPrior >( prior.c_str() ) );
displayResult( i1, i2, gt, pcaflow, pcaflowpriDisp, "PCAFlow with prior", useGpu );
}
{
Ptr< DenseOpticalFlow > pcaflow = makePtr< OpticalFlowPCAFlow >();
displayResult( i1, i2, gt, pcaflow, pcaflowDisp, "PCAFlow without prior", useGpu );
}
{
Ptr< DenseOpticalFlow > farneback = optflow::createOptFlow_Farneback();
displayResult( i1g, i2g, gt, farneback, farnebackDisp, "Farneback", useGpu );
}
displayGT( gt, gtDisp, "Ground truth" );
Mat disp1, disp2;
vconcat( pcaflowpriDisp, farnebackDisp, disp1 );
vconcat( pcaflowDisp, gtDisp, disp2 );
hconcat( disp1, disp2, disp1 );
disp1 *= 255;
imwrite( outimg, disp1 );
return 0;
}
+222
View File
@@ -0,0 +1,222 @@
#include "opencv2/optflow.hpp"
#include <opencv2/core/utility.hpp>
#include "opencv2/imgproc.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include <cstdio>
#include <iostream>
using namespace cv;
using namespace cv::optflow;
using namespace std;
#define APP_NAME "simpleflow_demo : "
static void help()
{
// print a welcome message, and the OpenCV version
printf("This is a demo of SimpleFlow optical flow algorithm,\n"
"Using OpenCV version %s\n\n", CV_VERSION);
printf("Usage: simpleflow_demo frame1 frame2 output_flow"
"\nApplication will write estimated flow "
"\nbetween 'frame1' and 'frame2' in binary format"
"\ninto file 'output_flow'"
"\nThen one can use code from http://vision.middlebury.edu/flow/data/"
"\nto convert flow in binary file to image\n");
}
// binary file format for flow data specified here:
// http://vision.middlebury.edu/flow/data/
static void writeOpticalFlowToFile(const Mat& flow, FILE* file) {
int cols = flow.cols;
int rows = flow.rows;
fprintf(file, "PIEH");
if (fwrite(&cols, sizeof(int), 1, file) != 1 ||
fwrite(&rows, sizeof(int), 1, file) != 1) {
printf(APP_NAME "writeOpticalFlowToFile : problem writing header\n");
exit(1);
}
for (int i= 0; i < rows; ++i) {
for (int j = 0; j < cols; ++j) {
Vec2f flow_at_point = flow.at<Vec2f>(i, j);
if (fwrite(&(flow_at_point[0]), sizeof(float), 1, file) != 1 ||
fwrite(&(flow_at_point[1]), sizeof(float), 1, file) != 1) {
printf(APP_NAME "writeOpticalFlowToFile : problem writing data\n");
exit(1);
}
}
}
}
static void run(int argc, char** argv) {
if (argc < 3) {
printf(APP_NAME "Wrong number of command line arguments for mode `run`: %d (expected %d)\n",
argc, 3);
exit(1);
}
Mat frame1 = imread(argv[0]);
Mat frame2 = imread(argv[1]);
if (frame1.empty()) {
printf(APP_NAME "Image #1 : %s cannot be read\n", argv[0]);
exit(1);
}
if (frame2.empty()) {
printf(APP_NAME "Image #2 : %s cannot be read\n", argv[1]);
exit(1);
}
if (frame1.rows != frame2.rows && frame1.cols != frame2.cols) {
printf(APP_NAME "Images should be of equal sizes\n");
exit(1);
}
if (frame1.type() != 16 || frame2.type() != 16) {
printf(APP_NAME "Images should be of equal type CV_8UC3\n");
exit(1);
}
printf(APP_NAME "Read two images of size [rows = %d, cols = %d]\n",
frame1.rows, frame1.cols);
Mat flow;
float start = (float)getTickCount();
calcOpticalFlowSF(frame1, frame2,
flow,
3, 2, 4, 4.1, 25.5, 18, 55.0, 25.5, 0.35, 18, 55.0, 25.5, 10);
printf(APP_NAME "calcOpticalFlowSF : %lf sec\n", (getTickCount() - start) / getTickFrequency());
FILE* file = fopen(argv[2], "wb");
if (file == NULL) {
printf(APP_NAME "Unable to open file '%s' for writing\n", argv[2]);
exit(1);
}
printf(APP_NAME "Writing to file\n");
writeOpticalFlowToFile(flow, file);
fclose(file);
}
static bool readOpticalFlowFromFile(FILE* file, Mat& flow) {
char header[5];
if (fread(header, 1, 4, file) < 4 && (string)header != "PIEH") {
return false;
}
int cols, rows;
if (fread(&cols, sizeof(int), 1, file) != 1||
fread(&rows, sizeof(int), 1, file) != 1) {
return false;
}
flow = Mat::zeros(rows, cols, CV_32FC2);
for (int i = 0; i < rows; ++i) {
for (int j = 0; j < cols; ++j) {
Vec2f flow_at_point;
if (fread(&(flow_at_point[0]), sizeof(float), 1, file) != 1 ||
fread(&(flow_at_point[1]), sizeof(float), 1, file) != 1) {
return false;
}
flow.at<Vec2f>(i, j) = flow_at_point;
}
}
return true;
}
static bool isFlowCorrect(float u) {
return !cvIsNaN(u) && (fabs(u) < 1e9);
}
static float calc_rmse(Mat flow1, Mat flow2) {
float sum = 0;
int counter = 0;
const int rows = flow1.rows;
const int cols = flow1.cols;
for (int y = 0; y < rows; ++y) {
for (int x = 0; x < cols; ++x) {
Vec2f flow1_at_point = flow1.at<Vec2f>(y, x);
Vec2f flow2_at_point = flow2.at<Vec2f>(y, x);
float u1 = flow1_at_point[0];
float v1 = flow1_at_point[1];
float u2 = flow2_at_point[0];
float v2 = flow2_at_point[1];
if (isFlowCorrect(u1) && isFlowCorrect(u2) && isFlowCorrect(v1) && isFlowCorrect(v2)) {
sum += (u1-u2)*(u1-u2) + (v1-v2)*(v1-v2);
counter++;
}
}
}
return (float)sqrt(sum / (1e-9 + counter));
}
static void eval(int argc, char** argv) {
if (argc < 2) {
printf(APP_NAME "Wrong number of command line arguments for mode `eval` : %d (expected %d)\n",
argc, 2);
exit(1);
}
Mat flow1, flow2;
FILE* flow_file_1 = fopen(argv[0], "rb");
if (flow_file_1 == NULL) {
printf(APP_NAME "Cannot open file with first flow : %s\n", argv[0]);
exit(1);
}
if (!readOpticalFlowFromFile(flow_file_1, flow1)) {
printf(APP_NAME "Cannot read flow data from file %s\n", argv[0]);
exit(1);
}
fclose(flow_file_1);
FILE* flow_file_2 = fopen(argv[1], "rb");
if (flow_file_2 == NULL) {
printf(APP_NAME "Cannot open file with first flow : %s\n", argv[1]);
exit(1);
}
if (!readOpticalFlowFromFile(flow_file_2, flow2)) {
printf(APP_NAME "Cannot read flow data from file %s\n", argv[1]);
exit(1);
}
fclose(flow_file_2);
float rmse = calc_rmse(flow1, flow2);
printf("%lf\n", rmse);
}
int main(int argc, char** argv) {
if (argc < 2) {
printf(APP_NAME "Mode is not specified\n");
help();
exit(1);
}
string mode = (string)argv[1];
int new_argc = argc - 2;
char** new_argv = &argv[2];
if ("run" == mode) {
run(new_argc, new_argv);
} else if ("eval" == mode) {
eval(new_argc, new_argv);
} else if ("help" == mode)
help();
else {
printf(APP_NAME "Unknown mode : %s\n", argv[1]);
help();
}
return 0;
}
@@ -0,0 +1,206 @@
#include <iostream>
#include <fstream>
#include <opencv2/core/utility.hpp>
#include "opencv2/video.hpp"
#include "opencv2/optflow.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
using namespace cv;
using namespace std;
using namespace optflow;
inline bool isFlowCorrect(Point2f u)
{
return !cvIsNaN(u.x) && !cvIsNaN(u.y) && fabs(u.x) < 1e9 && fabs(u.y) < 1e9;
}
static Vec3b computeColor(float fx, float fy)
{
static bool first = true;
// relative lengths of color transitions:
// these are chosen based on perceptual similarity
// (e.g. one can distinguish more shades between red and yellow
// than between yellow and green)
const int RY = 15;
const int YG = 6;
const int GC = 4;
const int CB = 11;
const int BM = 13;
const int MR = 6;
const int NCOLS = RY + YG + GC + CB + BM + MR;
static Vec3i colorWheel[NCOLS];
if (first)
{
int k = 0;
for (int i = 0; i < RY; ++i, ++k)
colorWheel[k] = Vec3i(255, 255 * i / RY, 0);
for (int i = 0; i < YG; ++i, ++k)
colorWheel[k] = Vec3i(255 - 255 * i / YG, 255, 0);
for (int i = 0; i < GC; ++i, ++k)
colorWheel[k] = Vec3i(0, 255, 255 * i / GC);
for (int i = 0; i < CB; ++i, ++k)
colorWheel[k] = Vec3i(0, 255 - 255 * i / CB, 255);
for (int i = 0; i < BM; ++i, ++k)
colorWheel[k] = Vec3i(255 * i / BM, 0, 255);
for (int i = 0; i < MR; ++i, ++k)
colorWheel[k] = Vec3i(255, 0, 255 - 255 * i / MR);
first = false;
}
const float rad = sqrt(fx * fx + fy * fy);
const float a = atan2(-fy, -fx) / (float)CV_PI;
const float fk = (a + 1.0f) / 2.0f * (NCOLS - 1);
const int k0 = static_cast<int>(fk);
const int k1 = (k0 + 1) % NCOLS;
const float f = fk - k0;
Vec3b pix;
for (int b = 0; b < 3; b++)
{
const float col0 = colorWheel[k0][b] / 255.f;
const float col1 = colorWheel[k1][b] / 255.f;
float col = (1 - f) * col0 + f * col1;
if (rad <= 1)
col = 1 - rad * (1 - col); // increase saturation with radius
else
col *= .75; // out of range
pix[2 - b] = static_cast<uchar>(255.f * col);
}
return pix;
}
static void drawOpticalFlow(const Mat_<Point2f>& flow, Mat& dst, float maxmotion = -1)
{
dst.create(flow.size(), CV_8UC3);
dst.setTo(Scalar::all(0));
// determine motion range:
float maxrad = maxmotion;
if (maxmotion <= 0)
{
maxrad = 1;
for (int y = 0; y < flow.rows; ++y)
{
for (int x = 0; x < flow.cols; ++x)
{
Point2f u = flow(y, x);
if (!isFlowCorrect(u))
continue;
maxrad = max(maxrad, sqrt(u.x * u.x + u.y * u.y));
}
}
}
for (int y = 0; y < flow.rows; ++y)
{
for (int x = 0; x < flow.cols; ++x)
{
Point2f u = flow(y, x);
if (isFlowCorrect(u))
dst.at<Vec3b>(y, x) = computeColor(u.x / maxrad, u.y / maxrad);
}
}
}
// binary file format for flow data specified here:
// http://vision.middlebury.edu/flow/data/
static void writeOpticalFlowToFile(const Mat_<Point2f>& flow, const string& fileName)
{
static const char FLO_TAG_STRING[] = "PIEH";
ofstream file(fileName.c_str(), ios_base::binary);
file << FLO_TAG_STRING;
file.write((const char*) &flow.cols, sizeof(int));
file.write((const char*) &flow.rows, sizeof(int));
for (int i = 0; i < flow.rows; ++i)
{
for (int j = 0; j < flow.cols; ++j)
{
const Point2f u = flow(i, j);
file.write((const char*) &u.x, sizeof(float));
file.write((const char*) &u.y, sizeof(float));
}
}
}
int main(int argc, const char* argv[])
{
cv::CommandLineParser parser(argc, argv, "{help h || show help message}"
"{ @frame0 | | frame 0}{ @frame1 | | frame 1}{ @output | | output flow}");
if (parser.has("help"))
{
parser.printMessage();
return 0;
}
string frame0_name = parser.get<string>("@frame0");
string frame1_name = parser.get<string>("@frame1");
string file = parser.get<string>("@output");
if (frame0_name.empty() || frame1_name.empty() || file.empty())
{
cerr << "Usage : " << argv[0] << " [<frame0>] [<frame1>] [<output_flow>]" << endl;
return -1;
}
Mat frame0 = imread(frame0_name, IMREAD_GRAYSCALE);
Mat frame1 = imread(frame1_name, IMREAD_GRAYSCALE);
if (frame0.empty())
{
cerr << "Can't open image [" << parser.get<string>("frame0") << "]" << endl;
return -1;
}
if (frame1.empty())
{
cerr << "Can't open image [" << parser.get<string>("frame1") << "]" << endl;
return -1;
}
if (frame1.size() != frame0.size())
{
cerr << "Images should be of equal sizes" << endl;
return -1;
}
Mat_<Point2f> flow;
Ptr<DualTVL1OpticalFlow> tvl1 = DualTVL1OpticalFlow::create();
const double start = (double)getTickCount();
tvl1->calc(frame0, frame1, flow);
const double timeSec = (getTickCount() - start) / getTickFrequency();
cout << "calcOpticalFlowDual_TVL1 : " << timeSec << " sec" << endl;
Mat out;
drawOpticalFlow(flow, out);
if (!file.empty())
writeOpticalFlowToFile(flow, file);
imshow("Flow", out);
waitKey();
return 0;
}