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