-
Notifications
You must be signed in to change notification settings - Fork 132
Rotate position in motion model constraint #365
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
base: devel
Are you sure you want to change the base?
Rotate position in motion model constraint #365
Conversation
|
@svwilliams @ayrton04 I'm not sure how to request a review directly so just commenting here |
|
I'm going to tag @efernandez, as Anyway, just thinking aloud here. We have three poses: The residual we want is effectively the But I would also probably eschew the Eigen rotation matrix and just do it directly: In any case, I'm not sure if it's correct for the residual to be in the frame of |
|
@ayrton04 The reasoning behind putting the pose error into pose1's frame is mainly due to an issue found in this PR where the covariance is not in the same frame as the error. Since these constraints are relative, its normal to assume the covariance should be in the local frame. Currently the covariance and pose error are in the global frame, which can lead to someone accidentally defining their covariance in the local frame |
|
I'm trying to get my head wrapped around this. "What frame is the covariance represented in?" is a perennial question in my experience. In devel right now, the residuals are computed as:
But that doesn't seem like how we would want a relative pose to work. Both And that matches my intuition about what frame the covariance should be in. The measurement is a transform from Pose1 to Pose2. I.e. The robot moved forward 1m. Thus, the measurement is represented in the Pose1 frame. It makes certain intuitive sense for the covariance to be represented in the same frame as the measurement. |
|
And as an implementation note, we should be able to compute the predicted delta directly from the |
|
Makes sense to me! |
| // rotate the position residual into the local frame | ||
| auto sin_pred_inv = std::sin(-yaw_pred); | ||
| auto cos_pred_inv= std::cos(-yaw_pred); | ||
|
|
||
| residuals[0] = cos_pred_inv * delta_position_pred_x - sin_pred_inv * delta_position_pred_y; | ||
| residuals[1] = sin_pred_inv * delta_position_pred_x + cos_pred_inv * delta_position_pred_y; | ||
| residuals[2] = parameters[6][0] - yaw_pred; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see why residuals[0] and residual[1] no longer depend on the previous pose (defined in parameters[0] and parameters[1], i.e. position1_x, position1_y and yaw1) and the next pose (defined in parameters[5] and parameters[6], i.e. position2_x, position2_y and yaw2).
Shouldn't we be computing the delta between those two and then compute the residuals as the delta between that and the predicted pose. That is:
| // rotate the position residual into the local frame | |
| auto sin_pred_inv = std::sin(-yaw_pred); | |
| auto cos_pred_inv= std::cos(-yaw_pred); | |
| residuals[0] = cos_pred_inv * delta_position_pred_x - sin_pred_inv * delta_position_pred_y; | |
| residuals[1] = sin_pred_inv * delta_position_pred_x + cos_pred_inv * delta_position_pred_y; | |
| residuals[2] = parameters[6][0] - yaw_pred; | |
| const Eigen::Matrix<T, 2, 1> position1(parameters[0][0], parameters[0][1]); | |
| const Eigen::Matrix<T, 2, 1> position2(parameters[5][0], parameters[5][1]); | |
| const Eigen::Matrix<T, 2, 1> delta_position = fuse_core::rotationMatrix2D(parameters[1][0]).transpose() * (position2 - position1); | |
| const T delta_yaw = parameters[6][0] - parameters[1][0]; // omitting fuse_core::wrapAngle2D because it is done later on | |
| const Eigen::Matrix<T, 2, 1> delta_position_pred(delta_position_pred_x, delta_position_pred_y); | |
| residuals.template head<2>() = fuse_core::rotationMatrix2D(delta_yaw).transpose() * (delta_position_pred - delta_position); | |
| residuals[2] = delta_yaw - yaw_pred; // omitting fuse_core::wrapAngle2D because it is done later on |
In a way, this is similar to
fuse/fuse_constraints/include/fuse_constraints/normal_delta_pose_2d_cost_functor.h
Lines 118 to 125 in 2c652c7
| Eigen::Map<const Eigen::Matrix<T, 2, 1>> position1_vector(position1); | |
| Eigen::Map<const Eigen::Matrix<T, 2, 1>> position2_vector(position2); | |
| Eigen::Matrix<T, 3, 1> full_residuals_vector; | |
| full_residuals_vector.template head<2>() = | |
| fuse_core::rotationMatrix2D(orientation1[0]).transpose() * (position2_vector - position1_vector) - | |
| b_.head<2>().template cast<T>(); | |
| full_residuals_vector(2) = fuse_core::wrapAngle2D(orientation2[0] - orientation1[0] - T(b_(2))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this case, I feel it would just be better to keep what I originally had, and just rotate the error back into the local frame
| const Eigen::Matrix<T, 2, 1> position1_map(position1[0], position1[1]); | ||
| const Eigen::Matrix<T, 2, 1> position2_map(position2[0], position2[1]); | ||
| const Eigen::Matrix<T, 2, 1> delta_position = fuse_core::rotationMatrix2D(-yaw1[0]) * (position2_map - position1_map); | ||
| const T delta_yaw = yaw2[0] - yaw1[0]; // omitting fuse_core::wrapAngle2D because it is done later on | ||
| const Eigen::Matrix<T, 2, 1> delta_position_pred_map(delta_position_pred[0], delta_position_pred[1]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These aren't really Eigen maps, but I just realized you can use position1 because it's already taken.
Just highlighting that. I don't really have a better option, so it's ok for me. Maybe others have a better suggestion 🤔
| init_position[0] = (T)0.0; | ||
| init_position[1] = (T)0.0; | ||
| init_yaw[0] = (T)0.0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe it's more common to see the following:
| init_position[0] = (T)0.0; | |
| init_position[1] = (T)0.0; | |
| init_yaw[0] = (T)0.0; | |
| init_position[0] = T(0.0); | |
| init_position[1] = T(0.0); | |
| init_yaw[0] = T(0.0); |
|
This now LGTM. I just made some minor comments. Does this look good to you now @svwilliams @ayrton04 ? Does it align with what you mentioned before @svwilliams ? |
| double position_pred_y; | ||
| double delta_position_pred_x; | ||
| double delta_position_pred_y; | ||
| double yaw_pred; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be renamed to delta_yaw_pred to be consistent with the other variables
| const Eigen::Vector2d delta_position_pred(delta_position_pred_x, delta_position_pred_y); | ||
|
|
||
| Eigen::Map<Eigen::Matrix<double, 8, 1>> residuals_map(residuals); | ||
| residuals_map.head<2>() = fuse_core::rotationMatrix2D(-delta_yaw) * (delta_position - delta_position_pred); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I understand why the difference of position changes are being transformed into....is this the Pose2 frame?
I feel like this should simply be:
residuals_map(0) = delta_position(0) - delta_position_pred(0);
residuals_map(1) = delta_position(1) - delta_position_pred(1);
Both delta_position and delta_position_pred are represented in the Pose1 frame. If we just subtract the positions, that error will also be in the Pose1 frame/orientaiton. And that seems like the natural frame to compute the measurement covariance in as well.
| residuals[5] = parameters[8][0] - vel_yaw_pred; | ||
| residuals[6] = parameters[9][0] - acc_linear_pred_x; | ||
| residuals[7] = parameters[9][1] - acc_linear_pred_y; | ||
| const double delta_yaw = parameters[6][0] - parameters[1][0]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be consistent with naming
| const double delta_yaw = parameters[6][0] - parameters[1][0]; | |
| const double delta_yaw_est = parameters[6][0] - parameters[1][0]; |
| const fuse_core::Matrix2d R_delta_yaw_inv = fuse_core::rotationMatrix2D(-delta_yaw); | ||
|
|
||
| Eigen::Map<Eigen::Matrix<double, 8, 1>> residuals_map(residuals); | ||
| residuals_map.head<2>() = R_delta_yaw_inv * (delta_position_pred - delta_position_est); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The delta_position_est is computed as Pose1^1 * Pose2. That effectively transforms Pose2 into the frame of Pose1.
Similarly, delta_position_pred is computed with Pose1 as the origin. So that quantity is also in the frame of Pose1.
If we did a simple subtraction, residual[0] = delta_position_est.x - delta_position_prod.x, then the residual would also be represented in the frame of Pose1. Which seems like the natural frame to compute the measurement covariance as well.
Here you are rotating the error into the ... Pose2 frame?
I feel like this should just be:
residuals_map(0) = delta_position_est.x - delta_position_pred.x;
residuals_map(1) = delta_position_est.y - delta_position_pred.y;
residuals_map(2) = delta_yaw_est - delta_yaw_pred;
...
But that is not a firm belief. I could be convinced otherwise.
|
@svwilliams @efernandez what did we conclude on this one? |
@ayrton04 - @jakemclaughlin6 still has to address @svwilliams comments, but this is a low priority task for us atm. Other than that, I believe we're all happy with the changes proposed in this PR. |
I got a little stuck on computing the manual jacobians for the new cost functor so I put this on the backburner for a bit until I have time to get back to it |
|
@ayrton04 @svwilliams @efernandez I fixed the manual jacobians and the tests are passing. Let me know if we would want more tests and I can make some more, but the existing one should be fairly comprehensive as its running 100 randomized cases |
efernandez
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That Jacobians look way simpler. I haven't really look at the math, but if the test is passing now, that should be because they're good now.
I just made a few suggestion to simplify the code a bit more, since some operations in the Jacobians are also done for the residuals computation.
fuse_models/include/fuse_models/unicycle_2d_state_cost_function.h
Outdated
Show resolved
Hide resolved
fuse_models/include/fuse_models/unicycle_2d_state_cost_function.h
Outdated
Show resolved
Hide resolved
fuse_models/include/fuse_models/unicycle_2d_state_cost_function.h
Outdated
Show resolved
Hide resolved
fuse_models/include/fuse_models/unicycle_2d_state_cost_function.h
Outdated
Show resolved
Hide resolved
fuse_models/include/fuse_models/unicycle_2d_state_cost_function.h
Outdated
Show resolved
Hide resolved
fuse_models/include/fuse_models/unicycle_2d_state_cost_functor.h
Outdated
Show resolved
Hide resolved
efernandez
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM - just two minor comments
fuse_models/include/fuse_models/unicycle_2d_state_cost_function.h
Outdated
Show resolved
Hide resolved
fuse_models/include/fuse_models/unicycle_2d_state_cost_functor.h
Outdated
Show resolved
Hide resolved
Co-authored-by: Enrique Fernandez Perdomo <efernandez@clearpath.ai>
|
@svwilliams is this good to merge, or are you guys going to do some validations with this before choosing to merge it? |
|
Sorry, I still want to do some validations on this PR. Or at least re-review it. |
@svwilliams Did you have a chance to look into this MR? I'd be happy to help, if there's anything I can help with 😃 |
No description provided.