Complete the A-to-Z Lardon3D maintenance and coherence pass. Generalize host resource policy, remove the global CPU12 ceiling, preserve host CPU/RAM reserves, scale Task capabilities through the Resource Governor, and validate deterministic parallel GV execution. Migrate Project DB to v23 with data-driven camera, lens, optical configuration and calibration profiles, including manual lenses without EXIF. Integrate safe optional LARDON SSD swap/scratch control with Governor and F10 drain/safe-to-unplug semantics. Refactor the ncurses TUI into a runtime observatory with durable progress, elapsed time, smoothed ETA, throughput, resource telemetry, Governor state, optics workflow, colors and compact/no-color fallbacks. Reconcile Queue lifetime, persistence, concurrency, comments, tests, README, AGENTS and canonical documentation. GLOBAL_MAINTENANCE_AUDIT=PASS/FROZEN
794 lines
38 KiB
C++
794 lines
38 KiB
C++
#include <algorithm>
|
|
#include <cmath>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <limits>
|
|
#include <vector>
|
|
|
|
#include <lardon3d/sparse_sfm_geometry.h>
|
|
|
|
#define CHECK(value) \
|
|
do { \
|
|
if (!(value)) { \
|
|
std::fprintf(stderr, "geometry failure line %d: %s\n", __LINE__, #value); \
|
|
return 1; \
|
|
} \
|
|
} while (0)
|
|
|
|
static Lardon3DSparseGeometryPoint2 project(
|
|
const Lardon3DSparseGeometryCalibration &calibration,
|
|
const Lardon3DSparseGeometryPoint3 &point,
|
|
const Lardon3DSparseGeometryPose &pose) {
|
|
double x = pose.rotation_cw[0] * point.x + pose.rotation_cw[1] * point.y +
|
|
pose.rotation_cw[2] * point.z + pose.translation_cw[0];
|
|
double y = pose.rotation_cw[3] * point.x + pose.rotation_cw[4] * point.y +
|
|
pose.rotation_cw[5] * point.z + pose.translation_cw[1];
|
|
double z = pose.rotation_cw[6] * point.x + pose.rotation_cw[7] * point.y +
|
|
pose.rotation_cw[8] * point.z + pose.translation_cw[2];
|
|
Lardon3DSparseGeometryPoint2 projected = {calibration.fx * x / z + calibration.cx,
|
|
calibration.fy * y / z + calibration.cy};
|
|
return projected;
|
|
}
|
|
|
|
static double matrix_noise(size_t index, double amplitude) {
|
|
int value = static_cast<int>((index * 37U + 11U) % 17U) - 8;
|
|
return amplitude * static_cast<double>(value) / 8.0;
|
|
}
|
|
|
|
static double rotation_error(const Lardon3DSparseGeometryPose &pose) {
|
|
double trace = pose.rotation_cw[0] + pose.rotation_cw[4] +
|
|
pose.rotation_cw[8];
|
|
return std::acos(std::clamp((trace - 1.0) / 2.0, -1.0, 1.0));
|
|
}
|
|
|
|
static double translation_direction_error(
|
|
const Lardon3DSparseGeometryPose &pose, double x, double y, double z) {
|
|
double norm = std::sqrt(pose.translation_cw[0] * pose.translation_cw[0] +
|
|
pose.translation_cw[1] * pose.translation_cw[1] +
|
|
pose.translation_cw[2] * pose.translation_cw[2]);
|
|
double target_norm = std::sqrt(x * x + y * y + z * z);
|
|
double dot = (pose.translation_cw[0] * x + pose.translation_cw[1] * y +
|
|
pose.translation_cw[2] * z) /
|
|
(norm * target_norm);
|
|
return std::acos(std::clamp(dot, -1.0, 1.0));
|
|
}
|
|
|
|
static bool relative_result_equal(
|
|
const Lardon3DSparseGeometryRelativePoseResult &left,
|
|
const Lardon3DSparseGeometryRelativePoseResult &right) {
|
|
return std::memcmp(left.pose_ba.rotation_cw, right.pose_ba.rotation_cw,
|
|
sizeof(left.pose_ba.rotation_cw)) == 0 &&
|
|
std::memcmp(left.pose_ba.translation_cw, right.pose_ba.translation_cw,
|
|
sizeof(left.pose_ba.translation_cw)) == 0 &&
|
|
left.inlier_count == right.inlier_count &&
|
|
left.inlier_ratio == right.inlier_ratio &&
|
|
left.median_parallax_rad == right.median_parallax_rad &&
|
|
left.inlier_mask == right.inlier_mask &&
|
|
left.inlier_mask_capacity == right.inlier_mask_capacity;
|
|
}
|
|
|
|
static bool pnp_result_equal(const Lardon3DSparseGeometryPnPResult &left,
|
|
const Lardon3DSparseGeometryPnPResult &right) {
|
|
return std::memcmp(left.pose_cw.rotation_cw, right.pose_cw.rotation_cw,
|
|
sizeof(left.pose_cw.rotation_cw)) == 0 &&
|
|
std::memcmp(left.pose_cw.translation_cw, right.pose_cw.translation_cw,
|
|
sizeof(left.pose_cw.translation_cw)) == 0 &&
|
|
left.inlier_count == right.inlier_count &&
|
|
left.inlier_ratio == right.inlier_ratio &&
|
|
left.inlier_mask == right.inlier_mask &&
|
|
left.inlier_mask_capacity == right.inlier_mask_capacity;
|
|
}
|
|
|
|
static int signed_parameter_boundary_test() {
|
|
const Lardon3DSparseGeometryCalibration calibration =
|
|
{1280, 960, 800, 800, 640, 480, 0, 0, 0, 0};
|
|
constexpr size_t count = 8;
|
|
Lardon3DSparseGeometryPoint2 relative_a[count];
|
|
Lardon3DSparseGeometryPoint2 relative_b[count];
|
|
Lardon3DSparseGeometryPoint3 pnp_points[count];
|
|
Lardon3DSparseGeometryPoint2 pnp_pixels[count];
|
|
for (size_t index = 0; index < count; ++index) {
|
|
relative_a[index] = {500.0 + static_cast<double>(index), 480.0};
|
|
relative_b[index] = {510.0 + static_cast<double>(index), 480.0};
|
|
pnp_points[index] = {static_cast<double>(index), 0.0, 4.0};
|
|
pnp_pixels[index] = {500.0 + static_cast<double>(index), 480.0};
|
|
}
|
|
|
|
auto relative_rejects_without_mutation =
|
|
[&](uint32_t max_iterations, uint32_t minimum_inliers) {
|
|
uint8_t mask[count];
|
|
std::fill(mask, mask + count, UINT8_C(0xa5));
|
|
uint8_t mask_before[count];
|
|
std::memcpy(mask_before, mask, sizeof(mask));
|
|
Lardon3DSparseGeometryRelativePoseResult result = {};
|
|
for (size_t index = 0; index < 9; ++index)
|
|
result.pose_ba.rotation_cw[index] = 10.0 + static_cast<double>(index);
|
|
for (size_t index = 0; index < 3; ++index)
|
|
result.pose_ba.translation_cw[index] =
|
|
20.0 + static_cast<double>(index);
|
|
result.inlier_count = 71;
|
|
result.inlier_ratio = 0.375;
|
|
result.median_parallax_rad = 0.125;
|
|
result.inlier_mask = mask;
|
|
result.inlier_mask_capacity = count;
|
|
const Lardon3DSparseGeometryRelativePoseResult before = result;
|
|
Lardon3DSparseGeometryRelativePoseParameters parameters =
|
|
{1.0, 0.999, max_iterations, minimum_inliers, 0.5, 1e-4, 0.5, 7};
|
|
|
|
const Lardon3DSparseGeometryResult status =
|
|
lardon3d_sparse_geometry_relative_pose(
|
|
&calibration, &calibration, relative_a, relative_b, count,
|
|
¶meters, &result);
|
|
return status == LARDON3D_SPARSE_GEOMETRY_INVALID_ARGUMENT &&
|
|
relative_result_equal(result, before) &&
|
|
std::memcmp(mask, mask_before, sizeof(mask)) == 0;
|
|
};
|
|
|
|
auto pnp_rejects_without_mutation =
|
|
[&](uint32_t max_iterations, uint32_t minimum_inliers) {
|
|
uint8_t mask[count];
|
|
std::fill(mask, mask + count, UINT8_C(0x5a));
|
|
uint8_t mask_before[count];
|
|
std::memcpy(mask_before, mask, sizeof(mask));
|
|
Lardon3DSparseGeometryPnPResult result = {};
|
|
for (size_t index = 0; index < 9; ++index)
|
|
result.pose_cw.rotation_cw[index] = 30.0 + static_cast<double>(index);
|
|
for (size_t index = 0; index < 3; ++index)
|
|
result.pose_cw.translation_cw[index] =
|
|
40.0 + static_cast<double>(index);
|
|
result.inlier_count = 83;
|
|
result.inlier_ratio = 0.625;
|
|
result.inlier_mask = mask;
|
|
result.inlier_mask_capacity = count;
|
|
const Lardon3DSparseGeometryPnPResult before = result;
|
|
Lardon3DSparseGeometryPnPParameters parameters =
|
|
{1.0, 0.999, max_iterations, minimum_inliers, 0.5, 9};
|
|
|
|
const Lardon3DSparseGeometryResult status = lardon3d_sparse_geometry_pnp(
|
|
&calibration, pnp_points, pnp_pixels, count, ¶meters, &result);
|
|
return status == LARDON3D_SPARSE_GEOMETRY_INVALID_ARGUMENT &&
|
|
pnp_result_equal(result, before) &&
|
|
std::memcmp(mask, mask_before, sizeof(mask)) == 0;
|
|
};
|
|
|
|
const uint32_t signed_maximum =
|
|
static_cast<uint32_t>(std::numeric_limits<int>::max());
|
|
const uint32_t unrepresentable_values[] = {
|
|
signed_maximum + UINT32_C(1), std::numeric_limits<uint32_t>::max()};
|
|
for (uint32_t value : unrepresentable_values) {
|
|
CHECK(relative_rejects_without_mutation(value, 6));
|
|
CHECK(relative_rejects_without_mutation(100, value));
|
|
CHECK(pnp_rejects_without_mutation(value, 6));
|
|
CHECK(pnp_rejects_without_mutation(100, value));
|
|
}
|
|
CHECK(relative_rejects_without_mutation(0, 6));
|
|
CHECK(pnp_rejects_without_mutation(0, 6));
|
|
|
|
/*
|
|
* INT_MAX itself remains valid. These collinear fixtures reach the existing
|
|
* deterministic degeneracy gate without asking either robust solver to run
|
|
* an impractically large iteration budget.
|
|
*/
|
|
Lardon3DSparseGeometryRelativePoseParameters relative_parameters =
|
|
{1.0, 0.999, signed_maximum, signed_maximum, 0.5, 1e-4, 0.5, 11};
|
|
Lardon3DSparseGeometryRelativePoseResult relative_result = {};
|
|
CHECK(lardon3d_sparse_geometry_relative_pose(
|
|
&calibration, &calibration, relative_a, relative_b, count,
|
|
&relative_parameters, &relative_result) ==
|
|
LARDON3D_SPARSE_GEOMETRY_DEGENERATE);
|
|
relative_parameters.max_iterations = 1;
|
|
relative_parameters.minimum_inliers = 0;
|
|
CHECK(lardon3d_sparse_geometry_relative_pose(
|
|
&calibration, &calibration, relative_a, relative_b, count,
|
|
&relative_parameters, &relative_result) ==
|
|
LARDON3D_SPARSE_GEOMETRY_DEGENERATE);
|
|
|
|
Lardon3DSparseGeometryPnPParameters pnp_parameters =
|
|
{1.0, 0.999, signed_maximum, signed_maximum, 0.5, 13};
|
|
Lardon3DSparseGeometryPnPResult pnp_result = {};
|
|
CHECK(lardon3d_sparse_geometry_pnp(
|
|
&calibration, pnp_points, pnp_pixels, count, &pnp_parameters,
|
|
&pnp_result) == LARDON3D_SPARSE_GEOMETRY_DEGENERATE);
|
|
pnp_parameters.max_iterations = 1;
|
|
pnp_parameters.minimum_inliers = 0;
|
|
CHECK(lardon3d_sparse_geometry_pnp(
|
|
&calibration, pnp_points, pnp_pixels, count, &pnp_parameters,
|
|
&pnp_result) == LARDON3D_SPARSE_GEOMETRY_DEGENERATE);
|
|
return 0;
|
|
}
|
|
|
|
static int matrix_test() {
|
|
Lardon3DSparseGeometryCalibration calibration =
|
|
{1280, 960, 800, 800, 640, 480, 0, 0, 0, 0};
|
|
Lardon3DSparseGeometryPose pose_a = {{1, 0, 0, 0, 1, 0, 0, 0, 1},
|
|
{0, 0, 0}};
|
|
Lardon3DSparseGeometryPose pose_b = pose_a;
|
|
pose_b.translation_cw[0] = 1.0;
|
|
double pnp_worst_rotation_error = 0.0;
|
|
double pnp_worst_translation_error = 0.0;
|
|
double pnp_worst_precision = 1.0;
|
|
double pnp_worst_recall = 1.0;
|
|
constexpr size_t count = 64;
|
|
Lardon3DSparseGeometryPoint2 pixels_a[count];
|
|
Lardon3DSparseGeometryPoint2 clean_b[count];
|
|
for (size_t index = 0; index < count; ++index) {
|
|
Lardon3DSparseGeometryPoint3 point = {
|
|
-1.0 + static_cast<double>(index % 8) * 0.28,
|
|
-0.7 + static_cast<double>(index / 8) * 0.18,
|
|
4.0 + static_cast<double>(index % 11) * 0.2};
|
|
pixels_a[index] = project(calibration, point, pose_a);
|
|
clean_b[index] = project(calibration, point, pose_b);
|
|
}
|
|
const double noises[] = {0.0, 0.25, 0.5, 1.5};
|
|
const double outlier_rates[] = {0.0, 0.1, 0.25, 0.4};
|
|
for (double noise : noises) {
|
|
for (double outlier_rate : outlier_rates) {
|
|
Lardon3DSparseGeometryPoint2 pixels_b[count];
|
|
size_t outliers = static_cast<size_t>(count * outlier_rate);
|
|
for (size_t index = 0; index < count; ++index) {
|
|
pixels_b[index] = clean_b[index];
|
|
pixels_b[index].x += matrix_noise(index, noise);
|
|
pixels_b[index].y += matrix_noise(index + 19, noise);
|
|
if (index >= count - outliers) {
|
|
pixels_b[index].x = 80.0 + static_cast<double>(index * 31U % 1100U);
|
|
pixels_b[index].y = 60.0 + static_cast<double>(index * 17U % 800U);
|
|
}
|
|
}
|
|
uint8_t mask[count] = {0};
|
|
Lardon3DSparseGeometryRelativePoseParameters parameters =
|
|
{1.5, 0.999, 1500, 24, 0.5, 1e-4, 0.5, 100 +
|
|
static_cast<uint64_t>(noise * 10)};
|
|
Lardon3DSparseGeometryRelativePoseResult result = {};
|
|
result.inlier_mask = mask;
|
|
result.inlier_mask_capacity = count;
|
|
Lardon3DSparseGeometryResult status =
|
|
lardon3d_sparse_geometry_relative_pose(
|
|
&calibration, &calibration, pixels_a, pixels_b, count,
|
|
¶meters, &result);
|
|
if (noise == 0.0 && outlier_rate == 0.0) {
|
|
CHECK(status == LARDON3D_SPARSE_GEOMETRY_OK);
|
|
CHECK(rotation_error(result.pose_ba) < 0.02);
|
|
CHECK(translation_direction_error(result.pose_ba, 1, 0, 0) < 0.05);
|
|
}
|
|
if (status == LARDON3D_SPARSE_GEOMETRY_OK && noise <= 0.75 &&
|
|
outlier_rate <= 0.25) {
|
|
CHECK(rotation_error(result.pose_ba) < 0.25);
|
|
CHECK(translation_direction_error(result.pose_ba, 1, 0, 0) < 0.35);
|
|
CHECK(result.inlier_count >= 24);
|
|
}
|
|
CHECK(status != LARDON3D_SPARSE_GEOMETRY_INVALID_ARGUMENT);
|
|
}
|
|
}
|
|
Lardon3DSparseGeometryPoint2 collinear_a[count];
|
|
Lardon3DSparseGeometryPoint2 collinear_b[count];
|
|
for (size_t index = 0; index < count; ++index) {
|
|
collinear_a[index] = {500.0 + static_cast<double>(index), 480.0};
|
|
collinear_b[index] = {500.0 + static_cast<double>(index), 480.0};
|
|
}
|
|
uint8_t mask[count] = {0};
|
|
Lardon3DSparseGeometryRelativePoseParameters parameters =
|
|
{1.0, 0.999, 500, 8, 0.5, 1e-4, 0.5, 77};
|
|
Lardon3DSparseGeometryRelativePoseResult result = {};
|
|
result.inlier_mask = mask;
|
|
result.inlier_mask_capacity = count;
|
|
CHECK(lardon3d_sparse_geometry_relative_pose(
|
|
&calibration, &calibration, collinear_a, collinear_b, count,
|
|
¶meters, &result) != LARDON3D_SPARSE_GEOMETRY_OK);
|
|
CHECK(lardon3d_sparse_geometry_relative_pose(
|
|
&calibration, &calibration, pixels_a, clean_b, 4, ¶meters,
|
|
&result) == LARDON3D_SPARSE_GEOMETRY_INSUFFICIENT_CORRESPONDENCES);
|
|
Lardon3DSparseGeometryPoint2 nan_point = {NAN, 0};
|
|
CHECK(lardon3d_sparse_geometry_normalize(&calibration, &nan_point, 1,
|
|
&nan_point) ==
|
|
LARDON3D_SPARSE_GEOMETRY_NONFINITE_INPUT);
|
|
Lardon3DSparseGeometryPose forward = pose_b;
|
|
forward.translation_cw[0] = 0;
|
|
forward.translation_cw[2] = 0.01;
|
|
for (size_t index = 0; index < count; ++index)
|
|
clean_b[index] = project(calibration,
|
|
{-1.0 + static_cast<double>(index % 8) * 0.28,
|
|
-0.7 + static_cast<double>(index / 8) * 0.18,
|
|
4.0 + static_cast<double>(index % 11) * 0.2},
|
|
forward);
|
|
CHECK(lardon3d_sparse_geometry_relative_pose(
|
|
&calibration, &calibration, pixels_a, clean_b, count, ¶meters,
|
|
&result) != LARDON3D_SPARSE_GEOMETRY_OK);
|
|
const size_t pnp_count = 32;
|
|
Lardon3DSparseGeometryPoint3 pnp_points[pnp_count];
|
|
Lardon3DSparseGeometryPoint2 pnp_pixels[pnp_count];
|
|
for (size_t index = 0; index < pnp_count; ++index) {
|
|
pnp_points[index] = {-1.0 + static_cast<double>(index % 8) * 0.3,
|
|
-0.7 + static_cast<double>(index / 8) * 0.2,
|
|
4.0 + static_cast<double>(index % 5) * 0.3};
|
|
pnp_pixels[index] = project(calibration, pnp_points[index], pose_b);
|
|
}
|
|
uint8_t pnp_mask[pnp_count] = {0};
|
|
Lardon3DSparseGeometryPnPParameters pnp_parameters =
|
|
{1.5, 0.999, 1000, 12, 0.5, 404};
|
|
Lardon3DSparseGeometryPnPResult pnp_result = {};
|
|
pnp_result.inlier_mask = pnp_mask;
|
|
pnp_result.inlier_mask_capacity = pnp_count;
|
|
CHECK(lardon3d_sparse_geometry_pnp(
|
|
&calibration, pnp_points, pnp_pixels, pnp_count,
|
|
&pnp_parameters, &pnp_result) == LARDON3D_SPARSE_GEOMETRY_OK);
|
|
const double pnp_noises[] = {0.0, 0.25, 0.75, 2.0};
|
|
const double pnp_outliers[] = {0.0, 0.125, 0.25, 0.4};
|
|
for (double noise : pnp_noises) {
|
|
for (double outlier_rate : pnp_outliers) {
|
|
Lardon3DSparseGeometryPoint2 altered[pnp_count];
|
|
size_t outlier_count = static_cast<size_t>(pnp_count * outlier_rate);
|
|
for (size_t index = 0; index < pnp_count; ++index) {
|
|
altered[index] = pnp_pixels[index];
|
|
altered[index].x += matrix_noise(index, noise);
|
|
altered[index].y += matrix_noise(index + 31, noise);
|
|
if (index >= pnp_count - outlier_count) {
|
|
altered[index].x = 100.0 + static_cast<double>(index * 41U % 1000U);
|
|
altered[index].y = 100.0 + static_cast<double>(index * 23U % 700U);
|
|
}
|
|
}
|
|
Lardon3DSparseGeometryResult status = lardon3d_sparse_geometry_pnp(
|
|
&calibration, pnp_points, altered, pnp_count, &pnp_parameters,
|
|
&pnp_result);
|
|
CHECK(status != LARDON3D_SPARSE_GEOMETRY_INVALID_ARGUMENT);
|
|
if (status == LARDON3D_SPARSE_GEOMETRY_OK) {
|
|
size_t true_positive = 0;
|
|
size_t false_positive = 0;
|
|
size_t false_negative = 0;
|
|
for (size_t index = 0; index < pnp_count; ++index) {
|
|
bool expected_inlier = index < pnp_count - outlier_count;
|
|
bool actual_inlier = pnp_mask[index] != 0;
|
|
if (expected_inlier && actual_inlier)
|
|
++true_positive;
|
|
else if (!expected_inlier && actual_inlier)
|
|
++false_positive;
|
|
else if (expected_inlier)
|
|
++false_negative;
|
|
}
|
|
double precision = true_positive == 0
|
|
? 0.0
|
|
: static_cast<double>(true_positive) /
|
|
static_cast<double>(true_positive + false_positive);
|
|
double recall = static_cast<double>(true_positive) /
|
|
static_cast<double>(true_positive + false_negative);
|
|
pnp_worst_precision = std::min(pnp_worst_precision, precision);
|
|
pnp_worst_recall = std::min(pnp_worst_recall, recall);
|
|
pnp_worst_rotation_error =
|
|
std::max(pnp_worst_rotation_error, rotation_error(pnp_result.pose_cw));
|
|
pnp_worst_translation_error = std::max(
|
|
pnp_worst_translation_error,
|
|
std::sqrt((pnp_result.pose_cw.translation_cw[0] - 1.0) *
|
|
(pnp_result.pose_cw.translation_cw[0] - 1.0) +
|
|
pnp_result.pose_cw.translation_cw[1] *
|
|
pnp_result.pose_cw.translation_cw[1] +
|
|
pnp_result.pose_cw.translation_cw[2] *
|
|
pnp_result.pose_cw.translation_cw[2]));
|
|
}
|
|
}
|
|
}
|
|
Lardon3DSparseGeometryPoint3 collinear_points[pnp_count];
|
|
Lardon3DSparseGeometryPoint2 collinear_pixels[pnp_count];
|
|
for (size_t index = 0; index < pnp_count; ++index) {
|
|
collinear_points[index] = {static_cast<double>(index) * 0.1, 0, 4};
|
|
collinear_pixels[index] = project(calibration, collinear_points[index], pose_b);
|
|
}
|
|
CHECK(lardon3d_sparse_geometry_pnp(
|
|
&calibration, collinear_points, collinear_pixels, pnp_count,
|
|
&pnp_parameters, &pnp_result) ==
|
|
LARDON3D_SPARSE_GEOMETRY_DEGENERATE);
|
|
Lardon3DSparseGeometryPoint3 duplicate_points[pnp_count];
|
|
Lardon3DSparseGeometryPoint2 duplicate_pixels[pnp_count];
|
|
for (size_t index = 0; index < pnp_count; ++index) {
|
|
duplicate_points[index] = pnp_points[0];
|
|
duplicate_pixels[index] = pnp_pixels[0];
|
|
}
|
|
CHECK(lardon3d_sparse_geometry_pnp(
|
|
&calibration, duplicate_points, duplicate_pixels, pnp_count,
|
|
&pnp_parameters, &pnp_result) ==
|
|
LARDON3D_SPARSE_GEOMETRY_DEGENERATE);
|
|
Lardon3DSparseGeometryPoint3 planar_points[pnp_count];
|
|
Lardon3DSparseGeometryPoint3 near_planar_points[pnp_count];
|
|
Lardon3DSparseGeometryPoint3 far_points[pnp_count];
|
|
Lardon3DSparseGeometryPoint2 planar_pixels[pnp_count];
|
|
Lardon3DSparseGeometryPoint2 near_planar_pixels[pnp_count];
|
|
Lardon3DSparseGeometryPoint2 far_pixels[pnp_count];
|
|
for (size_t index = 0; index < pnp_count; ++index) {
|
|
double x = -1.0 + static_cast<double>(index % 8) * 0.3;
|
|
double y = -0.7 + static_cast<double>(index / 8) * 0.2;
|
|
planar_points[index] = {x, y, 4.0};
|
|
near_planar_points[index] = {x, y, 4.0 + static_cast<double>(index % 3) * 1e-4};
|
|
far_points[index] = {x, y, 1000.0 + static_cast<double>(index % 3)};
|
|
planar_pixels[index] = project(calibration, planar_points[index], pose_b);
|
|
near_planar_pixels[index] =
|
|
project(calibration, near_planar_points[index], pose_b);
|
|
far_pixels[index] = project(calibration, far_points[index], pose_b);
|
|
}
|
|
CHECK(lardon3d_sparse_geometry_pnp(
|
|
&calibration, planar_points, planar_pixels, pnp_count,
|
|
&pnp_parameters, &pnp_result) == LARDON3D_SPARSE_GEOMETRY_OK);
|
|
CHECK(lardon3d_sparse_geometry_pnp(
|
|
&calibration, near_planar_points, near_planar_pixels, pnp_count,
|
|
&pnp_parameters, &pnp_result) !=
|
|
LARDON3D_SPARSE_GEOMETRY_INVALID_ARGUMENT);
|
|
CHECK(lardon3d_sparse_geometry_pnp(
|
|
&calibration, far_points, far_pixels, pnp_count, &pnp_parameters,
|
|
&pnp_result) != LARDON3D_SPARSE_GEOMETRY_INVALID_ARGUMENT);
|
|
for (size_t view_count : {size_t(2), size_t(3), size_t(5), size_t(10)}) {
|
|
Lardon3DSparseGeometryPoint2 observations[10];
|
|
Lardon3DSparseGeometryPose poses[10];
|
|
Lardon3DSparseGeometryPoint3 truth_point = {0.2, -0.1, 4.0};
|
|
for (size_t view = 0; view < view_count; ++view) {
|
|
poses[view] = pose_a;
|
|
poses[view].translation_cw[0] = static_cast<double>(view) * 0.5;
|
|
observations[view] = project(calibration, truth_point, poses[view]);
|
|
CHECK(lardon3d_sparse_geometry_normalize(
|
|
&calibration, &observations[view], 1, &observations[view]) ==
|
|
LARDON3D_SPARSE_GEOMETRY_OK);
|
|
}
|
|
Lardon3DSparseGeometryPoint3 multi;
|
|
CHECK(lardon3d_sparse_geometry_triangulate_multi_view(
|
|
observations, poses, view_count, &multi) ==
|
|
LARDON3D_SPARSE_GEOMETRY_OK);
|
|
CHECK(std::abs(multi.z - truth_point.z) < 1e-7);
|
|
}
|
|
Lardon3DSparseGeometryPoint2 planar_a[count];
|
|
Lardon3DSparseGeometryPoint2 planar_b[count];
|
|
Lardon3DSparseGeometryPoint2 far_a[count];
|
|
Lardon3DSparseGeometryPoint2 far_b[count];
|
|
Lardon3DSparseGeometryPose far_pose = pose_b;
|
|
for (size_t index = 0; index < count; ++index) {
|
|
Lardon3DSparseGeometryPoint3 planar_point = {
|
|
-1.0 + static_cast<double>(index % 8) * 0.28,
|
|
-0.7 + static_cast<double>(index / 8) * 0.18, 4.0};
|
|
Lardon3DSparseGeometryPoint3 far_point = {planar_point.x, planar_point.y,
|
|
10000.0};
|
|
planar_a[index] = project(calibration, planar_point, pose_a);
|
|
planar_b[index] = project(calibration, planar_point, pose_b);
|
|
far_a[index] = project(calibration, far_point, pose_a);
|
|
far_b[index] = project(calibration, far_point, far_pose);
|
|
}
|
|
CHECK(lardon3d_sparse_geometry_relative_pose(
|
|
&calibration, &calibration, planar_a, planar_b, count, ¶meters,
|
|
&result) != LARDON3D_SPARSE_GEOMETRY_INVALID_ARGUMENT);
|
|
parameters.minimum_parallax_rad = 1e-4;
|
|
CHECK(lardon3d_sparse_geometry_relative_pose(
|
|
&calibration, &calibration, far_a, far_b, count, ¶meters,
|
|
&result) != LARDON3D_SPARSE_GEOMETRY_OK);
|
|
std::printf("pnp_worst_rotation=%.17g pnp_worst_translation=%.17g "
|
|
"pnp_worst_precision=%.17g pnp_worst_recall=%.17g\n",
|
|
pnp_worst_rotation_error, pnp_worst_translation_error,
|
|
pnp_worst_precision, pnp_worst_recall);
|
|
return 0;
|
|
}
|
|
|
|
static int multiview_matrix_test() {
|
|
const Lardon3DSparseGeometryCalibration calibration =
|
|
{1280, 960, 800, 800, 640, 480, 0, 0, 0, 0};
|
|
const Lardon3DSparseGeometryPoint3 truth = {0.35, -0.2, 4.0};
|
|
const size_t view_counts[] = {2, 3, 5, 10};
|
|
double worst_error = 0.0;
|
|
double worst_mean_reprojection = 0.0;
|
|
double worst_max_reprojection = 0.0;
|
|
double lowest_accepted_parallax = std::numeric_limits<double>::max();
|
|
double highest_rejected_parallax = 0.0;
|
|
for (size_t view_count : view_counts) {
|
|
const double noise_levels[] = {0.0, 0.25, 0.75, 1.5};
|
|
for (size_t noise_index = 0; noise_index < 4; ++noise_index) {
|
|
Lardon3DSparseGeometryPoint2 observations[10];
|
|
Lardon3DSparseGeometryPose poses[10];
|
|
for (size_t view = 0; view < view_count; ++view) {
|
|
poses[view] = {{1, 0, 0, 0, 1, 0, 0, 0, 1},
|
|
{static_cast<double>(view) * 0.5,
|
|
static_cast<double>(view % 2) * 0.2, 0}};
|
|
observations[view] = project(calibration, truth, poses[view]);
|
|
observations[view].x +=
|
|
matrix_noise(view + noise_index * 17, noise_levels[noise_index]);
|
|
observations[view].y +=
|
|
matrix_noise(view + noise_index * 23, noise_levels[noise_index]);
|
|
CHECK(lardon3d_sparse_geometry_normalize(
|
|
&calibration, &observations[view], 1, &observations[view]) ==
|
|
LARDON3D_SPARSE_GEOMETRY_OK);
|
|
}
|
|
Lardon3DSparseGeometryPoint3 output;
|
|
Lardon3DSparseGeometryResult status =
|
|
lardon3d_sparse_geometry_triangulate_multi_view(
|
|
observations, poses, view_count, &output);
|
|
CHECK(status == LARDON3D_SPARSE_GEOMETRY_OK);
|
|
CHECK(std::isfinite(output.x) && std::isfinite(output.y) &&
|
|
std::isfinite(output.z));
|
|
double error = std::sqrt((output.x - truth.x) * (output.x - truth.x) +
|
|
(output.y - truth.y) * (output.y - truth.y) +
|
|
(output.z - truth.z) * (output.z - truth.z));
|
|
double mean_reprojection = 0.0;
|
|
double max_reprojection = 0.0;
|
|
for (size_t view = 0; view < view_count; ++view) {
|
|
Lardon3DSparseGeometryPoint3 camera_point = output;
|
|
camera_point.x += poses[view].translation_cw[0];
|
|
camera_point.y += poses[view].translation_cw[1];
|
|
camera_point.z += poses[view].translation_cw[2];
|
|
double dx = camera_point.x / camera_point.z - observations[view].x;
|
|
double dy = camera_point.y / camera_point.z - observations[view].y;
|
|
double reprojection = std::hypot(dx, dy);
|
|
mean_reprojection += reprojection;
|
|
max_reprojection = std::max(max_reprojection, reprojection);
|
|
}
|
|
mean_reprojection /= static_cast<double>(view_count);
|
|
worst_error = std::max(worst_error, error);
|
|
worst_mean_reprojection =
|
|
std::max(worst_mean_reprojection, mean_reprojection);
|
|
worst_max_reprojection = std::max(worst_max_reprojection, max_reprojection);
|
|
Lardon3DSparseGeometryPoint3 repeated;
|
|
CHECK(lardon3d_sparse_geometry_triangulate_multi_view(
|
|
observations, poses, view_count, &repeated) ==
|
|
LARDON3D_SPARSE_GEOMETRY_OK);
|
|
CHECK(std::memcmp(&output, &repeated, sizeof(output)) == 0);
|
|
}
|
|
const double boundary_baselines[] = {0.001, 0.00045, 0.00035, 0.00005};
|
|
for (double baseline : boundary_baselines) {
|
|
Lardon3DSparseGeometryPoint2 observations[10];
|
|
Lardon3DSparseGeometryPose poses[10];
|
|
for (size_t view = 0; view < view_count; ++view) {
|
|
poses[view] = {{1, 0, 0, 0, 1, 0, 0, 0, 1},
|
|
{static_cast<double>(view) * baseline, 0, 0}};
|
|
observations[view] = project(calibration, truth, poses[view]);
|
|
CHECK(lardon3d_sparse_geometry_normalize(
|
|
&calibration, &observations[view], 1, &observations[view]) ==
|
|
LARDON3D_SPARSE_GEOMETRY_OK);
|
|
}
|
|
Lardon3DSparseGeometryPoint3 output;
|
|
Lardon3DSparseGeometryResult status =
|
|
lardon3d_sparse_geometry_triangulate_multi_view(
|
|
observations, poses, view_count, &output);
|
|
double effective_parallax = baseline / truth.z;
|
|
if (effective_parallax >= 1e-4) {
|
|
CHECK(status == LARDON3D_SPARSE_GEOMETRY_OK);
|
|
lowest_accepted_parallax =
|
|
std::min(lowest_accepted_parallax, effective_parallax);
|
|
} else {
|
|
CHECK(status == LARDON3D_SPARSE_GEOMETRY_LOW_PARALLAX);
|
|
highest_rejected_parallax =
|
|
std::max(highest_rejected_parallax, effective_parallax);
|
|
}
|
|
}
|
|
Lardon3DSparseGeometryPoint2 observations[10];
|
|
Lardon3DSparseGeometryPose poses[10];
|
|
Lardon3DSparseGeometryPoint3 far_truth = {0.35, -0.2, 10000.0};
|
|
for (size_t view = 0; view < view_count; ++view) {
|
|
poses[view] = {{1, 0, 0, 0, 1, 0, 0, 0, 1},
|
|
{static_cast<double>(view) * 2.0, 0, 0}};
|
|
observations[view] = project(calibration, far_truth, poses[view]);
|
|
CHECK(lardon3d_sparse_geometry_normalize(
|
|
&calibration, &observations[view], 1, &observations[view]) ==
|
|
LARDON3D_SPARSE_GEOMETRY_OK);
|
|
}
|
|
Lardon3DSparseGeometryPoint3 output;
|
|
CHECK(lardon3d_sparse_geometry_triangulate_multi_view(
|
|
observations, poses, view_count, &output) ==
|
|
LARDON3D_SPARSE_GEOMETRY_OK);
|
|
CHECK(std::isfinite(output.z));
|
|
}
|
|
std::printf("multi_view_worst_error=%.17g mean_reprojection=%.17g "
|
|
"max_reprojection=%.17g lowest_parallax=%.17g "
|
|
"highest_rejected_parallax=%.17g\n",
|
|
worst_error, worst_mean_reprojection, worst_max_reprojection,
|
|
lowest_accepted_parallax, highest_rejected_parallax);
|
|
return 0;
|
|
}
|
|
|
|
static int resource_test() {
|
|
Lardon3DSparseGeometryCalibration calibration =
|
|
{1280, 960, 800, 800, 640, 480, 0, 0, 0, 0};
|
|
Lardon3DSparseGeometryPose pose_a = {{1, 0, 0, 0, 1, 0, 0, 0, 1},
|
|
{0, 0, 0}};
|
|
Lardon3DSparseGeometryPose pose_b = pose_a;
|
|
pose_b.translation_cw[0] = 1.0;
|
|
const size_t relative_count = 8192;
|
|
std::vector<Lardon3DSparseGeometryPoint2> pixels_a(relative_count);
|
|
std::vector<Lardon3DSparseGeometryPoint2> pixels_b(relative_count);
|
|
for (size_t index = 0; index < relative_count; ++index) {
|
|
Lardon3DSparseGeometryPoint3 point = {
|
|
-1.0 + static_cast<double>(index % 128) * 0.015,
|
|
-0.8 + static_cast<double>((index / 128) % 64) * 0.025,
|
|
4.0 + static_cast<double>(index % 17) * 0.1};
|
|
pixels_a[index] = project(calibration, point, pose_a);
|
|
pixels_b[index] = project(calibration, point, pose_b);
|
|
}
|
|
std::vector<uint8_t> mask(relative_count);
|
|
Lardon3DSparseGeometryRelativePoseParameters relative_parameters =
|
|
{1.0, 0.999, 1000, 32, 0.1, 1e-5, 0.5, 99};
|
|
Lardon3DSparseGeometryRelativePoseResult relative = {};
|
|
relative.inlier_mask = mask.data();
|
|
relative.inlier_mask_capacity = mask.size();
|
|
CHECK(lardon3d_sparse_geometry_relative_pose(
|
|
&calibration, &calibration, pixels_a.data(), pixels_b.data(),
|
|
relative_count, &relative_parameters, &relative) ==
|
|
LARDON3D_SPARSE_GEOMETRY_OK);
|
|
const size_t pnp_count = 2000;
|
|
std::vector<Lardon3DSparseGeometryPoint3> points(pnp_count);
|
|
std::vector<Lardon3DSparseGeometryPoint2> pnp_pixels(pnp_count);
|
|
for (size_t index = 0; index < pnp_count; ++index) {
|
|
points[index] = {-1.0 + static_cast<double>(index % 100) * 0.02,
|
|
-0.8 + static_cast<double>((index / 100) % 20) * 0.03,
|
|
4.0 + static_cast<double>(index % 13) * 0.1};
|
|
pnp_pixels[index] = project(calibration, points[index], pose_b);
|
|
}
|
|
std::vector<uint8_t> pnp_mask(pnp_count);
|
|
Lardon3DSparseGeometryPnPParameters pnp_parameters =
|
|
{1.0, 0.999, 1000, 32, 0.1, 99};
|
|
Lardon3DSparseGeometryPnPResult pnp = {};
|
|
pnp.inlier_mask = pnp_mask.data();
|
|
pnp.inlier_mask_capacity = pnp_mask.size();
|
|
CHECK(lardon3d_sparse_geometry_pnp(
|
|
&calibration, points.data(), pnp_pixels.data(), pnp_count,
|
|
&pnp_parameters, &pnp) == LARDON3D_SPARSE_GEOMETRY_OK);
|
|
Lardon3DSparseGeometryPoint2 normalized_a;
|
|
Lardon3DSparseGeometryPoint2 normalized_b;
|
|
CHECK(lardon3d_sparse_geometry_normalize(&calibration, &pixels_a[0], 1,
|
|
&normalized_a) ==
|
|
LARDON3D_SPARSE_GEOMETRY_OK);
|
|
CHECK(lardon3d_sparse_geometry_normalize(&calibration, &pixels_b[0], 1,
|
|
&normalized_b) ==
|
|
LARDON3D_SPARSE_GEOMETRY_OK);
|
|
Lardon3DSparseGeometryPoint3 output;
|
|
for (size_t index = 0; index < 100000; ++index)
|
|
CHECK(lardon3d_sparse_geometry_triangulate_two_view(
|
|
&normalized_a, &normalized_b, &pose_a, &pose_b, &output) ==
|
|
LARDON3D_SPARSE_GEOMETRY_OK);
|
|
std::printf("relative=%zu inliers=%u pose=%.17g,%.17g,%.17g pnp=%zu "
|
|
"pnp_inliers=%u pnp_pose=%.17g,%.17g,%.17g triangulations=%d\n",
|
|
relative_count, relative.inlier_count,
|
|
relative.pose_ba.translation_cw[0],
|
|
relative.pose_ba.translation_cw[1],
|
|
relative.pose_ba.translation_cw[2], pnp_count,
|
|
pnp.inlier_count, pnp.pose_cw.translation_cw[0],
|
|
pnp.pose_cw.translation_cw[1], pnp.pose_cw.translation_cw[2],
|
|
100000);
|
|
return 0;
|
|
}
|
|
|
|
int main(int argc, char **argv) {
|
|
CHECK(signed_parameter_boundary_test() == 0);
|
|
if (argc > 1)
|
|
if (std::strcmp(argv[1], "matrix") == 0)
|
|
return matrix_test();
|
|
if (argc > 1)
|
|
if (std::strcmp(argv[1], "multiview-matrix") == 0)
|
|
return multiview_matrix_test();
|
|
if (argc > 1)
|
|
return resource_test();
|
|
Lardon3DSparseGeometryCalibration calibration =
|
|
{1280, 960, 800, 800, 640, 480, 0, 0, 0, 0};
|
|
Lardon3DSparseGeometryPoint2 pixel = {720, 520};
|
|
Lardon3DSparseGeometryPoint2 normalized;
|
|
CHECK(lardon3d_sparse_geometry_normalize(&calibration, &pixel, 1,
|
|
&normalized) ==
|
|
LARDON3D_SPARSE_GEOMETRY_OK);
|
|
CHECK(std::abs(normalized.x - 0.1) < 1e-12);
|
|
CHECK(std::abs(normalized.y - 0.05) < 1e-12);
|
|
Lardon3DSparseGeometryCalibration distorted_calibration = calibration;
|
|
distorted_calibration.k1 = 0.08;
|
|
distorted_calibration.k2 = -0.01;
|
|
distorted_calibration.p1 = 0.001;
|
|
distorted_calibration.p2 = -0.002;
|
|
const double ideal_x = 0.2;
|
|
const double ideal_y = -0.15;
|
|
const double radius_squared = ideal_x * ideal_x + ideal_y * ideal_y;
|
|
const double radial = 1.0 + distorted_calibration.k1 * radius_squared +
|
|
distorted_calibration.k2 * radius_squared * radius_squared;
|
|
const double distorted_x = ideal_x * radial +
|
|
2.0 * distorted_calibration.p1 * ideal_x * ideal_y +
|
|
distorted_calibration.p2 *
|
|
(radius_squared + 2.0 * ideal_x * ideal_x);
|
|
const double distorted_y = ideal_y * radial +
|
|
distorted_calibration.p1 *
|
|
(radius_squared + 2.0 * ideal_y * ideal_y) +
|
|
2.0 * distorted_calibration.p2 * ideal_x * ideal_y;
|
|
Lardon3DSparseGeometryPoint2 distorted_pixel = {
|
|
distorted_calibration.fx * distorted_x + distorted_calibration.cx,
|
|
distorted_calibration.fy * distorted_y + distorted_calibration.cy};
|
|
CHECK(lardon3d_sparse_geometry_normalize(
|
|
&distorted_calibration, &distorted_pixel, 1, &normalized) ==
|
|
LARDON3D_SPARSE_GEOMETRY_OK);
|
|
CHECK(std::abs(normalized.x - ideal_x) < 1e-8);
|
|
CHECK(std::abs(normalized.y - ideal_y) < 1e-8);
|
|
|
|
Lardon3DSparseGeometryPose pose_a = {{1, 0, 0, 0, 1, 0, 0, 0, 1},
|
|
{0, 0, 0}};
|
|
Lardon3DSparseGeometryPose pose_b = pose_a;
|
|
pose_b.translation_cw[0] = 1.0;
|
|
Lardon3DSparseGeometryPoint3 truth = {0.2, -0.1, 4.0};
|
|
Lardon3DSparseGeometryPoint2 pixels_a[8];
|
|
Lardon3DSparseGeometryPoint2 pixels_b[8];
|
|
for (size_t index = 0; index < 8; ++index) {
|
|
Lardon3DSparseGeometryPoint3 point = {
|
|
truth.x + static_cast<double>(index) * 0.17,
|
|
truth.y + static_cast<double>(index % 3) * 0.13,
|
|
truth.z + static_cast<double>(index) * 0.21};
|
|
pixels_a[index] = project(calibration, point, pose_a);
|
|
pixels_b[index] = project(calibration, point, pose_b);
|
|
}
|
|
uint8_t mask[8] = {0};
|
|
Lardon3DSparseGeometryRelativePoseParameters relative_parameters =
|
|
{1.0, 0.999, 1000, 6, 0.75, 1e-4, 0.5, 1234};
|
|
Lardon3DSparseGeometryRelativePoseResult relative = {};
|
|
relative.inlier_mask = mask;
|
|
relative.inlier_mask_capacity = 8;
|
|
CHECK(lardon3d_sparse_geometry_relative_pose(
|
|
&calibration, &calibration, pixels_a, pixels_b, 8,
|
|
&relative_parameters, &relative) ==
|
|
LARDON3D_SPARSE_GEOMETRY_OK);
|
|
CHECK(relative.inlier_count >= 6);
|
|
CHECK(relative.median_parallax_rad > 1e-4);
|
|
Lardon3DSparseGeometryRelativePoseResult repeated = {};
|
|
repeated.inlier_mask = mask;
|
|
repeated.inlier_mask_capacity = 8;
|
|
for (int run = 0; run < 20; ++run) {
|
|
CHECK(lardon3d_sparse_geometry_relative_pose(
|
|
&calibration, &calibration, pixels_a, pixels_b, 8,
|
|
&relative_parameters, &repeated) ==
|
|
LARDON3D_SPARSE_GEOMETRY_OK);
|
|
CHECK(repeated.inlier_count == relative.inlier_count);
|
|
CHECK(std::memcmp(mask, relative.inlier_mask, sizeof(mask)) == 0);
|
|
}
|
|
Lardon3DSparseGeometryPoint2 nonfinite_pixel = {NAN, 0};
|
|
CHECK(lardon3d_sparse_geometry_normalize(
|
|
&calibration, &nonfinite_pixel, 1, &normalized) ==
|
|
LARDON3D_SPARSE_GEOMETRY_NONFINITE_INPUT);
|
|
Lardon3DSparseGeometryPoint2 pure_pixels_b[8];
|
|
for (size_t index = 0; index < 8; ++index)
|
|
pure_pixels_b[index] = pixels_a[index];
|
|
CHECK(lardon3d_sparse_geometry_relative_pose(
|
|
&calibration, &calibration, pixels_a, pure_pixels_b, 8,
|
|
&relative_parameters, &repeated) !=
|
|
LARDON3D_SPARSE_GEOMETRY_OK);
|
|
|
|
Lardon3DSparseGeometryPoint2 normalized_a;
|
|
Lardon3DSparseGeometryPoint2 normalized_b;
|
|
CHECK(lardon3d_sparse_geometry_normalize(&calibration, &pixels_a[0], 1,
|
|
&normalized_a) ==
|
|
LARDON3D_SPARSE_GEOMETRY_OK);
|
|
CHECK(lardon3d_sparse_geometry_normalize(&calibration, &pixels_b[0], 1,
|
|
&normalized_b) ==
|
|
LARDON3D_SPARSE_GEOMETRY_OK);
|
|
Lardon3DSparseGeometryPoint3 triangulated;
|
|
CHECK(lardon3d_sparse_geometry_triangulate_two_view(
|
|
&normalized_a, &normalized_b, &pose_a, &pose_b, &triangulated) ==
|
|
LARDON3D_SPARSE_GEOMETRY_OK);
|
|
CHECK(std::abs(triangulated.x - truth.x) < 1e-8);
|
|
CHECK(std::abs(triangulated.y - truth.y) < 1e-8);
|
|
CHECK(std::abs(triangulated.z - truth.z) < 1e-8);
|
|
Lardon3DSparseGeometryPoint2 multi_points[3] = {
|
|
normalized_a, normalized_b, normalized_b};
|
|
Lardon3DSparseGeometryPose multi_poses[3] = {pose_a, pose_b, pose_b};
|
|
Lardon3DSparseGeometryPoint3 multi_point;
|
|
CHECK(lardon3d_sparse_geometry_triangulate_multi_view(
|
|
multi_points, multi_poses, 3, &multi_point) ==
|
|
LARDON3D_SPARSE_GEOMETRY_OK);
|
|
CHECK(std::abs(multi_point.z - truth.z) < 1e-8);
|
|
Lardon3DSparseGeometryPointRefinementParameters refinement_parameters =
|
|
{30, 1e-12};
|
|
Lardon3DSparseGeometryPoint3 initial = {truth.x + 0.1, truth.y - 0.1,
|
|
truth.z + 0.2};
|
|
Lardon3DSparseGeometryPoint3 refined;
|
|
CHECK(lardon3d_sparse_geometry_refine_point(
|
|
multi_points, multi_poses, 3, &initial, &refinement_parameters,
|
|
&refined) == LARDON3D_SPARSE_GEOMETRY_OK);
|
|
CHECK(std::abs(refined.x - truth.x) < 1e-7);
|
|
CHECK(std::abs(refined.y - truth.y) < 1e-7);
|
|
CHECK(std::abs(refined.z - truth.z) < 1e-7);
|
|
|
|
Lardon3DSparseGeometryPnPParameters pnp_parameters =
|
|
{1.0, 0.999, 1000, 6, 0.75, 1234};
|
|
Lardon3DSparseGeometryPoint3 points[8];
|
|
for (size_t index = 0; index < 8; ++index)
|
|
points[index] = {truth.x + static_cast<double>(index) * 0.17,
|
|
truth.y + static_cast<double>(index % 3) * 0.13,
|
|
truth.z + static_cast<double>(index) * 0.21};
|
|
Lardon3DSparseGeometryPnPResult pnp = {};
|
|
pnp.inlier_mask = mask;
|
|
pnp.inlier_mask_capacity = 8;
|
|
CHECK(lardon3d_sparse_geometry_pnp(&calibration, points, pixels_b, 8,
|
|
&pnp_parameters, &pnp) ==
|
|
LARDON3D_SPARSE_GEOMETRY_OK);
|
|
CHECK(pnp.inlier_count >= 6);
|
|
return 0;
|
|
}
|