blob: 77806d58033652317d04a581155b20a4b305dae1 [file] [log] [blame]
Parker Schuh2a1447c2019-02-17 00:25:29 -08001#include "y2019/vision/target_finder.h"
2
3#include "aos/vision/blob/hierarchical_contour_merge.h"
Brian Silverman63236772019-03-23 22:02:44 -07004#include "ceres/ceres.h"
Parker Schuh2a1447c2019-02-17 00:25:29 -08005
6using namespace aos::vision;
7
8namespace y2019 {
9namespace vision {
10
Brian Silverman63236772019-03-23 22:02:44 -070011TargetFinder::TargetFinder()
12 : target_template_(Target::MakeTemplate()),
13 ceres_context_(ceres::Context::Create()) {}
14
15TargetFinder::~TargetFinder() {}
Parker Schuh2a1447c2019-02-17 00:25:29 -080016
17aos::vision::RangeImage TargetFinder::Threshold(aos::vision::ImagePtr image) {
18 const uint8_t threshold_value = GetThresholdValue();
Brian Silverman37b15b32019-03-10 13:30:18 -070019 return aos::vision::ThresholdImageWithFunction(
20 image, [&](aos::vision::PixelRef px) {
21 if (px.g > threshold_value && px.b > threshold_value &&
22 px.r > threshold_value) {
23 return true;
24 }
25 return false;
26 });
Parker Schuh2a1447c2019-02-17 00:25:29 -080027}
28
29// Filter blobs on size.
30void TargetFinder::PreFilter(BlobList *imgs) {
31 imgs->erase(
32 std::remove_if(imgs->begin(), imgs->end(),
33 [](RangeImage &img) {
34 // We can drop images with a small number of
35 // pixels, but images
36 // must be over 20px or the math will have issues.
37 return (img.npixels() < 100 || img.height() < 25);
38 }),
39 imgs->end());
40}
41
Austin Schuh7d2ef032019-03-10 14:59:34 -070042ContourNode *TargetFinder::GetContour(const RangeImage &blob) {
Ben Fredricksonf7b68522019-03-02 21:19:42 -080043 alloc_.reset();
44 return RangeImgToContour(blob, &alloc_);
45}
46
Ben Fredricksonec575822019-03-02 22:03:20 -080047// TODO(ben): These values will be moved into the constants.h file.
Ben Fredricksonf7b68522019-03-02 21:19:42 -080048namespace {
49
Austin Schuh7d2ef032019-03-10 14:59:34 -070050::Eigen::Vector2f AosVectorToEigenVector(Vector<2> in) {
51 return ::Eigen::Vector2f(in.x(), in.y());
52}
53
Ben Fredricksonec575822019-03-02 22:03:20 -080054constexpr double f_x = 481.4957;
55constexpr double c_x = 341.215;
56constexpr double f_y = 484.314;
57constexpr double c_y = 251.29;
Ben Fredricksonf7b68522019-03-02 21:19:42 -080058
Ben Fredricksonec575822019-03-02 22:03:20 -080059constexpr double f_x_prime = 363.1424;
60constexpr double c_x_prime = 337.9895;
61constexpr double f_y_prime = 366.4837;
62constexpr double c_y_prime = 240.0702;
Ben Fredricksonf7b68522019-03-02 21:19:42 -080063
Ben Fredricksonec575822019-03-02 22:03:20 -080064constexpr double k_1 = -0.2739;
65constexpr double k_2 = 0.01583;
66constexpr double k_3 = 0.04201;
Ben Fredricksonf7b68522019-03-02 21:19:42 -080067
68constexpr int iterations = 7;
69
70}
71
Austin Schuhe5015972019-03-09 17:47:34 -080072::Eigen::Vector2f UnWarpPoint(const Point point) {
Ben Fredricksonec575822019-03-02 22:03:20 -080073 const double x0 = ((double)point.x - c_x) / f_x;
74 const double y0 = ((double)point.y - c_y) / f_y;
Ben Fredricksonf7b68522019-03-02 21:19:42 -080075 double x = x0;
76 double y = y0;
77 for (int i = 0; i < iterations; i++) {
78 const double r_sqr = x * x + y * y;
Alex Perry2ca15742019-03-10 20:38:40 -070079 const double coeff = 1.0 + r_sqr * (k_1 + r_sqr * (k_2 + r_sqr * (k_3)));
Ben Fredricksonf7b68522019-03-02 21:19:42 -080080 x = x0 / coeff;
81 y = y0 / coeff;
82 }
Austin Schuhe5015972019-03-09 17:47:34 -080083 const double nx = x * f_x_prime + c_x_prime;
84 const double ny = y * f_y_prime + c_y_prime;
85 return ::Eigen::Vector2f(nx, ny);
Ben Fredricksonf7b68522019-03-02 21:19:42 -080086}
87
Austin Schuhe5015972019-03-09 17:47:34 -080088::std::vector<::Eigen::Vector2f> TargetFinder::UnWarpContour(
89 ContourNode *start) const {
90 ::std::vector<::Eigen::Vector2f> result;
Ben Fredricksonf7b68522019-03-02 21:19:42 -080091 ContourNode *node = start;
92 while (node->next != start) {
Austin Schuhe5015972019-03-09 17:47:34 -080093 result.push_back(UnWarpPoint(node->pt));
Ben Fredricksonf7b68522019-03-02 21:19:42 -080094 node = node->next;
95 }
Austin Schuhe5015972019-03-09 17:47:34 -080096 result.push_back(UnWarpPoint(node->pt));
97 return result;
Ben Fredricksonf7b68522019-03-02 21:19:42 -080098}
99
Parker Schuh2a1447c2019-02-17 00:25:29 -0800100// TODO: Try hierarchical merge for this.
101// Convert blobs into polygons.
Austin Schuh6e56faf2019-03-10 14:04:57 -0700102Polygon TargetFinder::FindPolygon(::std::vector<::Eigen::Vector2f> &&contour,
103 bool verbose) {
Parker Schuh2a1447c2019-02-17 00:25:29 -0800104 if (verbose) printf("Process Polygon.\n");
Parker Schuh2a1447c2019-02-17 00:25:29 -0800105
Austin Schuhe5015972019-03-09 17:47:34 -0800106 ::std::vector<::Eigen::Vector2f> slopes;
Parker Schuh2a1447c2019-02-17 00:25:29 -0800107
108 // Collect all slopes from the contour.
Austin Schuhe5015972019-03-09 17:47:34 -0800109 ::Eigen::Vector2f previous_point = contour[0];
110 for (size_t i = 0; i < contour.size(); ++i) {
111 ::Eigen::Vector2f next_point = contour[(i + 1) % contour.size()];
Parker Schuh2a1447c2019-02-17 00:25:29 -0800112
Austin Schuhe5015972019-03-09 17:47:34 -0800113 slopes.push_back(next_point - previous_point);
Parker Schuh2a1447c2019-02-17 00:25:29 -0800114
Austin Schuhe5015972019-03-09 17:47:34 -0800115 previous_point = next_point;
Parker Schuh2a1447c2019-02-17 00:25:29 -0800116 }
117
Austin Schuhe5015972019-03-09 17:47:34 -0800118 const int num_points = slopes.size();
119 auto get_pt = [&slopes, num_points](int i) {
120 return slopes[(i + num_points * 2) % num_points];
Austin Schuh335eef12019-03-02 17:04:17 -0800121 };
Parker Schuh2a1447c2019-02-17 00:25:29 -0800122
Austin Schuh6a484962019-03-09 21:51:27 -0800123 // Bigger objects should be more filtered. Filter roughly proportional to the
124 // perimeter of the object.
125 const int range = slopes.size() / 50;
126 if (verbose) printf("Corner range: %d.\n", range);
127
Austin Schuhe5015972019-03-09 17:47:34 -0800128 ::std::vector<::Eigen::Vector2f> filtered_slopes = slopes;
Austin Schuh335eef12019-03-02 17:04:17 -0800129 // Three box filter makith a guassian?
130 // Run gaussian filter over the slopes 3 times. That'll get us pretty close
131 // to running a gausian over it.
132 for (int k = 0; k < 3; ++k) {
Austin Schuh6a484962019-03-09 21:51:27 -0800133 const int window_size = ::std::max(2, range);
Austin Schuhe5015972019-03-09 17:47:34 -0800134 for (size_t i = 0; i < slopes.size(); ++i) {
135 ::Eigen::Vector2f a = ::Eigen::Vector2f::Zero();
Parker Schuh2a1447c2019-02-17 00:25:29 -0800136 for (int j = -window_size; j <= window_size; ++j) {
Austin Schuhe5015972019-03-09 17:47:34 -0800137 ::Eigen::Vector2f p = get_pt(j + i);
138 a += p;
Parker Schuh2a1447c2019-02-17 00:25:29 -0800139 }
Austin Schuhe5015972019-03-09 17:47:34 -0800140 a /= (window_size * 2 + 1);
Parker Schuh2a1447c2019-02-17 00:25:29 -0800141
Austin Schuhe5015972019-03-09 17:47:34 -0800142 filtered_slopes[i] = a;
Parker Schuh2a1447c2019-02-17 00:25:29 -0800143 }
Austin Schuhe5015972019-03-09 17:47:34 -0800144 slopes = filtered_slopes;
Austin Schuh335eef12019-03-02 17:04:17 -0800145 }
Austin Schuh6a484962019-03-09 21:51:27 -0800146 if (verbose) printf("Point count: %zu.\n", slopes.size());
Parker Schuh2a1447c2019-02-17 00:25:29 -0800147
Austin Schuh6a484962019-03-09 21:51:27 -0800148 ::std::vector<float> corner_metric(slopes.size(), 0.0);
Parker Schuh2a1447c2019-02-17 00:25:29 -0800149
Austin Schuh6a484962019-03-09 21:51:27 -0800150 for (size_t i = 0; i < slopes.size(); ++i) {
151 const ::Eigen::Vector2f a = get_pt(i - ::std::max(3, range));
152 const ::Eigen::Vector2f b = get_pt(i + ::std::max(3, range));
153 corner_metric[i] = (a - b).squaredNorm();
154 }
155
156 // We want to find the Nth highest peaks.
157 // Clever algorithm: Find the highest point. Then, walk forwards and
158 // backwards to find the next valley each direction which is over x% lower
159 // than the peak.
160 // We want to ignore those points in the future. Set them to 0.
161 // Repeat until we've found the Nth highest peak.
Parker Schuh2a1447c2019-02-17 00:25:29 -0800162
163 // Find all centers of corners.
Austin Schuhe5015972019-03-09 17:47:34 -0800164 // Because they round, multiple slopes may be a corner.
165 ::std::vector<size_t> edges;
Parker Schuh2a1447c2019-02-17 00:25:29 -0800166
Austin Schuh6a484962019-03-09 21:51:27 -0800167 constexpr float peak_acceptance_ratio = 0.16;
168 constexpr float valley_ratio = 0.75;
169
170 float highest_peak_value = 0.0;
171
172 // Nth higest points.
Austin Schuh32ffac22019-03-09 22:42:02 -0800173 while (edges.size() < 5) {
Austin Schuh6a484962019-03-09 21:51:27 -0800174 const ::std::vector<float>::iterator max_element =
175 ::std::max_element(corner_metric.begin(), corner_metric.end());
176 const size_t highest_index =
177 ::std::distance(corner_metric.begin(), max_element);
178 const float max_value = *max_element;
Austin Schuh32ffac22019-03-09 22:42:02 -0800179 if (edges.size() == 0) {
Austin Schuh6a484962019-03-09 21:51:27 -0800180 highest_peak_value = max_value;
181 }
Austin Schuh32ffac22019-03-09 22:42:02 -0800182 if (max_value < highest_peak_value * peak_acceptance_ratio &&
183 edges.size() == 4) {
Austin Schuh6a484962019-03-09 21:51:27 -0800184 if (verbose)
185 printf("Rejecting index: %zu, %f (%f %%)\n", highest_index, max_value,
186 max_value / highest_peak_value);
187 break;
188 }
189 const float valley_value = max_value * valley_ratio;
190
191 if (verbose)
192 printf("Highest index: %zu, %f (%f %%)\n", highest_index, max_value,
193 max_value / highest_peak_value);
194
Austin Schuh32ffac22019-03-09 22:42:02 -0800195 bool foothill = false;
Austin Schuh6a484962019-03-09 21:51:27 -0800196 {
197 float min_value = max_value;
198 size_t fwd_index = (highest_index + 1) % corner_metric.size();
199 while (true) {
200 const float current_value = corner_metric[fwd_index];
Austin Schuh32ffac22019-03-09 22:42:02 -0800201
202 if (current_value == -1.0) {
203 if (min_value >= valley_value) {
204 if (verbose) printf("Foothill\n");
205 foothill = true;
206 }
207 break;
208 }
209
Austin Schuh6a484962019-03-09 21:51:27 -0800210 min_value = ::std::min(current_value, min_value);
211
Austin Schuh32ffac22019-03-09 22:42:02 -0800212 if (min_value < valley_value && current_value > min_value) {
Austin Schuh6a484962019-03-09 21:51:27 -0800213 break;
214 }
215 // Kill!!!
Austin Schuh32ffac22019-03-09 22:42:02 -0800216 corner_metric[fwd_index] = -1.0;
Austin Schuh6a484962019-03-09 21:51:27 -0800217
218 fwd_index = (fwd_index + 1) % corner_metric.size();
Parker Schuh2a1447c2019-02-17 00:25:29 -0800219 }
220 }
Austin Schuh6a484962019-03-09 21:51:27 -0800221
222 {
223 float min_value = max_value;
224 size_t rev_index =
225 (highest_index - 1 + corner_metric.size()) % corner_metric.size();
226 while (true) {
227 const float current_value = corner_metric[rev_index];
Austin Schuh32ffac22019-03-09 22:42:02 -0800228
229 if (current_value == -1.0) {
230 if (min_value >= valley_value) {
231 if (verbose) printf("Foothill\n");
232 foothill = true;
233 }
234 break;
235 }
236
Austin Schuh6a484962019-03-09 21:51:27 -0800237 min_value = ::std::min(current_value, min_value);
238
Austin Schuh32ffac22019-03-09 22:42:02 -0800239 if (min_value < valley_value && current_value > min_value) {
Austin Schuh6a484962019-03-09 21:51:27 -0800240 break;
241 }
242 // Kill!!!
Austin Schuh32ffac22019-03-09 22:42:02 -0800243 corner_metric[rev_index] = -1.0;
Austin Schuh6a484962019-03-09 21:51:27 -0800244
245 rev_index =
246 (rev_index - 1 + corner_metric.size()) % corner_metric.size();
247 }
Parker Schuh2a1447c2019-02-17 00:25:29 -0800248 }
Austin Schuh6a484962019-03-09 21:51:27 -0800249
Austin Schuh32ffac22019-03-09 22:42:02 -0800250 *max_element = -1.0;
251 if (!foothill) {
252 edges.push_back(highest_index);
253 }
Parker Schuh2a1447c2019-02-17 00:25:29 -0800254 }
255
Austin Schuh6a484962019-03-09 21:51:27 -0800256 ::std::sort(edges.begin(), edges.end());
Parker Schuh2a1447c2019-02-17 00:25:29 -0800257
258 if (verbose) printf("Edge Count (%zu).\n", edges.size());
259
Parker Schuh2a1447c2019-02-17 00:25:29 -0800260 // Run best-fits over each line segment.
Austin Schuh6e56faf2019-03-10 14:04:57 -0700261 Polygon polygon;
Austin Schuh6a484962019-03-09 21:51:27 -0800262 if (edges.size() >= 3) {
Austin Schuhe5015972019-03-09 17:47:34 -0800263 for (size_t i = 0; i < edges.size(); ++i) {
264 // Include the corners in both line fits.
265 const size_t segment_start_index = edges[i];
266 const size_t segment_end_index =
267 (edges[(i + 1) % edges.size()] + 1) % contour.size();
Parker Schuh2a1447c2019-02-17 00:25:29 -0800268 float mx = 0.0;
269 float my = 0.0;
270 int n = 0;
Austin Schuhe5015972019-03-09 17:47:34 -0800271 for (size_t j = segment_start_index; j != segment_end_index;
272 (j = (j + 1) % contour.size())) {
273 mx += contour[j].x();
274 my += contour[j].y();
Parker Schuh2a1447c2019-02-17 00:25:29 -0800275 ++n;
276 // (x - [x] / N) ** 2 = [x * x] - 2 * [x] * [x] / N + [x] * [x] / N / N;
277 }
278 mx /= n;
279 my /= n;
280
281 float xx = 0.0;
282 float xy = 0.0;
283 float yy = 0.0;
Austin Schuhe5015972019-03-09 17:47:34 -0800284 for (size_t j = segment_start_index; j != segment_end_index;
285 (j = (j + 1) % contour.size())) {
286 const float x = contour[j].x() - mx;
287 const float y = contour[j].y() - my;
Parker Schuh2a1447c2019-02-17 00:25:29 -0800288 xx += x * x;
289 xy += x * y;
290 yy += y * y;
291 }
292
293 // TODO: Extract common to hierarchical merge.
Austin Schuh335eef12019-03-02 17:04:17 -0800294 const float neg_b_over_2 = (xx + yy) / 2.0;
295 const float c = (xx * yy - xy * xy);
Parker Schuh2a1447c2019-02-17 00:25:29 -0800296
Austin Schuh335eef12019-03-02 17:04:17 -0800297 const float sqr = sqrt(neg_b_over_2 * neg_b_over_2 - c);
Parker Schuh2a1447c2019-02-17 00:25:29 -0800298
299 {
Austin Schuh335eef12019-03-02 17:04:17 -0800300 const float lam = neg_b_over_2 + sqr;
Parker Schuh2a1447c2019-02-17 00:25:29 -0800301 float x = xy;
302 float y = lam - xx;
303
Austin Schuh335eef12019-03-02 17:04:17 -0800304 const float norm = hypot(x, y);
Parker Schuh2a1447c2019-02-17 00:25:29 -0800305 x /= norm;
306 y /= norm;
307
Austin Schuh6e56faf2019-03-10 14:04:57 -0700308 polygon.segments.push_back(
Parker Schuh2a1447c2019-02-17 00:25:29 -0800309 Segment<2>(Vector<2>(mx, my), Vector<2>(mx + x, my + y)));
310 }
311
312 /* Characteristic polynomial
313 1 lam^2 - (xx + yy) lam + (xx * yy - xy * xy) = 0
314
315 [a b]
316 [c d]
317
318 // covariance matrix.
319 [xx xy] [nx]
320 [xy yy] [ny]
321 */
322 }
323 }
Austin Schuh6e56faf2019-03-10 14:04:57 -0700324 if (verbose) printf("Poly Count (%zu).\n", polygon.segments.size());
325 polygon.contour = ::std::move(contour);
326 return polygon;
Parker Schuh2a1447c2019-02-17 00:25:29 -0800327}
328
329// Convert segments into target components (left or right)
Austin Schuh6e56faf2019-03-10 14:04:57 -0700330::std::vector<TargetComponent> TargetFinder::FillTargetComponentList(
331 const ::std::vector<Polygon> &seg_list, bool verbose) {
332 ::std::vector<TargetComponent> list;
Parker Schuh2a1447c2019-02-17 00:25:29 -0800333 TargetComponent new_target;
Austin Schuh7d2ef032019-03-10 14:59:34 -0700334 for (const Polygon &polygon : seg_list) {
Parker Schuh2a1447c2019-02-17 00:25:29 -0800335 // Reject missized pollygons for now. Maybe rectify them here in the future;
Austin Schuh7d2ef032019-03-10 14:59:34 -0700336 if (polygon.segments.size() != 4) {
Austin Schuh9f859ca2019-03-06 20:46:01 -0800337 continue;
338 }
Austin Schuh32ffac22019-03-09 22:42:02 -0800339 ::std::vector<Vector<2>> corners;
Parker Schuh2a1447c2019-02-17 00:25:29 -0800340 for (size_t i = 0; i < 4; ++i) {
Austin Schuh7d2ef032019-03-10 14:59:34 -0700341 Vector<2> corner =
342 polygon.segments[i].Intersect(polygon.segments[(i + 1) % 4]);
Austin Schuh9f859ca2019-03-06 20:46:01 -0800343 if (::std::isnan(corner.x()) || ::std::isnan(corner.y())) {
344 break;
345 }
346 corners.push_back(corner);
347 }
348 if (corners.size() != 4) {
349 continue;
Parker Schuh2a1447c2019-02-17 00:25:29 -0800350 }
351
352 // Select the closest two points. Short side of the rectangle.
353 double min_dist = -1;
Austin Schuh32ffac22019-03-09 22:42:02 -0800354 ::std::pair<size_t, size_t> closest;
Parker Schuh2a1447c2019-02-17 00:25:29 -0800355 for (size_t i = 0; i < 4; ++i) {
356 size_t next = (i + 1) % 4;
357 double nd = corners[i].SquaredDistanceTo(corners[next]);
358 if (min_dist == -1 || nd < min_dist) {
359 min_dist = nd;
360 closest.first = i;
361 closest.second = next;
362 }
363 }
364
365 // Verify our top is above the bottom.
366 size_t bot_index = closest.first;
367 size_t top_index = (closest.first + 2) % 4;
368 if (corners[top_index].y() < corners[bot_index].y()) {
369 closest.first = top_index;
370 closest.second = (top_index + 1) % 4;
371 }
372
373 // Find the major axis.
374 size_t far_first = (closest.first + 2) % 4;
375 size_t far_second = (closest.second + 2) % 4;
376 Segment<2> major_axis(
377 (corners[closest.first] + corners[closest.second]) * 0.5,
378 (corners[far_first] + corners[far_second]) * 0.5);
379 if (major_axis.AsVector().AngleToZero() > M_PI / 180.0 * 120.0 ||
380 major_axis.AsVector().AngleToZero() < M_PI / 180.0 * 60.0) {
381 // Target is angled way too much, drop it.
382 continue;
383 }
384
385 // organize the top points.
386 Vector<2> topA = corners[closest.first] - major_axis.B();
387 new_target.major_axis = major_axis;
388 if (major_axis.AsVector().AngleToZero() > M_PI / 2.0) {
389 // We have a left target since we are leaning positive.
390 new_target.is_right = false;
391 if (topA.AngleTo(major_axis.AsVector()) > 0.0) {
392 // And our A point is left of the major axis.
393 new_target.inside = corners[closest.second];
394 new_target.top = corners[closest.first];
395 } else {
396 // our A point is to the right of the major axis.
397 new_target.inside = corners[closest.first];
398 new_target.top = corners[closest.second];
399 }
400 } else {
401 // We have a right target since we are leaning negative.
402 new_target.is_right = true;
403 if (topA.AngleTo(major_axis.AsVector()) > 0.0) {
404 // And our A point is left of the major axis.
405 new_target.inside = corners[closest.first];
406 new_target.top = corners[closest.second];
407 } else {
408 // our A point is to the right of the major axis.
409 new_target.inside = corners[closest.second];
410 new_target.top = corners[closest.first];
411 }
412 }
413
414 // organize the top points.
415 Vector<2> botA = corners[far_first] - major_axis.A();
416 if (major_axis.AsVector().AngleToZero() > M_PI / 2.0) {
417 // We have a right target since we are leaning positive.
418 if (botA.AngleTo(major_axis.AsVector()) < M_PI) {
419 // And our A point is left of the major axis.
420 new_target.outside = corners[far_second];
421 new_target.bottom = corners[far_first];
422 } else {
423 // our A point is to the right of the major axis.
424 new_target.outside = corners[far_first];
425 new_target.bottom = corners[far_second];
426 }
427 } else {
428 // We have a left target since we are leaning negative.
429 if (botA.AngleTo(major_axis.AsVector()) < M_PI) {
430 // And our A point is left of the major axis.
431 new_target.outside = corners[far_first];
432 new_target.bottom = corners[far_second];
433 } else {
434 // our A point is to the right of the major axis.
435 new_target.outside = corners[far_second];
436 new_target.bottom = corners[far_first];
437 }
438 }
439
Austin Schuh7d2ef032019-03-10 14:59:34 -0700440 // Take the vector which points from the bottom to the top of the target
441 // along the outside edge.
442 const ::Eigen::Vector2f outer_edge_vector =
443 AosVectorToEigenVector(new_target.top - new_target.outside);
444 // Now, dot each point in the perimeter along this vector. The one with the
445 // smallest component will be the one closest to the bottom along this
446 // direction vector.
447 ::Eigen::Vector2f smallest_point = polygon.contour[0];
448 float smallest_value = outer_edge_vector.transpose() * smallest_point;
449 for (const ::Eigen::Vector2f point : polygon.contour) {
450 const float current_value = outer_edge_vector.transpose() * point;
451 if (current_value < smallest_value) {
452 smallest_value = current_value;
453 smallest_point = point;
454 }
455 }
456
457 // This piece of the target should be ready now.
458 new_target.bottom_point = smallest_point;
459 if (verbose) {
460 printf("Lowest point in the blob is (%f, %f)\n", smallest_point.x(),
461 smallest_point.y());
462 }
463
Parker Schuh2a1447c2019-02-17 00:25:29 -0800464 // This piece of the target should be ready now.
465 list.emplace_back(new_target);
Austin Schuh7d2ef032019-03-10 14:59:34 -0700466
Austin Schuh32ffac22019-03-09 22:42:02 -0800467 if (verbose) printf("Happy with a target\n");
Parker Schuh2a1447c2019-02-17 00:25:29 -0800468 }
469
470 return list;
471}
472
473// Match components into targets.
474std::vector<Target> TargetFinder::FindTargetsFromComponents(
475 const std::vector<TargetComponent> component_list, bool verbose) {
476 std::vector<Target> target_list;
477 using namespace aos::vision;
478 if (component_list.size() < 2) {
479 // We don't enough parts for a target.
480 return target_list;
481 }
482
483 for (size_t i = 0; i < component_list.size(); i++) {
484 const TargetComponent &a = component_list[i];
485 for (size_t j = 0; j < i; j++) {
486 bool target_valid = false;
487 Target new_target;
488 const TargetComponent &b = component_list[j];
489
Parker Schuh2a1447c2019-02-17 00:25:29 -0800490 if (a.is_right && !b.is_right) {
491 if (a.top.x() > b.top.x()) {
492 new_target.right = a;
493 new_target.left = b;
494 target_valid = true;
495 }
496 } else if (!a.is_right && b.is_right) {
497 if (b.top.x() > a.top.x()) {
498 new_target.right = b;
499 new_target.left = a;
500 target_valid = true;
501 }
Alex Perrybac3d3f2019-03-10 14:26:51 -0700502 } else if (verbose) {
503 printf("Found same side components: %s.\n",
504 a.is_right ? "right" : "left");
Parker Schuh2a1447c2019-02-17 00:25:29 -0800505 }
506 if (target_valid) {
507 target_list.emplace_back(new_target);
508 }
509 }
510 }
511 if (verbose) printf("Possible Target: %zu.\n", target_list.size());
512 return target_list;
513}
514
Austin Schuhe6cfbe32019-03-10 18:05:57 -0700515bool TargetFinder::MaybePickAndUpdateResult(IntermediateResult *result,
516 bool verbose) {
517 // Based on a linear regression between error and distance to target.
518 // Closer targets can have a higher error because they are bigger.
519 const double acceptable_error =
Alex Perryebc6dd52019-03-16 07:54:48 -0700520 std::max(2 * (75 - 12 * result->extrinsics.z), 75.0);
Austin Schuhe6cfbe32019-03-10 18:05:57 -0700521 if (result->solver_error < acceptable_error) {
522 if (verbose) {
523 printf("Using an 8 point solve: %f < %f \n", result->solver_error,
524 acceptable_error);
525 }
526 return true;
527 } else if (result->backup_solver_error < acceptable_error) {
528 if (verbose) {
529 printf("Using a 4 point solve: %f < %f \n", result->backup_solver_error,
530 acceptable_error);
531 }
532 IntermediateResult backup;
533 result->extrinsics = result->backup_extrinsics;
534 result->solver_error = result->backup_solver_error;
535 return true;
536 } else if (verbose) {
537 printf("Rejecting a target with errors: (%f, %f) > %f \n",
538 result->solver_error, result->backup_solver_error, acceptable_error);
539 }
540 return false;
541}
542
Parker Schuh2a1447c2019-02-17 00:25:29 -0800543std::vector<IntermediateResult> TargetFinder::FilterResults(
Alex Perrybac3d3f2019-03-10 14:26:51 -0700544 const std::vector<IntermediateResult> &results, uint64_t print_rate,
545 bool verbose) {
Parker Schuh2a1447c2019-02-17 00:25:29 -0800546 std::vector<IntermediateResult> filtered;
547 for (const IntermediateResult &res : results) {
Austin Schuhe6cfbe32019-03-10 18:05:57 -0700548 IntermediateResult updatable_result = res;
549 if (MaybePickAndUpdateResult(&updatable_result, verbose)) {
550 filtered.emplace_back(updatable_result);
Parker Schuh2a1447c2019-02-17 00:25:29 -0800551 }
552 }
Ben Fredricksona8c3d552019-03-03 14:14:53 -0800553 frame_count_++;
554 if (!filtered.empty()) {
555 valid_result_count_++;
556 }
557 if (print_rate > 0 && frame_count_ > print_rate) {
Brian Silverman63236772019-03-23 22:02:44 -0700558 LOG(INFO) << "Found (" << valid_result_count_ << " / " << frame_count_
559 << ")(" << ((double)valid_result_count_ / (double)frame_count_)
560 << " targets.";
Ben Fredricksona8c3d552019-03-03 14:14:53 -0800561 frame_count_ = 0;
562 valid_result_count_ = 0;
563 }
564
Parker Schuh2a1447c2019-02-17 00:25:29 -0800565 return filtered;
566}
567
568} // namespace vision
569} // namespace y2019