Relative Pose Evaluation (RelativePoses Evaluation)
Relative pose evaluation is used to measure the accuracy of relative pose estimation between two camera views. In PoSDK, relative poses are defined by rotation matrix \(R_{ij}\) and translation vector \(\mathbf{t}_{ij}\).
Mathematical Definition
Relative Pose Representation
The relative pose between two cameras (view \(i\) and view \(j\)) is defined as:
where:
\(R_{ij}\): Rotation matrix from camera \(i\) to camera \(j\) (3×3)
\(\mathbf{t}_{ij}\): Coordinates of camera \(i\)’s origin in camera \(j\)’s coordinate system (3×1)
\(\mathbf{x}_i, \mathbf{x}_j\): Normalized image coordinates in camera \(i\) and \(j\) coordinate systems respectively
Evaluation Metrics
1. Rotation Error
For ground truth rotation \(R_{ij}^{gt}\) and estimated rotation \(R_{ij}^{est}\), rotation error is calculated as:
Physical Meaning: Minimum rotation angle between two rotation matrices, in degrees.
2. Translation Angular Error
For ground truth translation \(\mathbf{t}_{ij}^{gt}\) and estimated translation \(\mathbf{t}_{ij}^{est}\), translation angular error is calculated as:
Physical Meaning: Angle between two translation directions, in degrees. Note that this evaluates direction rather than scale, as translation scale is arbitrary in two-view geometry.
Implementation Details
Core Evaluation Function
// Located in types::RelativePoses class
size_t EvaluateAgainst(
const RelativePoses& gt_poses,
std::vector<double>& rotation_errors,
std::vector<double>& translation_errors) const;
Evaluation Process
Data Matching: Match estimated and ground truth values based on view pairs \((i,j)\)
Error Calculation: Calculate rotation and translation errors for each matched view pair
Result Output: Return the number of matched view pairs and error vectors
Data Structure
// Relative pose data structure (types.hpp)
struct RelativePose {
ViewId i, j; // View pair IDs
Matrix3d Rij; // Rotation matrix
Vector3d tij; // Translation vector
double confidence; // Confidence (optional)
};
using RelativePoses = std::vector<RelativePose>;
Usage Examples
Basic Usage
#include <po_core/data/data_relative_poses.hpp>
// Create relative pose data objects
DataRelativePoses estimated_poses;
DataRelativePoses ground_truth_poses;
// Load data (example)
estimated_poses.LoadFromFile("estimated_relative_poses.pb");
ground_truth_poses.LoadFromFile("gt_relative_poses.pb");
// Execute evaluation
EvaluatorStatus eval_result = estimated_poses.Evaluate(ground_truth_poses);
if (eval_result.is_successful) {
// Get evaluation results
auto rotation_errors = eval_result.GetResults("rotation_error_deg");
auto translation_errors = eval_result.GetResults("translation_error_deg");
// Calculate statistics
double mean_rot_error = eval_result.GetMeanError("rotation_error_deg");
double mean_trans_error = eval_result.GetMeanError("translation_error_deg");
std::cout << "Evaluation successful!" << std::endl;
std::cout << "Number of matched view pairs: " << rotation_errors.size() << std::endl;
std::cout << "Mean rotation error: " << mean_rot_error << "°" << std::endl;
std::cout << "Mean translation angular error: " << mean_trans_error << "°" << std::endl;
}
Direct Low-Level API Usage
#include <po_core/types.hpp>
using namespace PoSDK::types;
// Create relative pose data
RelativePoses estimated_poses = {
{0, 1, R01_est, t01_est, 1.0},
{0, 2, R02_est, t02_est, 0.9},
// ... more pose pairs
};
RelativePoses ground_truth_poses = {
{0, 1, R01_gt, t01_gt, 1.0},
{0, 2, R02_gt, t02_gt, 1.0},
// ... corresponding ground truth
};
// Direct evaluation
std::vector<double> rotation_errors, translation_errors;
size_t matched_pairs = estimated_poses.EvaluateAgainst(
ground_truth_poses, rotation_errors, translation_errors);
std::cout << "Matched view pairs: " << matched_pairs << std::endl;
for (size_t i = 0; i < rotation_errors.size(); ++i) {
std::cout << "View pair (" << estimated_poses[i].i << "," << estimated_poses[i].j
<< "): rotation error=" << rotation_errors[i] << "°, "
<< "translation error=" << translation_errors[i] << "°" << std::endl;
}
Typical Application Scenarios
1. Two-View Geometry Verification
// Verify relative pose obtained from fundamental matrix decomposition
Matrix3d F = ComputeFundamentalMatrix(matches);
std::vector<RelativePose> candidates = DecomposeFundamentalMatrix(F);
// Compare with ground truth to select best candidate
for (const auto& candidate : candidates) {
RelativePoses est_poses = {candidate};
auto eval_result = EvaluateRelativePoses(est_poses, gt_poses);
// Select candidate with minimum error
}
2. Incremental SfM Quality Monitoring
// Monitor relative pose quality during incremental SfM process
for (const auto& new_view_pair : new_pairs) {
RelativePose estimated = EstimateRelativePose(new_view_pair);
RelativePose ground_truth = GetGroundTruthPose(new_view_pair);
double rot_error = ComputeRotationError(estimated.Rij, ground_truth.Rij);
if (rot_error > threshold) {
LOG_WARNING << "View pair " << new_view_pair << " rotation error too large: " << rot_error;
}
}
Notes
1. Coordinate System Consistency
Ensure estimated and ground truth values use the same coordinate system convention:
// PoSDK relative pose convention
// x_j ~ R_ij * x_i + t_ij
// where R_ij represents rotation from camera i to camera j
// t_ij represents coordinates of camera i's origin in camera j's coordinate system
2. Translation Scale Uncertainty
Absolute translation scale is uncertain in two-view geometry, so we evaluate translation direction rather than magnitude:
// Correct: Evaluate translation direction angle
double angle_error = acos(dot(t_gt_normalized, t_est_normalized));
// Incorrect: Directly compare translation magnitude
double magnitude_error = norm(t_gt) - norm(t_est); // Not recommended
3. Data Matching Strategy
Correct view pair matching is required during evaluation:
// Automatic matching: Based on view IDs
size_t matched = estimated.EvaluateAgainst(ground_truth, rot_errors, trans_errors);
// Manual matching: Ensure correct data correspondence
for (const auto& est_pose : estimated_poses) {
auto gt_it = std::find_if(gt_poses.begin(), gt_poses.end(),
[&](const RelativePose& gt) {
return gt.i == est_pose.i && gt.j == est_pose.j;
});
if (gt_it != gt_poses.end()) {
// Calculate error for this view pair
}
}
4. Outlier Handling
// Check validity of calculation results
if (std::isnan(rotation_error) || std::isinf(rotation_error)) {
LOG_WARNING << "Rotation error calculation abnormal, skipping this view pair";
continue;
}
// Set reasonable error thresholds
const double MAX_ROTATION_ERROR = 180.0; // degrees
const double MAX_TRANSLATION_ERROR = 180.0; // degrees
Related References: