CPPxDIC
C++ Digital Image Correlation library for fingertip 3D reconstruction
Loading...
Searching...
No Matches
stereo_calibration.cpp
Go to the documentation of this file.
1
10#include "utils.h"
11
12#include <Eigen/Dense>
13#include <algorithm>
14#include <cmath>
15#include <fstream>
16#include <iostream>
17#include <sstream>
18
19namespace cppxdic {
20namespace stepsABC {
21
22// TODO(stepC): the MATLAB stepC script also derives the per-camera 2D image
23// points by extracting the calibration markers from the marked calibration
24// images (camera_info_from_view / marked-image parsing) and manages global
25// paths/settings via theGlobalSettings_MNG. This port takes the 2D image points
26// as pre-extracted input files (readImagePointsFile) instead of re-deriving them
27// from the marked images. Automatic marker extraction is a deliberate gap and a
28// follow-up; the DLT fit and reconstruction-error metric below are full ports.
30 const CylinderCalibSpec& spec) {
31 std::vector<CalibPoint3D> pts;
32 pts.reserve(static_cast<size_t>(std::max(0, spec.num_rows)) *
33 static_cast<size_t>(std::max(0, spec.num_columns)));
34
35 // Angular step around the cylinder. For a full circle the columns wrap all
36 // the way around (dtheta = 2*pi/Ncol); otherwise they span an open arc.
37 const double two_pi = 2.0 * M_PI;
38 double dtheta = 0.0;
39 if (spec.num_columns > 0) {
40 dtheta = spec.full_circle ? (two_pi / spec.num_columns)
41 : (spec.num_columns > 1 ? (two_pi / spec.num_columns) : 0.0);
42 }
43 const double theta0 = spec.theta0_deg * M_PI / 180.0;
44
45 // MATLAB ordering: outer loop over rows (z), inner over columns (theta).
46 for (int r = 0; r < spec.num_rows; ++r) {
47 const double z = r * spec.dz;
48 for (int c = 0; c < spec.num_columns; ++c) {
49 const double theta = theta0 + c * dtheta;
51 p.x = spec.radius * std::cos(theta);
52 p.y = spec.radius * std::sin(theta);
53 p.z = z;
54 pts.push_back(p);
55 }
56 }
57 return pts;
58}
59
60bool StereoCalibration::writeObjectFile(const std::vector<CalibPoint3D>& pts,
61 const std::string& path, std::string& error_message) {
62 std::ofstream ofs(path);
63 if (!ofs) {
64 error_message = "Cannot open for writing: " + path;
65 return false;
66 }
67 ofs << "# xdic cylindrical calibration object v1 (x y z, mm)\n";
68 for (const auto& p : pts) {
69 ofs << p.x << " " << p.y << " " << p.z << "\n";
70 }
71 return true;
72}
73
74std::vector<CalibPoint3D> StereoCalibration::readObjectFile(const std::string& path,
75 std::string& error_message) {
76 std::vector<CalibPoint3D> pts;
77 std::ifstream ifs(path);
78 if (!ifs) {
79 error_message = "Cannot open object file: " + path;
80 return pts;
81 }
82 std::string line;
83 while (std::getline(ifs, line)) {
84 if (line.empty() || line[0] == '#') continue;
85 std::istringstream iss(line);
87 if (iss >> p.x >> p.y >> p.z) pts.push_back(p);
88 }
89 return pts;
90}
91
92std::vector<CalibPoint2D> StereoCalibration::readImagePointsFile(const std::string& path,
93 std::string& error_message) {
94 std::vector<CalibPoint2D> pts;
95 std::ifstream ifs(path);
96 if (!ifs) {
97 error_message = "Cannot open image-points file: " + path;
98 return pts;
99 }
100 std::string line;
101 while (std::getline(ifs, line)) {
102 if (line.empty() || line[0] == '#') continue;
103 std::istringstream iss(line);
104 CalibPoint2D p;
105 if (iss >> p.u >> p.v) pts.push_back(p);
106 }
107 return pts;
108}
109
111 const std::vector<CalibPoint2D>& image_points,
112 const std::vector<CalibPoint3D>& object_points,
113 std::string& error_message) {
114 CameraDLT cam;
115 cam.camera_id = camera_id;
116
117 if (image_points.size() != object_points.size()) {
118 error_message = "calcDLTParameters: image/object point count mismatch (" +
119 std::to_string(image_points.size()) + " vs " +
120 std::to_string(object_points.size()) + ")";
121 return cam;
122 }
123 const size_t N = image_points.size();
124 if (N < 6) {
125 error_message = "calcDLTParameters: need >=6 correspondences, got " + std::to_string(N);
126 return cam;
127 }
128
129 // Flatten into the row-major layout expected by Utils::DLT11Calibration.
130 std::vector<double> P2(N * 2);
131 std::vector<double> P3(N * 3);
132 for (size_t i = 0; i < N; ++i) {
133 P2[i * 2 + 0] = image_points[i].u;
134 P2[i * 2 + 1] = image_points[i].v;
135 P3[i * 3 + 0] = object_points[i].x;
136 P3[i * 3 + 1] = object_points[i].y;
137 P3[i * 3 + 2] = object_points[i].z;
138 }
139
140 std::vector<double> L;
141 if (!Utils::DLT11Calibration(P2.data(), P3.data(), N, L) || L.size() != 11) {
142 error_message =
143 "calcDLTParameters: DLT11Calibration failed for camera " + std::to_string(camera_id);
144 return cam;
145 }
146 for (int i = 0; i < 11; ++i) cam.L[i] = L[i];
147
148 // RMS image-plane reprojection error.
149 // u = (L1 X + L2 Y + L3 Z + L4) / (L9 X + L10 Y + L11 Z + 1)
150 // v = (L5 X + L6 Y + L7 Z + L8) / (L9 X + L10 Y + L11 Z + 1)
151 double sse = 0.0;
152 for (size_t i = 0; i < N; ++i) {
153 const double X = object_points[i].x;
154 const double Y = object_points[i].y;
155 const double Z = object_points[i].z;
156 const double den = cam.L[8] * X + cam.L[9] * Y + cam.L[10] * Z + 1.0;
157 if (std::abs(den) < 1e-12) continue;
158 const double u = (cam.L[0] * X + cam.L[1] * Y + cam.L[2] * Z + cam.L[3]) / den;
159 const double v = (cam.L[4] * X + cam.L[5] * Y + cam.L[6] * Z + cam.L[7]) / den;
160 const double du = u - image_points[i].u;
161 const double dv = v - image_points[i].v;
162 sse += du * du + dv * dv;
163 }
164 cam.rms_reprojection = std::sqrt(sse / static_cast<double>(N));
165 cam.valid = true;
166 return cam;
167}
168
170 const CameraDLT& c2, const CalibPoint2D& p2) {
171 // Linear DLT triangulation. For each camera and observation (u, v):
172 // (u*L9 - L1) X + (u*L10 - L2) Y + (u*L11 - L3) Z = L4 - u
173 // (v*L9 - L5) X + (v*L10 - L6) Y + (v*L11 - L7) Z = L8 - v
174 // Stack the two cameras -> 4x3 least-squares system A*[X Y Z]^T = b.
175 Eigen::Matrix<double, 4, 3> A;
176 Eigen::Matrix<double, 4, 1> b;
177
178 auto fill = [](const CameraDLT& c, const CalibPoint2D& p, Eigen::Matrix<double, 4, 3>& A,
179 Eigen::Matrix<double, 4, 1>& b, int row) {
180 A(row, 0) = p.u * c.L[8] - c.L[0];
181 A(row, 1) = p.u * c.L[9] - c.L[1];
182 A(row, 2) = p.u * c.L[10] - c.L[2];
183 b(row) = c.L[3] - p.u;
184 A(row + 1, 0) = p.v * c.L[8] - c.L[4];
185 A(row + 1, 1) = p.v * c.L[9] - c.L[5];
186 A(row + 1, 2) = p.v * c.L[10] - c.L[6];
187 b(row + 1) = c.L[7] - p.v;
188 };
189
190 fill(c1, p1, A, b, 0);
191 fill(c2, p2, A, b, 2);
192
193 Eigen::Vector3d X = A.colPivHouseholderQr().solve(b);
194 CalibPoint3D out;
195 out.x = X(0);
196 out.y = X(1);
197 out.z = X(2);
198 return out;
199}
200
202 const CameraDLT& cam1, const std::vector<CalibPoint2D>& img1, const CameraDLT& cam2,
203 const std::vector<CalibPoint2D>& img2, const std::vector<CalibPoint3D>& object_points,
204 std::vector<CalibPoint3D>& reconstructed_out) {
206 reconstructed_out.clear();
207
208 const size_t N = std::min({img1.size(), img2.size(), object_points.size()});
209 if (N == 0) return err;
210
211 reconstructed_out.reserve(N);
212 err.per_point.reserve(N);
213
214 double sum = 0.0;
215 double sum_sq = 0.0;
216 for (size_t i = 0; i < N; ++i) {
217 CalibPoint3D rec = reconstructPoint(cam1, img1[i], cam2, img2[i]);
218 reconstructed_out.push_back(rec);
219 const double dx = rec.x - object_points[i].x;
220 const double dy = rec.y - object_points[i].y;
221 const double dz = rec.z - object_points[i].z;
222 const double d = std::sqrt(dx * dx + dy * dy + dz * dz);
223 err.per_point.push_back(d);
224 sum += d;
225 sum_sq += d * d;
226 if (d > err.max) err.max = d;
227 }
228 const double n = static_cast<double>(N);
229 err.mean = sum / n;
230 err.rms = std::sqrt(sum_sq / n);
231 double var = 0.0;
232 for (double d : err.per_point) var += (d - err.mean) * (d - err.mean);
233 err.std = std::sqrt(var / n);
234 return err;
235}
236
238 int cam_first_id, const std::vector<CalibPoint2D>& img_first, int cam_second_id,
239 const std::vector<CalibPoint2D>& img_second, const std::vector<CalibPoint3D>& object_points,
240 std::string& error_message) {
242
243 result.cam_first = calcDLTParameters(cam_first_id, img_first, object_points, error_message);
244 if (!result.cam_first.valid) return result;
245
246 result.cam_second = calcDLTParameters(cam_second_id, img_second, object_points, error_message);
247 if (!result.cam_second.valid) return result;
248
249 result.recon_error = reconstructionError(result.cam_first, img_first, result.cam_second,
250 img_second, object_points, result.reconstructed);
251 result.valid = true;
252 return result;
253}
254
255} // namespace stepsABC
256} // namespace cppxdic
static bool DLT11Calibration(const double *P2, const double *P3, size_t N, std::vector< double > &L)
Compute DLT (Direct Linear Transformation) 11 parameters from 2D-3D point correspondences Matches MAT...
Definition utils.cpp:999
static ReconstructionError reconstructionError(const CameraDLT &cam1, const std::vector< CalibPoint2D > &img1, const CameraDLT &cam2, const std::vector< CalibPoint2D > &img2, const std::vector< CalibPoint3D > &object_points, std::vector< CalibPoint3D > &reconstructed_out)
DLT reconstruction-error metric: triangulate every calibration point from the two cameras and compare...
static bool writeObjectFile(const std::vector< CalibPoint3D > &pts, const std::string &path, std::string &error_message)
Write generated 3D coordinates to a text file (one "x y z" per line).
static std::vector< CalibPoint3D > readObjectFile(const std::string &path, std::string &error_message)
Read a "x y z" per line 3D coordinate file.
static CalibPoint3D reconstructPoint(const CameraDLT &cam1, const CalibPoint2D &p1, const CameraDLT &cam2, const CalibPoint2D &p2)
Triangulate a 3D point from two cameras' DLT parameters and the matched image observations (linear le...
static CameraDLT calcDLTParameters(int camera_id, const std::vector< CalibPoint2D > &image_points, const std::vector< CalibPoint3D > &object_points, std::string &error_message)
Port of STEP1_CalcDLTparameters_rewrited.m for a single camera.
static std::vector< CalibPoint3D > generateCylindricalObject(const CylinderCalibSpec &spec)
Port of generate_cylindrical_calibration_object_coordinate_file.m.
static std::vector< CalibPoint2D > readImagePointsFile(const std::string &path, std::string &error_message)
Read a "u v" per line 2D image-point file (one per calibration dot).
static StereoCalibrationResult calibrateStereoPair(int cam_first_id, const std::vector< CalibPoint2D > &img_first, int cam_second_id, const std::vector< CalibPoint2D > &img_second, const std::vector< CalibPoint3D > &object_points, std::string &error_message)
Full StepC driver for one stereo pair: fit both cameras and compute the reconstruction error against ...
DataSerializer - Abstract base class for format-agnostic DIC data I/O.
A 2D image observation (pixels).
A single calibration point: 3D world coordinate (mm).
DLT parameters for one camera plus its fit residual.
std::array< double, 11 > L
Geometry of the cylindrical calibration object.
Reconstruction-error metrics for a stereo pair.
Full StepC result for a stereo pair.