blob: b502f3bed83f8eaff50e1b929b6285cb10b534db [file] [log] [blame]
Parker Schuh9e1d1692019-02-24 14:34:04 -08001#include <fstream>
2
Austin Schuh99f7c6a2024-06-25 22:07:44 -07003#include "absl/flags/flag.h"
4
5#include "aos/init.h"
Parker Schuh9e1d1692019-02-24 14:34:04 -08006#include "aos/logging/implementations.h"
7#include "aos/logging/logging.h"
8#include "aos/vision/blob/codec.h"
9#include "aos/vision/blob/find_blob.h"
10#include "aos/vision/events/socket_types.h"
11#include "aos/vision/events/udp.h"
12#include "aos/vision/image/image_dataset.h"
13#include "aos/vision/image/image_stream.h"
14#include "aos/vision/image/reader.h"
15#include "y2019/vision/target_finder.h"
16
Parker Schuh9e1d1692019-02-24 14:34:04 -080017// CERES Clashes with logging symbols...
18#include "ceres/ceres.h"
19
Austin Schuh99f7c6a2024-06-25 22:07:44 -070020ABSL_FLAG(int32_t, camera_id, -1, "The camera ID to calibrate");
21ABSL_FLAG(std::string, prefix, "", "The image filename prefix");
Austin Schuhe623f9f2019-03-03 12:24:03 -080022
Austin Schuh99f7c6a2024-06-25 22:07:44 -070023ABSL_FLAG(std::string, constants, "y2019/vision/constants.cc",
24 "Path to the constants file to update");
Austin Schuhd6cc1e92019-03-08 21:01:51 -080025
Austin Schuh99f7c6a2024-06-25 22:07:44 -070026ABSL_FLAG(double, beginning_tape_measure_reading, 11,
27 "The tape measure measurement (in inches) of the first image.");
28ABSL_FLAG(int32_t, image_count, 75, "The number of images to capture");
29ABSL_FLAG(double, tape_start_x, -12.5,
30 "The starting location of the tape measure in x relative to the "
31 "CG in inches.");
32ABSL_FLAG(double, tape_start_y, -0.5,
33 "The starting location of the tape measure in y relative to the "
34 "CG in inches.");
Austin Schuhd2e48da2019-03-08 20:52:32 -080035
Austin Schuh99f7c6a2024-06-25 22:07:44 -070036ABSL_FLAG(double, tape_direction_x, -1.0,
37 "The x component of \"1\" inch along the tape measure in meters.");
38ABSL_FLAG(double, tape_direction_y, 0.0,
39 "The y component of \"1\" inch along the tape measure in meters.");
Austin Schuhd2e48da2019-03-08 20:52:32 -080040
Philipp Schrader790cb542023-07-05 21:06:52 -070041using ::aos::monotonic_clock;
Parker Schuh9e1d1692019-02-24 14:34:04 -080042using ::aos::events::DataSocket;
43using ::aos::events::RXUdpSocket;
44using ::aos::events::TCPServer;
45using ::aos::vision::DataRef;
46using ::aos::vision::Int32Codec;
Parker Schuh9e1d1692019-02-24 14:34:04 -080047using aos::vision::Segment;
48
Parker Schuh9e1d1692019-02-24 14:34:04 -080049using ceres::CENTRAL;
50using ceres::CostFunction;
Philipp Schrader790cb542023-07-05 21:06:52 -070051using ceres::NumericDiffCostFunction;
Parker Schuh9e1d1692019-02-24 14:34:04 -080052using ceres::Problem;
Parker Schuh9e1d1692019-02-24 14:34:04 -080053using ceres::Solve;
Philipp Schrader790cb542023-07-05 21:06:52 -070054using ceres::Solver;
Parker Schuh9e1d1692019-02-24 14:34:04 -080055
Stephan Pleinesf63bde82024-01-13 15:59:33 -080056namespace y2019::vision {
Austin Schuh4d6e9bd2019-03-08 19:54:17 -080057namespace {
Parker Schuh9e1d1692019-02-24 14:34:04 -080058
59constexpr double kInchesToMeters = 0.0254;
60
Austin Schuh4d6e9bd2019-03-08 19:54:17 -080061} // namespace
Parker Schuh9e1d1692019-02-24 14:34:04 -080062
Austin Schuh4d6e9bd2019-03-08 19:54:17 -080063::std::array<double, 3> GetError(const DatasetInfo &info,
64 const double *const extrinsics,
65 const double *const geometry, int i) {
66 const ExtrinsicParams extrinsic_params = ExtrinsicParams::get(extrinsics);
67 const CameraGeometry geo = CameraGeometry::get(geometry);
Parker Schuh9e1d1692019-02-24 14:34:04 -080068
Parker Schuha4e52fb2019-02-24 18:18:15 -080069 const double s = sin(geo.heading + extrinsic_params.r2);
70 const double c = cos(geo.heading + extrinsic_params.r2);
Parker Schuh9e1d1692019-02-24 14:34:04 -080071
Parker Schuha4e52fb2019-02-24 18:18:15 -080072 // Take the tape measure starting point (this will be at the perimeter of the
73 // robot), add the offset to the first sample, and then add the per sample
74 // offset.
Austin Schuh6cac52c2019-03-08 20:03:33 -080075 const double dx = (info.to_tape_measure_start[0] +
76 (info.beginning_tape_measure_reading + i) *
77 info.tape_measure_direction[0]) -
78 (geo.location[0] + c * extrinsic_params.z);
79 const double dy = (info.to_tape_measure_start[1] +
80 (info.beginning_tape_measure_reading + i) *
81 info.tape_measure_direction[1]) -
82 (geo.location[1] + s * extrinsic_params.z);
Parker Schuha4e52fb2019-02-24 18:18:15 -080083
Austin Schuh6cac52c2019-03-08 20:03:33 -080084 constexpr double kCalibrationTargetHeight = 28.5 * kInchesToMeters;
85 const double dz =
86 kCalibrationTargetHeight - (geo.location[2] + extrinsic_params.y);
Parker Schuh9e1d1692019-02-24 14:34:04 -080087 return {{dx, dy, dz}};
88}
89
90void main(int argc, char **argv) {
Parker Schuh9e1d1692019-02-24 14:34:04 -080091 using namespace y2019::vision;
Austin Schuh99f7c6a2024-06-25 22:07:44 -070092 aos::InitGoogle(&argc, &argv);
Parker Schuha4e52fb2019-02-24 18:18:15 -080093
Austin Schuh99f7c6a2024-06-25 22:07:44 -070094 std::string prefix = absl::GetFlag(FLAGS_prefix);
Parker Schuha4e52fb2019-02-24 18:18:15 -080095 DatasetInfo info = {
Austin Schuh99f7c6a2024-06-25 22:07:44 -070096 absl::GetFlag(FLAGS_camera_id),
97 {{absl::GetFlag(FLAGS_tape_start_x) * kInchesToMeters,
98 absl::GetFlag(FLAGS_tape_start_y) * kInchesToMeters}},
99 {{absl::GetFlag(FLAGS_tape_direction_x) * kInchesToMeters,
100 absl::GetFlag(FLAGS_tape_direction_y) * kInchesToMeters}},
101 absl::GetFlag(FLAGS_beginning_tape_measure_reading),
102 prefix.c_str(),
103 absl::GetFlag(FLAGS_image_count),
Parker Schuha4e52fb2019-02-24 18:18:15 -0800104 };
105
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800106 TargetFinder target_finder;
Parker Schuh9e1d1692019-02-24 14:34:04 -0800107
108 ceres::Problem problem;
109
110 struct Sample {
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800111 ::std::unique_ptr<double[]> extrinsics;
Parker Schuh9e1d1692019-02-24 14:34:04 -0800112 int i;
113 };
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800114 ::std::vector<Sample> all_extrinsics;
Parker Schuh9e1d1692019-02-24 14:34:04 -0800115 double intrinsics[IntrinsicParams::kNumParams];
116 IntrinsicParams().set(&intrinsics[0]);
117
Parker Schuha4e52fb2019-02-24 18:18:15 -0800118 double geometry[CameraGeometry::kNumParams];
Austin Schuh9bb57fe2019-03-08 20:23:47 -0800119 {
120 // Assume the camera is near the center of the robot, and start by pointing
121 // it from the center of the robot to the location of the first target.
122 CameraGeometry camera_geometry;
123 const double x =
124 info.to_tape_measure_start[0] +
125 info.beginning_tape_measure_reading * info.tape_measure_direction[0];
126 const double y =
127 info.to_tape_measure_start[1] +
128 info.beginning_tape_measure_reading * info.tape_measure_direction[1];
129 camera_geometry.heading = ::std::atan2(y, x);
130 printf("Inital heading is %f\n", camera_geometry.heading);
131 camera_geometry.set(&geometry[0]);
132 }
Parker Schuh9e1d1692019-02-24 14:34:04 -0800133
134 Solver::Options options;
135 options.minimizer_progress_to_stdout = false;
136 Solver::Summary summary;
137
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800138 ::std::cout << summary.BriefReport() << "\n";
Austin Schuhcacc6922019-03-06 20:44:30 -0800139 IntrinsicParams intrinsics_ = IntrinsicParams::get(&intrinsics[0]);
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800140 ::std::cout << "rup = " << intrinsics_.mount_angle * 180 / M_PI << ";\n";
141 ::std::cout << "fl = " << intrinsics_.focal_length << ";\n";
142 ::std::cout << "error = " << summary.final_cost << ";\n";
Parker Schuh9e1d1692019-02-24 14:34:04 -0800143
Parker Schuha4e52fb2019-02-24 18:18:15 -0800144 for (int i = 0; i < info.num_images; ++i) {
Austin Schuh99f7c6a2024-06-25 22:07:44 -0700145 ::aos::vision::DatasetFrame frame = aos::vision::LoadFile(
146 absl::GetFlag(FLAGS_prefix) + std::to_string(i) + ".yuyv");
Parker Schuh9e1d1692019-02-24 14:34:04 -0800147
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800148 const ::aos::vision::ImageFormat fmt{640, 480};
149 ::aos::vision::BlobList imgs =
Brian Silverman37b15b32019-03-10 13:30:18 -0700150 ::aos::vision::FindBlobs(aos::vision::SlowYuyvYThreshold(
Austin Schuh2851e7f2019-03-06 20:45:19 -0800151 fmt, frame.data.data(), TargetFinder::GetThresholdValue()));
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800152 target_finder.PreFilter(&imgs);
Parker Schuh9e1d1692019-02-24 14:34:04 -0800153
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800154 constexpr bool verbose = false;
Austin Schuh6e56faf2019-03-10 14:04:57 -0700155 ::std::vector<Polygon> raw_polys;
Parker Schuh9e1d1692019-02-24 14:34:04 -0800156 for (const RangeImage &blob : imgs) {
Ben Fredricksonf7b68522019-03-02 21:19:42 -0800157 // Convert blobs to contours in the corrected space.
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800158 ContourNode *contour = target_finder.GetContour(blob);
Austin Schuh6e56faf2019-03-10 14:04:57 -0700159 ::std::vector<::Eigen::Vector2f> unwarped_contour =
Austin Schuhe5015972019-03-09 17:47:34 -0800160 target_finder.UnWarpContour(contour);
Austin Schuh6e56faf2019-03-10 14:04:57 -0700161 const Polygon polygon =
162 target_finder.FindPolygon(::std::move(unwarped_contour), verbose);
163 if (!polygon.segments.empty()) {
Parker Schuh9e1d1692019-02-24 14:34:04 -0800164 raw_polys.push_back(polygon);
165 }
166 }
167
168 // Calculate each component side of a possible target.
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800169 const ::std::vector<TargetComponent> target_component_list =
Austin Schuh32ffac22019-03-09 22:42:02 -0800170 target_finder.FillTargetComponentList(raw_polys, verbose);
Parker Schuh9e1d1692019-02-24 14:34:04 -0800171
172 // Put the compenents together into targets.
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800173 const ::std::vector<Target> target_list =
174 target_finder.FindTargetsFromComponents(target_component_list, verbose);
Parker Schuh9e1d1692019-02-24 14:34:04 -0800175
Austin Schuh0fd1cef2019-03-08 20:05:14 -0800176 // Now, iterate over the targets (in pixel space). Generate a residual
177 // block for each valid target which compares the actual pixel locations
178 // with the expected pixel locations given the intrinsics and extrinsics.
Parker Schuh9e1d1692019-02-24 14:34:04 -0800179 for (const Target &target : target_list) {
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800180 const ::std::array<::aos::vision::Vector<2>, 8> target_value =
Austin Schuh2894e902019-03-03 21:12:46 -0800181 target.ToPointList();
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800182 const ::std::array<::aos::vision::Vector<2>, 8> template_value =
183 target_finder.GetTemplateTarget().ToPointList();
Parker Schuh9e1d1692019-02-24 14:34:04 -0800184
Austin Schuh0fd1cef2019-03-08 20:05:14 -0800185 all_extrinsics.push_back(
186 {::std::unique_ptr<double[]>(new double[ExtrinsicParams::kNumParams]),
187 i});
188 double *extrinsics = all_extrinsics.back().extrinsics.get();
Parker Schuh9e1d1692019-02-24 14:34:04 -0800189 ExtrinsicParams().set(extrinsics);
Parker Schuh9e1d1692019-02-24 14:34:04 -0800190
191 for (size_t j = 0; j < 8; ++j) {
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800192 // Template target in target space as documented by GetTemplateTarget()
193 const ::aos::vision::Vector<2> template_point = template_value[j];
194 // Target in pixel space.
195 const ::aos::vision::Vector<2> target_point = target_value[j];
Parker Schuh9e1d1692019-02-24 14:34:04 -0800196
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800197 // Now build up the residual block.
James Kuszmaul3ae42262019-11-08 12:33:41 -0800198 auto ftor = [template_point, target_point](
Philipp Schrader790cb542023-07-05 21:06:52 -0700199 const double *const intrinsics,
200 const double *const extrinsics, double *residual) {
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800201 const IntrinsicParams intrinsic_params =
202 IntrinsicParams::get(intrinsics);
203 const ExtrinsicParams extrinsic_params =
204 ExtrinsicParams::get(extrinsics);
205 // Project the target location into pixel coordinates.
206 const ::aos::vision::Vector<2> projected_point =
207 Project(template_point, intrinsic_params, extrinsic_params);
208 const ::aos::vision::Vector<2> residual_point =
209 target_point - projected_point;
210 residual[0] = residual_point.x();
211 residual[1] = residual_point.y();
Parker Schuh9e1d1692019-02-24 14:34:04 -0800212 return true;
213 };
214 problem.AddResidualBlock(
215 new NumericDiffCostFunction<decltype(ftor), CENTRAL, 2,
216 IntrinsicParams::kNumParams,
217 ExtrinsicParams::kNumParams>(
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800218 new decltype(ftor)(::std::move(ftor))),
Parker Schuh9e1d1692019-02-24 14:34:04 -0800219 NULL, &intrinsics[0], extrinsics);
220 }
221
Austin Schuh6cac52c2019-03-08 20:03:33 -0800222 // Now, compare the estimated location of the target that we just solved
223 // for with the extrinsic params above with the known location of the
224 // target.
Parker Schuha4e52fb2019-02-24 18:18:15 -0800225 auto ftor = [&info, i](const double *const extrinsics,
226 const double *const geometry, double *residual) {
Austin Schuh6cac52c2019-03-08 20:03:33 -0800227 const ::std::array<double, 3> err =
228 GetError(info, extrinsics, geometry, i);
229 // We care a lot more about geometry than pixels. Especially since
230 // pixels are small and meters are big, so there's a small penalty to
231 // being off by meters.
232 residual[0] = err[0] * 1259.0;
233 residual[1] = err[1] * 1259.0;
234 residual[2] = err[2] * 1259.0;
Parker Schuh9e1d1692019-02-24 14:34:04 -0800235 return true;
236 };
237
238 problem.AddResidualBlock(
239 new NumericDiffCostFunction<decltype(ftor), CENTRAL, 3,
240 ExtrinsicParams::kNumParams,
Parker Schuha4e52fb2019-02-24 18:18:15 -0800241 CameraGeometry::kNumParams>(
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800242 new decltype(ftor)(::std::move(ftor))),
Parker Schuh9e1d1692019-02-24 14:34:04 -0800243 NULL, extrinsics, &geometry[0]);
244 }
245 }
246 // TODO: Debug solver convergence?
247 Solve(options, &problem, &summary);
248 Solve(options, &problem, &summary);
249 Solve(options, &problem, &summary);
250
251 {
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800252 ::std::cout << summary.BriefReport() << ::std::endl;
Austin Schuhcacc6922019-03-06 20:44:30 -0800253 IntrinsicParams intrinsics_ = IntrinsicParams::get(&intrinsics[0]);
254 CameraGeometry geometry_ = CameraGeometry::get(&geometry[0]);
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800255 ::std::cout << "rup = " << intrinsics_.mount_angle * 180 / M_PI << ";"
256 << ::std::endl;
257 ::std::cout << "fl = " << intrinsics_.focal_length << ";" << ::std::endl;
258 ::std::cout << "error = " << summary.final_cost << ";" << ::std::endl;
Parker Schuh9e1d1692019-02-24 14:34:04 -0800259
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800260 ::std::cout << "camera_angle = " << geometry_.heading * 180 / M_PI
261 << ::std::endl;
262 ::std::cout << "camera_x = " << geometry_.location[0] << ::std::endl;
263 ::std::cout << "camera_y = " << geometry_.location[1] << ::std::endl;
264 ::std::cout << "camera_z = " << geometry_.location[2] << ::std::endl;
265 ::std::cout << "camera_barrel = " << intrinsics_.barrel_mount * 180.0 / M_PI
266 << ::std::endl;
Parker Schuh9e1d1692019-02-24 14:34:04 -0800267
Austin Schuhcacc6922019-03-06 20:44:30 -0800268 for (const Sample &sample : all_extrinsics) {
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800269 const int i = sample.i;
Parker Schuh9e1d1692019-02-24 14:34:04 -0800270 double *data = sample.extrinsics.get();
271
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800272 const ExtrinsicParams extrinsic_params = ExtrinsicParams::get(data);
Parker Schuh9e1d1692019-02-24 14:34:04 -0800273
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800274 const ::std::array<double, 3> error =
275 GetError(info, data, &geometry[0], i);
Parker Schuh9e1d1692019-02-24 14:34:04 -0800276
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800277 ::std::cout << i << ", ";
278 ::std::cout << extrinsic_params.z << "m, ";
279 ::std::cout << extrinsic_params.y << "m, ";
280 ::std::cout << extrinsic_params.r1 * 180 / M_PI << ", ";
281 ::std::cout << extrinsic_params.r2 * 180 / M_PI << ", ";
Parker Schuh9e1d1692019-02-24 14:34:04 -0800282 // TODO: Methodology problem: This should really have a separate solve for
283 // extrinsics...
Austin Schuh6cac52c2019-03-08 20:03:33 -0800284 ::std::cout << error[0] << "m, ";
285 ::std::cout << error[1] << "m, ";
286 ::std::cout << error[2] << "m" << ::std::endl;
Parker Schuh9e1d1692019-02-24 14:34:04 -0800287 }
288 }
Parker Schuha4e52fb2019-02-24 18:18:15 -0800289
290 CameraCalibration results;
291 results.dataset = info;
292 results.intrinsics = IntrinsicParams::get(&intrinsics[0]);
293 results.geometry = CameraGeometry::get(&geometry[0]);
Austin Schuh99f7c6a2024-06-25 22:07:44 -0700294 DumpCameraConstants(absl::GetFlag(FLAGS_constants).c_str(), info.camera_id,
295 results);
Parker Schuh9e1d1692019-02-24 14:34:04 -0800296}
297
Stephan Pleinesf63bde82024-01-13 15:59:33 -0800298} // namespace y2019::vision
Parker Schuh9e1d1692019-02-24 14:34:04 -0800299
300int main(int argc, char **argv) { y2019::vision::main(argc, argv); }