Skip to content

[GSoC] robust function for affine transform estimation#6615

Closed
hrnr wants to merge 101 commits intoopencv:masterfrom
hrnr:estimate_affine_2d
Closed

[GSoC] robust function for affine transform estimation#6615
hrnr wants to merge 101 commits intoopencv:masterfrom
hrnr:estimate_affine_2d

Conversation

@hrnr
Copy link
Copy Markdown
Contributor

@hrnr hrnr commented Jun 1, 2016

implements estimateAffine2D. estimates affine transformation using robust RANSAC method.

  • uses RANSAC framework in calib3d
  • includes accuracy test
  • uses SVD decomposition for solving 3 point equation

things to consider:

  • is solving through SVD fast enough? should we make it symmetric and use EIGEN val decomposition?
  • is checking for coplanar points robust enough? (it seems so)

I plan to write perf test to see if make sense to do some tricks and solve with EIGEN val decomp. Checking for coplanar points seems to work well for me. I adapted the check from estimateAffine3D, estimateRigidTransform have some addition checks (checking if points are not too close), but I'm not sure if it is worth to implement those.

I will implement also estimateAffinePartial2D that will estimate transformation using only rotation, translation an scaling (similar to estimateRigidTransform). I'm only not sure about the name estimateAffinePartial2D, any ideas welcome.

cc: @prclibo

@hrnr
Copy link
Copy Markdown
Contributor Author

hrnr commented Jun 1, 2016

I don't have any idea why this fails OpenCL perf test. It does not make any sense to me. Is that some bug in OCL_PyrLKOpticalFlowFixture_PyrLKOpticalFlow.PyrLKOpticalFlow?

@hrnr hrnr force-pushed the estimate_affine_2d branch from 4fc37fe to 9e32c5c Compare June 1, 2016 13:48
@hrnr
Copy link
Copy Markdown
Contributor Author

hrnr commented Jun 1, 2016

added estimateAffinePartial2D and rebased.

between 0.95 and 0.99 is usually good enough. Values too close to 1 can slow down the estimation
significantly. Values lower than 0.8-0.9 can result in an incorrectly estimated transformation.

The function estimates an optimal 2D affine transformation with 5 degrees of freedom limited to
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you mean 4 dof? 1 rotation + 1 scale + 2 translation?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes. I will fix it.

@hrnr
Copy link
Copy Markdown
Contributor Author

hrnr commented Jun 1, 2016

added perf test for both functions. Here are my results:

Time compensation is 0
CTEST_FULL_OUTPUT
OpenCV version: 3.1.0-dev
OpenCV VCS version: 3.1.0-663-g9e32c5c
Build type: release
Parallel framework: pthreads
CPU features: mmx sse sse2 sse3
OpenCL is disabled
Note: Google Test filter = *Estimate*
[==========] Running 18 tests from 2 test cases.
[----------] Global test environment set-up.
[----------] 9 tests from EstimateAffine_EstimateAffine2D
[ RUN      ] EstimateAffine_EstimateAffine2D.EstimateAffine2D/0
[ PERFSTAT ]    (samples = 10, mean = 2.93, median = 2.91, stddev = 0.07 (2.5%))
[ VALUE    ]    (5000, 0.99)
[       OK ] EstimateAffine_EstimateAffine2D.EstimateAffine2D/0 (30 ms)
[ RUN      ] EstimateAffine_EstimateAffine2D.EstimateAffine2D/1
[ PERFSTAT ]    (samples = 10, mean = 1.87, median = 1.87, stddev = 0.02 (0.9%))
[ VALUE    ]    (5000, 0.95)
[       OK ] EstimateAffine_EstimateAffine2D.EstimateAffine2D/1 (19 ms)
[ RUN      ] EstimateAffine_EstimateAffine2D.EstimateAffine2D/2
[ PERFSTAT ]    (samples = 10, mean = 1.46, median = 1.45, stddev = 0.02 (1.2%))
[ VALUE    ]    (5000, 0.9)
[       OK ] EstimateAffine_EstimateAffine2D.EstimateAffine2D/2 (14 ms)
[ RUN      ] EstimateAffine_EstimateAffine2D.EstimateAffine2D/3
[ PERFSTAT ]    (samples = 10, mean = 0.73, median = 0.72, stddev = 0.01 (1.5%))
[ VALUE    ]    (1000, 0.99)
[       OK ] EstimateAffine_EstimateAffine2D.EstimateAffine2D/3 (8 ms)
[ RUN      ] EstimateAffine_EstimateAffine2D.EstimateAffine2D/4
[ PERFSTAT ]    (samples = 10, mean = 0.48, median = 0.47, stddev = 0.01 (2.9%))
[ VALUE    ]    (1000, 0.95)
[       OK ] EstimateAffine_EstimateAffine2D.EstimateAffine2D/4 (5 ms)
[ RUN      ] EstimateAffine_EstimateAffine2D.EstimateAffine2D/5
[ PERFSTAT ]    (samples = 10, mean = 0.41, median = 0.41, stddev = 0.01 (2.4%))
[ VALUE    ]    (1000, 0.9)
[       OK ] EstimateAffine_EstimateAffine2D.EstimateAffine2D/5 (4 ms)
[ RUN      ] EstimateAffine_EstimateAffine2D.EstimateAffine2D/6
[ PERFSTAT ]    (samples = 13, mean = 0.24, median = 0.24, stddev = 0.01 (2.1%))
[ VALUE    ]    (100, 0.99)
[       OK ] EstimateAffine_EstimateAffine2D.EstimateAffine2D/6 (3 ms)
[ RUN      ] EstimateAffine_EstimateAffine2D.EstimateAffine2D/7
[ PERFSTAT ]    (samples = 17, mean = 0.16, median = 0.15, stddev = 0.00 (3.0%))
[ VALUE    ]    (100, 0.95)
[       OK ] EstimateAffine_EstimateAffine2D.EstimateAffine2D/7 (3 ms)
[ RUN      ] EstimateAffine_EstimateAffine2D.EstimateAffine2D/8
[ PERFSTAT ]    (samples = 13, mean = 0.13, median = 0.13, stddev = 0.00 (1.2%))
[ VALUE    ]    (100, 0.9)
[       OK ] EstimateAffine_EstimateAffine2D.EstimateAffine2D/8 (2 ms)
[----------] 9 tests from EstimateAffine_EstimateAffine2D (88 ms total)

