image_alignment.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. /*
  2. * This sample demonstrates the use of the function
  3. * findTransformECC that implements the image alignment ECC algorithm
  4. *
  5. *
  6. * The demo loads an image (defaults to fruits.jpg) and it artificially creates
  7. * a template image based on the given motion type. When two images are given,
  8. * the first image is the input image and the second one defines the template image.
  9. * In the latter case, you can also parse the warp's initialization.
  10. *
  11. * Input and output warp files consist of the raw warp (transform) elements
  12. *
  13. * Authors: G. Evangelidis, INRIA, Grenoble, France
  14. * M. Asbach, Fraunhofer IAIS, St. Augustin, Germany
  15. */
  16. #include <opencv2/imgcodecs.hpp>
  17. #include <opencv2/highgui.hpp>
  18. #include <opencv2/video.hpp>
  19. #include <opencv2/imgproc.hpp>
  20. #include <opencv2/core/utility.hpp>
  21. #include <stdio.h>
  22. #include <string>
  23. #include <time.h>
  24. #include <iostream>
  25. #include <fstream>
  26. using namespace cv;
  27. using namespace std;
  28. static void help(const char** argv);
  29. static int readWarp(string iFilename, Mat& warp, int motionType);
  30. static int saveWarp(string fileName, const Mat& warp, int motionType);
  31. static void draw_warped_roi(Mat& image, const int width, const int height, Mat& W);
  32. #define HOMO_VECTOR(H, x, y)\
  33. H.at<float>(0,0) = (float)(x);\
  34. H.at<float>(1,0) = (float)(y);\
  35. H.at<float>(2,0) = 1.;
  36. #define GET_HOMO_VALUES(X, x, y)\
  37. (x) = static_cast<float> (X.at<float>(0,0)/X.at<float>(2,0));\
  38. (y) = static_cast<float> (X.at<float>(1,0)/X.at<float>(2,0));
  39. const std::string keys =
  40. "{@inputImage | fruits.jpg | input image filename }"
  41. "{@templateImage | | template image filename (optional)}"
  42. "{@inputWarp | | input warp (matrix) filename (optional)}"
  43. "{n numOfIter | 50 | ECC's iterations }"
  44. "{e epsilon | 0.0001 | ECC's convergence epsilon }"
  45. "{o outputWarp | outWarp.ecc | output warp (matrix) filename }"
  46. "{m motionType | affine | type of motion (translation, euclidean, affine, homography) }"
  47. "{v verbose | 1 | display initial and final images }"
  48. "{w warpedImfile | warpedECC.png | warped input image }"
  49. "{h help | | print help message }"
  50. ;
  51. static void help(const char** argv)
  52. {
  53. cout << "\nThis file demonstrates the use of the ECC image alignment algorithm. When one image"
  54. " is given, the template image is artificially formed by a random warp. When both images"
  55. " are given, the initialization of the warp by command line parsing is possible. "
  56. "If inputWarp is missing, the identity transformation initializes the algorithm. \n" << endl;
  57. cout << "\nUsage example (one image): \n"
  58. << argv[0]
  59. << " fruits.jpg -o=outWarp.ecc "
  60. "-m=euclidean -e=1e-6 -N=70 -v=1 \n" << endl;
  61. cout << "\nUsage example (two images with initialization): \n"
  62. << argv[0]
  63. << " yourInput.png yourTemplate.png "
  64. "yourInitialWarp.ecc -o=outWarp.ecc -m=homography -e=1e-6 -N=70 -v=1 -w=yourFinalImage.png \n" << endl;
  65. }
  66. static int readWarp(string iFilename, Mat& warp, int motionType){
  67. // it reads from file a specific number of raw values:
  68. // 9 values for homography, 6 otherwise
  69. CV_Assert(warp.type()==CV_32FC1);
  70. int numOfElements;
  71. if (motionType==MOTION_HOMOGRAPHY)
  72. numOfElements=9;
  73. else
  74. numOfElements=6;
  75. int i;
  76. int ret_value;
  77. ifstream myfile(iFilename.c_str());
  78. if (myfile.is_open()){
  79. float* matPtr = warp.ptr<float>(0);
  80. for(i=0; i<numOfElements; i++){
  81. myfile >> matPtr[i];
  82. }
  83. ret_value = 1;
  84. }
  85. else {
  86. cout << "Unable to open file " << iFilename.c_str() << endl;
  87. ret_value = 0;
  88. }
  89. return ret_value;
  90. }
  91. static int saveWarp(string fileName, const Mat& warp, int motionType)
  92. {
  93. // it saves the raw matrix elements in a file
  94. CV_Assert(warp.type()==CV_32FC1);
  95. const float* matPtr = warp.ptr<float>(0);
  96. int ret_value;
  97. ofstream outfile(fileName.c_str());
  98. if( !outfile ) {
  99. cerr << "error in saving "
  100. << "Couldn't open file '" << fileName.c_str() << "'!" << endl;
  101. ret_value = 0;
  102. }
  103. else {//save the warp's elements
  104. outfile << matPtr[0] << " " << matPtr[1] << " " << matPtr[2] << endl;
  105. outfile << matPtr[3] << " " << matPtr[4] << " " << matPtr[5] << endl;
  106. if (motionType==MOTION_HOMOGRAPHY){
  107. outfile << matPtr[6] << " " << matPtr[7] << " " << matPtr[8] << endl;
  108. }
  109. ret_value = 1;
  110. }
  111. return ret_value;
  112. }
  113. static void draw_warped_roi(Mat& image, const int width, const int height, Mat& W)
  114. {
  115. Point2f top_left, top_right, bottom_left, bottom_right;
  116. Mat H = Mat (3, 1, CV_32F);
  117. Mat U = Mat (3, 1, CV_32F);
  118. Mat warp_mat = Mat::eye (3, 3, CV_32F);
  119. for (int y = 0; y < W.rows; y++)
  120. for (int x = 0; x < W.cols; x++)
  121. warp_mat.at<float>(y,x) = W.at<float>(y,x);
  122. //warp the corners of rectangle
  123. // top-left
  124. HOMO_VECTOR(H, 1, 1);
  125. gemm(warp_mat, H, 1, 0, 0, U);
  126. GET_HOMO_VALUES(U, top_left.x, top_left.y);
  127. // top-right
  128. HOMO_VECTOR(H, width, 1);
  129. gemm(warp_mat, H, 1, 0, 0, U);
  130. GET_HOMO_VALUES(U, top_right.x, top_right.y);
  131. // bottom-left
  132. HOMO_VECTOR(H, 1, height);
  133. gemm(warp_mat, H, 1, 0, 0, U);
  134. GET_HOMO_VALUES(U, bottom_left.x, bottom_left.y);
  135. // bottom-right
  136. HOMO_VECTOR(H, width, height);
  137. gemm(warp_mat, H, 1, 0, 0, U);
  138. GET_HOMO_VALUES(U, bottom_right.x, bottom_right.y);
  139. // draw the warped perimeter
  140. line(image, top_left, top_right, Scalar(255));
  141. line(image, top_right, bottom_right, Scalar(255));
  142. line(image, bottom_right, bottom_left, Scalar(255));
  143. line(image, bottom_left, top_left, Scalar(255));
  144. }
  145. int main (const int argc, const char * argv[])
  146. {
  147. CommandLineParser parser(argc, argv, keys);
  148. parser.about("ECC demo");
  149. parser.printMessage();
  150. help(argv);
  151. string imgFile = parser.get<string>(0);
  152. string tempImgFile = parser.get<string>(1);
  153. string inWarpFile = parser.get<string>(2);
  154. int number_of_iterations = parser.get<int>("n");
  155. double termination_eps = parser.get<double>("e");
  156. string warpType = parser.get<string>("m");
  157. int verbose = parser.get<int>("v");
  158. string finalWarp = parser.get<string>("o");
  159. string warpedImFile = parser.get<string>("w");
  160. if (!parser.check())
  161. {
  162. parser.printErrors();
  163. return -1;
  164. }
  165. if (!(warpType == "translation" || warpType == "euclidean"
  166. || warpType == "affine" || warpType == "homography"))
  167. {
  168. cerr << "Invalid motion transformation" << endl;
  169. return -1;
  170. }
  171. int mode_temp;
  172. if (warpType == "translation")
  173. mode_temp = MOTION_TRANSLATION;
  174. else if (warpType == "euclidean")
  175. mode_temp = MOTION_EUCLIDEAN;
  176. else if (warpType == "affine")
  177. mode_temp = MOTION_AFFINE;
  178. else
  179. mode_temp = MOTION_HOMOGRAPHY;
  180. Mat inputImage = imread(samples::findFile(imgFile), IMREAD_GRAYSCALE);
  181. if (inputImage.empty())
  182. {
  183. cerr << "Unable to load the inputImage" << endl;
  184. return -1;
  185. }
  186. Mat target_image;
  187. Mat template_image;
  188. if (tempImgFile!="") {
  189. inputImage.copyTo(target_image);
  190. template_image = imread(samples::findFile(tempImgFile), IMREAD_GRAYSCALE);
  191. if (template_image.empty()){
  192. cerr << "Unable to load the template image" << endl;
  193. return -1;
  194. }
  195. }
  196. else{ //apply random warp to input image
  197. resize(inputImage, target_image, Size(216, 216), 0, 0, INTER_LINEAR_EXACT);
  198. Mat warpGround;
  199. RNG rng(getTickCount());
  200. double angle;
  201. switch (mode_temp) {
  202. case MOTION_TRANSLATION:
  203. warpGround = (Mat_<float>(2,3) << 1, 0, (rng.uniform(10.f, 20.f)),
  204. 0, 1, (rng.uniform(10.f, 20.f)));
  205. warpAffine(target_image, template_image, warpGround,
  206. Size(200,200), INTER_LINEAR + WARP_INVERSE_MAP);
  207. break;
  208. case MOTION_EUCLIDEAN:
  209. angle = CV_PI/30 + CV_PI*rng.uniform((double)-2.f, (double)2.f)/180;
  210. warpGround = (Mat_<float>(2,3) << cos(angle), -sin(angle), (rng.uniform(10.f, 20.f)),
  211. sin(angle), cos(angle), (rng.uniform(10.f, 20.f)));
  212. warpAffine(target_image, template_image, warpGround,
  213. Size(200,200), INTER_LINEAR + WARP_INVERSE_MAP);
  214. break;
  215. case MOTION_AFFINE:
  216. warpGround = (Mat_<float>(2,3) << (1-rng.uniform(-0.05f, 0.05f)),
  217. (rng.uniform(-0.03f, 0.03f)), (rng.uniform(10.f, 20.f)),
  218. (rng.uniform(-0.03f, 0.03f)), (1-rng.uniform(-0.05f, 0.05f)),
  219. (rng.uniform(10.f, 20.f)));
  220. warpAffine(target_image, template_image, warpGround,
  221. Size(200,200), INTER_LINEAR + WARP_INVERSE_MAP);
  222. break;
  223. case MOTION_HOMOGRAPHY:
  224. warpGround = (Mat_<float>(3,3) << (1-rng.uniform(-0.05f, 0.05f)),
  225. (rng.uniform(-0.03f, 0.03f)), (rng.uniform(10.f, 20.f)),
  226. (rng.uniform(-0.03f, 0.03f)), (1-rng.uniform(-0.05f, 0.05f)),(rng.uniform(10.f, 20.f)),
  227. (rng.uniform(0.0001f, 0.0003f)), (rng.uniform(0.0001f, 0.0003f)), 1.f);
  228. warpPerspective(target_image, template_image, warpGround,
  229. Size(200,200), INTER_LINEAR + WARP_INVERSE_MAP);
  230. break;
  231. }
  232. }
  233. const int warp_mode = mode_temp;
  234. // initialize or load the warp matrix
  235. Mat warp_matrix;
  236. if (warpType == "homography")
  237. warp_matrix = Mat::eye(3, 3, CV_32F);
  238. else
  239. warp_matrix = Mat::eye(2, 3, CV_32F);
  240. if (inWarpFile!=""){
  241. int readflag = readWarp(inWarpFile, warp_matrix, warp_mode);
  242. if ((!readflag) || warp_matrix.empty())
  243. {
  244. cerr << "-> Check warp initialization file" << endl << flush;
  245. return -1;
  246. }
  247. }
  248. else {
  249. printf("\n ->Performance Warning: Identity warp ideally assumes images of "
  250. "similar size. If the deformation is strong, the identity warp may not "
  251. "be a good initialization. \n");
  252. }
  253. if (number_of_iterations > 200)
  254. cout << "-> Warning: too many iterations " << endl;
  255. if (warp_mode != MOTION_HOMOGRAPHY)
  256. warp_matrix.rows = 2;
  257. // start timing
  258. const double tic_init = (double) getTickCount ();
  259. double cc = findTransformECC (template_image, target_image, warp_matrix, warp_mode,
  260. TermCriteria (TermCriteria::COUNT+TermCriteria::EPS,
  261. number_of_iterations, termination_eps));
  262. if (cc == -1)
  263. {
  264. cerr << "The execution was interrupted. The correlation value is going to be minimized." << endl;
  265. cerr << "Check the warp initialization and/or the size of images." << endl << flush;
  266. }
  267. // end timing
  268. const double toc_final = (double) getTickCount ();
  269. const double total_time = (toc_final-tic_init)/(getTickFrequency());
  270. if (verbose){
  271. cout << "Alignment time (" << warpType << " transformation): "
  272. << total_time << " sec" << endl << flush;
  273. // cout << "Final correlation: " << cc << endl << flush;
  274. }
  275. // save the final warp matrix
  276. saveWarp(finalWarp, warp_matrix, warp_mode);
  277. if (verbose){
  278. cout << "\nThe final warp has been saved in the file: " << finalWarp << endl << flush;
  279. }
  280. // save the final warped image
  281. Mat warped_image = Mat(template_image.rows, template_image.cols, CV_32FC1);
  282. if (warp_mode != MOTION_HOMOGRAPHY)
  283. warpAffine (target_image, warped_image, warp_matrix, warped_image.size(),
  284. INTER_LINEAR + WARP_INVERSE_MAP);
  285. else
  286. warpPerspective (target_image, warped_image, warp_matrix, warped_image.size(),
  287. INTER_LINEAR + WARP_INVERSE_MAP);
  288. //save the warped image
  289. imwrite(warpedImFile, warped_image);
  290. // display resulting images
  291. if (verbose)
  292. {
  293. cout << "The warped image has been saved in the file: " << warpedImFile << endl << flush;
  294. namedWindow ("image", WINDOW_AUTOSIZE);
  295. namedWindow ("template", WINDOW_AUTOSIZE);
  296. namedWindow ("warped image", WINDOW_AUTOSIZE);
  297. namedWindow ("error (black: no error)", WINDOW_AUTOSIZE);
  298. moveWindow ("image", 20, 300);
  299. moveWindow ("template", 300, 300);
  300. moveWindow ("warped image", 600, 300);
  301. moveWindow ("error (black: no error)", 900, 300);
  302. // draw boundaries of corresponding regions
  303. Mat identity_matrix = Mat::eye(3,3,CV_32F);
  304. draw_warped_roi (target_image, template_image.cols-2, template_image.rows-2, warp_matrix);
  305. draw_warped_roi (template_image, template_image.cols-2, template_image.rows-2, identity_matrix);
  306. Mat errorImage;
  307. subtract(template_image, warped_image, errorImage);
  308. double max_of_error;
  309. minMaxLoc(errorImage, NULL, &max_of_error);
  310. // show images
  311. cout << "Press any key to exit the demo (you might need to click on the images before)." << endl << flush;
  312. imshow ("image", target_image);
  313. waitKey (200);
  314. imshow ("template", template_image);
  315. waitKey (200);
  316. imshow ("warped image", warped_image);
  317. waitKey(200);
  318. imshow ("error (black: no error)", abs(errorImage)*255/max_of_error);
  319. waitKey(0);
  320. }
  321. // done
  322. return 0;
  323. }