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}