OpenCV  4.2.0
Open Source Computer Vision
samples/cpp/image_alignment.cpp

An example using the image alignment ECC algorithm

/*
* This sample demonstrates the use of the function
* findTransformECC that implements the image alignment ECC algorithm
*
*
* The demo loads an image (defaults to 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 <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 | 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) }"
"{v verbose | 1 | display initial and final images }"
"{w warpedImfile | warpedECC.png | warped input image }"
"{h help | | print help message }"
;
static void help(void)
{
cout << "\nThis file demonstrates 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./image_alignment fruits.jpg -o=outWarp.ecc "
"-m=euclidean -e=1e-6 -N=70 -v=1 \n" << endl;
cout << "\nUsage example (two images with initialization): \n./image_alignment 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
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
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
line(image, top_left, top_right, Scalar(255));
line(image, top_right, bottom_right, Scalar(255));
line(image, bottom_right, bottom_left, Scalar(255));
line(image, bottom_left, top_left, Scalar(255));
}
int main (const int argc, const char * argv[])
{
CommandLineParser parser(argc, argv, keys);
parser.about("ECC demo");
parser.printMessage();
help();
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(samples::findFile(imgFile), IMREAD_GRAYSCALE);
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(samples::findFile(tempImgFile), IMREAD_GRAYSCALE);
if (template_image.empty()){
cerr << "Unable to load the template image" << endl;
return -1;
}
}
else{ //apply random warp to input image
resize(inputImage, target_image, Size(216, 216), 0, 0, INTER_LINEAR_EXACT);
Mat warpGround;
RNG rng(getTickCount());
double angle;
switch (mode_temp) {
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,
break;
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,
break;
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,
break;
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,
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 {
printf("\n ->Performance Warning: Identity warp ideally assumes images of "
"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,
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(),
else
warpPerspective (target_image, warped_image, warp_matrix, warped_image.size(),
//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 ("warped image", WINDOW_AUTOSIZE);
namedWindow ("error (black: no error)", WINDOW_AUTOSIZE);
moveWindow ("image", 20, 300);
moveWindow ("template", 300, 300);
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;
}
cv::Mat::rows
int rows
the number of rows and columns or (-1, -1) when the matrix has more than 2 dimensions
Definition: mat.hpp:2086
cv::gemm
void gemm(InputArray src1, InputArray src2, double alpha, InputArray src3, double beta, OutputArray dst, int flags=0)
Performs generalized matrix multiplication.
cv::Point_< float >
cv::TermCriteria
The class defining termination criteria for iterative algorithms.
Definition: types.hpp:852
cv::moveWindow
void moveWindow(const String &winname, int x, int y)
Moves window to the specified position.
cv::sin
softdouble sin(const softdouble &a)
Sine.
cv::samples::findFile
cv::String findFile(const cv::String &relative_path, bool required=true, bool silentMode=false)
Try to find requested data file.
cv::warpAffine
void warpAffine(InputArray src, OutputArray dst, InputArray M, Size dsize, int flags=INTER_LINEAR, int borderMode=BORDER_CONSTANT, const Scalar &borderValue=Scalar())
Applies an affine transformation to an image.
cv::Mat::at
_Tp & at(int i0=0)
Returns a reference to the specified array element.
cv::Mat::ptr
uchar * ptr(int i0=0)
Returns a pointer to the specified matrix row.
cv::WARP_INVERSE_MAP
Definition: imgproc.hpp:270
cv::waitKey
int waitKey(int delay=0)
Waits for a pressed key.
cv::TermCriteria::COUNT
the maximum number of iterations or elements to compute
Definition: types.hpp:860
cv::TermCriteria::EPS
the desired accuracy or change in parameters at which the iterative algorithm stops
Definition: types.hpp:862
cv::MOTION_TRANSLATION
Definition: tracking.hpp:262
cv::Point_::y
_Tp y
y coordinate of the point
Definition: types.hpp:187
cv::Point_::x
_Tp x
x coordinate of the point
Definition: types.hpp:186
highgui.hpp
cv::IMREAD_GRAYSCALE
If set, always convert image to the single channel grayscale image (codec internal conversion).
Definition: imgcodecs.hpp:66
cv::subtract
void subtract(InputArray src1, InputArray src2, OutputArray dst, InputArray mask=noArray(), int dtype=-1)
Calculates the per-element difference between two arrays or array and a scalar.
cv::namedWindow
void namedWindow(const String &winname, int flags=WINDOW_AUTOSIZE)
Creates a window.
cv::Size
Size2i Size
Definition: types.hpp:347
cv::line
void line(InputOutputArray img, Point pt1, Point pt2, const Scalar &color, int thickness=1, int lineType=LINE_8, int shift=0)
Draws a line segment connecting two points.
CV_32F
#define CV_32F
Definition: interface.h:78
cv::imread
Mat imread(const String &filename, int flags=IMREAD_COLOR)
Loads an image from a file.
cv::MOTION_AFFINE
Definition: tracking.hpp:264
cv::Mat::empty
bool empty() const
Returns true if the array has no elements.
cv::Mat::cols
int cols
Definition: mat.hpp:2086
cv::INTER_LINEAR
Definition: imgproc.hpp:248
cv::cos
softdouble cos(const softdouble &a)
Cosine.
cv::Mat::size
MatSize size
Definition: mat.hpp:2108
imgcodecs.hpp
cv::abs
static uchar abs(uchar a)
Definition: cvstd.hpp:66
video.hpp
cv::INTER_LINEAR_EXACT
Definition: imgproc.hpp:258
cv::getTickCount
int64 getTickCount()
Returns the number of ticks.
cv::MOTION_HOMOGRAPHY
Definition: tracking.hpp:265
cv::imshow
void imshow(const String &winname, InputArray mat)
Displays an image in the specified window.
cv::minMaxLoc
void minMaxLoc(InputArray src, double *minVal, double *maxVal=0, Point *minLoc=0, Point *maxLoc=0, InputArray mask=noArray())
Finds the global minimum and maximum in an array.
cv::getTickFrequency
double getTickFrequency()
Returns the number of ticks per second.
cv::Scalar
Scalar_< double > Scalar
Definition: types.hpp:669
cv::RNG
Random Number Generator.
Definition: core.hpp:2768
cv::warpPerspective
void warpPerspective(InputArray src, OutputArray dst, InputArray M, Size dsize, int flags=INTER_LINEAR, int borderMode=BORDER_CONSTANT, const Scalar &borderValue=Scalar())
Applies a perspective transformation to an image.
CV_Assert
#define CV_Assert(expr)
Checks a condition at runtime and throws exception if it fails.
Definition: base.hpp:342
cv::Mat
n-dimensional dense array class
Definition: mat.hpp:791
cv::Mat::type
int type() const
Returns the type of a matrix element.
cv::Mat::copyTo
void copyTo(OutputArray m) const
Copies the matrix to another one.
cv::Mat::eye
static MatExpr eye(int rows, int cols, int type)
Returns an identity matrix of the specified size and type.
cv::resize
void resize(InputArray src, OutputArray dst, Size dsize, double fx=0, double fy=0, int interpolation=INTER_LINEAR)
Resizes an image.
cv::CommandLineParser
Designed for command line parsing.
Definition: utility.hpp:796
cv::imwrite
bool imwrite(const String &filename, InputArray img, const std::vector< int > &params=std::vector< int >())
Saves an image to a specified file.
cv
"black box" representation of the file storage associated with a file on disk.
Definition: affine.hpp:51
imgproc.hpp
CV_PI
#define CV_PI
Definition: cvdef.h:326
cv::WINDOW_AUTOSIZE
the user cannot resize the window, the size is constrainted by the image displayed.
Definition: highgui.hpp:184
cv::findTransformECC
double findTransformECC(InputArray templateImage, InputArray inputImage, InputOutputArray warpMatrix, int motionType, TermCriteria criteria, InputArray inputMask, int gaussFiltSize)
Finds the geometric transform (warp) between two images in terms of the ECC criterion .
cv::Mat_< float >
cv::MOTION_EUCLIDEAN
Definition: tracking.hpp:263
utility.hpp
CV_32FC1
#define CV_32FC1
Definition: interface.h:118