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