opencv_visualisation.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. ////////////////////////////////////////////////////////////////////////////////////////
  2. //
  3. // IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
  4. //
  5. // By downloading, copying, installing or using the software you agree to this license.
  6. // If you do not agree to this license, do not download, install,
  7. // copy or use the software.
  8. //
  9. //
  10. // License Agreement
  11. // For Open Source Computer Vision Library
  12. //
  13. // Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
  14. // Copyright (C) 2009, Willow Garage Inc., all rights reserved.
  15. // Copyright (C) 2013, OpenCV Foundation, all rights reserved.
  16. // Third party copyrights are property of their respective owners.
  17. //
  18. // Redistribution and use in source and binary forms, with or without modification,
  19. // are permitted provided that the following conditions are met:
  20. //
  21. // * Redistribution's of source code must retain the above copyright notice,
  22. // this list of conditions and the following disclaimer.
  23. //
  24. // * Redistribution's in binary form must reproduce the above copyright notice,
  25. // this list of conditions and the following disclaimer in the documentation
  26. // and/or other materials provided with the distribution.
  27. //
  28. // * The name of the copyright holders may not be used to endorse or promote products
  29. // derived from this software without specific prior written permission.
  30. //
  31. // This software is provided by the copyright holders and contributors "as is" and
  32. // any express or implied warranties, including, but not limited to, the implied
  33. // warranties of merchantability and fitness for a particular purpose are disclaimed.
  34. // In no event shall the Intel Corporation or contributors be liable for any direct,
  35. // indirect, incidental, special, exemplary, or consequential damages
  36. // (including, but not limited to, procurement of substitute goods or services;
  37. // loss of use, data, or profits; or business interruption) however caused
  38. // and on any theory of liability, whether in contract, strict liability,
  39. // or tort (including negligence or otherwise) arising in any way out of
  40. // the use of this software, even if advised of the possibility of such damage.
  41. //
  42. ////////////////////////////////////////////////////////////////////////////////////////
  43. /*****************************************************************************************************
  44. Software for visualising cascade classifier models trained by OpenCV and to get a better
  45. understanding of the used features.
  46. USAGE:
  47. ./opencv_visualisation --model=<model.xml> --image=<ref.png> --data=<video output folder>
  48. Created by: Puttemans Steven - April 2016
  49. *****************************************************************************************************/
  50. #include <opencv2/core.hpp>
  51. #include <opencv2/highgui.hpp>
  52. #include <opencv2/imgproc.hpp>
  53. #include <opencv2/imgcodecs.hpp>
  54. #include <opencv2/videoio.hpp>
  55. #include <fstream>
  56. #include <iostream>
  57. #include <sstream>
  58. using namespace std;
  59. using namespace cv;
  60. struct rect_data{
  61. int x;
  62. int y;
  63. int w;
  64. int h;
  65. float weight;
  66. };
  67. static void printLimits(){
  68. cerr << "Limits of the current interface:" << endl;
  69. cerr << " - Only handles cascade classifier models, trained with the opencv_traincascade tool, containing stumps as decision trees [default settings]." << endl;
  70. cerr << " - The image provided needs to be a sample window with the original model dimensions, passed to the --image parameter." << endl;
  71. cerr << " - ONLY handles HAAR and LBP features." << endl;
  72. }
  73. int main( int argc, const char** argv )
  74. {
  75. CommandLineParser parser(argc, argv,
  76. "{ help h usage ? | | show this message }"
  77. "{ image i | | (required) path to reference image }"
  78. "{ model m | | (required) path to cascade xml file }"
  79. "{ data d | | (optional) path to video output folder }"
  80. "{ ext | avi | (optional) output video file extension e.g. avi (default) or mp4 }"
  81. "{ fourcc | XVID | (optional) output video file's 4-character codec e.g. XVID (default) or H264 }"
  82. "{ fps | 15 | (optional) output video file's frames-per-second rate }"
  83. );
  84. // Read in the input arguments
  85. if (parser.has("help")){
  86. parser.printMessage();
  87. printLimits();
  88. return 0;
  89. }
  90. string model(parser.get<string>("model"));
  91. string output_folder(parser.get<string>("data"));
  92. string image_ref = (parser.get<string>("image"));
  93. string fourcc = (parser.get<string>("fourcc"));
  94. int fps = parser.get<int>("fps");
  95. if (model.empty() || image_ref.empty() || fourcc.size()!=4 || fps<1){
  96. parser.printMessage();
  97. printLimits();
  98. return -1;
  99. }
  100. // Value for timing
  101. // You can increase this to have a better visualisation during the generation
  102. int timing = 1;
  103. // Value for cols of storing elements
  104. int cols_preferred = 5;
  105. // Open the XML model
  106. FileStorage fs;
  107. bool model_ok = fs.open(model, FileStorage::READ);
  108. if (!model_ok){
  109. cerr << "the cascade file '" << model << "' could not be loaded." << endl;
  110. return -1;
  111. }
  112. // Get a the required information
  113. // First decide which feature type we are using
  114. FileNode cascade = fs["cascade"];
  115. string feature_type = cascade["featureType"];
  116. bool haar = false, lbp = false;
  117. if (feature_type.compare("HAAR") == 0){
  118. haar = true;
  119. }
  120. if (feature_type.compare("LBP") == 0){
  121. lbp = true;
  122. }
  123. if ( feature_type.compare("HAAR") != 0 && feature_type.compare("LBP")){
  124. cerr << "The model is not an HAAR or LBP feature based model!" << endl;
  125. cerr << "Please select a model that can be visualized by the software." << endl;
  126. return -1;
  127. }
  128. // We make a visualisation mask - which increases the window to make it at least a bit more visible
  129. int resize_factor = 10;
  130. int resize_storage_factor = 10;
  131. Mat reference_image = imread(image_ref, IMREAD_GRAYSCALE );
  132. if (reference_image.empty()){
  133. cerr << "the reference image '" << image_ref << "'' could not be loaded." << endl;
  134. return -1;
  135. }
  136. Mat visualization;
  137. resize(reference_image, visualization, Size(reference_image.cols * resize_factor, reference_image.rows * resize_factor), 0, 0, INTER_LINEAR_EXACT);
  138. // First recover for each stage the number of weak features and their index
  139. // Important since it is NOT sequential when using LBP features
  140. vector< vector<int> > stage_features;
  141. FileNode stages = cascade["stages"];
  142. FileNodeIterator it_stages = stages.begin(), it_stages_end = stages.end();
  143. int idx = 0;
  144. for( ; it_stages != it_stages_end; it_stages++, idx++ ){
  145. vector<int> current_feature_indexes;
  146. FileNode weak_classifiers = (*it_stages)["weakClassifiers"];
  147. FileNodeIterator it_weak = weak_classifiers.begin(), it_weak_end = weak_classifiers.end();
  148. vector<int> values;
  149. for(int idy = 0; it_weak != it_weak_end; it_weak++, idy++ ){
  150. (*it_weak)["internalNodes"] >> values;
  151. current_feature_indexes.push_back( (int)values[2] );
  152. }
  153. stage_features.push_back(current_feature_indexes);
  154. }
  155. // If the output option has been chosen than we will store a combined image plane for
  156. // each stage, containing all weak classifiers for that stage.
  157. bool draw_planes = false;
  158. stringstream output_video;
  159. output_video << output_folder << "model_visualization." << parser.get<string>("ext");
  160. VideoWriter result_video;
  161. if( output_folder.compare("") != 0 ){
  162. draw_planes = true;
  163. result_video.open(output_video.str(), VideoWriter::fourcc(fourcc[0],fourcc[1],fourcc[2],fourcc[3]), fps, visualization.size(), false);
  164. if (!result_video.isOpened()){
  165. cerr << "the output video '" << output_video.str() << "' could not be opened."
  166. << " fourcc=" << fourcc
  167. << " fps=" << fps
  168. << " frameSize=" << visualization.size()
  169. << endl;
  170. return -1;
  171. }
  172. }
  173. if(haar){
  174. // Grab the corresponding features dimensions and weights
  175. FileNode features = cascade["features"];
  176. vector< vector< rect_data > > feature_data;
  177. FileNodeIterator it_features = features.begin(), it_features_end = features.end();
  178. for(int idf = 0; it_features != it_features_end; it_features++, idf++ ){
  179. vector< rect_data > current_feature_rectangles;
  180. FileNode rectangles = (*it_features)["rects"];
  181. int nrects = (int)rectangles.size();
  182. for(int k = 0; k < nrects; k++){
  183. rect_data current_data;
  184. FileNode single_rect = rectangles[k];
  185. current_data.x = (int)single_rect[0];
  186. current_data.y = (int)single_rect[1];
  187. current_data.w = (int)single_rect[2];
  188. current_data.h = (int)single_rect[3];
  189. current_data.weight = (float)single_rect[4];
  190. current_feature_rectangles.push_back(current_data);
  191. }
  192. feature_data.push_back(current_feature_rectangles);
  193. }
  194. // Loop over each possible feature on its index, visualise on the mask and wait a bit,
  195. // then continue to the next feature.
  196. // If visualisations should be stored then do the in between calculations
  197. Mat image_plane;
  198. Mat metadata = Mat::zeros(150, 1000, CV_8UC1);
  199. vector< rect_data > current_rects;
  200. for(int sid = 0; sid < (int)stage_features.size(); sid ++){
  201. if(draw_planes){
  202. int features_nmbr = (int)stage_features[sid].size();
  203. int cols = cols_preferred;
  204. int rows = features_nmbr / cols;
  205. if( (features_nmbr % cols) > 0){
  206. rows++;
  207. }
  208. image_plane = Mat::zeros(reference_image.rows * resize_storage_factor * rows, reference_image.cols * resize_storage_factor * cols, CV_8UC1);
  209. }
  210. for(int fid = 0; fid < (int)stage_features[sid].size(); fid++){
  211. stringstream meta1, meta2;
  212. meta1 << "Stage " << sid << " / Feature " << fid;
  213. meta2 << "Rectangles: ";
  214. Mat temp_window = visualization.clone();
  215. Mat temp_metadata = metadata.clone();
  216. int current_feature_index = stage_features[sid][fid];
  217. current_rects = feature_data[current_feature_index];
  218. Mat single_feature = reference_image.clone();
  219. resize(single_feature, single_feature, Size(), resize_storage_factor, resize_storage_factor, INTER_LINEAR_EXACT);
  220. for(int i = 0; i < (int)current_rects.size(); i++){
  221. rect_data local = current_rects[i];
  222. if(draw_planes){
  223. if(local.weight >= 0){
  224. rectangle(single_feature, Rect(local.x * resize_storage_factor, local.y * resize_storage_factor, local.w * resize_storage_factor, local.h * resize_storage_factor), Scalar(0), FILLED);
  225. }else{
  226. rectangle(single_feature, Rect(local.x * resize_storage_factor, local.y * resize_storage_factor, local.w * resize_storage_factor, local.h * resize_storage_factor), Scalar(255), FILLED);
  227. }
  228. }
  229. Rect part(local.x * resize_factor, local.y * resize_factor, local.w * resize_factor, local.h * resize_factor);
  230. meta2 << part << " (w " << local.weight << ") ";
  231. if(local.weight >= 0){
  232. rectangle(temp_window, part, Scalar(0), FILLED);
  233. }else{
  234. rectangle(temp_window, part, Scalar(255), FILLED);
  235. }
  236. }
  237. imshow("features", temp_window);
  238. putText(temp_window, meta1.str(), Point(15,15), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(255));
  239. result_video.write(temp_window);
  240. // Copy the feature image if needed
  241. if(draw_planes){
  242. single_feature.copyTo(image_plane(Rect(0 + (fid%cols_preferred)*single_feature.cols, 0 + (fid/cols_preferred) * single_feature.rows, single_feature.cols, single_feature.rows)));
  243. }
  244. putText(temp_metadata, meta1.str(), Point(15,15), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(255));
  245. putText(temp_metadata, meta2.str(), Point(15,40), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(255));
  246. imshow("metadata", temp_metadata);
  247. waitKey(timing);
  248. }
  249. //Store the stage image if needed
  250. if(draw_planes){
  251. stringstream save_location;
  252. save_location << output_folder << "stage_" << sid << ".png";
  253. imwrite(save_location.str(), image_plane);
  254. }
  255. }
  256. }
  257. if(lbp){
  258. // Grab the corresponding features dimensions and weights
  259. FileNode features = cascade["features"];
  260. vector<Rect> feature_data;
  261. FileNodeIterator it_features = features.begin(), it_features_end = features.end();
  262. for(int idf = 0; it_features != it_features_end; it_features++, idf++ ){
  263. FileNode rectangle = (*it_features)["rect"];
  264. Rect current_feature ((int)rectangle[0], (int)rectangle[1], (int)rectangle[2], (int)rectangle[3]);
  265. feature_data.push_back(current_feature);
  266. }
  267. // Loop over each possible feature on its index, visualise on the mask and wait a bit,
  268. // then continue to the next feature.
  269. Mat image_plane;
  270. Mat metadata = Mat::zeros(150, 1000, CV_8UC1);
  271. for(int sid = 0; sid < (int)stage_features.size(); sid ++){
  272. if(draw_planes){
  273. int features_nmbr = (int)stage_features[sid].size();
  274. int cols = cols_preferred;
  275. int rows = features_nmbr / cols;
  276. if( (features_nmbr % cols) > 0){
  277. rows++;
  278. }
  279. image_plane = Mat::zeros(reference_image.rows * resize_storage_factor * rows, reference_image.cols * resize_storage_factor * cols, CV_8UC1);
  280. }
  281. for(int fid = 0; fid < (int)stage_features[sid].size(); fid++){
  282. stringstream meta1, meta2;
  283. meta1 << "Stage " << sid << " / Feature " << fid;
  284. meta2 << "Rectangle: ";
  285. Mat temp_window = visualization.clone();
  286. Mat temp_metadata = metadata.clone();
  287. int current_feature_index = stage_features[sid][fid];
  288. Rect current_rect = feature_data[current_feature_index];
  289. Mat single_feature = reference_image.clone();
  290. resize(single_feature, single_feature, Size(), resize_storage_factor, resize_storage_factor, INTER_LINEAR_EXACT);
  291. // VISUALISATION
  292. // The rectangle is the top left one of a 3x3 block LBP constructor
  293. Rect resized(current_rect.x * resize_factor, current_rect.y * resize_factor, current_rect.width * resize_factor, current_rect.height * resize_factor);
  294. meta2 << resized;
  295. // Top left
  296. rectangle(temp_window, resized, Scalar(255), 1);
  297. // Top middle
  298. rectangle(temp_window, Rect(resized.x + resized.width, resized.y, resized.width, resized.height), Scalar(255), 1);
  299. // Top right
  300. rectangle(temp_window, Rect(resized.x + 2*resized.width, resized.y, resized.width, resized.height), Scalar(255), 1);
  301. // Middle left
  302. rectangle(temp_window, Rect(resized.x, resized.y + resized.height, resized.width, resized.height), Scalar(255), 1);
  303. // Middle middle
  304. rectangle(temp_window, Rect(resized.x + resized.width, resized.y + resized.height, resized.width, resized.height), Scalar(255), FILLED);
  305. // Middle right
  306. rectangle(temp_window, Rect(resized.x + 2*resized.width, resized.y + resized.height, resized.width, resized.height), Scalar(255), 1);
  307. // Bottom left
  308. rectangle(temp_window, Rect(resized.x, resized.y + 2*resized.height, resized.width, resized.height), Scalar(255), 1);
  309. // Bottom middle
  310. rectangle(temp_window, Rect(resized.x + resized.width, resized.y + 2*resized.height, resized.width, resized.height), Scalar(255), 1);
  311. // Bottom right
  312. rectangle(temp_window, Rect(resized.x + 2*resized.width, resized.y + 2*resized.height, resized.width, resized.height), Scalar(255), 1);
  313. if(draw_planes){
  314. Rect resized_inner(current_rect.x * resize_storage_factor, current_rect.y * resize_storage_factor, current_rect.width * resize_storage_factor, current_rect.height * resize_storage_factor);
  315. // Top left
  316. rectangle(single_feature, resized_inner, Scalar(255), 1);
  317. // Top middle
  318. rectangle(single_feature, Rect(resized_inner.x + resized_inner.width, resized_inner.y, resized_inner.width, resized_inner.height), Scalar(255), 1);
  319. // Top right
  320. rectangle(single_feature, Rect(resized_inner.x + 2*resized_inner.width, resized_inner.y, resized_inner.width, resized_inner.height), Scalar(255), 1);
  321. // Middle left
  322. rectangle(single_feature, Rect(resized_inner.x, resized_inner.y + resized_inner.height, resized_inner.width, resized_inner.height), Scalar(255), 1);
  323. // Middle middle
  324. rectangle(single_feature, Rect(resized_inner.x + resized_inner.width, resized_inner.y + resized_inner.height, resized_inner.width, resized_inner.height), Scalar(255), FILLED);
  325. // Middle right
  326. rectangle(single_feature, Rect(resized_inner.x + 2*resized_inner.width, resized_inner.y + resized_inner.height, resized_inner.width, resized_inner.height), Scalar(255), 1);
  327. // Bottom left
  328. rectangle(single_feature, Rect(resized_inner.x, resized_inner.y + 2*resized_inner.height, resized_inner.width, resized_inner.height), Scalar(255), 1);
  329. // Bottom middle
  330. rectangle(single_feature, Rect(resized_inner.x + resized_inner.width, resized_inner.y + 2*resized_inner.height, resized_inner.width, resized_inner.height), Scalar(255), 1);
  331. // Bottom right
  332. rectangle(single_feature, Rect(resized_inner.x + 2*resized_inner.width, resized_inner.y + 2*resized_inner.height, resized_inner.width, resized_inner.height), Scalar(255), 1);
  333. single_feature.copyTo(image_plane(Rect(0 + (fid%cols_preferred)*single_feature.cols, 0 + (fid/cols_preferred) * single_feature.rows, single_feature.cols, single_feature.rows)));
  334. }
  335. putText(temp_metadata, meta1.str(), Point(15,15), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(255));
  336. putText(temp_metadata, meta2.str(), Point(15,40), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(255));
  337. imshow("metadata", temp_metadata);
  338. imshow("features", temp_window);
  339. putText(temp_window, meta1.str(), Point(15,15), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(255));
  340. result_video.write(temp_window);
  341. waitKey(timing);
  342. }
  343. //Store the stage image if needed
  344. if(draw_planes){
  345. stringstream save_location;
  346. save_location << output_folder << "stage_" << sid << ".png";
  347. imwrite(save_location.str(), image_plane);
  348. }
  349. }
  350. }
  351. return 0;
  352. }