Calibration Library 1.0.0
A C++ library for camera calibration and vision-related geometric transformations
Loading...
Searching...
No Matches
bundle_utils.cpp
Go to the documentation of this file.
2
3#include <algorithm>
4#include <stdexcept>
5
8
10
12 const BundleRigConfig& rig,
13 const std::unordered_map<std::string, IntrinsicCalibrationOutputs>& intrinsics)
16 setup.cameras.reserve(rig.sensors.size());
17 for (std::size_t idx = 0; idx < rig.sensors.size(); ++idx) {
18 const auto& sensor_id = rig.sensors[idx];
19 const auto intrinsics_it = intrinsics.find(sensor_id);
20 if (intrinsics_it == intrinsics.end()) {
21 setup.missing_sensors.push_back(sensor_id);
22 continue;
23 }
24 setup.sensor_to_index.emplace(sensor_id, idx);
25 setup.cameras.push_back(intrinsics_it->second.refine_result.camera);
26 }
27 return setup;
28}
29
30const std::vector<HandEyeObservationConfig>* select_bundle_observations(
31 const BundleRigConfig& rig, const HandEyePipelineConfig* handeye_cfg) {
32 if (!rig.observations.empty()) {
33 return &rig.observations;
34 }
35 if (handeye_cfg == nullptr) {
36 return nullptr;
37 }
38 if (const auto* he_rig = find_handeye_rig(*handeye_cfg, rig.rig_id)) {
39 if (!he_rig->observations.empty()) {
40 return &he_rig->observations;
41 }
42 }
43 return nullptr;
44}
45
47 const std::vector<HandEyeObservationConfig>& observation_config,
48 const std::vector<std::string>& sensors,
49 const std::unordered_map<std::string, std::size_t>& sensor_to_index,
50 const std::unordered_map<std::string, SensorDetectionsIndex>& sensor_index,
51 const std::unordered_map<std::string, IntrinsicCalibrationOutputs>& intrinsics)
54 output.views = nlohmann::json::array();
55 output.accumulators.resize(sensors.size());
56
57 for (const auto& view_cfg : observation_config) {
58 nlohmann::json view_json;
59 if (!view_cfg.view_id.empty()) {
60 view_json["id"] = view_cfg.view_id;
61 }
62 view_json["base_pose"] = view_cfg.base_se3_gripper;
63
64 nlohmann::json sensor_reports = nlohmann::json::array();
65 bool view_used = false;
66
67 for (const auto& sensor_id : sensors) {
68 nlohmann::json sensor_entry;
69 sensor_entry["sensor_id"] = sensor_id;
70
71 const auto sensor_idx_it = sensor_to_index.find(sensor_id);
72 if (sensor_idx_it == sensor_to_index.end()) {
73 sensor_entry["status"] = "sensor_not_configured";
74 sensor_reports.push_back(std::move(sensor_entry));
75 continue;
76 }
77 const std::size_t sensor_idx = sensor_idx_it->second;
78
79 const auto image_it = view_cfg.images.find(sensor_id);
80 if (image_it == view_cfg.images.end()) {
81 sensor_entry["status"] = "missing_image_reference";
82 sensor_reports.push_back(std::move(sensor_entry));
83 continue;
84 }
85
86 const auto det_index_it = sensor_index.find(sensor_id);
87 if (det_index_it == sensor_index.end()) {
88 sensor_entry["status"] = "missing_detections";
89 sensor_reports.push_back(std::move(sensor_entry));
90 continue;
91 }
92
93 const auto lookup_it = det_index_it->second.image_lookup.find(image_it->second);
94 if (lookup_it == det_index_it->second.image_lookup.end()) {
95 sensor_entry["status"] = "image_not_in_dataset";
96 sensor_entry["image"] = image_it->second;
97 sensor_reports.push_back(std::move(sensor_entry));
98 continue;
99 }
100
101 const auto intrinsics_it = intrinsics.find(sensor_id);
102 if (intrinsics_it == intrinsics.end()) {
103 sensor_entry["status"] = "missing_intrinsics";
104 sensor_reports.push_back(std::move(sensor_entry));
105 continue;
106 }
107 const auto& camera = intrinsics_it->second.refine_result.camera;
108
109 const auto* image_det = lookup_it->second;
110 auto planar_view = make_planar_view(*image_det);
111
112 sensor_entry["image"] = image_it->second;
113 sensor_entry["points"] = planar_view.size();
114
115 if (planar_view.size() < 4U) {
116 sensor_entry["status"] = "insufficient_points";
117 sensor_reports.push_back(std::move(sensor_entry));
118 continue;
119 }
120
121 Eigen::Isometry3d cam_se3_target = estimate_planar_pose(planar_view, camera);
122
124 obs.view = std::move(planar_view);
125 obs.b_se3_g = view_cfg.base_se3_gripper;
126 obs.camera_index = sensor_idx;
127 output.observations.push_back(std::move(obs));
128
129 output.accumulators[sensor_idx].base.push_back(view_cfg.base_se3_gripper);
130 output.accumulators[sensor_idx].cam.push_back(cam_se3_target);
131
132 sensor_entry["status"] = "ok";
133 view_used = true;
134 sensor_reports.push_back(std::move(sensor_entry));
135 }
136
137 view_json["sensors"] = std::move(sensor_reports);
138 view_json["used"] = view_used;
139 output.views.push_back(std::move(view_json));
140 if (view_used) {
141 ++output.used_view_count;
142 }
143 }
144
145 return output;
146}
147
149 const BundleRigConfig& rig,
150 const std::unordered_map<std::string, std::unordered_map<std::string, HandeyeResult>>&
151 handeye_results,
152 const std::vector<SensorAccumulator>& accumulators) -> HandeyeInitializationResult {
154 output.report = nlohmann::json::array();
155 output.transforms.assign(rig.sensors.size(), Eigen::Isometry3d::Identity());
156
157 const auto rig_handeye_it = handeye_results.find(rig.rig_id);
158 const auto* sensor_map =
159 rig_handeye_it != handeye_results.end() ? &rig_handeye_it->second : nullptr;
160
161 for (std::size_t idx = 0; idx < rig.sensors.size(); ++idx) {
162 const auto& sensor_id = rig.sensors[idx];
163 nlohmann::json entry;
164 entry["sensor_id"] = sensor_id;
165 entry["source"] = "identity";
166
167 if (sensor_map != nullptr) {
168 const auto he_it = sensor_map->find(sensor_id);
169 if (he_it != sensor_map->end() && he_it->second.core.success) {
170 output.transforms[idx] = he_it->second.g_se3_c;
171 entry["source"] = "handeye";
172 entry["success"] = true;
173 output.report.push_back(std::move(entry));
174 continue;
175 }
176 }
177
178 if (idx < accumulators.size() && accumulators[idx].cam.size() >= 2U) {
179 try {
180 output.transforms[idx] = estimate_handeye_dlt(
181 accumulators[idx].base, accumulators[idx].cam, rig.min_angle_deg);
182 entry["source"] = "dlt";
183 entry["success"] = true;
184 } catch (const std::exception& ex) {
185 entry["source"] = "dlt";
186 entry["success"] = false;
187 entry["error"] = ex.what();
188 output.failed = true;
189 }
190 } else {
191 entry["success"] = false;
192 entry["error"] = "insufficient_observations";
193 output.failed = true;
194 }
195
196 output.report.push_back(std::move(entry));
197 }
198
199 return output;
200}
201
203 const std::vector<SensorAccumulator>& accumulators,
204 const std::vector<Eigen::Isometry3d>& init_g_se3_c)
207
208 if (rig.initial_target.has_value()) {
209 output.pose = *rig.initial_target;
210 output.source = "config";
211 return output;
212 }
213
214 std::vector<Eigen::Isometry3d> candidates;
215 for (std::size_t idx = 0; idx < accumulators.size(); ++idx) {
216 const auto& acc = accumulators[idx];
217 if (idx >= init_g_se3_c.size()) {
218 continue;
219 }
220 const auto& g_pose = init_g_se3_c[idx];
221 for (std::size_t obs_idx = 0; obs_idx < acc.base.size(); ++obs_idx) {
222 const auto& base_pose = acc.base[obs_idx];
223 const auto& cam_pose = acc.cam[obs_idx];
224 candidates.push_back(base_pose * g_pose * cam_pose);
225 }
226 }
227
228 if (!candidates.empty()) {
229 output.pose = average_isometries(candidates);
230 output.source = "estimated";
231 } else {
232 output.pose = Eigen::Isometry3d::Identity();
233 output.source = "identity";
234 }
235
236 return output;
237}
238
239} // namespace calib::pipeline::detail
Hand-eye calibration algorithms and utilities.
std::vector< Eigen::Isometry3d > base
std::vector< Eigen::Isometry3d > cam
const HandEyeRigConfig * find_handeye_rig(const HandEyePipelineConfig &cfg, const std::string &rig_id)
auto make_planar_view(const PlanarImageDetections &detections) -> PlanarView
auto average_isometries(const std::vector< Eigen::Isometry3d > &poses) -> Eigen::Isometry3d
const std::vector< HandEyeObservationConfig > * select_bundle_observations(const BundleRigConfig &rig, const HandEyePipelineConfig *handeye_cfg)
auto collect_bundle_sensor_setup(const BundleRigConfig &rig, const std::unordered_map< std::string, IntrinsicCalibrationOutputs > &intrinsics) -> BundleSensorSetup
auto compute_handeye_initialization(const BundleRigConfig &rig, const std::unordered_map< std::string, std::unordered_map< std::string, HandeyeResult > > &handeye_results, const std::vector< SensorAccumulator > &accumulators) -> HandeyeInitializationResult
auto choose_initial_target(const BundleRigConfig &rig, const std::vector< SensorAccumulator > &accumulators, const std::vector< Eigen::Isometry3d > &init_g_se3_c) -> TargetInitializationResult
auto collect_bundle_observations(const std::vector< HandEyeObservationConfig > &observation_config, const std::vector< std::string > &sensors, const std::unordered_map< std::string, std::size_t > &sensor_to_index, const std::unordered_map< std::string, SensorDetectionsIndex > &sensor_index, const std::unordered_map< std::string, IntrinsicCalibrationOutputs > &intrinsics) -> BundleViewProcessingResult
auto estimate_planar_pose(PlanarView view, const CameraMatrix &intrinsics) -> Eigen::Isometry3d
auto estimate_handeye_dlt(const std::vector< Eigen::Isometry3d > &base_se3_gripper, const std::vector< Eigen::Isometry3d > &camera_se3_target, double min_angle_deg=1.0) -> Eigen::Isometry3d
Estimates the hand-eye transformation using the Tsai-Lenz algorithm with all pairs of input transform...
Single observation used for hand-eye calibration.
Definition bundle.h:22
size_t camera_index
Which camera acquired this view.
Definition bundle.h:25
Eigen::Isometry3d b_se3_g
Pose of the gripper in the base frame.
Definition bundle.h:24
PlanarView view
Planar target observations.
Definition bundle.h:23
Bundle-adjustment rig configuration extending the hand-eye observations.
Definition handeye.h:59
std::vector< HandEyeObservationConfig > observations
Definition handeye.h:62
std::unordered_map< std::string, std::size_t > sensor_to_index
std::vector< PinholeCamera< BrownConradyd > > cameras
std::vector< std::string > missing_sensors
std::vector< SensorAccumulator > accumulators
std::vector< BundleObservation > observations
std::vector< Eigen::Isometry3d > transforms