image_alignment.cpp 13.2 KB
Newer Older
wester committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
/*
* This sample demonstrates the use of the function
* findTransformECC that implements the image alignment ECC algorithm
*
*
* The demo loads an image (defaults to ../data/fruits.jpg) and it artificially creates
* a template image based on the given motion type. When two images are given,
* the first image is the input image and the second one defines the template image.
* In the latter case, you can also parse the warp's initialization.
*
* Input and output warp files consist of the raw warp (transform) elements
*
* Authors: G. Evangelidis, INRIA, Grenoble, France
*          M. Asbach, Fraunhofer IAIS, St. Augustin, Germany
*/
#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/video.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/core/utility.hpp>

#include <stdio.h>
#include <string>
#include <time.h>
#include <iostream>
#include <fstream>

using namespace cv;
using namespace std;

static void help(void);
static int readWarp(string iFilename, Mat& warp, int motionType);
static int saveWarp(string fileName, const Mat& warp, int motionType);
static void draw_warped_roi(Mat& image, const int width, const int height, Mat& W);

#define HOMO_VECTOR(H, x, y)\
    H.at<float>(0,0) = (float)(x);\
    H.at<float>(1,0) = (float)(y);\
    H.at<float>(2,0) = 1.;

#define GET_HOMO_VALUES(X, x, y)\
    (x) = static_cast<float> (X.at<float>(0,0)/X.at<float>(2,0));\
    (y) = static_cast<float> (X.at<float>(1,0)/X.at<float>(2,0));


const std::string keys =
    "{@inputImage    | ../data/fruits.jpg | input image filename }"
    "{@templateImage |               | template image filename (optional)}"
    "{@inputWarp     |               | input warp (matrix) filename (optional)}"
    "{n numOfIter    | 50            | ECC's iterations }"
    "{e epsilon      | 0.0001        | ECC's convergence epsilon }"
    "{o outputWarp   | outWarp.ecc   | output warp (matrix) filename }"
    "{m motionType   | affine        | type of motion (translation, euclidean, affine, homography) }"
a  
Kai Westerkamp committed
54
    "{v verbose      | 0             | display initial and final images }"
wester committed
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
    "{w warpedImfile | warpedECC.png | warped input image }"
    "{h help | | print help message }"
;


static void help(void)
{

    cout << "\nThis file demostrates the use of the ECC image alignment algorithm. When one image"
        " is given, the template image is artificially formed by a random warp. When both images"
        " are given, the initialization of the warp by command line parsing is possible. "
        "If inputWarp is missing, the identity transformation initializes the algorithm. \n" << endl;

    cout << "\nUsage example (one image): \n./ecc ../data/fruits.jpg -o=outWarp.ecc "
        "-m=euclidean -e=1e-6 -N=70 -v=1 \n" << endl;

    cout << "\nUsage example (two images with initialization): \n./ecc yourInput.png yourTemplate.png "
        "yourInitialWarp.ecc -o=outWarp.ecc -m=homography -e=1e-6 -N=70 -v=1 -w=yourFinalImage.png \n" << endl;

}

static int readWarp(string iFilename, Mat& warp, int motionType){

    // it reads from file a specific number of raw values:
    // 9 values for homography, 6 otherwise
    CV_Assert(warp.type()==CV_32FC1);
    int numOfElements;
    if (motionType==MOTION_HOMOGRAPHY)
        numOfElements=9;
    else
        numOfElements=6;

    int i;
    int ret_value;

    ifstream myfile(iFilename.c_str());
    if (myfile.is_open()){
        float* matPtr = warp.ptr<float>(0);
        for(i=0; i<numOfElements; i++){
            myfile >> matPtr[i];
        }
        ret_value = 1;
    }
    else {
        cout << "Unable to open file " << iFilename.c_str() << endl;
        ret_value = 0;
    }
    return ret_value;
}

static int saveWarp(string fileName, const Mat& warp, int motionType)
{
    // it saves the raw matrix elements in a file
    CV_Assert(warp.type()==CV_32FC1);

    const float* matPtr = warp.ptr<float>(0);
    int ret_value;

    ofstream outfile(fileName.c_str());
    if( !outfile ) {
        cerr << "error in saving "
            << "Couldn't open file '" << fileName.c_str() << "'!" << endl;
        ret_value = 0;
    }
    else {//save the warp's elements
        outfile << matPtr[0] << " " << matPtr[1] << " " << matPtr[2] << endl;
        outfile << matPtr[3] << " " << matPtr[4] << " " << matPtr[5] << endl;
        if (motionType==MOTION_HOMOGRAPHY){
            outfile << matPtr[6] << " " << matPtr[7] << " " << matPtr[8] << endl;
        }
        ret_value = 1;
    }
    return ret_value;

}


