blob: d21e184a4230e371c940e76c03aa7c1b86fd344a [file] [log] [blame]
Parker Schuh9e1d1692019-02-24 14:34:04 -08001#include <fstream>
2
3#include "aos/logging/implementations.h"
4#include "aos/logging/logging.h"
5#include "aos/vision/blob/codec.h"
6#include "aos/vision/blob/find_blob.h"
7#include "aos/vision/events/socket_types.h"
8#include "aos/vision/events/udp.h"
9#include "aos/vision/image/image_dataset.h"
10#include "aos/vision/image/image_stream.h"
11#include "aos/vision/image/reader.h"
12#include "y2019/vision/target_finder.h"
13
14#undef CHECK_NOTNULL
15#undef CHECK_OP
16#undef PCHECK
17// CERES Clashes with logging symbols...
18#include "ceres/ceres.h"
19
Austin Schuhe623f9f2019-03-03 12:24:03 -080020DEFINE_int32(camera_id, -1, "The camera ID to calibrate");
Austin Schuh3b815f12019-03-08 20:10:37 -080021DEFINE_string(prefix, "", "The image filename prefix");
Austin Schuhe623f9f2019-03-03 12:24:03 -080022
Austin Schuhd6cc1e92019-03-08 21:01:51 -080023DEFINE_string(constants, "y2019/vision/constants.cc",
24 "Path to the constants file to update");
25
Austin Schuhd2e48da2019-03-08 20:52:32 -080026DEFINE_double(beginning_tape_measure_reading, 11,
27 "The tape measure measurement (in inches) of the first image.");
28DEFINE_int32(image_count, 75, "The number of images to capture");
29DEFINE_double(
30 tape_start_x, -12.5,
31 "The starting location of the tape measure in x relative to the CG in inches.");
32DEFINE_double(
33 tape_start_y, -0.5,
34 "The starting location of the tape measure in y relative to the CG in inches.");
35
36DEFINE_double(
37 tape_direction_x, -1.0,
38 "The x component of \"1\" inch along the tape measure in meters.");
39DEFINE_double(
40 tape_direction_y, 0.0,
41 "The y component of \"1\" inch along the tape measure in meters.");
42
Parker Schuh9e1d1692019-02-24 14:34:04 -080043using ::aos::events::DataSocket;
44using ::aos::events::RXUdpSocket;
45using ::aos::events::TCPServer;
46using ::aos::vision::DataRef;
47using ::aos::vision::Int32Codec;
48using ::aos::monotonic_clock;
49using aos::vision::Segment;
50
51using ceres::NumericDiffCostFunction;
52using ceres::CENTRAL;
53using ceres::CostFunction;
54using ceres::Problem;
55using ceres::Solver;
56using ceres::Solve;
57
58namespace y2019 {
59namespace vision {
Austin Schuh4d6e9bd2019-03-08 19:54:17 -080060namespace {
Parker Schuh9e1d1692019-02-24 14:34:04 -080061
62constexpr double kInchesToMeters = 0.0254;
63
Austin Schuh4d6e9bd2019-03-08 19:54:17 -080064} // namespace
Parker Schuh9e1d1692019-02-24 14:34:04 -080065
Austin Schuh4d6e9bd2019-03-08 19:54:17 -080066::std::array<double, 3> GetError(const DatasetInfo &info,
67 const double *const extrinsics,
68 const double *const geometry, int i) {
69 const ExtrinsicParams extrinsic_params = ExtrinsicParams::get(extrinsics);
70 const CameraGeometry geo = CameraGeometry::get(geometry);
Parker Schuh9e1d1692019-02-24 14:34:04 -080071
Parker Schuha4e52fb2019-02-24 18:18:15 -080072 const double s = sin(geo.heading + extrinsic_params.r2);
73 const double c = cos(geo.heading + extrinsic_params.r2);
Parker Schuh9e1d1692019-02-24 14:34:04 -080074
Parker Schuha4e52fb2019-02-24 18:18:15 -080075 // Take the tape measure starting point (this will be at the perimeter of the
76 // robot), add the offset to the first sample, and then add the per sample
77 // offset.
Austin Schuh6cac52c2019-03-08 20:03:33 -080078 const double dx = (info.to_tape_measure_start[0] +
79 (info.beginning_tape_measure_reading + i) *
80 info.tape_measure_direction[0]) -
81 (geo.location[0] + c * extrinsic_params.z);
82 const double dy = (info.to_tape_measure_start[1] +
83 (info.beginning_tape_measure_reading + i) *
84 info.tape_measure_direction[1]) -
85 (geo.location[1] + s * extrinsic_params.z);
Parker Schuha4e52fb2019-02-24 18:18:15 -080086
Austin Schuh6cac52c2019-03-08 20:03:33 -080087 constexpr double kCalibrationTargetHeight = 28.5 * kInchesToMeters;
88 const double dz =
89 kCalibrationTargetHeight - (geo.location[2] + extrinsic_params.y);
Parker Schuh9e1d1692019-02-24 14:34:04 -080090 return {{dx, dy, dz}};
91}
92
93void main(int argc, char **argv) {
Parker Schuh9e1d1692019-02-24 14:34:04 -080094 using namespace y2019::vision;
Austin Schuh3b815f12019-03-08 20:10:37 -080095 ::gflags::ParseCommandLineFlags(&argc, &argv, false);
Parker Schuha4e52fb2019-02-24 18:18:15 -080096
97 DatasetInfo info = {
Austin Schuhe623f9f2019-03-03 12:24:03 -080098 FLAGS_camera_id,
Austin Schuhd2e48da2019-03-08 20:52:32 -080099 {{FLAGS_tape_start_x * kInchesToMeters,
100 FLAGS_tape_start_y * kInchesToMeters}},
101 {{FLAGS_tape_direction_x * kInchesToMeters,
102 FLAGS_tape_direction_y * kInchesToMeters}},
103 FLAGS_beginning_tape_measure_reading,
Austin Schuh3b815f12019-03-08 20:10:37 -0800104 FLAGS_prefix.c_str(),
Austin Schuhd2e48da2019-03-08 20:52:32 -0800105 FLAGS_image_count,
Parker Schuha4e52fb2019-02-24 18:18:15 -0800106 };
107
Parker Schuh9e1d1692019-02-24 14:34:04 -0800108 ::aos::logging::Init();
109 ::aos::logging::AddImplementation(
110 new ::aos::logging::StreamLogImplementation(stderr));
111
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800112 TargetFinder target_finder;
Parker Schuh9e1d1692019-02-24 14:34:04 -0800113
114 ceres::Problem problem;
115
116 struct Sample {
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800117 ::std::unique_ptr<double[]> extrinsics;
Parker Schuh9e1d1692019-02-24 14:34:04 -0800118 int i;
119 };
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800120 ::std::vector<Sample> all_extrinsics;
Parker Schuh9e1d1692019-02-24 14:34:04 -0800121 double intrinsics[IntrinsicParams::kNumParams];
122 IntrinsicParams().set(&intrinsics[0]);
123
Parker Schuha4e52fb2019-02-24 18:18:15 -0800124 double geometry[CameraGeometry::kNumParams];
Austin Schuh9bb57fe2019-03-08 20:23:47 -0800125 {
126 // Assume the camera is near the center of the robot, and start by pointing
127 // it from the center of the robot to the location of the first target.
128 CameraGeometry camera_geometry;
129 const double x =
130 info.to_tape_measure_start[0] +
131 info.beginning_tape_measure_reading * info.tape_measure_direction[0];
132 const double y =
133 info.to_tape_measure_start[1] +
134 info.beginning_tape_measure_reading * info.tape_measure_direction[1];
135 camera_geometry.heading = ::std::atan2(y, x);
136 printf("Inital heading is %f\n", camera_geometry.heading);
137 camera_geometry.set(&geometry[0]);
138 }
Parker Schuh9e1d1692019-02-24 14:34:04 -0800139
140 Solver::Options options;
141 options.minimizer_progress_to_stdout = false;
142 Solver::Summary summary;
143
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800144 ::std::cout << summary.BriefReport() << "\n";
Austin Schuhcacc6922019-03-06 20:44:30 -0800145 IntrinsicParams intrinsics_ = IntrinsicParams::get(&intrinsics[0]);
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800146 ::std::cout << "rup = " << intrinsics_.mount_angle * 180 / M_PI << ";\n";
147 ::std::cout << "fl = " << intrinsics_.focal_length << ";\n";
148 ::std::cout << "error = " << summary.final_cost << ";\n";
Parker Schuh9e1d1692019-02-24 14:34:04 -0800149
Parker Schuha4e52fb2019-02-24 18:18:15 -0800150 for (int i = 0; i < info.num_images; ++i) {
Austin Schuh3b815f12019-03-08 20:10:37 -0800151 ::aos::vision::DatasetFrame frame =
152 aos::vision::LoadFile(FLAGS_prefix + std::to_string(i) + ".yuyv");
Parker Schuh9e1d1692019-02-24 14:34:04 -0800153
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800154 const ::aos::vision::ImageFormat fmt{640, 480};
155 ::aos::vision::BlobList imgs =
156 ::aos::vision::FindBlobs(aos::vision::DoThresholdYUYV(
Austin Schuh2851e7f2019-03-06 20:45:19 -0800157 fmt, frame.data.data(), TargetFinder::GetThresholdValue()));
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800158 target_finder.PreFilter(&imgs);
Parker Schuh9e1d1692019-02-24 14:34:04 -0800159
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800160 constexpr bool verbose = false;
161 ::std::vector<std::vector<Segment<2>>> raw_polys;
Parker Schuh9e1d1692019-02-24 14:34:04 -0800162 for (const RangeImage &blob : imgs) {
Ben Fredricksonf7b68522019-03-02 21:19:42 -0800163 // Convert blobs to contours in the corrected space.
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800164 ContourNode *contour = target_finder.GetContour(blob);
165 target_finder.UnWarpContour(contour);
166 const ::std::vector<Segment<2>> polygon =
167 target_finder.FillPolygon(contour, verbose);
168 if (!polygon.empty()) {
Parker Schuh9e1d1692019-02-24 14:34:04 -0800169 raw_polys.push_back(polygon);
170 }
171 }
172
173 // Calculate each component side of a possible target.
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800174 const ::std::vector<TargetComponent> target_component_list =
175 target_finder.FillTargetComponentList(raw_polys);
Parker Schuh9e1d1692019-02-24 14:34:04 -0800176
177 // Put the compenents together into targets.
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800178 const ::std::vector<Target> target_list =
179 target_finder.FindTargetsFromComponents(target_component_list, verbose);
Parker Schuh9e1d1692019-02-24 14:34:04 -0800180
Austin Schuh0fd1cef2019-03-08 20:05:14 -0800181 // Now, iterate over the targets (in pixel space). Generate a residual
182 // block for each valid target which compares the actual pixel locations
183 // with the expected pixel locations given the intrinsics and extrinsics.
Parker Schuh9e1d1692019-02-24 14:34:04 -0800184 for (const Target &target : target_list) {
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800185 const ::std::array<::aos::vision::Vector<2>, 8> target_value =
Austin Schuh2894e902019-03-03 21:12:46 -0800186 target.ToPointList();
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800187 const ::std::array<::aos::vision::Vector<2>, 8> template_value =
188 target_finder.GetTemplateTarget().ToPointList();
Parker Schuh9e1d1692019-02-24 14:34:04 -0800189
Austin Schuh0fd1cef2019-03-08 20:05:14 -0800190 all_extrinsics.push_back(
191 {::std::unique_ptr<double[]>(new double[ExtrinsicParams::kNumParams]),
192 i});
193 double *extrinsics = all_extrinsics.back().extrinsics.get();
Parker Schuh9e1d1692019-02-24 14:34:04 -0800194 ExtrinsicParams().set(extrinsics);
Parker Schuh9e1d1692019-02-24 14:34:04 -0800195
196 for (size_t j = 0; j < 8; ++j) {
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800197 // Template target in target space as documented by GetTemplateTarget()
198 const ::aos::vision::Vector<2> template_point = template_value[j];
199 // Target in pixel space.
200 const ::aos::vision::Vector<2> target_point = target_value[j];
Parker Schuh9e1d1692019-02-24 14:34:04 -0800201
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800202 // Now build up the residual block.
203 auto ftor = [template_point, target_point, i](
204 const double *const intrinsics, const double *const extrinsics,
205 double *residual) {
206 const IntrinsicParams intrinsic_params =
207 IntrinsicParams::get(intrinsics);
208 const ExtrinsicParams extrinsic_params =
209 ExtrinsicParams::get(extrinsics);
210 // Project the target location into pixel coordinates.
211 const ::aos::vision::Vector<2> projected_point =
212 Project(template_point, intrinsic_params, extrinsic_params);
213 const ::aos::vision::Vector<2> residual_point =
214 target_point - projected_point;
215 residual[0] = residual_point.x();
216 residual[1] = residual_point.y();
Parker Schuh9e1d1692019-02-24 14:34:04 -0800217 return true;
218 };
219 problem.AddResidualBlock(
220 new NumericDiffCostFunction<decltype(ftor), CENTRAL, 2,
221 IntrinsicParams::kNumParams,
222 ExtrinsicParams::kNumParams>(
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800223 new decltype(ftor)(::std::move(ftor))),
Parker Schuh9e1d1692019-02-24 14:34:04 -0800224 NULL, &intrinsics[0], extrinsics);
225 }
226
Austin Schuh6cac52c2019-03-08 20:03:33 -0800227 // Now, compare the estimated location of the target that we just solved
228 // for with the extrinsic params above with the known location of the
229 // target.
Parker Schuha4e52fb2019-02-24 18:18:15 -0800230 auto ftor = [&info, i](const double *const extrinsics,
231 const double *const geometry, double *residual) {
Austin Schuh6cac52c2019-03-08 20:03:33 -0800232 const ::std::array<double, 3> err =
233 GetError(info, extrinsics, geometry, i);
234 // We care a lot more about geometry than pixels. Especially since
235 // pixels are small and meters are big, so there's a small penalty to
236 // being off by meters.
237 residual[0] = err[0] * 1259.0;
238 residual[1] = err[1] * 1259.0;
239 residual[2] = err[2] * 1259.0;
Parker Schuh9e1d1692019-02-24 14:34:04 -0800240 return true;
241 };
242
243 problem.AddResidualBlock(
244 new NumericDiffCostFunction<decltype(ftor), CENTRAL, 3,
245 ExtrinsicParams::kNumParams,
Parker Schuha4e52fb2019-02-24 18:18:15 -0800246 CameraGeometry::kNumParams>(
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800247 new decltype(ftor)(::std::move(ftor))),
Parker Schuh9e1d1692019-02-24 14:34:04 -0800248 NULL, extrinsics, &geometry[0]);
249 }
250 }
251 // TODO: Debug solver convergence?
252 Solve(options, &problem, &summary);
253 Solve(options, &problem, &summary);
254 Solve(options, &problem, &summary);
255
256 {
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800257 ::std::cout << summary.BriefReport() << ::std::endl;
Austin Schuhcacc6922019-03-06 20:44:30 -0800258 IntrinsicParams intrinsics_ = IntrinsicParams::get(&intrinsics[0]);
259 CameraGeometry geometry_ = CameraGeometry::get(&geometry[0]);
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800260 ::std::cout << "rup = " << intrinsics_.mount_angle * 180 / M_PI << ";"
261 << ::std::endl;
262 ::std::cout << "fl = " << intrinsics_.focal_length << ";" << ::std::endl;
263 ::std::cout << "error = " << summary.final_cost << ";" << ::std::endl;
Parker Schuh9e1d1692019-02-24 14:34:04 -0800264
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800265 ::std::cout << "camera_angle = " << geometry_.heading * 180 / M_PI
266 << ::std::endl;
267 ::std::cout << "camera_x = " << geometry_.location[0] << ::std::endl;
268 ::std::cout << "camera_y = " << geometry_.location[1] << ::std::endl;
269 ::std::cout << "camera_z = " << geometry_.location[2] << ::std::endl;
270 ::std::cout << "camera_barrel = " << intrinsics_.barrel_mount * 180.0 / M_PI
271 << ::std::endl;
Parker Schuh9e1d1692019-02-24 14:34:04 -0800272
Austin Schuhcacc6922019-03-06 20:44:30 -0800273 for (const Sample &sample : all_extrinsics) {
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800274 const int i = sample.i;
Parker Schuh9e1d1692019-02-24 14:34:04 -0800275 double *data = sample.extrinsics.get();
276
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800277 const ExtrinsicParams extrinsic_params = ExtrinsicParams::get(data);
Parker Schuh9e1d1692019-02-24 14:34:04 -0800278
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800279 const ::std::array<double, 3> error =
280 GetError(info, data, &geometry[0], i);
Parker Schuh9e1d1692019-02-24 14:34:04 -0800281
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800282 ::std::cout << i << ", ";
283 ::std::cout << extrinsic_params.z << "m, ";
284 ::std::cout << extrinsic_params.y << "m, ";
285 ::std::cout << extrinsic_params.r1 * 180 / M_PI << ", ";
286 ::std::cout << extrinsic_params.r2 * 180 / M_PI << ", ";
Parker Schuh9e1d1692019-02-24 14:34:04 -0800287 // TODO: Methodology problem: This should really have a separate solve for
288 // extrinsics...
Austin Schuh6cac52c2019-03-08 20:03:33 -0800289 ::std::cout << error[0] << "m, ";
290 ::std::cout << error[1] << "m, ";
291 ::std::cout << error[2] << "m" << ::std::endl;
Parker Schuh9e1d1692019-02-24 14:34:04 -0800292 }
293 }
Parker Schuha4e52fb2019-02-24 18:18:15 -0800294
295 CameraCalibration results;
296 results.dataset = info;
297 results.intrinsics = IntrinsicParams::get(&intrinsics[0]);
298 results.geometry = CameraGeometry::get(&geometry[0]);
Austin Schuhd6cc1e92019-03-08 21:01:51 -0800299 DumpCameraConstants(FLAGS_constants.c_str(), info.camera_id, results);
Parker Schuh9e1d1692019-02-24 14:34:04 -0800300}
301
302} // namespace y2019
303} // namespace vision
304
305int main(int argc, char **argv) { y2019::vision::main(argc, argv); }