[----------] 9 tests from EstimateAffine_EstimateAffinePartial2D
[ RUN      ] EstimateAffine_EstimateAffinePartial2D.EstimateAffinePartial2D/0
[ PERFSTAT ]    (samples = 10, mean = 1.07, median = 1.05, stddev = 0.03 (2.9%))
[ VALUE    ]    (5000, 0.99)
[       OK ] EstimateAffine_EstimateAffinePartial2D.EstimateAffinePartial2D/0 (10 ms)
[ RUN      ] EstimateAffine_EstimateAffinePartial2D.EstimateAffinePartial2D/1
[ PERFSTAT ]    (samples = 10, mean = 0.70, median = 0.69, stddev = 0.01 (1.6%))
[ VALUE    ]    (5000, 0.95)
[       OK ] EstimateAffine_EstimateAffinePartial2D.EstimateAffinePartial2D/1 (8 ms)
[ RUN      ] EstimateAffine_EstimateAffinePartial2D.EstimateAffinePartial2D/2
[ PERFSTAT ]    (samples = 10, mean = 0.54, median = 0.53, stddev = 0.01 (2.0%))
[ VALUE    ]    (5000, 0.9)
[       OK ] EstimateAffine_EstimateAffinePartial2D.EstimateAffinePartial2D/2 (5 ms)
[ RUN      ] EstimateAffine_EstimateAffinePartial2D.EstimateAffinePartial2D/3
[ PERFSTAT ]    (samples = 25, mean = 0.25, median = 0.25, stddev = 0.01 (2.7%))
[ VALUE    ]    (1000, 0.99)
[       OK ] EstimateAffine_EstimateAffinePartial2D.EstimateAffinePartial2D/3 (7 ms)
[ RUN      ] EstimateAffine_EstimateAffinePartial2D.EstimateAffinePartial2D/4
[ PERFSTAT ]    (samples = 13, mean = 0.17, median = 0.16, stddev = 0.00 (2.8%))
[ VALUE    ]    (1000, 0.95)
[       OK ] EstimateAffine_EstimateAffinePartial2D.EstimateAffinePartial2D/4 (2 ms)
[ RUN      ] EstimateAffine_EstimateAffinePartial2D.EstimateAffinePartial2D/5
[ PERFSTAT ]    (samples = 13, mean = 0.13, median = 0.13, stddev = 0.00 (2.9%))
[ VALUE    ]    (1000, 0.9)
[       OK ] EstimateAffine_EstimateAffinePartial2D.EstimateAffinePartial2D/5 (2 ms)
[ RUN      ] EstimateAffine_EstimateAffinePartial2D.EstimateAffinePartial2D/6
[ PERFSTAT ]    (samples = 36, mean = 0.07, median = 0.07, stddev = 0.00 (3.0%))
[ VALUE    ]    (100, 0.99)
[       OK ] EstimateAffine_EstimateAffinePartial2D.EstimateAffinePartial2D/6 (2 ms)
[ RUN      ] EstimateAffine_EstimateAffinePartial2D.EstimateAffinePartial2D/7
[ PERFSTAT ]    (samples = 25, mean = 0.05, median = 0.05, stddev = 0.00 (2.9%))
[ VALUE    ]    (100, 0.95)
[       OK ] EstimateAffine_EstimateAffinePartial2D.EstimateAffinePartial2D/7 (1 ms)
[ RUN      ] EstimateAffine_EstimateAffinePartial2D.EstimateAffinePartial2D/8
[ PERFSTAT ]    (samples = 25, mean = 0.04, median = 0.04, stddev = 0.00 (2.8%))
[ VALUE    ]    (100, 0.9)
[       OK ] EstimateAffine_EstimateAffinePartial2D.EstimateAffinePartial2D/8 (1 ms)
[----------] 9 tests from EstimateAffine_EstimateAffinePartial2D (38 ms total)

