blob: e647f1f9b51329cf4218a01792f37ff25ab33285 [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
58Vector<2> Project(Vector<2> pt, const IntrinsicParams &intrinsics,
59 const ExtrinsicParams &extrinsics) {
James Kuszmaul289756f2019-03-05 21:52:10 -080060 const double y = extrinsics.y; // height
61 const double z = extrinsics.z; // distance
62 const double r1 = extrinsics.r1; // skew
63 const double r2 = extrinsics.r2; // heading
Austin Schuhcacc6922019-03-06 20:44:30 -080064 const double rup = intrinsics.mount_angle;
65 const double rbarrel = intrinsics.barrel_mount;
66 const double fl = intrinsics.focal_length;
Parker Schuh2a1447c2019-02-17 00:25:29 -080067
James Kuszmaul289756f2019-03-05 21:52:10 -080068 // Start by translating point in target-space to be at correct height.
Parker Schuh2a1447c2019-02-17 00:25:29 -080069 ::Eigen::Matrix<double, 1, 3> pts{pt.x(), pt.y() + y, 0.0};
70
71 {
James Kuszmaul289756f2019-03-05 21:52:10 -080072 // Rotate to compensate for skew angle, to get into a frame still at the
73 // same (x, y) position as the target but rotated to be facing straight
74 // towards the camera.
Austin Schuhcacc6922019-03-06 20:44:30 -080075 const double theta = r1;
76 const double s = sin(theta);
77 const double c = cos(theta);
Parker Schuh2a1447c2019-02-17 00:25:29 -080078 pts = (::Eigen::Matrix<double, 3, 3>() << c, 0, -s, 0, 1, 0, s, 0,
79 c).finished() *
80 pts.transpose();
81 }
82
James Kuszmaul289756f2019-03-05 21:52:10 -080083 // Translate the coordinate frame to have (x, y) centered at the camera, but
84 // still oriented to be facing along the line from the camera to the target.
Parker Schuh2a1447c2019-02-17 00:25:29 -080085 pts(2) += z;
86
87 {
James Kuszmaul289756f2019-03-05 21:52:10 -080088 // Rotate out the heading so that the frame is oriented to line up with the
89 // camera's viewpoint in the yaw-axis.
Austin Schuhcacc6922019-03-06 20:44:30 -080090 const double theta = r2;
91 const double s = sin(theta);
92 const double c = cos(theta);
Parker Schuh2a1447c2019-02-17 00:25:29 -080093 pts = (::Eigen::Matrix<double, 3, 3>() << c, 0, -s, 0, 1, 0, s, 0,
94 c).finished() *
95 pts.transpose();
96 }
97
98 // TODO: Apply 15 degree downward rotation.
99 {
James Kuszmaul289756f2019-03-05 21:52:10 -0800100 // Compensate for rotation in the pitch of the camera up/down to get into
101 // the coordinate frame lined up with the plane of the camera sensor.
Austin Schuhcacc6922019-03-06 20:44:30 -0800102 const double theta = rup;
103 const double s = sin(theta);
104 const double c = cos(theta);
Parker Schuh2a1447c2019-02-17 00:25:29 -0800105
106 pts = (::Eigen::Matrix<double, 3, 3>() << 1, 0, 0, 0, c, -s, 0, s,
107 c).finished() *
108 pts.transpose();
109 }
110
James Kuszmaul289756f2019-03-05 21:52:10 -0800111 // Compensate for rotation of the barrel of the camera, i.e. about the axis
112 // that points straight out from the camera lense, using an AngleAxis instead
113 // of manually constructing the rotation matrices because once you get into
114 // this frame you no longer need to be masochistic.
Parker Schuh9e1d1692019-02-24 14:34:04 -0800115 // TODO: Maybe barrel should be extrinsics to allow rocking?
116 // Also, in this case, barrel should go above the rotation above?
117 pts = ::Eigen::AngleAxis<double>(rbarrel, ::Eigen::Vector3d(0.0, 0.0, 1.0)) *
118 pts.transpose();
119
Parker Schuh2a1447c2019-02-17 00:25:29 -0800120 // TODO: Final image projection.
Austin Schuhcacc6922019-03-06 20:44:30 -0800121 const ::Eigen::Matrix<double, 1, 3> res = pts;
Parker Schuh2a1447c2019-02-17 00:25:29 -0800122
James Kuszmaul289756f2019-03-05 21:52:10 -0800123 // Finally, scale to account for focal length and translate to get into
124 // pixel-space.
Austin Schuhcacc6922019-03-06 20:44:30 -0800125 const float scale = fl / res.z();
Parker Schuh2a1447c2019-02-17 00:25:29 -0800126 return Vector<2>(res.x() * scale + 320.0, 240.0 - res.y() * scale);
127}
128
129Target Project(const Target &target, const IntrinsicParams &intrinsics,
130 const ExtrinsicParams &extrinsics) {
Parker Schuh2a1447c2019-02-17 00:25:29 -0800131 Target new_targ;
132 new_targ.right.is_right = true;
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800133 new_targ.right.top = Project(target.right.top, intrinsics, extrinsics);
134 new_targ.right.inside = Project(target.right.inside, intrinsics, extrinsics);
135 new_targ.right.bottom = Project(target.right.bottom, intrinsics, extrinsics);
136 new_targ.right.outside =
137 Project(target.right.outside, intrinsics, extrinsics);
Parker Schuh2a1447c2019-02-17 00:25:29 -0800138
Austin Schuh4d6e9bd2019-03-08 19:54:17 -0800139 new_targ.left.top = Project(target.left.top, intrinsics, extrinsics);
140 new_targ.left.inside = Project(target.left.inside, intrinsics, extrinsics);
141 new_targ.left.bottom = Project(target.left.bottom, intrinsics, extrinsics);
142 new_targ.left.outside = Project(target.left.outside, intrinsics, extrinsics);
Parker Schuh2a1447c2019-02-17 00:25:29 -0800143
144 return new_targ;
145}
146
147// Used at runtime on a single image given camera parameters.
Alex Perrybac3d3f2019-03-10 14:26:51 -0700148struct PointCostFunctor {
149 PointCostFunctor(Vector<2> result, Vector<2> template_pt,
Parker Schuh2a1447c2019-02-17 00:25:29 -0800150 IntrinsicParams intrinsics)
151 : result(result), template_pt(template_pt), intrinsics(intrinsics) {}
152
153 bool operator()(const double *const x, double *residual) const {
154 auto extrinsics = ExtrinsicParams::get(x);
155 auto pt = result - Project(template_pt, intrinsics, extrinsics);
156 residual[0] = pt.x();
157 residual[1] = pt.y();
158 return true;
159 }
160
161 Vector<2> result;
162 Vector<2> template_pt;
163 IntrinsicParams intrinsics;
164};
165
Alex Perrybac3d3f2019-03-10 14:26:51 -0700166// Find the distance from a lower target point to the 'vertical' line it should
167// be on.
168struct LineCostFunctor {
169 LineCostFunctor(Vector<2> result, Segment<2> template_seg,
170 IntrinsicParams intrinsics)
171 : result(result), template_seg(template_seg), intrinsics(intrinsics) {}
172
173 bool operator()(const double *const x, double *residual) const {
174 const auto extrinsics = ExtrinsicParams::get(x);
Austin Schuhe6cfbe32019-03-10 18:05:57 -0700175 const Vector<2> p1 = Project(template_seg.A(), intrinsics, extrinsics);
176 const Vector<2> p2 = Project(template_seg.B(), intrinsics, extrinsics);
Alex Perrybac3d3f2019-03-10 14:26:51 -0700177 // distance from line (P1, P2) to point result
178 double dx = p2.x() - p1.x();
179 double dy = p2.y() - p1.y();
180 double denom = p2.DistanceTo(p1);
181 residual[0] = ::std::abs(dy * result.x() - dx * result.y() +
182 p2.x() * p1.y() - p2.y() * p1.x()) /
183 denom;
184 return true;
185 }
186
187 Vector<2> result;
188 Segment<2> template_seg;
189 IntrinsicParams intrinsics;
190};
191
Austin Schuhe6cfbe32019-03-10 18:05:57 -0700192// Find the distance that the bottom point is outside the target and penalize
193// that linearly.
194class BottomPointCostFunctor {
195 public:
196 BottomPointCostFunctor(::Eigen::Vector2f bottom_point,
197 Segment<2> template_seg, IntrinsicParams intrinsics)
198 : bottom_point_(bottom_point.x(), bottom_point.y()),
199 template_seg_(template_seg),
200 intrinsics_(intrinsics) {}
201
202 bool operator()(const double *const x, double *residual) const {
203 const ExtrinsicParams extrinsics = ExtrinsicParams::get(x);
204 const Vector<2> p1 = Project(template_seg_.A(), intrinsics_, extrinsics);
205 const Vector<2> p2 = Project(template_seg_.B(), intrinsics_, extrinsics);
206
207 // Construct a vector pointed perpendicular to the line. This vector is
208 // pointed down out the bottom of the target.
209 ::Eigen::Vector2d down_axis(-(p1.y() - p2.y()), p1.x() - p2.x());
210 down_axis.normalize();
211
212 // Positive means out.
213 const double component =
214 down_axis.transpose() * (bottom_point_ - p1.GetData().transpose());
215
216 if (component > 0) {
217 residual[0] = component * 1.0;
218 } else {
219 residual[0] = 0.0;
220 }
221 return true;
222 }
223
224 private:
225 ::Eigen::Vector2d bottom_point_;
226 Segment<2> template_seg_;
227
228 IntrinsicParams intrinsics_;
229};
230
Parker Schuh2a1447c2019-02-17 00:25:29 -0800231IntermediateResult TargetFinder::ProcessTargetToResult(const Target &target,
232 bool verbose) {
233 // Memory for the ceres solver.
Alex Perrybac3d3f2019-03-10 14:26:51 -0700234 double params_8point[ExtrinsicParams::kNumParams];
235 default_extrinsics_.set(&params_8point[0]);
236 double params_4point[ExtrinsicParams::kNumParams];
237 default_extrinsics_.set(&params_4point[0]);
Parker Schuh2a1447c2019-02-17 00:25:29 -0800238
Brian Silverman63236772019-03-23 22:02:44 -0700239 Problem::Options problem_options;
240 problem_options.context = ceres_context_.get();
241 Problem problem_8point(problem_options);
242 Problem problem_4point(problem_options);
Parker Schuh2a1447c2019-02-17 00:25:29 -0800243
Austin Schuh2894e902019-03-03 21:12:46 -0800244 ::std::array<aos::vision::Vector<2>, 8> target_value = target.ToPointList();
245 ::std::array<aos::vision::Vector<2>, 8> template_value =
246 target_template_.ToPointList();
Parker Schuh2a1447c2019-02-17 00:25:29 -0800247
248 for (size_t i = 0; i < 8; ++i) {
Austin Schuh2894e902019-03-03 21:12:46 -0800249 aos::vision::Vector<2> a = template_value[i];
250 aos::vision::Vector<2> b = target_value[i];
Parker Schuh2a1447c2019-02-17 00:25:29 -0800251
Alex Perrybac3d3f2019-03-10 14:26:51 -0700252 if (i % 2 == 1) {
253 aos::vision::Vector<2> a2 = template_value[i-1];
254 aos::vision::Segment<2> line = Segment<2>(a, a2);
255
256 problem_4point.AddResidualBlock(
257 new NumericDiffCostFunction<LineCostFunctor, CENTRAL, 1, 4>(
258 new LineCostFunctor(b, line, intrinsics_)),
Austin Schuhe6cfbe32019-03-10 18:05:57 -0700259 NULL, &params_4point[0]);
Alex Perrybac3d3f2019-03-10 14:26:51 -0700260 } else {
261 problem_4point.AddResidualBlock(
262 new NumericDiffCostFunction<PointCostFunctor, CENTRAL, 2, 4>(
263 new PointCostFunctor(b, a, intrinsics_)),
Austin Schuhe6cfbe32019-03-10 18:05:57 -0700264 NULL, &params_4point[0]);
Alex Perrybac3d3f2019-03-10 14:26:51 -0700265 }
266
267 problem_8point.AddResidualBlock(
268 new NumericDiffCostFunction<PointCostFunctor, CENTRAL, 2, 4>(
269 new PointCostFunctor(b, a, intrinsics_)),
Austin Schuhe6cfbe32019-03-10 18:05:57 -0700270 NULL, &params_8point[0]);
Parker Schuh2a1447c2019-02-17 00:25:29 -0800271 }
272
Austin Schuhbb2ac922019-03-11 01:09:57 -0700273 Solver::Options options;
274 options.minimizer_progress_to_stdout = false;
275 Solver::Summary summary_8point;
276 Solve(options, &problem_8point, &summary_8point);
277
278
279 // So, let's sneak up on it. Start by warm-starting it with where we got on the 8 point solution.
280 ExtrinsicParams::get(&params_8point[0]).set(&params_4point[0]);
281 // Then solve without the bottom constraint.
282 Solver::Summary summary_4point1;
283 Solve(options, &problem_4point, &summary_4point1);
284
Austin Schuhe6cfbe32019-03-10 18:05:57 -0700285 // Now, add a large cost for the bottom point being below the bottom line.
286 problem_4point.AddResidualBlock(
287 new NumericDiffCostFunction<BottomPointCostFunctor, CENTRAL, 1, 4>(
288 new BottomPointCostFunctor(target.left.bottom_point,
289 Segment<2>(target_template_.left.outside,
290 target_template_.left.bottom),
291 intrinsics_)),
292 NULL, &params_4point[0]);
293 // Make sure to point the segment the other way so when we do a -pi/2 rotation
294 // on the line, it points down in target space.
295 problem_4point.AddResidualBlock(
296 new NumericDiffCostFunction<BottomPointCostFunctor, CENTRAL, 1, 4>(
297 new BottomPointCostFunctor(target.right.bottom_point,
298 Segment<2>(target_template_.right.bottom,
299 target_template_.right.outside),
300 intrinsics_)),
301 NULL, &params_4point[0]);
302
Austin Schuhbb2ac922019-03-11 01:09:57 -0700303 // And then re-solve.
304 Solver::Summary summary_4point2;
305 Solve(options, &problem_4point, &summary_4point2);
Parker Schuh2a1447c2019-02-17 00:25:29 -0800306
307 IntermediateResult IR;
Alex Perrybac3d3f2019-03-10 14:26:51 -0700308 IR.extrinsics = ExtrinsicParams::get(&params_8point[0]);
309 IR.solver_error = summary_8point.final_cost;
310 IR.backup_extrinsics = ExtrinsicParams::get(&params_4point[0]);
Austin Schuhbb2ac922019-03-11 01:09:57 -0700311 IR.backup_solver_error = summary_4point2.final_cost;
Parker Schuh2a1447c2019-02-17 00:25:29 -0800312
Alex Perryb6f334d2019-03-23 13:10:45 -0700313 // Normalize all angles to (-M_PI, M_PI]
314 IR.extrinsics.r1 = ::aos::math::NormalizeAngle(IR.extrinsics.r1);
315 IR.extrinsics.r2 = ::aos::math::NormalizeAngle(IR.extrinsics.r2);
316 IR.backup_extrinsics.r1 = ::aos::math::NormalizeAngle(IR.backup_extrinsics.r1);
317 IR.backup_extrinsics.r2 = ::aos::math::NormalizeAngle(IR.backup_extrinsics.r2);
318
Parker Schuh2a1447c2019-02-17 00:25:29 -0800319 if (verbose) {
Alex Perrybac3d3f2019-03-10 14:26:51 -0700320 std::cout << "rup = " << intrinsics_.mount_angle * 180 / M_PI << ";\n";
321 std::cout << "fl = " << intrinsics_.focal_length << ";\n";
322 std::cout << "8 points:\n";
323 std::cout << summary_8point.BriefReport() << "\n";
324 std::cout << "error = " << summary_8point.final_cost << ";\n";
Parker Schuh2a1447c2019-02-17 00:25:29 -0800325 std::cout << "y = " << IR.extrinsics.y / kInchesToMeters << ";\n";
326 std::cout << "z = " << IR.extrinsics.z / kInchesToMeters << ";\n";
327 std::cout << "r1 = " << IR.extrinsics.r1 * 180 / M_PI << ";\n";
328 std::cout << "r2 = " << IR.extrinsics.r2 * 180 / M_PI << ";\n";
Alex Perrybac3d3f2019-03-10 14:26:51 -0700329 std::cout << "4 points:\n";
Austin Schuhbb2ac922019-03-11 01:09:57 -0700330 std::cout << summary_4point1.BriefReport() << "\n";
331 std::cout << "error = " << summary_4point1.final_cost << ";\n\n";
332 std::cout << "4 points:\n";
333 std::cout << summary_4point2.BriefReport() << "\n";
334 std::cout << "error = " << summary_4point2.final_cost << ";\n\n";
Alex Perrybac3d3f2019-03-10 14:26:51 -0700335 std::cout << "y = " << IR.backup_extrinsics.y / kInchesToMeters << ";\n";
336 std::cout << "z = " << IR.backup_extrinsics.z / kInchesToMeters << ";\n";
337 std::cout << "r1 = " << IR.backup_extrinsics.r1 * 180 / M_PI << ";\n";
338 std::cout << "r2 = " << IR.backup_extrinsics.r2 * 180 / M_PI << ";\n";
Parker Schuh2a1447c2019-02-17 00:25:29 -0800339 }
340 return IR;
341}
342
343} // namespace vision
344} // namespace y2019