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

Vision constraints #352

Draft
wants to merge 12 commits into
base: devel
Choose a base branch
from
Draft

Vision constraints #352

wants to merge 12 commits into from

Conversation

oscarmendezm
Copy link
Contributor

Adding improved vision constraints, including worked out jacobian for fixed landmark, some extra tests and a simple reprojection error constraint. Also adding a test using BAL, and python scripts used to generate test-cases.

Copy link
Contributor

@svwilliams svwilliams left a comment

Choose a reason for hiding this comment

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

Round 1: I made it through the constraints include/src. I'll work on the tests tomorrow. And then the camera variables.

{
residual[i * 2] = d.row(i)[0];
residual[i * 2 + 1] = d.row(i)[1];
// Get the covariance weigthing to point losses from a pose uncertainty
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// Get the covariance weigthing to point losses from a pose uncertainty
// Get the covariance weighting to point losses from a pose uncertainty

Comment on lines 190 to 191
// [ (fx/gz) (0) (-fx * gx / gz^2) (-fx * gx gy / gz^2) fx(1+gx^2/gz^2) -fx gy/gz]
// [ 0 (fy/gz)) (-fy * gy / gz^2) -fy(1+gy^2/gz^2) (-fy * gx gy / gz^2) -fy gx/gz]
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not going to pretend I independently verified this derivation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I didn't manually compute the entire thing, but it make sense to me and I think it is the right way to do this given the info on the ArXiv. I could be wrong, but short of spending ages in Jacobian hell, it works?

Comment on lines +198 to +199
J << fx/gz, T(0), -fx * (gx / gz2), -fx*gxyz, fx*(T(1)+(gx*gx)/gz2), -fx * gy/gz,
T(0), fy/gz, -fy * (gy / gz2), -fy*(T(1)+(gy*gy)/gz2), fy*gxyz, fy * gx/gz;
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm just matching the equations here to the comments posted above. Elements [2, 5] and [2, 6] have a different sign in the code versus the comments. Not sure which one is correct.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Comment was wrong. Verified against paper.

/**
* @brief Read-only access to the square root information matrix.
*
* Order is (x, y, z, qx, qy, qz)
Copy link
Contributor

Choose a reason for hiding this comment

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

The covariance/information matrix is only 2x2. The order should be (x, y) or (u, v), correct?
Same with the covariance() method below.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Correct, this case is the "simple" covariance (i.e. pixel error)

Comment on lines 160 to 164
/**
* @brief Read-only access to the observation Matrix (Nx2).
*
* Order is (x, y)
*/
Copy link
Contributor

Choose a reason for hiding this comment

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

These are 3D points, correct? Shouldn't the size should be (Nx3) and they should have a Z?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep. My copy pasting has bit me in the butt again.

* where, the matrix A and the vector b are fixed, p is the camera position variable, and q is the camera orientation
* variable, K is the calibration matrix created from the calibration variable, X is the set of marker 3D points,
* R_b(0:3) is the Rotation matrix from the fixed landmark orentation (b(3:6)), b(0:2) is the fixed landmark position
* and x is the 2D ovservations.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* and x is the 2D ovservations.
* and x is the 2D observations.

*
* @param[in] A The residual weighting matrix, most likely derived from the square root information
* matrix in order (u, v)
* @param[in] b The 2D pose measurement or prior in order (u, v
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* @param[in] b The 2D pose measurement or prior in order (u, v
* @param[in] b The 2D pose measurement or prior in order (u, v)

@svwilliams svwilliams self-requested a review December 19, 2023 22:44
Copy link
Contributor

@svwilliams svwilliams left a comment

Choose a reason for hiding this comment

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

I have some questions about how camera UUIDs should work. And I don't remember if there was anything of consequence from my previous review.

Comment on lines +69 to +70
// BALProblem adapted from:
// https://ceres-solver.googlesource.com/ceres-solver/+/master/examples/simple_bundle_adjuster.cc
Copy link
Contributor

Choose a reason for hiding this comment

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

Since this is using external code as a template, I'm holding all of my comments about raw pointers, etc. Just know that I am still thinking them in my head. 🙂

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hide the pain, push it down. Its what i did. :). I can leave a to-do note to rewrite this properly sometime in the future?

template <typename T>
bool operator()(const T* const camera, const T* const point, T* residuals) const
{
// camera[0,1,2,3] is are the rotation of the camera as a quaternion.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// camera[0,1,2,3] is are the rotation of the camera as a quaternion.
// camera[0,1,2,3] is the rotation of the camera as a quaternion.

Comment on lines +402 to +405
// Define Observation Covariance
fuse_core::Matrix2d cov;
cov << 1e-5, 0.0, // NOLINT
0.0, 1e-5; // NOLINT
Copy link
Contributor

Choose a reason for hiding this comment

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

The really small observation covariance was implemented because inside the reference implementation (bal_problem_ceres), the observations are assumed to be perfect?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think so? At least the residuals are not being scaled here: https://github.com/ceres-solver/ceres-solver/blob/master/examples/snavely_reprojection_error.h and there is no-where that the pixel covariance comes into play.

/**
* @brief Construct a pinhole camera variable given a camera id and intrinsic parameters
*
* @param[in] camera_id The id associated to a camera
Copy link
Contributor

Choose a reason for hiding this comment

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

The constructor has a uuid input as well. Doxygen is out of date.

Comment on lines +76 to +77
explicit BaseCamera(const fuse_core::UUID& uuid, const uint64_t& camera_id):
FixedSizeVariable<N>(uuid), id_(camera_id) {}
Copy link
Contributor

Choose a reason for hiding this comment

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

In the Point2DLandmark class, I have this constructor hidden from the public. The UUID is supposed to be generated by some function of the class name and the landmark ID. Maybe "hash" is a better description than "uuid"? But the idea was that every Landmark ID generated a unique UUID for fuse bookkeeping purposes. And the same landmark UUID should be generated regardless of which robot observed the landmark. For example, an Apriltag has an ID embedded in it. So regradless of whether Tag1 is observed by RobotA or RobotB, the same landmark variable should be used in both cases.

Whereas the Orientation2DStamped accepts a timestamp and an optional "device_id". Here the intent is that a device (robot is what I was thinking, but however someone wants to use the device) may move around over time. A device UUID is not sufficient to identify a variable uniquely in the fuse/Ceres system. But a device at a specific point in time should be unique. I.e. (DeviceA, Time=1.0) generates a different variable UUID than (DeviceA, Time=2.0) or (DeviceB, Time=1.0). This allows different devices/robots to have orientations are the same time without generating clashing UUIDs.

I'm not sure whether a BaseCamera is closer to a Landmark? Or closer to an Orientation?

The Camera variable contains the calibration values. We do not expect the calibration values to be time-varying. So we would want one Camera variable for every physical camera. E.g. (RobotA, CenterCamera) is one variable, (RobotA, RearCamera) is a second variable, and (RobotB, CenterCamera) is a third variable. But there is no timestamp component.

So you could does something like the Orientation, but instead of a timestamp to differentiate different variables from the same robot, you would have some sort of camera ID or camera position?

explicit Orientation2DStamped(const uint64_t& id, const fuse_core::UUID& device_id = fuse_core::uuid::NIL)
 : FixedSizeVariable(fuse_core::uuid::generate(detail::type(), id, device_id)),

or

explicit Orientation2DStamped(const std::string& camera_name, const fuse_core::UUID& device_id = fuse_core::uuid::NIL)
 : FixedSizeVariable(fuse_core::uuid::generate(detail::type(), camera_name, device_id)),

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is something that's been playing in the back of my mind. In a "standard" bundle adjustment, each cameras would also have a pose associated with it, which presumably varies over time in the case of SfM, so it would make sense for the camera object to include details of the pose and therefore time. However, this seems a bit inelegant. The graph would have a new camera constraint for every frame when the robot is moving. Instead, I chose to have the camera only represent intrinsics. That way, we can represent the pose of the robot as one variable, the cam->bot extrinsics by another (probably fixed) variable and the intrinsics by this. So basically, yes, I agree. I will change it to use the "orientation" with an ID instead of a stamp.

@@ -137,7 +138,7 @@ class PinholeCamera : public FixedSizeVariable<4>
/**
* @brief Read-only access to the id
*/
const uint64_t& id() const { return id_; }
// const uint64_t& id() const { return id_; }
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove commented code if it is not needed?

* construction and dependent on a user input database id. As such, the database id cannot be altered after
* construction.
*/
class PinholeCameraSimple : public BaseCamera<3>
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this a SnavellyCamera? Or RadialDistortionCamera?
Just trying to think of a better name than "Simple".

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Both, I think...

Strictly speaking, its a radial distortion model. Snavelly make some assumptions about the coordiate frame on the image plane, but it is still a radial distortion model. I'll call it radial.

/**
* @brief Construct a pinhole camera variable given a camera id and intrinsic parameters
*
* @param[in] camera_id The id associated to a camera
Copy link
Contributor

Choose a reason for hiding this comment

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

Doxygen does not list all constructor inputs

private:
// Allow Boost Serialization access to private methods
friend class boost::serialization::access;
// uint64_t id_ { 0 };
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove commented code

Comment on lines 109 to 111
return new ceres::AutoDiffCostFunction<Fixed3DLandmarkSimpleCovarianceCostFunctor, 8, 3, 4, 4>(
new Fixed3DLandmarkSimpleCovarianceCostFunctor(sqrt_information_, mean_, observations_, pts3d_));
}

Choose a reason for hiding this comment

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

A question about the numbers here, 8, 3, 4, 4>? The first parameter is the dimension of the residues. If we look into the implementation of the CostFunctor inside ' Fixed3DLandmarkSimpleCovarianceCostFunctor', it will generate 2*N residules, where N is the number of observations. Should it be something dynamic or it doesn't matter?

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

Successfully merging this pull request may close these issues.

4 participants