[----------] Global test environment tear-down
[==========] 18 tests from 2 test cases ran. (126 ms total)
[  PASSED  ] 18 tests.

estimateAffinePartial2D is about 3 times faster. This is expected result since it needs only 2 points for model (instead of 3) and the system to solve is also smaller.

@hrnr
Copy link
Copy Markdown
Contributor Author

hrnr commented Jun 1, 2016

Is it normal that perf test fails on a first run? It fails with No regression data for aff_est argument, that must be because of SANITY_CHECK macro.

It does not happen locally.

@hrnr
Copy link
Copy Markdown
Contributor Author

hrnr commented Jun 2, 2016

added regression data in opencv/opencv_extra#296. Hope that it will make build bot happy.

@hrnr
Copy link
Copy Markdown
Contributor Author

hrnr commented Jun 2, 2016

I have experimented with using eigenval decomposition instead of SVD in eba30a8 (+ trick to make the matrix symmetric), but it is not any faster.

From my POV I consider this feature complete. If we convice the buildbot to use the new generated regression data I can rebase and this could be the first thing to go in.

Since this blocks #6609, I will focus on parallel feature finding subtask for now and continue with affine registration in sttiching pipeline once this will go in.

@prclibo
Copy link
Copy Markdown
Contributor

prclibo commented Jun 3, 2016

Basically looks ok to me.

the test size of (100000, 5000, 100) is different from what you have in the sanity sheet in opencv/opencv_extra#296. Do you need to regenerate it?

@hrnr hrnr force-pushed the estimate_affine_2d branch from e4c38a7 to 480eea3 Compare June 3, 2016 15:04
@hrnr
Copy link
Copy Markdown
Contributor Author

hrnr commented Jun 3, 2016

Thanks for noticing, I have regenerated the data. Hope that buildbot will be happy now.

I have rebased my commits.

@hrnr
Copy link
Copy Markdown
Contributor Author

hrnr commented Jun 3, 2016

Buildbot is failing to merge opencv_extra with my regression data. Is there anything I can do to convice buildbot to use the generated data?

@alalek
Copy link
Copy Markdown
Member

alalek commented Jun 3, 2016

You should use the same name for branch in opencv_extra: estimate_affine_2d

@hrnr
Copy link
Copy Markdown
Contributor Author

hrnr commented Jun 3, 2016

Thanks, reopened as opencv/opencv_extra#297 with correct branch name.

Rebased against newest master to force builtbot run.

@hrnr hrnr force-pushed the estimate_affine_2d branch from 480eea3 to 3dde7fe Compare June 3, 2016 18:02
@hrnr
Copy link
Copy Markdown
Contributor Author

hrnr commented Jun 6, 2016

All green. For me this is good to go in.

@hrnr
Copy link
Copy Markdown
Contributor Author

hrnr commented Jul 4, 2016

incorporated fix from #6768

EDIT: I pushed a commit into my branch, but this PR is not updating. Hopefully it will update soon.

hrnr added 5 commits July 4, 2016 17:14
estimates affine transformation using robust RANSAC method.

* uses RANSAC framework in calib3d
* includes accuracy test
* uses SVD decomposition for solving 3 point equation
estimates limited affine transformation

* includes accuracy test
test Calib3d_EstimateAffineTransform rename to Calib3d_EstimateAffine3D. This is more descriptive and prevents confusion with estimateAffine2D tests.
tests both estimateAffine2D and estimateAffinePartial2D
@hrnr hrnr force-pushed the estimate_affine_2d branch from 72719d8 to dbce5f3 Compare July 4, 2016 15:14
@vpisarev
Copy link
Copy Markdown
Contributor

