blob: 45e1ef6e5a456c2b04b6efb7eed7a02e328c1677 [file] [log] [blame]
Parker Schuh2a1447c2019-02-17 00:25:29 -08001#include "y2019/vision/target_finder.h"
2
3#include "ceres/ceres.h"
4
5#include <math.h>
6
Alex Perryb6f334d2019-03-23 13:10:45 -07007#include "aos/util/math.h"
8
Parker Schuh2a1447c2019-02-17 00:25:29 -08009using ceres::NumericDiffCostFunction;
10using ceres::CENTRAL;
11using ceres::CostFunction;
12using ceres::Problem;
13using ceres::Solver;
14using ceres::Solve;
15
16namespace y2019 {
17namespace vision {
18
19static constexpr double kInchesToMeters = 0.0254;
20
21using namespace aos::vision;
22using aos::vision::Vector;
23
24Target Target::MakeTemplate() {
25 Target out;
26 // This is how off-vertical the tape is.
27 const double theta = 14.5 * M_PI / 180.0;
28
29 const double tape_offset = 4 * kInchesToMeters;
30 const double tape_width = 2 * kInchesToMeters;
31 const double tape_length = 5.5 * kInchesToMeters;
32
33 const double s = sin(theta);
34 const double c = cos(theta);
35 out.right.top = Vector<2>(tape_offset, 0.0);
36 out.right.inside = Vector<2>(tape_offset + tape_width * c, tape_width * s);
37 out.right.bottom = Vector<2>(tape_offset + tape_width * c + tape_length * s,
38 tape_width * s - tape_length * c);
39 out.right.outside =
40 Vector<2>(tape_offset + tape_length * s, -tape_length * c);
41
42 out.right.is_right = true;
43 out.left.top = Vector<2>(-out.right.top.x(), out.right.top.y());
44 out.left.inside = Vector<2>(-out.right.inside.x(), out.right.inside.y());
45 out.left.bottom = Vector<2>(-out.right.bottom.x(), out.right.bottom.y());
46 out.left.outside = Vector<2>(-out.right.outside.x(), out.right.outside.y());
47 return out;
48}
49
Austin Schuh2894e902019-03-03 21:12:46 -080050std::array<Vector<2>, 8> Target::ToPointList() const {
Austin Schuhe6cfbe32019-03-10 18:05:57 -070051 // Note, the even points are fit with the line solver in the 4 point solution
52 // while the odds are fit with the point matcher.
Alex Perrybac3d3f2019-03-10 14:26:51 -070053 return std::array<Vector<2>, 8>{{right.top, right.outside, right.inside,
54 right.bottom, left.top, left.outside,
55 left.inside, left.bottom}};
Parker Schuh2a1447c2019-02-17 00:25:29 -080056}
57
Parker Schuh2a1447c2019-02-17 00:25:29 -080058Target Project(const Target &target, const IntrinsicParams &intrinsics,
59 const ExtrinsicParams &extrinsics) {
Parker Schuh2a1447c2019-02-17 00:25:29 -080060 Target new_targ;
61 new_targ.right.is_right = true;
Austin Schuh4d6e9bd2019-03-08 19:54:17 -080062 new_targ.right.top = Project(target.right.top, intrinsics, extrinsics);
63 new_targ.right.inside = Project(target.right.inside, intrinsics, extrinsics);
64 new_targ.right.bottom = Project(target.right.bottom, intrinsics, extrinsics);
65 new_targ.right.outside =
66 Project(target.right.outside, intrinsics, extrinsics);
Parker Schuh2a1447c2019-02-17 00:25:29 -080067
Austin Schuh4d6e9bd2019-03-08 19:54:17 -080068 new_targ.left.top = Project(target.left.top, intrinsics, extrinsics);
69 new_targ.left.inside = Project(target.left.inside, intrinsics, extrinsics);
70 new_targ.left.bottom = Project(target.left.bottom, intrinsics, extrinsics);
71 new_targ.left.outside = Project(target.left.outside, intrinsics, extrinsics);
Parker Schuh2a1447c2019-02-17 00:25:29 -080072
73 return new_targ;
74}
75
76// Used at runtime on a single image given camera parameters.
Alex Perrybac3d3f2019-03-10 14:26:51 -070077struct PointCostFunctor {
78 PointCostFunctor(Vector<2> result, Vector<2> template_pt,
Parker Schuh2a1447c2019-02-17 00:25:29 -080079 IntrinsicParams intrinsics)
80 : result(result), template_pt(template_pt), intrinsics(intrinsics) {}
81
82 bool operator()(const double *const x, double *residual) const {
83 auto extrinsics = ExtrinsicParams::get(x);
84 auto pt = result - Project(template_pt, intrinsics, extrinsics);
85 residual[0] = pt.x();
86 residual[1] = pt.y();
87 return true;
88 }
89
90 Vector<2> result;
91 Vector<2> template_pt;
92 IntrinsicParams intrinsics;
93};
94
Alex Perrybac3d3f2019-03-10 14:26:51 -070095// Find the distance from a lower target point to the 'vertical' line it should
96// be on.
97struct LineCostFunctor {
98 LineCostFunctor(Vector<2> result, Segment<2> template_seg,
99 IntrinsicParams intrinsics)
100 : result(result), template_seg(template_seg), intrinsics(intrinsics) {}
101
102 bool operator()(const double *const x, double *residual) const {
103 const auto extrinsics = ExtrinsicParams::get(x);
Austin Schuhe6cfbe32019-03-10 18:05:57 -0700104 const Vector<2> p1 = Project(template_seg.A(), intrinsics, extrinsics);
105 const Vector<2> p2 = Project(template_seg.B(), intrinsics, extrinsics);
Alex Perrybac3d3f2019-03-10 14:26:51 -0700106 // distance from line (P1, P2) to point result
107 double dx = p2.x() - p1.x();
108 double dy = p2.y() - p1.y();
109 double denom = p2.DistanceTo(p1);
110 residual[0] = ::std::abs(dy * result.x() - dx * result.y() +
111 p2.x() * p1.y() - p2.y() * p1.x()) /
112 denom;
113 return true;
114 }
115
116 Vector<2> result;
117 Segment<2> template_seg;
118 IntrinsicParams intrinsics;
119};
120
Austin Schuhe6cfbe32019-03-10 18:05:57 -0700121// Find the distance that the bottom point is outside the target and penalize
122// that linearly.
123class BottomPointCostFunctor {
124 public:
125 BottomPointCostFunctor(::Eigen::Vector2f bottom_point,
126 Segment<2> template_seg, IntrinsicParams intrinsics)
127 : bottom_point_(bottom_point.x(), bottom_point.y()),
128 template_seg_(template_seg),
129 intrinsics_(intrinsics) {}
130
131 bool operator()(const double *const x, double *residual) const {
132 const ExtrinsicParams extrinsics = ExtrinsicParams::get(x);
133 const Vector<2> p1 = Project(template_seg_.A(), intrinsics_, extrinsics);
134 const Vector<2> p2 = Project(template_seg_.B(), intrinsics_, extrinsics);
135
136 // Construct a vector pointed perpendicular to the line. This vector is
137 // pointed down out the bottom of the target.
138 ::Eigen::Vector2d down_axis(-(p1.y() - p2.y()), p1.x() - p2.x());
139 down_axis.normalize();
140
141 // Positive means out.
142 const double component =
143 down_axis.transpose() * (bottom_point_ - p1.GetData().transpose());
144
145 if (component > 0) {
146 residual[0] = component * 1.0;
147 } else {
148 residual[0] = 0.0;
149 }
150 return true;
151 }
152
153 private:
154 ::Eigen::Vector2d bottom_point_;
155 Segment<2> template_seg_;
156
157 IntrinsicParams intrinsics_;
158};
159
Parker Schuh2a1447c2019-02-17 00:25:29 -0800160IntermediateResult TargetFinder::ProcessTargetToResult(const Target &target,
161 bool verbose) {
162 // Memory for the ceres solver.
Alex Perrybac3d3f2019-03-10 14:26:51 -0700163 double params_8point[ExtrinsicParams::kNumParams];
164 default_extrinsics_.set(&params_8point[0]);
165 double params_4point[ExtrinsicParams::kNumParams];
166 default_extrinsics_.set(&params_4point[0]);
Parker Schuh2a1447c2019-02-17 00:25:29 -0800167
Brian Silverman63236772019-03-23 22:02:44 -0700168 Problem::Options problem_options;
169 problem_options.context = ceres_context_.get();
170 Problem problem_8point(problem_options);
171 Problem problem_4point(problem_options);
Parker Schuh2a1447c2019-02-17 00:25:29 -0800172
Austin Schuh2894e902019-03-03 21:12:46 -0800173 ::std::array<aos::vision::Vector<2>, 8> target_value = target.ToPointList();
174 ::std::array<aos::vision::Vector<2>, 8> template_value =
175 target_template_.ToPointList();
Parker Schuh2a1447c2019-02-17 00:25:29 -0800176
177 for (size_t i = 0; i < 8; ++i) {
Austin Schuh2894e902019-03-03 21:12:46 -0800178 aos::vision::Vector<2> a = template_value[i];
179 aos::vision::Vector<2> b = target_value[i];
Parker Schuh2a1447c2019-02-17 00:25:29 -0800180
Alex Perrybac3d3f2019-03-10 14:26:51 -0700181 if (i % 2 == 1) {
182 aos::vision::Vector<2> a2 = template_value[i-1];
183 aos::vision::Segment<2> line = Segment<2>(a, a2);
184
185 problem_4point.AddResidualBlock(
186 new NumericDiffCostFunction<LineCostFunctor, CENTRAL, 1, 4>(
187 new LineCostFunctor(b, line, intrinsics_)),
Austin Schuhe6cfbe32019-03-10 18:05:57 -0700188 NULL, &params_4point[0]);
Alex Perrybac3d3f2019-03-10 14:26:51 -0700189 } else {
190 problem_4point.AddResidualBlock(
191 new NumericDiffCostFunction<PointCostFunctor, CENTRAL, 2, 4>(
192 new PointCostFunctor(b, a, intrinsics_)),
Austin Schuhe6cfbe32019-03-10 18:05:57 -0700193 NULL, &params_4point[0]);
Alex Perrybac3d3f2019-03-10 14:26:51 -0700194 }
195
196 problem_8point.AddResidualBlock(
197 new NumericDiffCostFunction<PointCostFunctor, CENTRAL, 2, 4>(
198 new PointCostFunctor(b, a, intrinsics_)),
Austin Schuhe6cfbe32019-03-10 18:05:57 -0700199 NULL, &params_8point[0]);
Parker Schuh2a1447c2019-02-17 00:25:29 -0800200 }
201
Austin Schuhbb2ac922019-03-11 01:09:57 -0700202 Solver::Options options;
203 options.minimizer_progress_to_stdout = false;
204 Solver::Summary summary_8point;
205 Solve(options, &problem_8point, &summary_8point);
206
207
208 // So, let's sneak up on it. Start by warm-starting it with where we got on the 8 point solution.
209 ExtrinsicParams::get(&params_8point[0]).set(&params_4point[0]);
210 // Then solve without the bottom constraint.
211 Solver::Summary summary_4point1;
212 Solve(options, &problem_4point, &summary_4point1);
213
Austin Schuhe6cfbe32019-03-10 18:05:57 -0700214 // Now, add a large cost for the bottom point being below the bottom line.
215 problem_4point.AddResidualBlock(
216 new NumericDiffCostFunction<BottomPointCostFunctor, CENTRAL, 1, 4>(
217 new BottomPointCostFunctor(target.left.bottom_point,
218 Segment<2>(target_template_.left.outside,
219 target_template_.left.bottom),
220 intrinsics_)),
221 NULL, &params_4point[0]);
222 // Make sure to point the segment the other way so when we do a -pi/2 rotation
223 // on the line, it points down in target space.
224 problem_4point.AddResidualBlock(
225 new NumericDiffCostFunction<BottomPointCostFunctor, CENTRAL, 1, 4>(
226 new BottomPointCostFunctor(target.right.bottom_point,
227 Segment<2>(target_template_.right.bottom,
228 target_template_.right.outside),
229 intrinsics_)),
230 NULL, &params_4point[0]);
231
Austin Schuhbb2ac922019-03-11 01:09:57 -0700232 // And then re-solve.
233 Solver::Summary summary_4point2;
234 Solve(options, &problem_4point, &summary_4point2);
Parker Schuh2a1447c2019-02-17 00:25:29 -0800235
236 IntermediateResult IR;
Alex Perrybac3d3f2019-03-10 14:26:51 -0700237 IR.extrinsics = ExtrinsicParams::get(&params_8point[0]);
238 IR.solver_error = summary_8point.final_cost;
239 IR.backup_extrinsics = ExtrinsicParams::get(&params_4point[0]);
Austin Schuhbb2ac922019-03-11 01:09:57 -0700240 IR.backup_solver_error = summary_4point2.final_cost;
Parker Schuh2a1447c2019-02-17 00:25:29 -0800241
Alex Perryb6f334d2019-03-23 13:10:45 -0700242 // Normalize all angles to (-M_PI, M_PI]
243 IR.extrinsics.r1 = ::aos::math::NormalizeAngle(IR.extrinsics.r1);
244 IR.extrinsics.r2 = ::aos::math::NormalizeAngle(IR.extrinsics.r2);
Austin Schuh45639882019-03-24 19:20:42 -0700245 IR.backup_extrinsics.r1 =
246 ::aos::math::NormalizeAngle(IR.backup_extrinsics.r1);
247 IR.backup_extrinsics.r2 =
248 ::aos::math::NormalizeAngle(IR.backup_extrinsics.r2);
249
250 // Ok, let's look at how perpendicular the corners are.
251 // Vector from the outside to inside along the top on the left.
252 const ::Eigen::Vector2d top_left_vector =
253 (target.left.top.GetData() - target.left.inside.GetData())
254 .transpose()
255 .normalized();
256 // Vector up the outside of the left target.
257 const ::Eigen::Vector2d outer_left_vector =
258 (target.left.top.GetData() - target.left.outside.GetData())
259 .transpose()
260 .normalized();
261 // Vector up the inside of the left target.
262 const ::Eigen::Vector2d inner_left_vector =
263 (target.left.inside.GetData() - target.left.bottom.GetData())
264 .transpose()
265 .normalized();
266
267 // Vector from the outside to inside along the top on the right.
268 const ::Eigen::Vector2d top_right_vector =
269 (target.right.top.GetData() - target.right.inside.GetData())
270 .transpose()
271 .normalized();
272 // Vector up the outside of the right target.
273 const ::Eigen::Vector2d outer_right_vector =
274 (target.right.top.GetData() - target.right.outside.GetData())
275 .transpose()
276 .normalized();
277 // Vector up the inside of the right target.
278 const ::Eigen::Vector2d inner_right_vector =
279 (target.right.inside.GetData() - target.right.bottom.GetData())
280 .transpose()
281 .normalized();
282
283 // Now dot the vectors and use that to compute angles.
284 // Left side, outside corner.
285 const double left_outer_corner_dot =
286 (outer_left_vector.transpose() * top_left_vector)(0);
287 // Left side, inside corner.
288 const double left_inner_corner_dot =
289 (inner_left_vector.transpose() * top_left_vector)(0);
290 // Right side, outside corner.
291 const double right_outer_corner_dot =
292 (outer_right_vector.transpose() * top_right_vector)(0);
293 // Right side, inside corner.
294 const double right_inner_corner_dot =
295 (inner_right_vector.transpose() * top_right_vector)(0);
296
297 constexpr double kCornerThreshold = 0.35;
298 if (::std::abs(left_outer_corner_dot) < kCornerThreshold &&
299 ::std::abs(left_inner_corner_dot) < kCornerThreshold &&
300 ::std::abs(right_outer_corner_dot) < kCornerThreshold &&
301 ::std::abs(right_inner_corner_dot) < kCornerThreshold) {
302 IR.good_corners = true;
303 } else {
304 IR.good_corners = false;
305 }
Alex Perryb6f334d2019-03-23 13:10:45 -0700306
Parker Schuh2a1447c2019-02-17 00:25:29 -0800307 if (verbose) {
Alex Perrybac3d3f2019-03-10 14:26:51 -0700308 std::cout << "rup = " << intrinsics_.mount_angle * 180 / M_PI << ";\n";
309 std::cout << "fl = " << intrinsics_.focal_length << ";\n";
310 std::cout << "8 points:\n";
311 std::cout << summary_8point.BriefReport() << "\n";
312 std::cout << "error = " << summary_8point.final_cost << ";\n";
Parker Schuh2a1447c2019-02-17 00:25:29 -0800313 std::cout << "y = " << IR.extrinsics.y / kInchesToMeters << ";\n";
314 std::cout << "z = " << IR.extrinsics.z / kInchesToMeters << ";\n";
315 std::cout << "r1 = " << IR.extrinsics.r1 * 180 / M_PI << ";\n";
316 std::cout << "r2 = " << IR.extrinsics.r2 * 180 / M_PI << ";\n";
Alex Perrybac3d3f2019-03-10 14:26:51 -0700317 std::cout << "4 points:\n";
Austin Schuhbb2ac922019-03-11 01:09:57 -0700318 std::cout << summary_4point1.BriefReport() << "\n";
319 std::cout << "error = " << summary_4point1.final_cost << ";\n\n";
320 std::cout << "4 points:\n";
321 std::cout << summary_4point2.BriefReport() << "\n";
322 std::cout << "error = " << summary_4point2.final_cost << ";\n\n";
Alex Perrybac3d3f2019-03-10 14:26:51 -0700323 std::cout << "y = " << IR.backup_extrinsics.y / kInchesToMeters << ";\n";
324 std::cout << "z = " << IR.backup_extrinsics.z / kInchesToMeters << ";\n";
325 std::cout << "r1 = " << IR.backup_extrinsics.r1 * 180 / M_PI << ";\n";
326 std::cout << "r2 = " << IR.backup_extrinsics.r2 * 180 / M_PI << ";\n";
Austin Schuh45639882019-03-24 19:20:42 -0700327
328
329 printf("left upper outer corner angle: %f, top (%f, %f), outer (%f, %f)\n",
330 (outer_left_vector.transpose() * top_left_vector)(0),
331 top_left_vector(0, 0), top_left_vector(1, 0),
332 outer_left_vector(0, 0), outer_left_vector(1, 0));
333 printf("left upper inner corner angle: %f\n",
334 (inner_left_vector.transpose() * top_left_vector)(0));
335
336 printf("right upper outer corner angle: %f, top (%f, %f), outer (%f, %f)\n",
337 (outer_right_vector.transpose() * top_right_vector)(0),
338 top_right_vector(0, 0), top_right_vector(1, 0),
339 outer_right_vector(0, 0), outer_right_vector(1, 0));
340 printf("right upper inner corner angle: %f\n",
341 (inner_right_vector.transpose() * top_right_vector)(0));
Parker Schuh2a1447c2019-02-17 00:25:29 -0800342 }
343 return IR;
344}
345
346} // namespace vision
347} // namespace y2019