static void draw_warped_roi(Mat& image, const int width, const int height, Mat& W)
{
    Point2f top_left, top_right, bottom_left, bottom_right;

    Mat  H = Mat (3, 1, CV_32F);
    Mat  U = Mat (3, 1, CV_32F);

    Mat warp_mat = Mat::eye (3, 3, CV_32F);

    for (int y = 0; y < W.rows; y++)
        for (int x = 0; x < W.cols; x++)
            warp_mat.at<float>(y,x) = W.at<float>(y,x);

    //warp the corners of rectangle

    // top-left
    HOMO_VECTOR(H, 1, 1);
    gemm(warp_mat, H, 1, 0, 0, U);
    GET_HOMO_VALUES(U, top_left.x, top_left.y);

    // top-right
    HOMO_VECTOR(H, width, 1);
    gemm(warp_mat, H, 1, 0, 0, U);
    GET_HOMO_VALUES(U, top_right.x, top_right.y);

    // bottom-left
    HOMO_VECTOR(H, 1, height);
    gemm(warp_mat, H, 1, 0, 0, U);
    GET_HOMO_VALUES(U, bottom_left.x, bottom_left.y);

    // bottom-right
    HOMO_VECTOR(H, width, height);
    gemm(warp_mat, H, 1, 0, 0, U);
    GET_HOMO_VALUES(U, bottom_right.x, bottom_right.y);

    // draw the warped perimeter
a  
Kai Westerkamp committed
168 169 170 171
    line(image, top_left, top_right, Scalar(255,0,255));
    line(image, top_right, bottom_right, Scalar(255,0,255));
    line(image, bottom_right, bottom_left, Scalar(255,0,255));
    line(image, bottom_left, top_left, Scalar(255,0,255));
wester committed
172 173 174 175 176 177 178 179
}