vpisarev commented Jul 8, 2016

this PR will be reopened because of CI issues

@phryniszak
Copy link
Copy Markdown

estimateAffinePartial2D and estimateAffine2D seems to be buggy. Fails for int input values. Returned values are cast to int so results makes not sens.
More details here:
http://answers.opencv.org/question/123476/estimateaffinepartial2d-and-estimateaffine2d/

@hrnr
Copy link
Copy Markdown
Contributor Author

hrnr commented Jan 23, 2017

In case estimateAffine* functions get something different than floats, inputs are converted to floats and then algorithm works the same. Returned matrix is always doubles.

estimateAffine* uses Levenberg-Marquart as final refinement step. Do you get expected results when you call it with refineIters=0?

@phryniszak
Copy link
Copy Markdown

phryniszak commented Jan 23, 2017

I tried with refineIters=0 and results are still the same and test:

#include "opencv2/highgui.hpp"
#include "opencv2/video/tracking.hpp"
#include "opencv2/calib3d.hpp"


#include <iostream>
#include <vector>

int main(int argc, char *argv[])
{
    std::vector<cv::Point> p1s,p2s;

    p1s.push_back(cv::Point2f( 100, 0));
    p1s.push_back(cv::Point2f( 0, 100));
    p1s.push_back(cv::Point2f(-100, 0));
    p1s.push_back(cv::Point2f( 0,-100));

    p2s.push_back(cv::Point2f(71, -71));
    p2s.push_back(cv::Point2f(71, 71));
    p2s.push_back(cv::Point2f(-71, 71));
    p2s.push_back(cv::Point2f(-71, -71));

    // 1.
    cv::Mat t_false = cv::estimateRigidTransform(p1s,p2s,false);
    std::cout << "estimateRigidTransform false: " << t_false << "\n" << std::flush;

    // 2.
    cv::Mat t_true = cv::estimateRigidTransform(p1s,p2s,true);
    std::cout << "estimateRigidTransform true:" << t_true << "\n" << std::flush;

    // 3.
    std::vector<uchar> inliers(p1s.size(), 0);
    cv::Mat affine1 = cv::estimateAffine2D(p1s, p2s, inliers, cv::RANSAC, 3, 2000, 0.99, 0);
    std::cout << "estimateAffine2D" << affine1 << "\n" << std::flush;

    // 4.
    cv::Mat affine2 = cv::estimateAffinePartial2D(p1s, p2s, inliers, cv::RANSAC, 3, 2000, 0.99, 0);
    std::cout << "estimateAffinePartial2D" << affine2 << "\n" << std::flush;
}

@hrnr
Copy link
Copy Markdown
Contributor Author

hrnr commented Jan 23, 2017

Thanks for report, I will take a look on that. There might be some degenerate points configuration not catched by tests.

I see you are now storing cv::Point2f into cv::Point. Are you sure conversion does what you expect?

@phryniszak
Copy link
Copy Markdown

std::vectorcv::Point p1s,p2s and results are:

estimateRigidTransform false: [0.7100000000000001, 0.7100000000000001, 0;
 -0.7100000000000001, 0.7100000000000001, 0]
estimateRigidTransform true:[0.7100000000000001, 0.7100000000000001, 0;
 -0.7100000000000001, 0.7100000000000001, 0]
estimateAffine2D[1, -0, -0;
 -0, 1, -0]
estimateAffinePartial2D[1, -0, 0;
 0, 1, 0]

and std::vectorcv::Point2f p1s,p2s; and correct results:


stimateRigidTransform false: [0.7100000000000001, 0.7100000000000001, 0;
 -0.7100000000000001, 0.7100000000000001, 0]
estimateRigidTransform true:[0.7100000000000001, 0.7100000000000001, 0;
 -0.7100000000000001, 0.7100000000000001, 0]
estimateAffine2D[0.7100000000000001, 0.7100000000000001, -0;
 -0.7100000000000001, 0.7100000000000001, -0]
estimateAffinePartial2D[0.7100000000000001, 0.7100000000000001, 0;
 -0.7100000000000001, 0.7100000000000001, 0]

@hrnr
Copy link
Copy Markdown
Contributor Author

hrnr commented Jan 23, 2017

You are right, there is a stupid bug in estimateAffine* affecting conversion of inputs.

I will fix that (please give me a few days, I'm quite busy now). Thank you for reporting this, we should have probably more tests to cover also conversions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.