Calibration Library 1.0.0
A C++ library for camera calibration and vision-related geometric transformations
Loading...
Searching...
No Matches
handeye_stage.cpp
Go to the documentation of this file.
5
6namespace calib::pipeline {
7
8namespace {
9
12
13struct SensorAccumulators {
14 std::vector<Eigen::Isometry3d> base;
15 std::vector<Eigen::Isometry3d> cam;
16};
17
18void ensure_artifact_slot(nlohmann::json& artifacts, const std::string& rig_id, double min_angle,
19 const OptimOptions& opts) {
20 auto& rig_artifact = artifacts[rig_id];
21 if (!rig_artifact.is_object()) {
22 rig_artifact = nlohmann::json::object();
23 }
24 rig_artifact["min_angle_deg"] = min_angle;
25 rig_artifact["options"] = opts;
26 auto& sensors = rig_artifact["sensors"];
27 if (!sensors.is_object()) {
28 sensors = nlohmann::json::object();
29 }
30}
31
32} // namespace
33
36 result.name = name();
37
38 if (context.intrinsic_results.empty()) {
39 result.summary["status"] = "waiting_for_intrinsic_stage";
40 result.success = false;
41 return result;
42 }
43 if (!context.has_handeye_config()) {
44 result.summary["status"] = "missing_config";
45 result.success = false;
46 return result;
47 }
48
49 const auto& cfg = context.handeye_config();
50 if (cfg.rigs.empty()) {
51 result.summary["status"] = "no_rigs_configured";
52 result.success = false;
53 return result;
54 }
55
56 const auto sensor_index = build_sensor_index(context.dataset.planar_cameras);
57
58 context.handeye_results.clear();
59 if (!context.artifacts.is_object()) {
60 context.artifacts = nlohmann::json::object();
61 }
62 auto& handeye_artifacts = context.artifacts["hand_eye"];
63 if (!handeye_artifacts.is_object()) {
64 handeye_artifacts = nlohmann::json::object();
65 }
66
67 bool overall_success = true;
68 bool any_success = false;
69 nlohmann::json rigs_json = nlohmann::json::array();
70
71 for (const auto& rig : cfg.rigs) {
72 nlohmann::json rig_json;
73 rig_json["rig_id"] = rig.rig_id;
74 rig_json["sensor_count"] = rig.sensors.size();
75 rig_json["min_angle_deg"] = rig.min_angle_deg;
76
77 nlohmann::json sensors_json = nlohmann::json::array();
78 bool rig_success = true;
79 bool rig_any_sensor = false;
80
81 ensure_artifact_slot(handeye_artifacts, rig.rig_id, rig.min_angle_deg, rig.options);
82 auto& sensors_artifact = handeye_artifacts[rig.rig_id]["sensors"];
83
84 for (const auto& sensor_id : rig.sensors) {
85 nlohmann::json sensor_json;
86 sensor_json["sensor_id"] = sensor_id;
87 sensor_json["requested_observations"] = rig.observations.size();
88 sensor_json["min_angle_deg"] = rig.min_angle_deg;
89
90 const auto intrinsics_it = context.intrinsic_results.find(sensor_id);
91 if (intrinsics_it == context.intrinsic_results.end()) {
92 sensor_json["status"] = "missing_intrinsics";
93 rig_success = false;
94 sensors_json.push_back(sensor_json);
95 sensors_artifact[sensor_id] = sensor_json;
96 continue;
97 }
98
99 const auto index_it = sensor_index.find(sensor_id);
100 if (index_it == sensor_index.end()) {
101 sensor_json["status"] = "missing_detections";
102 rig_success = false;
103 sensors_json.push_back(sensor_json);
104 sensors_artifact[sensor_id] = sensor_json;
105 continue;
106 }
107
108 const auto& intrinsics = intrinsics_it->second;
109 const auto& detection_index = index_it->second;
110 const auto& camera = intrinsics.refine_result.camera;
111
112 nlohmann::json view_reports = nlohmann::json::array();
113 SensorAccumulators accum;
114
115 for (const auto& view_cfg : rig.observations) {
116 nlohmann::json view_json;
117 if (!view_cfg.view_id.empty()) {
118 view_json["id"] = view_cfg.view_id;
119 }
120 view_json["base_pose"] = view_cfg.base_se3_gripper;
121
122 const auto image_it = view_cfg.images.find(sensor_id);
123 if (image_it == view_cfg.images.end()) {
124 view_json["status"] = "missing_image_reference";
125 view_reports.push_back(std::move(view_json));
126 continue;
127 }
128
129 const auto det_it = detection_index.image_lookup.find(image_it->second);
130 if (det_it == detection_index.image_lookup.end()) {
131 view_json["status"] = "image_not_in_dataset";
132 view_reports.push_back(std::move(view_json));
133 continue;
134 }
135
136 const auto* image_det = det_it->second;
137 auto planar_view = make_planar_view(*image_det);
138 view_json["points"] = planar_view.size();
139
140 if (planar_view.size() < 4U) {
141 view_json["status"] = "insufficient_points";
142 view_reports.push_back(std::move(view_json));
143 continue;
144 }
145
146 Eigen::Isometry3d cam_se3_target = estimate_planar_pose(planar_view, camera);
147 accum.base.push_back(view_cfg.base_se3_gripper);
148 accum.cam.push_back(cam_se3_target);
149 view_json["status"] = "ok";
150 view_reports.push_back(std::move(view_json));
151 }
152
153 sensor_json["used_observations"] = accum.cam.size();
154 sensor_json["views"] = view_reports;
155
156 if (accum.cam.size() < 2U) {
157 sensor_json["status"] =
158 accum.cam.empty() ? "no_observations" : "insufficient_observations";
159 rig_success = false;
160 sensors_json.push_back(sensor_json);
161 sensors_artifact[sensor_id] = sensor_json;
162 continue;
163 }
164
165 try {
166 auto he_result = estimate_and_optimize_handeye(accum.base, accum.cam,
167 rig.min_angle_deg, rig.options);
168 sensor_json["status"] = he_result.core.success ? "ok" : "optimization_failed";
169 sensor_json["success"] = he_result.core.success;
170 sensor_json["final_cost"] = he_result.core.final_cost;
171 sensor_json["report"] = he_result.core.report;
172 sensor_json["g_se3_c"] = he_result.g_se3_c;
173 if (he_result.core.covariance.size() > 0) {
174 sensor_json["covariance"] = he_result.core.covariance;
175 }
176
177 sensors_artifact[sensor_id] = sensor_json;
178
179 if (he_result.core.success) {
180 rig_any_sensor = true;
181 context.handeye_results[rig.rig_id][sensor_id] = he_result;
182 } else {
183 rig_success = false;
184 }
185 } catch (const std::exception& ex) {
186 sensor_json["status"] = "estimation_error";
187 sensor_json["error"] = ex.what();
188 sensors_artifact[sensor_id] = sensor_json;
189 rig_success = false;
190 }
191
192 sensors_json.push_back(sensor_json);
193 }
194
195 if (rig_any_sensor && rig_success) {
196 rig_json["status"] = "ok";
197 any_success = true;
198 } else if (rig_any_sensor) {
199 rig_json["status"] = "partial_success";
200 any_success = true;
201 overall_success = false;
202 } else {
203 rig_json["status"] = "failed";
204 overall_success = false;
205 }
206
207 rig_json["sensor_reports"] = std::move(sensors_json);
208 rigs_json.push_back(std::move(rig_json));
209 }
210
211 result.summary["rigs"] = std::move(rigs_json);
212 if (any_success && overall_success) {
213 result.summary["status"] = "ok";
214 result.success = true;
215 } else if (any_success) {
216 result.summary["status"] = "partial_success";
217 result.success = false;
218 } else {
219 result.summary["status"] = "failed";
220 result.success = false;
221 }
222
223 return result;
224}
225
226} // namespace calib::pipeline
auto run(PipelineContext &context) -> PipelineStageResult override
Hand-eye calibration algorithms and utilities.
std::vector< Eigen::Isometry3d > base
std::vector< Eigen::Isometry3d > cam
auto make_planar_view(const PlanarImageDetections &detections) -> PlanarView
auto build_sensor_index(const std::vector< PlanarDetections > &detections) -> std::unordered_map< std::string, SensorDetectionsIndex >
auto estimate_and_optimize_handeye(const std::vector< Eigen::Isometry3d > &base_se3_gripper, const std::vector< Eigen::Isometry3d > &camera_se3_target, double min_angle_deg=1.0, const OptimOptions &options={}) -> HandeyeResult
Estimates and refines the hand-eye transformation.
Definition handeye.cpp:80
auto estimate_planar_pose(PlanarView view, const CameraMatrix &intrinsics) -> Eigen::Isometry3d
Pipeline configuration helpers for hand-eye and bundle adjustment stages.