optical_flow_evaluation.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. #include "opencv2/highgui.hpp"
  2. #include "opencv2/video.hpp"
  3. #include "opencv2/optflow.hpp"
  4. #include "opencv2/core/ocl.hpp"
  5. #include <fstream>
  6. #include <limits>
  7. using namespace std;
  8. using namespace cv;
  9. using namespace optflow;
  10. const String keys = "{help h usage ? | | print this message }"
  11. "{@image1 | | image1 }"
  12. "{@image2 | | image2 }"
  13. "{@algorithm | | [farneback, simpleflow, tvl1, deepflow, sparsetodenseflow, RLOF_EPIC, RLOF_RIC, pcaflow, DISflow_ultrafast, DISflow_fast, DISflow_medium] }"
  14. "{@groundtruth | | path to the .flo file (optional), Middlebury format }"
  15. "{m measure |endpoint| error measure - [endpoint or angular] }"
  16. "{r region |all | region to compute stats about [all, discontinuities, untextured] }"
  17. "{d display | | display additional info images (pauses program execution) }"
  18. "{g gpu | | use OpenCL}"
  19. "{prior | | path to a prior file for PCAFlow}";
  20. inline bool isFlowCorrect( const Point2f u )
  21. {
  22. return !cvIsNaN(u.x) && !cvIsNaN(u.y) && (fabs(u.x) < 1e9) && (fabs(u.y) < 1e9);
  23. }
  24. inline bool isFlowCorrect( const Point3f u )
  25. {
  26. return !cvIsNaN(u.x) && !cvIsNaN(u.y) && !cvIsNaN(u.z) && (fabs(u.x) < 1e9) && (fabs(u.y) < 1e9)
  27. && (fabs(u.z) < 1e9);
  28. }
  29. static Mat endpointError( const Mat_<Point2f>& flow1, const Mat_<Point2f>& flow2 )
  30. {
  31. Mat result(flow1.size(), CV_32FC1);
  32. for ( int i = 0; i < flow1.rows; ++i )
  33. {
  34. for ( int j = 0; j < flow1.cols; ++j )
  35. {
  36. const Point2f u1 = flow1(i, j);
  37. const Point2f u2 = flow2(i, j);
  38. if ( isFlowCorrect(u1) && isFlowCorrect(u2) )
  39. {
  40. const Point2f diff = u1 - u2;
  41. result.at<float>(i, j) = sqrt((float)diff.ddot(diff)); //distance
  42. } else
  43. result.at<float>(i, j) = std::numeric_limits<float>::quiet_NaN();
  44. }
  45. }
  46. return result;
  47. }
  48. static Mat angularError( const Mat_<Point2f>& flow1, const Mat_<Point2f>& flow2 )
  49. {
  50. Mat result(flow1.size(), CV_32FC1);
  51. for ( int i = 0; i < flow1.rows; ++i )
  52. {
  53. for ( int j = 0; j < flow1.cols; ++j )
  54. {
  55. const Point2f u1_2d = flow1(i, j);
  56. const Point2f u2_2d = flow2(i, j);
  57. const Point3f u1(u1_2d.x, u1_2d.y, 1);
  58. const Point3f u2(u2_2d.x, u2_2d.y, 1);
  59. if ( isFlowCorrect(u1) && isFlowCorrect(u2) )
  60. result.at<float>(i, j) = acos((float)(u1.ddot(u2) / norm(u1) * norm(u2)));
  61. else
  62. result.at<float>(i, j) = std::numeric_limits<float>::quiet_NaN();
  63. }
  64. }
  65. return result;
  66. }
  67. // what fraction of pixels have errors higher than given threshold?
  68. static float stat_RX( Mat errors, float threshold, Mat mask )
  69. {
  70. CV_Assert(errors.size() == mask.size());
  71. CV_Assert(mask.depth() == CV_8U);
  72. int count = 0, all = 0;
  73. for ( int i = 0; i < errors.rows; ++i )
  74. {
  75. for ( int j = 0; j < errors.cols; ++j )
  76. {
  77. if ( mask.at<char>(i, j) != 0 )
  78. {
  79. ++all;
  80. if ( errors.at<float>(i, j) > threshold )
  81. ++count;
  82. }
  83. }
  84. }
  85. return (float)count / all;
  86. }
  87. static float stat_AX( Mat hist, int cutoff_count, float max_value )
  88. {
  89. int counter = 0;
  90. int bin = 0;
  91. int bin_count = hist.rows;
  92. while ( bin < bin_count && counter < cutoff_count )
  93. {
  94. counter += (int) hist.at<float>(bin, 0);
  95. ++bin;
  96. }
  97. return (float) bin / bin_count * max_value;
  98. }
  99. static void calculateStats( Mat errors, Mat mask = Mat(), bool display_images = false )
  100. {
  101. float R_thresholds[] = { 0.5f, 1.f, 2.f, 5.f, 10.f };
  102. float A_thresholds[] = { 0.5f, 0.75f, 0.95f };
  103. if ( mask.empty() )
  104. mask = Mat::ones(errors.size(), CV_8U);
  105. CV_Assert(errors.size() == mask.size());
  106. CV_Assert(mask.depth() == CV_8U);
  107. //displaying the mask
  108. if(display_images)
  109. {
  110. namedWindow( "Region mask", WINDOW_AUTOSIZE );
  111. imshow( "Region mask", mask );
  112. }
  113. //mean and std computation
  114. Scalar s_mean, s_std;
  115. float mean, std;
  116. meanStdDev(errors, s_mean, s_std, mask);
  117. mean = (float)s_mean[0];
  118. std = (float)s_std[0];
  119. printf("Average: %.2f\nStandard deviation: %.2f\n", mean, std);
  120. //RX stats - displayed in percent
  121. float R;
  122. int R_thresholds_count = sizeof(R_thresholds) / sizeof(float);
  123. for ( int i = 0; i < R_thresholds_count; ++i )
  124. {
  125. R = stat_RX(errors, R_thresholds[i], mask);
  126. printf("R%.1f: %.2f%%\n", R_thresholds[i], R * 100);
  127. }
  128. //AX stats
  129. double max_value;
  130. minMaxLoc(errors, NULL, &max_value, NULL, NULL, mask);
  131. Mat hist;
  132. const int n_images = 1;
  133. const int channels[] = { 0 };
  134. const int n_dimensions = 1;
  135. const int hist_bins[] = { 1024 };
  136. const float iranges[] = { 0, (float) max_value };
  137. const float* ranges[] = { iranges };
  138. const bool uniform = true;
  139. const bool accumulate = false;
  140. calcHist(&errors, n_images, channels, mask, hist, n_dimensions, hist_bins, ranges, uniform,
  141. accumulate);
  142. int all_pixels = countNonZero(mask);
  143. int cutoff_count;
  144. float A;
  145. int A_thresholds_count = sizeof(A_thresholds) / sizeof(float);
  146. for ( int i = 0; i < A_thresholds_count; ++i )
  147. {
  148. cutoff_count = (int) (floor(A_thresholds[i] * all_pixels + 0.5f));
  149. A = stat_AX(hist, cutoff_count, (float) max_value);
  150. printf("A%.2f: %.2f\n", A_thresholds[i], A);
  151. }
  152. }
  153. static Mat flowToDisplay(const Mat flow)
  154. {
  155. Mat flow_split[2];
  156. Mat magnitude, angle;
  157. Mat hsv_split[3], hsv, rgb;
  158. split(flow, flow_split);
  159. cartToPolar(flow_split[0], flow_split[1], magnitude, angle, true);
  160. normalize(magnitude, magnitude, 0, 1, NORM_MINMAX);
  161. hsv_split[0] = angle; // already in degrees - no normalization needed
  162. hsv_split[1] = Mat::ones(angle.size(), angle.type());
  163. hsv_split[2] = magnitude;
  164. merge(hsv_split, 3, hsv);
  165. cvtColor(hsv, rgb, COLOR_HSV2BGR);
  166. return rgb;
  167. }
  168. int main( int argc, char** argv )
  169. {
  170. CommandLineParser parser(argc, argv, keys);
  171. parser.about("OpenCV optical flow evaluation app");
  172. if ( parser.has("help") || argc < 4 )
  173. {
  174. parser.printMessage();
  175. printf("EXAMPLES:\n");
  176. printf("./example_optflow_optical_flow_evaluation im1.png im2.png farneback -d \n");
  177. printf("\t - compute flow field between im1 and im2 with farneback's method and display it");
  178. printf("./example_optflow_optical_flow_evaluation im1.png im2.png simpleflow groundtruth.flo \n");
  179. printf("\t - compute error statistics given the groundtruth; all pixels, endpoint error measure");
  180. printf("./example_optflow_optical_flow_evaluation im1.png im2.png farneback groundtruth.flo -m=angular -r=untextured \n");
  181. printf("\t - as before, but with changed error measure and stats computed only about \"untextured\" areas");
  182. printf("\n\n Flow file format description: http://vision.middlebury.edu/flow/code/flow-code/README.txt\n\n");
  183. return 0;
  184. }
  185. String i1_path = parser.get<String>(0);
  186. String i2_path = parser.get<String>(1);
  187. String method = parser.get<String>(2);
  188. String groundtruth_path = parser.get<String>(3);
  189. String error_measure = parser.get<String>("measure");
  190. String region = parser.get<String>("region");
  191. bool display_images = parser.has("display");
  192. const bool useGpu = parser.has("gpu");
  193. if ( !parser.check() )
  194. {
  195. parser.printErrors();
  196. return 0;
  197. }
  198. cv::ocl::setUseOpenCL(useGpu);
  199. printf("OpenCL Enabled: %u\n", useGpu && cv::ocl::haveOpenCL());
  200. Mat i1, i2;
  201. Mat_<Point2f> flow, ground_truth;
  202. Mat computed_errors;
  203. i1 = imread(i1_path, 1);
  204. i2 = imread(i2_path, 1);
  205. if ( !i1.data || !i2.data )
  206. {
  207. printf("No image data \n");
  208. return -1;
  209. }
  210. if ( i1.size() != i2.size() || i1.channels() != i2.channels() )
  211. {
  212. printf("Dimension mismatch between input images\n");
  213. return -1;
  214. }
  215. // 8-bit images expected by all algorithms
  216. if ( i1.depth() != CV_8U )
  217. i1.convertTo(i1, CV_8U);
  218. if ( i2.depth() != CV_8U )
  219. i2.convertTo(i2, CV_8U);
  220. if ( (method == "farneback" || method == "tvl1" || method == "deepflow" || method == "DISflow_ultrafast" || method == "DISflow_fast" || method == "DISflow_medium") && i1.channels() == 3 )
  221. { // 1-channel images are expected
  222. cvtColor(i1, i1, COLOR_BGR2GRAY);
  223. cvtColor(i2, i2, COLOR_BGR2GRAY);
  224. } else if ( method == "simpleflow" && i1.channels() == 1 )
  225. { // 3-channel images expected
  226. cvtColor(i1, i1, COLOR_GRAY2BGR);
  227. cvtColor(i2, i2, COLOR_GRAY2BGR);
  228. }
  229. flow = Mat(i1.size[0], i1.size[1], CV_32FC2);
  230. Ptr<DenseOpticalFlow> algorithm;
  231. if ( method == "farneback" )
  232. algorithm = createOptFlow_Farneback();
  233. else if ( method == "simpleflow" )
  234. algorithm = createOptFlow_SimpleFlow();
  235. else if ( method == "tvl1" )
  236. algorithm = createOptFlow_DualTVL1();
  237. else if ( method == "deepflow" )
  238. algorithm = createOptFlow_DeepFlow();
  239. else if ( method == "sparsetodenseflow" )
  240. algorithm = createOptFlow_SparseToDense();
  241. else if (method == "RLOF_EPIC")
  242. {
  243. algorithm = createOptFlow_DenseRLOF();
  244. Ptr<DenseRLOFOpticalFlow> rlof = algorithm.dynamicCast< DenseRLOFOpticalFlow>();
  245. rlof->setInterpolation(INTERP_EPIC);
  246. rlof->setForwardBackward(1.f);
  247. }
  248. else if (method == "RLOF_RIC")
  249. {
  250. algorithm = createOptFlow_DenseRLOF();
  251. Ptr<DenseRLOFOpticalFlow> rlof = algorithm.dynamicCast< DenseRLOFOpticalFlow>();;
  252. rlof->setInterpolation(INTERP_RIC);
  253. rlof->setForwardBackward(1.f);
  254. }
  255. else if ( method == "pcaflow" ) {
  256. if ( parser.has("prior") ) {
  257. String prior = parser.get<String>("prior");
  258. printf("Using prior file: %s\n", prior.c_str());
  259. algorithm = makePtr<OpticalFlowPCAFlow>(makePtr<PCAPrior>(prior.c_str()));
  260. }
  261. else
  262. algorithm = createOptFlow_PCAFlow();
  263. }
  264. else if ( method == "DISflow_ultrafast" )
  265. algorithm = DISOpticalFlow::create(DISOpticalFlow::PRESET_ULTRAFAST);
  266. else if (method == "DISflow_fast")
  267. algorithm = DISOpticalFlow::create(DISOpticalFlow::PRESET_FAST);
  268. else if (method == "DISflow_medium")
  269. algorithm = DISOpticalFlow::create(DISOpticalFlow::PRESET_MEDIUM);
  270. else
  271. {
  272. printf("Wrong method!\n");
  273. parser.printMessage();
  274. return -1;
  275. }
  276. double startTick, time;
  277. startTick = (double) getTickCount(); // measure time
  278. if (useGpu)
  279. algorithm->calc(i1, i2, flow.getUMat(ACCESS_RW));
  280. else
  281. algorithm->calc(i1, i2, flow);
  282. time = ((double) getTickCount() - startTick) / getTickFrequency();
  283. printf("\nTime [s]: %.3f\n", time);
  284. if(display_images)
  285. {
  286. Mat flow_image = flowToDisplay(flow);
  287. namedWindow( "Computed flow", WINDOW_AUTOSIZE );
  288. imshow( "Computed flow", flow_image );
  289. }
  290. if ( !groundtruth_path.empty() )
  291. { // compare to ground truth
  292. ground_truth = readOpticalFlow(groundtruth_path);
  293. if ( flow.size() != ground_truth.size() || flow.channels() != 2
  294. || ground_truth.channels() != 2 )
  295. {
  296. printf("Dimension mismatch between the computed flow and the provided ground truth\n");
  297. return -1;
  298. }
  299. if ( error_measure == "endpoint" )
  300. computed_errors = endpointError(flow, ground_truth);
  301. else if ( error_measure == "angular" )
  302. computed_errors = angularError(flow, ground_truth);
  303. else
  304. {
  305. printf("Invalid error measure! Available options: endpoint, angular\n");
  306. return -1;
  307. }
  308. Mat mask;
  309. if( region == "all" )
  310. mask = Mat::ones(ground_truth.size(), CV_8U) * 255;
  311. else if ( region == "discontinuities" )
  312. {
  313. Mat truth_merged, grad_x, grad_y, gradient;
  314. vector<Mat> truth_split;
  315. split(ground_truth, truth_split);
  316. truth_merged = truth_split[0] + truth_split[1];
  317. Sobel( truth_merged, grad_x, CV_16S, 1, 0, -1, 1, 0, BORDER_REPLICATE );
  318. grad_x = abs(grad_x);
  319. Sobel( truth_merged, grad_y, CV_16S, 0, 1, 1, 1, 0, BORDER_REPLICATE );
  320. grad_y = abs(grad_y);
  321. addWeighted(grad_x, 0.5, grad_y, 0.5, 0, gradient); //approximation!
  322. Scalar s_mean;
  323. s_mean = mean(gradient);
  324. double threshold = s_mean[0]; // threshold value arbitrary
  325. mask = gradient > threshold;
  326. dilate(mask, mask, Mat::ones(9, 9, CV_8U));
  327. }
  328. else if ( region == "untextured" )
  329. {
  330. Mat i1_grayscale, grad_x, grad_y, gradient;
  331. if( i1.channels() == 3 )
  332. cvtColor(i1, i1_grayscale, COLOR_BGR2GRAY);
  333. else
  334. i1_grayscale = i1;
  335. Sobel( i1_grayscale, grad_x, CV_16S, 1, 0, 7 );
  336. grad_x = abs(grad_x);
  337. Sobel( i1_grayscale, grad_y, CV_16S, 0, 1, 7 );
  338. grad_y = abs(grad_y);
  339. addWeighted(grad_x, 0.5, grad_y, 0.5, 0, gradient); //approximation!
  340. GaussianBlur(gradient, gradient, Size(5,5), 1, 1);
  341. Scalar s_mean;
  342. s_mean = mean(gradient);
  343. // arbitrary threshold value used - could be determined statistically from the image?
  344. double threshold = 1000;
  345. mask = gradient < threshold;
  346. dilate(mask, mask, Mat::ones(3, 3, CV_8U));
  347. }
  348. else
  349. {
  350. printf("Invalid region selected! Available options: all, discontinuities, untextured");
  351. return -1;
  352. }
  353. //masking out NaNs and incorrect GT values
  354. Mat truth_split[2];
  355. split(ground_truth, truth_split);
  356. Mat abs_mask = Mat((abs(truth_split[0]) < 1e9) & (abs(truth_split[1]) < 1e9));
  357. Mat nan_mask = Mat((truth_split[0]==truth_split[0]) & (truth_split[1] == truth_split[1]));
  358. bitwise_and(abs_mask, nan_mask, nan_mask);
  359. bitwise_and(nan_mask, mask, mask); //including the selected region
  360. if(display_images) // display difference between computed and GT flow
  361. {
  362. Mat difference = ground_truth - flow;
  363. Mat masked_difference;
  364. difference.copyTo(masked_difference, mask);
  365. Mat flow_image = flowToDisplay(masked_difference);
  366. namedWindow( "Error map", WINDOW_AUTOSIZE );
  367. imshow( "Error map", flow_image );
  368. }
  369. printf("Using %s error measure\n", error_measure.c_str());
  370. calculateStats(computed_errors, mask, display_images);
  371. }
  372. if(display_images) // wait for the user to see all the images
  373. waitKey(0);
  374. return 0;
  375. }