int main (const int argc, const char * argv[])
{

    CommandLineParser parser(argc, argv, keys);
    parser.about("ECC demo");

a  
Kai Westerkamp committed
180 181 182 183 184 185 186 187 188 189 190
    if (argc < 2) {
        parser.printMessage();
        help();
        return 1;
    }
    if (parser.has("help"))
    {
        parser.printMessage();
        help();
        return 1;
    }
wester committed
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
    string imgFile = parser.get<string>(0);
    string tempImgFile = parser.get<string>(1);
    string inWarpFile = parser.get<string>(2);

    int number_of_iterations = parser.get<int>("n");
    double termination_eps = parser.get<double>("e");
    string warpType = parser.get<string>("m");
    int verbose = parser.get<int>("v");
    string finalWarp = parser.get<string>("o");
    string warpedImFile = parser.get<string>("w");
    if (!parser.check())
    {
        parser.printErrors();
        return -1;
    }
    if (!(warpType == "translation" || warpType == "euclidean"
        || warpType == "affine" || warpType == "homography"))
    {
        cerr << "Invalid motion transformation" << endl;
        return -1;
    }

    int mode_temp;
    if (warpType == "translation")
        mode_temp = MOTION_TRANSLATION;
    else if (warpType == "euclidean")
        mode_temp = MOTION_EUCLIDEAN;
    else if (warpType == "affine")
        mode_temp = MOTION_AFFINE;
    else
        mode_temp = MOTION_HOMOGRAPHY;

    Mat inputImage = imread(imgFile,0);
    if (inputImage.empty())
    {
        cerr << "Unable to load the inputImage" <<  endl;
        return -1;
    }

    Mat target_image;
    Mat template_image;

    if (tempImgFile!="") {
        inputImage.copyTo(target_image);
        template_image = imread(tempImgFile,0);
        if (template_image.empty()){
            cerr << "Unable to load the template image" << endl;
            return -1;
        }

    }
a  
Kai Westerkamp committed
242
    else{ //apply random waro to input image
wester committed
243 244
        resize(inputImage, target_image, Size(216, 216));
        Mat warpGround;
a  
Kai Westerkamp committed
245
        cv::RNG rng;
wester committed
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301
        double angle;
        switch (mode_temp) {
        case MOTION_TRANSLATION:
            warpGround = (Mat_<float>(2,3) << 1, 0, (rng.uniform(10.f, 20.f)),
                0, 1, (rng.uniform(10.f, 20.f)));
            warpAffine(target_image, template_image, warpGround,
                Size(200,200), INTER_LINEAR + WARP_INVERSE_MAP);
            break;
        case MOTION_EUCLIDEAN:
            angle = CV_PI/30 + CV_PI*rng.uniform((double)-2.f, (double)2.f)/180;

            warpGround = (Mat_<float>(2,3) << cos(angle), -sin(angle), (rng.uniform(10.f, 20.f)),
                sin(angle), cos(angle), (rng.uniform(10.f, 20.f)));
            warpAffine(target_image, template_image, warpGround,
                Size(200,200), INTER_LINEAR + WARP_INVERSE_MAP);
            break;
        case MOTION_AFFINE:

            warpGround = (Mat_<float>(2,3) << (1-rng.uniform(-0.05f, 0.05f)),
                (rng.uniform(-0.03f, 0.03f)), (rng.uniform(10.f, 20.f)),
                (rng.uniform(-0.03f, 0.03f)), (1-rng.uniform(-0.05f, 0.05f)),
                (rng.uniform(10.f, 20.f)));
            warpAffine(target_image, template_image, warpGround,
                Size(200,200), INTER_LINEAR + WARP_INVERSE_MAP);
            break;
        case MOTION_HOMOGRAPHY:
            warpGround = (Mat_<float>(3,3) << (1-rng.uniform(-0.05f, 0.05f)),
                (rng.uniform(-0.03f, 0.03f)), (rng.uniform(10.f, 20.f)),
                (rng.uniform(-0.03f, 0.03f)), (1-rng.uniform(-0.05f, 0.05f)),(rng.uniform(10.f, 20.f)),
                (rng.uniform(0.0001f, 0.0003f)), (rng.uniform(0.0001f, 0.0003f)), 1.f);
            warpPerspective(target_image, template_image, warpGround,
                Size(200,200), INTER_LINEAR + WARP_INVERSE_MAP);
            break;
        }
    }


    const int warp_mode = mode_temp;

    // initialize or load the warp matrix
    Mat warp_matrix;
    if (warpType == "homography")
        warp_matrix = Mat::eye(3, 3, CV_32F);
    else
        warp_matrix = Mat::eye(2, 3, CV_32F);

    if (inWarpFile!=""){
        int readflag = readWarp(inWarpFile, warp_matrix, warp_mode);
        if ((!readflag) || warp_matrix.empty())
        {
            cerr << "-> Check warp initialization file" << endl << flush;
            return -1;
        }
    }
    else {

a  
Kai Westerkamp committed
302
        printf("\n ->Perfomarnce Warning: Identity warp ideally assumes images of "
wester committed
303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365
            "similar size. If the deformation is strong, the identity warp may not "
            "be a good initialization. \n");

    }

    if (number_of_iterations > 200)
        cout << "-> Warning: too many iterations " << endl;

    if (warp_mode != MOTION_HOMOGRAPHY)
        warp_matrix.rows = 2;

    // start timing
    const double tic_init = (double) getTickCount ();
    double cc = findTransformECC (template_image, target_image, warp_matrix, warp_mode,
        TermCriteria (TermCriteria::COUNT+TermCriteria::EPS,
        number_of_iterations, termination_eps));

    if (cc == -1)
    {
        cerr << "The execution was interrupted. The correlation value is going to be minimized." << endl;
        cerr << "Check the warp initialization and/or the size of images." << endl << flush;
    }

    // end timing
    const double toc_final  = (double) getTickCount ();
    const double total_time = (toc_final-tic_init)/(getTickFrequency());
    if (verbose){
        cout << "Alignment time (" << warpType << " transformation): "
            << total_time << " sec" << endl << flush;
        //  cout << "Final correlation: " << cc << endl << flush;

    }

    // save the final warp matrix
    saveWarp(finalWarp, warp_matrix, warp_mode);

    if (verbose){
        cout << "\nThe final warp has been saved in the file: " << finalWarp << endl << flush;
    }

    // save the final warped image
    Mat warped_image = Mat(template_image.rows, template_image.cols, CV_32FC1);
    if (warp_mode != MOTION_HOMOGRAPHY)
        warpAffine      (target_image, warped_image, warp_matrix, warped_image.size(),
        INTER_LINEAR + WARP_INVERSE_MAP);
    else
        warpPerspective (target_image, warped_image, warp_matrix, warped_image.size(),
        INTER_LINEAR + WARP_INVERSE_MAP);

    //save the warped image
    imwrite(warpedImFile, warped_image);

    // display resulting images
    if (verbose)
    {

        cout << "The warped image has been saved in the file: " << warpedImFile << endl << flush;

        namedWindow ("image",    WINDOW_AUTOSIZE);
        namedWindow ("template", WINDOW_AUTOSIZE);
        namedWindow ("warped image",   WINDOW_AUTOSIZE);
        namedWindow ("error (black: no error)", WINDOW_AUTOSIZE);

a  
Kai Westerkamp committed
366
        moveWindow  ("template", 350, 350);
wester committed
367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397
        moveWindow  ("warped image",   600, 300);
        moveWindow  ("error (black: no error)", 900, 300);

        // draw boundaries of corresponding regions
        Mat identity_matrix = Mat::eye(3,3,CV_32F);

        draw_warped_roi (target_image,   template_image.cols-2, template_image.rows-2, warp_matrix);
        draw_warped_roi (template_image, template_image.cols-2, template_image.rows-2, identity_matrix);

        Mat errorImage;
        subtract(template_image, warped_image, errorImage);
        double max_of_error;
        minMaxLoc(errorImage, NULL, &max_of_error);

        // show images
        cout << "Press any key to exit the demo (you might need to click on the images before)." << endl << flush;

        imshow ("image",    target_image);
        waitKey (200);
        imshow ("template", template_image);
        waitKey (200);
        imshow ("warped image",   warped_image);
        waitKey(200);
        imshow ("error (black: no error)",  abs(errorImage)*255/max_of_error);
        waitKey(0);

    }

    // done
    return 0;
}