Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update/sensor noise qual #52

Merged
merged 33 commits into from
Jun 29, 2020

Conversation

colin-lewis-19
Copy link
Contributor

Adds a tool for qualifying sensor noise, as well as an implementation of 3d pnp.

@colin-lewis-19 colin-lewis-19 force-pushed the update/sensor_noise_qual branch 2 times, most recently from 636ddf5 to f433458 Compare June 4, 2020 21:37
rct_examples/src/verification/noise_qualify_2d.cpp Outdated Show resolved Hide resolved
rct_examples/src/verification/noise_qualify_2d.cpp Outdated Show resolved Hide resolved
rct_examples/src/verification/noise_qualify_2d.cpp Outdated Show resolved Hide resolved
rct_optimizations/CMakeLists.txt Outdated Show resolved Hide resolved
rct_optimizations/test/noise_qualification_utest.cpp Outdated Show resolved Hide resolved
rct_optimizations/test/noise_qualification_utest.cpp Outdated Show resolved Hide resolved
rct_optimizations/test/pnp_utest.cpp Outdated Show resolved Hide resolved
rct_optimizations/test/pnp_utest.cpp Outdated Show resolved Hide resolved
@marip8
Copy link
Collaborator

marip8 commented Jun 8, 2020

One more comment: could you move the noise qualification code into a directory inside include and src called validation (instead of verification)? I want to be consistent with #51

rct_optimizations/test/noise_qualification_utest.cpp Outdated Show resolved Hide resolved
rct_optimizations/test/noise_qualification_utest.cpp Outdated Show resolved Hide resolved
rct_optimizations/test/noise_qualification_utest.cpp Outdated Show resolved Hide resolved
rct_optimizations/test/noise_qualification_utest.cpp Outdated Show resolved Hide resolved
rct_optimizations/test/noise_qualification_utest.cpp Outdated Show resolved Hide resolved
rct_optimizations/test/noise_qualification_utest.cpp Outdated Show resolved Hide resolved
rct_optimizations/test/noise_qualification_utest.cpp Outdated Show resolved Hide resolved
rct_optimizations/test/pnp_utest.cpp Outdated Show resolved Hide resolved
rct_optimizations/src/validation/noise_qualifier.cpp Outdated Show resolved Hide resolved
@colin-lewis-19 colin-lewis-19 force-pushed the update/sensor_noise_qual branch 4 times, most recently from 8804cf3 to 3162925 Compare June 16, 2020 18:36
Comment on lines 54 to 60
Eigen::Quaterniond q;
Eigen::Matrix3d m = result.camera_to_target.rotation();
q = m;

Eigen::Vector4d v(q.x(), q.y(), q.z(),q.w());

orientations.push_back(v);
Copy link
Collaborator

Choose a reason for hiding this comment

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

You could reduce with orientations.push_back(Eigen::Quaterniond(result.camera_to_target.rotation()).coeffs())

Comment on lines 71 to 82
//Mean quaternion is found using method described by Markley et al: Quaternion Averaging
//All quaternions are equally weighted

//Accumulator Matrix
Eigen::Matrix4d A = Eigen::Matrix4d::Zero();

for (std::size_t i = 0; i <= count; ++i)
{
//rank 1 update of 'A', the accumulator matrix, with the u,v = q,qT
Eigen::Matrix4d temp = (orientations[i] * orientations[i].transpose());
A += temp;
}

//scale A
A = (1.0/double(count)) * A;

//Eigenvectors,values should be strictly real
Eigen::EigenSolver<Eigen::Matrix4d> E(A, true);

//Each column of 4x4 vectors is an eigenvector; desired mean has max eigenvalue
Eigen::Index max_evi, one;

//find maximium eigenvalue, and store its index in max_evi
E.eigenvalues().real().maxCoeff(&max_evi, &one);
Eigen::Vector4d mean = E.eigenvectors().real().col(max_evi);

