blob: 88d657b3af24a4c6c8ba96bdbb4de2b09073cd76 [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
Parker Schuh9e1d1692019-02-24 14:34:04 -080023using ::aos::events::DataSocket;
24using ::aos::events::RXUdpSocket;
25using ::aos::events::TCPServer;
26using ::aos::vision::DataRef;
27using ::aos::vision::Int32Codec;
28using ::aos::monotonic_clock;
29using aos::vision::Segment;
30
31using ceres::NumericDiffCostFunction;
32using ceres::CENTRAL;
33using ceres::CostFunction;
34using ceres::Problem;
35using ceres::Solver;
36using ceres::Solve;
37
38namespace y2019 {
39namespace vision {
Austin Schuh4d6e9bd2019-03-08 19:54:17 -080040namespace {
Parker Schuh9e1d1692019-02-24 14:34:04 -080041
42constexpr double kInchesToMeters = 0.0254;
43
Austin Schuh4d6e9bd2019-03-08 19:54:17 -080044} // namespace
Parker Schuh9e1d1692019-02-24 14:34:04 -080045
Austin Schuh4d6e9bd2019-03-08 19:54:17 -080046::std::array<double, 3> GetError(const DatasetInfo &info,
47 const double *const extrinsics,
48 const double *const geometry, int i) {
49 const ExtrinsicParams extrinsic_params = ExtrinsicParams::get(extrinsics);
50 const CameraGeometry geo = CameraGeometry::get(geometry);
Parker Schuh9e1d1692019-02-24 14:34:04 -080051
Parker Schuha4e52fb2019-02-24 18:18:15 -080052 const double s = sin(geo.heading + extrinsic_params.r2);
53 const double c = cos(geo.heading + extrinsic_params.r2);
Parker Schuh9e1d1692019-02-24 14:34:04 -080054
Parker Schuha4e52fb2019-02-24 18:18:15 -080055 // Take the tape measure starting point (this will be at the perimeter of the
56 // robot), add the offset to the first sample, and then add the per sample
57 // offset.
Austin Schuh6cac52c2019-03-08 20:03:33 -080058 const double dx = (info.to_tape_measure_start[0] +
59 (info.beginning_tape_measure_reading + i) *
60 info.tape_measure_direction[0]) -
61 (geo.location[0] + c * extrinsic_params.z);
62 const double dy = (info.to_tape_measure_start[1] +
63 (info.beginning_tape_measure_reading + i) *
64 info.tape_measure_direction[1]) -
65 (geo.location[1] + s * extrinsic_params.z);
Parker Schuha4e52fb2019-02-24 18:18:15 -080066
Austin Schuh6cac52c2019-03-08 20:03:33 -080067 constexpr double kCalibrationTargetHeight = 28.5 * kInchesToMeters;
68 const double dz =
69 kCalibrationTargetHeight - (geo.location[2] + extrinsic_params.y);
Parker Schuh9e1d1692019-02-24 14:34:04 -080070 return {{dx, dy, dz}};
71}
72
73void main(int argc, char **argv) {
Parker Schuh9e1d1692019-02-24 14:34:04 -080074 using namespace y2019::vision;
Austin Schuh3b815f12019-03-08 20:10:37 -080075 ::gflags::ParseCommandLineFlags(&argc, &argv, false);
Parker Schuha4e52fb2019-02-24 18:18:15 -080076
77 DatasetInfo info = {
Austin Schuhe623f9f2019-03-03 12:24:03 -080078 FLAGS_camera_id,
Parker Schuha4e52fb2019-02-24 18:18:15 -080079 {{12.5 * kInchesToMeters, 0.5 * kInchesToMeters}},
80 {{kInchesToMeters, 0.0}},
81 26,
Austin Schuh3b815f12019-03-08 20:10:37 -080082 FLAGS_prefix.c_str(),
Parker Schuha4e52fb2019-02-24 18:18:15 -080083 59,
84 };
85
Parker Schuh9e1d1692019-02-24 14:34:04 -080086 ::aos::logging::Init();
87 ::aos::logging::AddImplementation(
88 new ::aos::logging::StreamLogImplementation(stderr));
89
Austin Schuh4d6e9bd2019-03-08 19:54:17 -080090 TargetFinder target_finder;
Parker Schuh9e1d1692019-02-24 14:34:04 -080091
92 ceres::Problem problem;
93
94 struct Sample {
Austin Schuh4d6e9bd2019-03-08 19:54:17 -080095 ::std::unique_ptr<double[]> extrinsics;
Parker Schuh9e1d1692019-02-24 14:34:04 -080096 int i;
97 };
Austin Schuh4d6e9bd2019-03-08 19:54:17 -080098 ::std::vector<Sample> all_extrinsics;
Parker Schuh9e1d1692019-02-24 14:34:04 -080099 double intrinsics[IntrinsicParams::kNumParams];
100 IntrinsicParams().set(&intrinsics[0]);
101
Parker Schuha4e52fb2019-02-24 18:18:15 -0800102 double geometry[CameraGeometry::kNumParams];
103 CameraGeometry().set(&geometry[0]);
Parker Schuh9e1d1692019-02-24 14:34:04 -0800104
105 Solver::Options options;
106 options.minimizer_progress_to_stdout = false;
107 Solver::Summary summary;
108
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800109 ::std::cout << summary.BriefReport() << "\n";
Austin Schuhcacc6922019-03-06 20:44:30 -0800110 IntrinsicParams intrinsics_ = IntrinsicParams::get(&intrinsics[0]);
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800111 ::std::cout << "rup = " << intrinsics_.mount_angle * 180 / M_PI << ";\n";
112 ::std::cout << "fl = " << intrinsics_.focal_length << ";\n";
113 ::std::cout << "error = " << summary.final_cost << ";\n";
Parker Schuh9e1d1692019-02-24 14:34:04 -0800114
Parker Schuha4e52fb2019-02-24 18:18:15 -0800115 for (int i = 0; i < info.num_images; ++i) {
Austin Schuh3b815f12019-03-08 20:10:37 -0800116 ::aos::vision::DatasetFrame frame =
117 aos::vision::LoadFile(FLAGS_prefix + std::to_string(i) + ".yuyv");
Parker Schuh9e1d1692019-02-24 14:34:04 -0800118
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800119 const ::aos::vision::ImageFormat fmt{640, 480};
120 ::aos::vision::BlobList imgs =
121 ::aos::vision::FindBlobs(aos::vision::DoThresholdYUYV(
Austin Schuh2851e7f2019-03-06 20:45:19 -0800122 fmt, frame.data.data(), TargetFinder::GetThresholdValue()));
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800123 target_finder.PreFilter(&imgs);
Parker Schuh9e1d1692019-02-24 14:34:04 -0800124
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800125 constexpr bool verbose = false;
126 ::std::vector<std::vector<Segment<2>>> raw_polys;
Parker Schuh9e1d1692019-02-24 14:34:04 -0800127 for (const RangeImage &blob : imgs) {
Ben Fredricksonf7b68522019-03-02 21:19:42 -0800128 // Convert blobs to contours in the corrected space.
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800129 ContourNode *contour = target_finder.GetContour(blob);
130 target_finder.UnWarpContour(contour);
131 const ::std::vector<Segment<2>> polygon =
132 target_finder.FillPolygon(contour, verbose);
133 if (!polygon.empty()) {
Parker Schuh9e1d1692019-02-24 14:34:04 -0800134 raw_polys.push_back(polygon);
135 }
136 }
137
138 // Calculate each component side of a possible target.
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800139 const ::std::vector<TargetComponent> target_component_list =
140 target_finder.FillTargetComponentList(raw_polys);
Parker Schuh9e1d1692019-02-24 14:34:04 -0800141
142 // Put the compenents together into targets.
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800143 const ::std::vector<Target> target_list =
144 target_finder.FindTargetsFromComponents(target_component_list, verbose);
Parker Schuh9e1d1692019-02-24 14:34:04 -0800145
Austin Schuh0fd1cef2019-03-08 20:05:14 -0800146 // Now, iterate over the targets (in pixel space). Generate a residual
147 // block for each valid target which compares the actual pixel locations
148 // with the expected pixel locations given the intrinsics and extrinsics.
Parker Schuh9e1d1692019-02-24 14:34:04 -0800149 for (const Target &target : target_list) {
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800150 const ::std::array<::aos::vision::Vector<2>, 8> target_value =
Austin Schuh2894e902019-03-03 21:12:46 -0800151 target.ToPointList();
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800152 const ::std::array<::aos::vision::Vector<2>, 8> template_value =
153 target_finder.GetTemplateTarget().ToPointList();
Parker Schuh9e1d1692019-02-24 14:34:04 -0800154
Austin Schuh0fd1cef2019-03-08 20:05:14 -0800155 all_extrinsics.push_back(
156 {::std::unique_ptr<double[]>(new double[ExtrinsicParams::kNumParams]),
157 i});
158 double *extrinsics = all_extrinsics.back().extrinsics.get();
Parker Schuh9e1d1692019-02-24 14:34:04 -0800159 ExtrinsicParams().set(extrinsics);
Parker Schuh9e1d1692019-02-24 14:34:04 -0800160
161 for (size_t j = 0; j < 8; ++j) {
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800162 // Template target in target space as documented by GetTemplateTarget()
163 const ::aos::vision::Vector<2> template_point = template_value[j];
164 // Target in pixel space.
165 const ::aos::vision::Vector<2> target_point = target_value[j];
Parker Schuh9e1d1692019-02-24 14:34:04 -0800166
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800167 // Now build up the residual block.
168 auto ftor = [template_point, target_point, i](
169 const double *const intrinsics, const double *const extrinsics,
170 double *residual) {
171 const IntrinsicParams intrinsic_params =
172 IntrinsicParams::get(intrinsics);
173 const ExtrinsicParams extrinsic_params =
174 ExtrinsicParams::get(extrinsics);
175 // Project the target location into pixel coordinates.
176 const ::aos::vision::Vector<2> projected_point =
177 Project(template_point, intrinsic_params, extrinsic_params);
178 const ::aos::vision::Vector<2> residual_point =
179 target_point - projected_point;
180 residual[0] = residual_point.x();
181 residual[1] = residual_point.y();
Parker Schuh9e1d1692019-02-24 14:34:04 -0800182 return true;
183 };
184 problem.AddResidualBlock(
185 new NumericDiffCostFunction<decltype(ftor), CENTRAL, 2,
186 IntrinsicParams::kNumParams,
187 ExtrinsicParams::kNumParams>(
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800188 new decltype(ftor)(::std::move(ftor))),
Parker Schuh9e1d1692019-02-24 14:34:04 -0800189 NULL, &intrinsics[0], extrinsics);
190 }
191
Austin Schuh6cac52c2019-03-08 20:03:33 -0800192 // Now, compare the estimated location of the target that we just solved
193 // for with the extrinsic params above with the known location of the
194 // target.
Parker Schuha4e52fb2019-02-24 18:18:15 -0800195 auto ftor = [&info, i](const double *const extrinsics,
196 const double *const geometry, double *residual) {
Austin Schuh6cac52c2019-03-08 20:03:33 -0800197 const ::std::array<double, 3> err =
198 GetError(info, extrinsics, geometry, i);
199 // We care a lot more about geometry than pixels. Especially since
200 // pixels are small and meters are big, so there's a small penalty to
201 // being off by meters.
202 residual[0] = err[0] * 1259.0;
203 residual[1] = err[1] * 1259.0;
204 residual[2] = err[2] * 1259.0;
Parker Schuh9e1d1692019-02-24 14:34:04 -0800205 return true;
206 };
207
208 problem.AddResidualBlock(
209 new NumericDiffCostFunction<decltype(ftor), CENTRAL, 3,
210 ExtrinsicParams::kNumParams,
Parker Schuha4e52fb2019-02-24 18:18:15 -0800211 CameraGeometry::kNumParams>(
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800212 new decltype(ftor)(::std::move(ftor))),
Parker Schuh9e1d1692019-02-24 14:34:04 -0800213 NULL, extrinsics, &geometry[0]);
214 }
215 }
216 // TODO: Debug solver convergence?
217 Solve(options, &problem, &summary);
218 Solve(options, &problem, &summary);
219 Solve(options, &problem, &summary);
220
221 {
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800222 ::std::cout << summary.BriefReport() << ::std::endl;
Austin Schuhcacc6922019-03-06 20:44:30 -0800223 IntrinsicParams intrinsics_ = IntrinsicParams::get(&intrinsics[0]);
224 CameraGeometry geometry_ = CameraGeometry::get(&geometry[0]);
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800225 ::std::cout << "rup = " << intrinsics_.mount_angle * 180 / M_PI << ";"
226 << ::std::endl;
227 ::std::cout << "fl = " << intrinsics_.focal_length << ";" << ::std::endl;
228 ::std::cout << "error = " << summary.final_cost << ";" << ::std::endl;
Parker Schuh9e1d1692019-02-24 14:34:04 -0800229
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800230 ::std::cout << "camera_angle = " << geometry_.heading * 180 / M_PI
231 << ::std::endl;
232 ::std::cout << "camera_x = " << geometry_.location[0] << ::std::endl;
233 ::std::cout << "camera_y = " << geometry_.location[1] << ::std::endl;
234 ::std::cout << "camera_z = " << geometry_.location[2] << ::std::endl;
235 ::std::cout << "camera_barrel = " << intrinsics_.barrel_mount * 180.0 / M_PI
236 << ::std::endl;
Parker Schuh9e1d1692019-02-24 14:34:04 -0800237
Austin Schuhcacc6922019-03-06 20:44:30 -0800238 for (const Sample &sample : all_extrinsics) {
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800239 const int i = sample.i;
Parker Schuh9e1d1692019-02-24 14:34:04 -0800240 double *data = sample.extrinsics.get();
241
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800242 const ExtrinsicParams extrinsic_params = ExtrinsicParams::get(data);
Parker Schuh9e1d1692019-02-24 14:34:04 -0800243
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800244 const ::std::array<double, 3> error =
245 GetError(info, data, &geometry[0], i);
Parker Schuh9e1d1692019-02-24 14:34:04 -0800246
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800247 ::std::cout << i << ", ";
248 ::std::cout << extrinsic_params.z << "m, ";
249 ::std::cout << extrinsic_params.y << "m, ";
250 ::std::cout << extrinsic_params.r1 * 180 / M_PI << ", ";
251 ::std::cout << extrinsic_params.r2 * 180 / M_PI << ", ";
Parker Schuh9e1d1692019-02-24 14:34:04 -0800252 // TODO: Methodology problem: This should really have a separate solve for
253 // extrinsics...
Austin Schuh6cac52c2019-03-08 20:03:33 -0800254 ::std::cout << error[0] << "m, ";
255 ::std::cout << error[1] << "m, ";
256 ::std::cout << error[2] << "m" << ::std::endl;
Parker Schuh9e1d1692019-02-24 14:34:04 -0800257 }
258 }
Parker Schuha4e52fb2019-02-24 18:18:15 -0800259
260 CameraCalibration results;
261 results.dataset = info;
262 results.intrinsics = IntrinsicParams::get(&intrinsics[0]);
263 results.geometry = CameraGeometry::get(&geometry[0]);
James Kuszmaule2c71ea2019-03-04 08:14:21 -0800264 DumpCameraConstants("y2019/vision/constants.cc", info.camera_id, results);
Parker Schuh9e1d1692019-02-24 14:34:04 -0800265}
266
267} // namespace y2019
268} // namespace vision
269
270int main(int argc, char **argv) { y2019::vision::main(argc, argv); }