vendor: OpenCV 5.0.0 snapshot at 755e50675d97db9b7d449d8bd6b09888646f6c6e
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -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 > >, 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()
|
||||
@@ -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;
|
||||
}
|
||||
Executable
+97
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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 >, 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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user