output.i.mean = mean[0];
output.j.mean = mean[1];
output.k.mean = mean[2];
output.w.mean = mean[3];

//Manually calculate standard deviations from mean
Eigen::Array4d std_dev = Eigen::Array4d::Zero();
Eigen::Array4d sum = Eigen::Array4d::Zero();
for (std::size_t i =0; i< count; ++i)
{
//absolute value taken because a quaternion is equal to its opposite
Eigen::Array4d term = orientations[i].cwiseAbs() - mean.cwiseAbs();
term = term.square();
sum+= term;
}

std_dev = sum/double(count);
std_dev = std_dev.sqrt();
Copy link
Collaborator

Choose a reason for hiding this comment

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

It probably makes sense to move this to a standalone function, since other features might use this (and so you don't have to copy it for the 3D case below). It would be nice to verify that this method also works as expected in a unit test since it just came from a paper we found online

output.y.std_dev = sqrt(boost::accumulators::variance(y_acc));
output.z.std_dev = sqrt(boost::accumulators::variance(z_acc));

//Mean quaternion is found using method described by Markley et al: Quaternion Averaging
Copy link
Collaborator

Choose a reason for hiding this comment

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

Add the website for future reference (or the PDF to the docs folder)

Comment on lines 27 to 30
NoiseStatistics i;
NoiseStatistics j;
NoiseStatistics k;
NoiseStatistics w;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please name these qx, qy, qz, and qw to conform to normal conventions

Comment on lines 33 to 63
/**
* @brief qualifyNoise2D This function qualifies 2d sensor noise by
* comparing PnP results from images taken with the same poses.
* Sensor noise can be understood by inspecting the returned standard
* deviations. Position and orientation is returned, but orientation
* is expressed in Euler angles and may not be consistent for
* similar positions.
* @param Sets of PnP 2D problem parameters
* @return Noise Statistics: a vector of means & std devs
*/
PnPNoiseStat qualifyNoise2D(const std::vector<PnPProblem>& params);

/**
* @brief qualifyNoise3D This function qualifies 3d sensor noise by
* comparing PnP results from scans taken with the same poses.
* Sensor noise can be understood by inspecting the returned standard
* deviations. Position and orientation is returned, but orientation
* is expressed in Euler angles and may not be consistent for
* similar positions.
* @param params 3D image parameters
* @return Noise Statiscics: a vector of standard deviations and the mean pos
*/
Copy link
Collaborator

Choose a reason for hiding this comment

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

Update documentation references to Euler angles

Comment on lines 31 to 33
Eigen::Quaterniond q_ver;
Eigen::Matrix3d m = camera_loc.rotation();
q_ver = m;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can construct the quaternion inline

Comment on lines 60 to 75
EXPECT_TRUE(output.x.std_dev < 1.0e-14);
EXPECT_TRUE(output.y.std_dev < 1.0e-14);
EXPECT_TRUE(output.z.std_dev < 1.0e-14);
EXPECT_TRUE(output.i.std_dev < 1.0e-14);
EXPECT_TRUE(output.j.std_dev < 1.0e-14);
EXPECT_TRUE(output.k.std_dev < 1.0e-14);
EXPECT_TRUE(output.w.std_dev < 1.0e-14);

//Absolute value of quaternion is taken, since quaternions equal their oppoisite
EXPECT_TRUE(abs(output.x.mean - camera_loc.translation()(0)) < 1.0e-14);
EXPECT_TRUE(abs(output.y.mean - camera_loc.translation()(1)) < 1.0e-14);
EXPECT_TRUE(abs(output.z.mean - camera_loc.translation()(2)) < 1.0e-14);
EXPECT_TRUE(abs(output.i.mean) - abs(q_ver.x()) < 1.0e-14);
EXPECT_TRUE(abs(output.j.mean) - abs(q_ver.y()) < 1.0e-14);
EXPECT_TRUE(abs(output.k.mean) - abs(q_ver.z()) < 1.0e-14);
EXPECT_TRUE(abs(output.w.mean) - abs(q_ver.w()) < 1.0e-14);
Copy link
Collaborator

Choose a reason for hiding this comment

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

You might consider using EXPECT_LT instead. If/when this fails, EXPECT_TRUE will only tell you the condition was not right, but EXPECT_LT will print the actual and expected values to the terminal which is helpful for debug


//add noise boilerplate
const double mean = 0.0;
const double stddev = 0.001;
Copy link
Collaborator

Choose a reason for hiding this comment

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

This standard deviation is in pixels (as you are applying it to the image u, v coordinates), and seems really small. You might make it something more reasonable like 1.0 pixel

Comment on lines 142 to 157
EXPECT_TRUE(output.x.std_dev < 1.5 * stddev);
EXPECT_TRUE(output.y.std_dev < 1.5 * stddev);
EXPECT_TRUE(output.z.std_dev < 1.5 * stddev);
EXPECT_TRUE(output.i.std_dev < 1.5 * stddev);
EXPECT_TRUE(output.j.std_dev < 1.5 * stddev);
EXPECT_TRUE(output.k.std_dev < 1.5 * stddev);
EXPECT_TRUE(output.w.std_dev < 1.5 * stddev);

//Absolute value of quaternion is taken, since quaternions equal their oppoisite
EXPECT_TRUE(abs(output.x.mean - camera_loc.translation()(0)) < 1.5 * stddev);
EXPECT_TRUE(abs(output.y.mean - camera_loc.translation()(1)) < 1.5 * stddev);
EXPECT_TRUE(abs(output.z.mean - camera_loc.translation()(2)) < 1.5 * stddev);
EXPECT_TRUE(abs(output.i.mean) - abs(q_ver.x()) < 1.5 * stddev);
EXPECT_TRUE(abs(output.j.mean) - abs(q_ver.y()) < 1.5 * stddev);
EXPECT_TRUE(abs(output.k.mean) - abs(q_ver.z()) < 1.5 * stddev);
EXPECT_TRUE(abs(output.w.mean) - abs(q_ver.w()) < 1.5 * stddev);
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we can compare position a little bit more accurately. Using the camera projection matrix we can come up with the differentials of (x, y, z) with respect to our perturbed input (u, v)

  • delta_x < (z / f_x) * delta_u
    • dx < (camera_loc.translation.z() / camera.fx()) * stddev
  • delta_y < (z / f_y) * delta_u
    • dy < (camera_loc.translation.z() / camera.fy()) * stddev
  • delta_z < (f_x * x * (u - c_x)^-2) * delta_u
    • dz < (camera.fx() * camera_loc.translation.x() * std::pow((u - camera.cx()), -2.0)) * stddev
    • u in this case is obtained by projecting the actual point into the camera

As far as orientation, I don't think we can directly compare it to the pixel noise level (i.e. stddev), so I would suggest setting a somewhat arbitrary value as the threshold and comparing distance between the mean and expected quaternion with:

EXPECT_LT(Eigen::Quaterniond(output.w.mean, output.i.mean, output.j.mean, output.k.mean).angularDistance(q_ver), angular_threshold);

PnPResult res = rct_optimizations::optimize(setup);

EXPECT_TRUE(res.converged);
//costs are oddly high for 3d, perfect guess
Copy link
Collaborator

@marip8 marip8 Jun 17, 2020

Choose a reason for hiding this comment

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

This would be an indication that your cost function is not correct. If you put in a perfect initial guess and there is no simulated noise in your correspondences, your initial cost per observation should be close to the machine epsilon. You should be seeing error on the order of 1.0e-15.

Another way to validate is to make a test where the starting condition is not perfect and see if it converges with low residual error

@marip8 marip8 merged commit 1273472 into Jmeyer1292:master Jun 29, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants