blob: a56d82c620e9f4dc848c8c1398c8b2cd793faef4 [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"
4
5using namespace aos::vision;
6
7namespace y2019 {
8namespace vision {
9
Austin Schuh4d6e9bd2019-03-08 19:54:17 -080010TargetFinder::TargetFinder() : target_template_(Target::MakeTemplate()) {}
Parker Schuh2a1447c2019-02-17 00:25:29 -080011
12aos::vision::RangeImage TargetFinder::Threshold(aos::vision::ImagePtr image) {
13 const uint8_t threshold_value = GetThresholdValue();
Brian Silverman37b15b32019-03-10 13:30:18 -070014 return aos::vision::ThresholdImageWithFunction(
15 image, [&](aos::vision::PixelRef px) {
16 if (px.g > threshold_value && px.b > threshold_value &&
17 px.r > threshold_value) {
18 return true;
19 }
20 return false;
21 });
Parker Schuh2a1447c2019-02-17 00:25:29 -080022}
23
24// Filter blobs on size.
25void TargetFinder::PreFilter(BlobList *imgs) {
26 imgs->erase(
27 std::remove_if(imgs->begin(), imgs->end(),
28 [](RangeImage &img) {
29 // We can drop images with a small number of
30 // pixels, but images
31 // must be over 20px or the math will have issues.
32 return (img.npixels() < 100 || img.height() < 25);
33 }),
34 imgs->end());
35}
36
Austin Schuh7d2ef032019-03-10 14:59:34 -070037ContourNode *TargetFinder::GetContour(const RangeImage &blob) {
Ben Fredricksonf7b68522019-03-02 21:19:42 -080038 alloc_.reset();
39 return RangeImgToContour(blob, &alloc_);
40}
41
Ben Fredricksonec575822019-03-02 22:03:20 -080042// TODO(ben): These values will be moved into the constants.h file.
Ben Fredricksonf7b68522019-03-02 21:19:42 -080043namespace {
44
Austin Schuh7d2ef032019-03-10 14:59:34 -070045::Eigen::Vector2f AosVectorToEigenVector(Vector<2> in) {
46 return ::Eigen::Vector2f(in.x(), in.y());
47}
48
Ben Fredricksonec575822019-03-02 22:03:20 -080049constexpr double f_x = 481.4957;
50constexpr double c_x = 341.215;
51constexpr double f_y = 484.314;
52constexpr double c_y = 251.29;
Ben Fredricksonf7b68522019-03-02 21:19:42 -080053
Ben Fredricksonec575822019-03-02 22:03:20 -080054constexpr double f_x_prime = 363.1424;
55constexpr double c_x_prime = 337.9895;
56constexpr double f_y_prime = 366.4837;
57constexpr double c_y_prime = 240.0702;
Ben Fredricksonf7b68522019-03-02 21:19:42 -080058
Ben Fredricksonec575822019-03-02 22:03:20 -080059constexpr double k_1 = -0.2739;
60constexpr double k_2 = 0.01583;
61constexpr double k_3 = 0.04201;
Ben Fredricksonf7b68522019-03-02 21:19:42 -080062
63constexpr int iterations = 7;
64
65}
66
Austin Schuhe5015972019-03-09 17:47:34 -080067::Eigen::Vector2f UnWarpPoint(const Point point) {
Ben Fredricksonec575822019-03-02 22:03:20 -080068 const double x0 = ((double)point.x - c_x) / f_x;
69 const double y0 = ((double)point.y - c_y) / f_y;
Ben Fredricksonf7b68522019-03-02 21:19:42 -080070 double x = x0;
71 double y = y0;
72 for (int i = 0; i < iterations; i++) {
73 const double r_sqr = x * x + y * y;
Alex Perry2ca15742019-03-10 20:38:40 -070074 const double coeff = 1.0 + r_sqr * (k_1 + r_sqr * (k_2 + r_sqr * (k_3)));
Ben Fredricksonf7b68522019-03-02 21:19:42 -080075 x = x0 / coeff;
76 y = y0 / coeff;
77 }
Austin Schuhe5015972019-03-09 17:47:34 -080078 const double nx = x * f_x_prime + c_x_prime;
79 const double ny = y * f_y_prime + c_y_prime;
80 return ::Eigen::Vector2f(nx, ny);
Ben Fredricksonf7b68522019-03-02 21:19:42 -080081}
82
Austin Schuhe5015972019-03-09 17:47:34 -080083::std::vector<::Eigen::Vector2f> TargetFinder::UnWarpContour(
84 ContourNode *start) const {
85 ::std::vector<::Eigen::Vector2f> result;
Ben Fredricksonf7b68522019-03-02 21:19:42 -080086 ContourNode *node = start;
87 while (node->next != start) {
Austin Schuhe5015972019-03-09 17:47:34 -080088 result.push_back(UnWarpPoint(node->pt));
Ben Fredricksonf7b68522019-03-02 21:19:42 -080089 node = node->next;
90 }
Austin Schuhe5015972019-03-09 17:47:34 -080091 result.push_back(UnWarpPoint(node->pt));
92 return result;
Ben Fredricksonf7b68522019-03-02 21:19:42 -080093}
94
Parker Schuh2a1447c2019-02-17 00:25:29 -080095// TODO: Try hierarchical merge for this.
96// Convert blobs into polygons.
Austin Schuh6e56faf2019-03-10 14:04:57 -070097Polygon TargetFinder::FindPolygon(::std::vector<::Eigen::Vector2f> &&contour,
98 bool verbose) {
Parker Schuh2a1447c2019-02-17 00:25:29 -080099 if (verbose) printf("Process Polygon.\n");
Parker Schuh2a1447c2019-02-17 00:25:29 -0800100
Austin Schuhe5015972019-03-09 17:47:34 -0800101 ::std::vector<::Eigen::Vector2f> slopes;
Parker Schuh2a1447c2019-02-17 00:25:29 -0800102
103 // Collect all slopes from the contour.
Austin Schuhe5015972019-03-09 17:47:34 -0800104 ::Eigen::Vector2f previous_point = contour[0];
105 for (size_t i = 0; i < contour.size(); ++i) {
106 ::Eigen::Vector2f next_point = contour[(i + 1) % contour.size()];
Parker Schuh2a1447c2019-02-17 00:25:29 -0800107
Austin Schuhe5015972019-03-09 17:47:34 -0800108 slopes.push_back(next_point - previous_point);
Parker Schuh2a1447c2019-02-17 00:25:29 -0800109
Austin Schuhe5015972019-03-09 17:47:34 -0800110 previous_point = next_point;
Parker Schuh2a1447c2019-02-17 00:25:29 -0800111 }
112
Austin Schuhe5015972019-03-09 17:47:34 -0800113 const int num_points = slopes.size();
114 auto get_pt = [&slopes, num_points](int i) {
115 return slopes[(i + num_points * 2) % num_points];
Austin Schuh335eef12019-03-02 17:04:17 -0800116 };
Parker Schuh2a1447c2019-02-17 00:25:29 -0800117
Austin Schuh6a484962019-03-09 21:51:27 -0800118 // Bigger objects should be more filtered. Filter roughly proportional to the
119 // perimeter of the object.
120 const int range = slopes.size() / 50;
121 if (verbose) printf("Corner range: %d.\n", range);
122
Austin Schuhe5015972019-03-09 17:47:34 -0800123 ::std::vector<::Eigen::Vector2f> filtered_slopes = slopes;
Austin Schuh335eef12019-03-02 17:04:17 -0800124 // Three box filter makith a guassian?
125 // Run gaussian filter over the slopes 3 times. That'll get us pretty close
126 // to running a gausian over it.
127 for (int k = 0; k < 3; ++k) {
Austin Schuh6a484962019-03-09 21:51:27 -0800128 const int window_size = ::std::max(2, range);
Austin Schuhe5015972019-03-09 17:47:34 -0800129 for (size_t i = 0; i < slopes.size(); ++i) {
130 ::Eigen::Vector2f a = ::Eigen::Vector2f::Zero();
Parker Schuh2a1447c2019-02-17 00:25:29 -0800131 for (int j = -window_size; j <= window_size; ++j) {
Austin Schuhe5015972019-03-09 17:47:34 -0800132 ::Eigen::Vector2f p = get_pt(j + i);
133 a += p;
Parker Schuh2a1447c2019-02-17 00:25:29 -0800134 }
Austin Schuhe5015972019-03-09 17:47:34 -0800135 a /= (window_size * 2 + 1);
Parker Schuh2a1447c2019-02-17 00:25:29 -0800136
Austin Schuhe5015972019-03-09 17:47:34 -0800137 filtered_slopes[i] = a;
Parker Schuh2a1447c2019-02-17 00:25:29 -0800138 }
Austin Schuhe5015972019-03-09 17:47:34 -0800139 slopes = filtered_slopes;
Austin Schuh335eef12019-03-02 17:04:17 -0800140 }
Austin Schuh6a484962019-03-09 21:51:27 -0800141 if (verbose) printf("Point count: %zu.\n", slopes.size());
Parker Schuh2a1447c2019-02-17 00:25:29 -0800142
Austin Schuh6a484962019-03-09 21:51:27 -0800143 ::std::vector<float> corner_metric(slopes.size(), 0.0);
Parker Schuh2a1447c2019-02-17 00:25:29 -0800144
Austin Schuh6a484962019-03-09 21:51:27 -0800145 for (size_t i = 0; i < slopes.size(); ++i) {
146 const ::Eigen::Vector2f a = get_pt(i - ::std::max(3, range));
147 const ::Eigen::Vector2f b = get_pt(i + ::std::max(3, range));
148 corner_metric[i] = (a - b).squaredNorm();
149 }
150
151 // We want to find the Nth highest peaks.
152 // Clever algorithm: Find the highest point. Then, walk forwards and
153 // backwards to find the next valley each direction which is over x% lower
154 // than the peak.
155 // We want to ignore those points in the future. Set them to 0.
156 // Repeat until we've found the Nth highest peak.
Parker Schuh2a1447c2019-02-17 00:25:29 -0800157
158 // Find all centers of corners.
Austin Schuhe5015972019-03-09 17:47:34 -0800159 // Because they round, multiple slopes may be a corner.
160 ::std::vector<size_t> edges;
Parker Schuh2a1447c2019-02-17 00:25:29 -0800161
Austin Schuh6a484962019-03-09 21:51:27 -0800162 constexpr float peak_acceptance_ratio = 0.16;
163 constexpr float valley_ratio = 0.75;
164
165 float highest_peak_value = 0.0;
166
167 // Nth higest points.
Austin Schuh32ffac22019-03-09 22:42:02 -0800168 while (edges.size() < 5) {
Austin Schuh6a484962019-03-09 21:51:27 -0800169 const ::std::vector<float>::iterator max_element =
170 ::std::max_element(corner_metric.begin(), corner_metric.end());
171 const size_t highest_index =
172 ::std::distance(corner_metric.begin(), max_element);
173 const float max_value = *max_element;
Austin Schuh32ffac22019-03-09 22:42:02 -0800174 if (edges.size() == 0) {
Austin Schuh6a484962019-03-09 21:51:27 -0800175 highest_peak_value = max_value;
176 }
Austin Schuh32ffac22019-03-09 22:42:02 -0800177 if (max_value < highest_peak_value * peak_acceptance_ratio &&
178 edges.size() == 4) {
Austin Schuh6a484962019-03-09 21:51:27 -0800179 if (verbose)
180 printf("Rejecting index: %zu, %f (%f %%)\n", highest_index, max_value,
181 max_value / highest_peak_value);
182 break;
183 }
184 const float valley_value = max_value * valley_ratio;
185
186 if (verbose)
187 printf("Highest index: %zu, %f (%f %%)\n", highest_index, max_value,
188 max_value / highest_peak_value);
189
Austin Schuh32ffac22019-03-09 22:42:02 -0800190 bool foothill = false;
Austin Schuh6a484962019-03-09 21:51:27 -0800191 {
192 float min_value = max_value;
193 size_t fwd_index = (highest_index + 1) % corner_metric.size();
194 while (true) {
195 const float current_value = corner_metric[fwd_index];
Austin Schuh32ffac22019-03-09 22:42:02 -0800196
197 if (current_value == -1.0) {
198 if (min_value >= valley_value) {
199 if (verbose) printf("Foothill\n");
200 foothill = true;
201 }
202 break;
203 }
204
Austin Schuh6a484962019-03-09 21:51:27 -0800205 min_value = ::std::min(current_value, min_value);
206
Austin Schuh32ffac22019-03-09 22:42:02 -0800207 if (min_value < valley_value && current_value > min_value) {
Austin Schuh6a484962019-03-09 21:51:27 -0800208 break;
209 }
210 // Kill!!!
Austin Schuh32ffac22019-03-09 22:42:02 -0800211 corner_metric[fwd_index] = -1.0;
Austin Schuh6a484962019-03-09 21:51:27 -0800212
213 fwd_index = (fwd_index + 1) % corner_metric.size();
Parker Schuh2a1447c2019-02-17 00:25:29 -0800214 }
215 }
Austin Schuh6a484962019-03-09 21:51:27 -0800216
217 {
218 float min_value = max_value;
219 size_t rev_index =
220 (highest_index - 1 + corner_metric.size()) % corner_metric.size();
221 while (true) {
222 const float current_value = corner_metric[rev_index];
Austin Schuh32ffac22019-03-09 22:42:02 -0800223
224 if (current_value == -1.0) {
225 if (min_value >= valley_value) {
226 if (verbose) printf("Foothill\n");
227 foothill = true;
228 }
229 break;
230 }
231
Austin Schuh6a484962019-03-09 21:51:27 -0800232 min_value = ::std::min(current_value, min_value);
233
Austin Schuh32ffac22019-03-09 22:42:02 -0800234 if (min_value < valley_value && current_value > min_value) {
Austin Schuh6a484962019-03-09 21:51:27 -0800235 break;
236 }
237 // Kill!!!
Austin Schuh32ffac22019-03-09 22:42:02 -0800238 corner_metric[rev_index] = -1.0;
Austin Schuh6a484962019-03-09 21:51:27 -0800239
240 rev_index =
241 (rev_index - 1 + corner_metric.size()) % corner_metric.size();
242 }
Parker Schuh2a1447c2019-02-17 00:25:29 -0800243 }
Austin Schuh6a484962019-03-09 21:51:27 -0800244
Austin Schuh32ffac22019-03-09 22:42:02 -0800245 *max_element = -1.0;
246 if (!foothill) {
247 edges.push_back(highest_index);
248 }
Parker Schuh2a1447c2019-02-17 00:25:29 -0800249 }
250
Austin Schuh6a484962019-03-09 21:51:27 -0800251 ::std::sort(edges.begin(), edges.end());
Parker Schuh2a1447c2019-02-17 00:25:29 -0800252
253 if (verbose) printf("Edge Count (%zu).\n", edges.size());
254
Parker Schuh2a1447c2019-02-17 00:25:29 -0800255 // Run best-fits over each line segment.
Austin Schuh6e56faf2019-03-10 14:04:57 -0700256 Polygon polygon;
Austin Schuh6a484962019-03-09 21:51:27 -0800257 if (edges.size() >= 3) {
Austin Schuhe5015972019-03-09 17:47:34 -0800258 for (size_t i = 0; i < edges.size(); ++i) {
259 // Include the corners in both line fits.
260 const size_t segment_start_index = edges[i];
261 const size_t segment_end_index =
262 (edges[(i + 1) % edges.size()] + 1) % contour.size();
Parker Schuh2a1447c2019-02-17 00:25:29 -0800263 float mx = 0.0;
264 float my = 0.0;
265 int n = 0;
Austin Schuhe5015972019-03-09 17:47:34 -0800266 for (size_t j = segment_start_index; j != segment_end_index;
267 (j = (j + 1) % contour.size())) {
268 mx += contour[j].x();
269 my += contour[j].y();
Parker Schuh2a1447c2019-02-17 00:25:29 -0800270 ++n;
271 // (x - [x] / N) ** 2 = [x * x] - 2 * [x] * [x] / N + [x] * [x] / N / N;
272 }
273 mx /= n;
274 my /= n;
275
276 float xx = 0.0;
277 float xy = 0.0;
278 float yy = 0.0;
Austin Schuhe5015972019-03-09 17:47:34 -0800279 for (size_t j = segment_start_index; j != segment_end_index;
280 (j = (j + 1) % contour.size())) {
281 const float x = contour[j].x() - mx;
282 const float y = contour[j].y() - my;
Parker Schuh2a1447c2019-02-17 00:25:29 -0800283 xx += x * x;
284 xy += x * y;
285 yy += y * y;
286 }
287
288 // TODO: Extract common to hierarchical merge.
Austin Schuh335eef12019-03-02 17:04:17 -0800289 const float neg_b_over_2 = (xx + yy) / 2.0;
290 const float c = (xx * yy - xy * xy);
Parker Schuh2a1447c2019-02-17 00:25:29 -0800291
Austin Schuh335eef12019-03-02 17:04:17 -0800292 const float sqr = sqrt(neg_b_over_2 * neg_b_over_2 - c);
Parker Schuh2a1447c2019-02-17 00:25:29 -0800293
294 {
Austin Schuh335eef12019-03-02 17:04:17 -0800295 const float lam = neg_b_over_2 + sqr;
Parker Schuh2a1447c2019-02-17 00:25:29 -0800296 float x = xy;
297 float y = lam - xx;
298
Austin Schuh335eef12019-03-02 17:04:17 -0800299 const float norm = hypot(x, y);
Parker Schuh2a1447c2019-02-17 00:25:29 -0800300 x /= norm;
301 y /= norm;
302
Austin Schuh6e56faf2019-03-10 14:04:57 -0700303 polygon.segments.push_back(
Parker Schuh2a1447c2019-02-17 00:25:29 -0800304 Segment<2>(Vector<2>(mx, my), Vector<2>(mx + x, my + y)));
305 }
306
307 /* Characteristic polynomial
308 1 lam^2 - (xx + yy) lam + (xx * yy - xy * xy) = 0
309
310 [a b]
311 [c d]
312
313 // covariance matrix.
314 [xx xy] [nx]
315 [xy yy] [ny]
316 */
317 }
318 }
Austin Schuh6e56faf2019-03-10 14:04:57 -0700319 if (verbose) printf("Poly Count (%zu).\n", polygon.segments.size());
320 polygon.contour = ::std::move(contour);
321 return polygon;
Parker Schuh2a1447c2019-02-17 00:25:29 -0800322}
323
324// Convert segments into target components (left or right)
Austin Schuh6e56faf2019-03-10 14:04:57 -0700325::std::vector<TargetComponent> TargetFinder::FillTargetComponentList(
326 const ::std::vector<Polygon> &seg_list, bool verbose) {
327 ::std::vector<TargetComponent> list;
Parker Schuh2a1447c2019-02-17 00:25:29 -0800328 TargetComponent new_target;
Austin Schuh7d2ef032019-03-10 14:59:34 -0700329 for (const Polygon &polygon : seg_list) {
Parker Schuh2a1447c2019-02-17 00:25:29 -0800330 // Reject missized pollygons for now. Maybe rectify them here in the future;
Austin Schuh7d2ef032019-03-10 14:59:34 -0700331 if (polygon.segments.size() != 4) {
Austin Schuh9f859ca2019-03-06 20:46:01 -0800332 continue;
333 }
Austin Schuh32ffac22019-03-09 22:42:02 -0800334 ::std::vector<Vector<2>> corners;
Parker Schuh2a1447c2019-02-17 00:25:29 -0800335 for (size_t i = 0; i < 4; ++i) {
Austin Schuh7d2ef032019-03-10 14:59:34 -0700336 Vector<2> corner =
337 polygon.segments[i].Intersect(polygon.segments[(i + 1) % 4]);
Austin Schuh9f859ca2019-03-06 20:46:01 -0800338 if (::std::isnan(corner.x()) || ::std::isnan(corner.y())) {
339 break;
340 }
341 corners.push_back(corner);
342 }
343 if (corners.size() != 4) {
344 continue;
Parker Schuh2a1447c2019-02-17 00:25:29 -0800345 }
346
347 // Select the closest two points. Short side of the rectangle.
348 double min_dist = -1;
Austin Schuh32ffac22019-03-09 22:42:02 -0800349 ::std::pair<size_t, size_t> closest;
Parker Schuh2a1447c2019-02-17 00:25:29 -0800350 for (size_t i = 0; i < 4; ++i) {
351 size_t next = (i + 1) % 4;
352 double nd = corners[i].SquaredDistanceTo(corners[next]);
353 if (min_dist == -1 || nd < min_dist) {
354 min_dist = nd;
355 closest.first = i;
356 closest.second = next;
357 }
358 }
359
360 // Verify our top is above the bottom.
361 size_t bot_index = closest.first;
362 size_t top_index = (closest.first + 2) % 4;
363 if (corners[top_index].y() < corners[bot_index].y()) {
364 closest.first = top_index;
365 closest.second = (top_index + 1) % 4;
366 }
367
368 // Find the major axis.
369 size_t far_first = (closest.first + 2) % 4;
370 size_t far_second = (closest.second + 2) % 4;
371 Segment<2> major_axis(
372 (corners[closest.first] + corners[closest.second]) * 0.5,
373 (corners[far_first] + corners[far_second]) * 0.5);
374 if (major_axis.AsVector().AngleToZero() > M_PI / 180.0 * 120.0 ||
375 major_axis.AsVector().AngleToZero() < M_PI / 180.0 * 60.0) {
376 // Target is angled way too much, drop it.
377 continue;
378 }
379
380 // organize the top points.
381 Vector<2> topA = corners[closest.first] - major_axis.B();
382 new_target.major_axis = major_axis;
383 if (major_axis.AsVector().AngleToZero() > M_PI / 2.0) {
384 // We have a left target since we are leaning positive.
385 new_target.is_right = false;
386 if (topA.AngleTo(major_axis.AsVector()) > 0.0) {
387 // And our A point is left of the major axis.
388 new_target.inside = corners[closest.second];
389 new_target.top = corners[closest.first];
390 } else {
391 // our A point is to the right of the major axis.
392 new_target.inside = corners[closest.first];
393 new_target.top = corners[closest.second];
394 }
395 } else {
396 // We have a right target since we are leaning negative.
397 new_target.is_right = true;
398 if (topA.AngleTo(major_axis.AsVector()) > 0.0) {
399 // And our A point is left of the major axis.
400 new_target.inside = corners[closest.first];
401 new_target.top = corners[closest.second];
402 } else {
403 // our A point is to the right of the major axis.
404 new_target.inside = corners[closest.second];
405 new_target.top = corners[closest.first];
406 }
407 }
408
409 // organize the top points.
410 Vector<2> botA = corners[far_first] - major_axis.A();
411 if (major_axis.AsVector().AngleToZero() > M_PI / 2.0) {
412 // We have a right target since we are leaning positive.
413 if (botA.AngleTo(major_axis.AsVector()) < M_PI) {
414 // And our A point is left of the major axis.
415 new_target.outside = corners[far_second];
416 new_target.bottom = corners[far_first];
417 } else {
418 // our A point is to the right of the major axis.
419 new_target.outside = corners[far_first];
420 new_target.bottom = corners[far_second];
421 }
422 } else {
423 // We have a left target since we are leaning negative.
424 if (botA.AngleTo(major_axis.AsVector()) < M_PI) {
425 // And our A point is left of the major axis.
426 new_target.outside = corners[far_first];
427 new_target.bottom = corners[far_second];
428 } else {
429 // our A point is to the right of the major axis.
430 new_target.outside = corners[far_second];
431 new_target.bottom = corners[far_first];
432 }
433 }
434
Austin Schuh7d2ef032019-03-10 14:59:34 -0700435 // Take the vector which points from the bottom to the top of the target
436 // along the outside edge.
437 const ::Eigen::Vector2f outer_edge_vector =
438 AosVectorToEigenVector(new_target.top - new_target.outside);
439 // Now, dot each point in the perimeter along this vector. The one with the
440 // smallest component will be the one closest to the bottom along this
441 // direction vector.
442 ::Eigen::Vector2f smallest_point = polygon.contour[0];
443 float smallest_value = outer_edge_vector.transpose() * smallest_point;
444 for (const ::Eigen::Vector2f point : polygon.contour) {
445 const float current_value = outer_edge_vector.transpose() * point;
446 if (current_value < smallest_value) {
447 smallest_value = current_value;
448 smallest_point = point;
449 }
450 }
451
452 // This piece of the target should be ready now.
453 new_target.bottom_point = smallest_point;
454 if (verbose) {
455 printf("Lowest point in the blob is (%f, %f)\n", smallest_point.x(),
456 smallest_point.y());
457 }
458
Parker Schuh2a1447c2019-02-17 00:25:29 -0800459 // This piece of the target should be ready now.
460 list.emplace_back(new_target);
Austin Schuh7d2ef032019-03-10 14:59:34 -0700461
Austin Schuh32ffac22019-03-09 22:42:02 -0800462 if (verbose) printf("Happy with a target\n");
Parker Schuh2a1447c2019-02-17 00:25:29 -0800463 }
464
465 return list;
466}
467
468// Match components into targets.
469std::vector<Target> TargetFinder::FindTargetsFromComponents(
470 const std::vector<TargetComponent> component_list, bool verbose) {
471 std::vector<Target> target_list;
472 using namespace aos::vision;
473 if (component_list.size() < 2) {
474 // We don't enough parts for a target.
475 return target_list;
476 }
477
478 for (size_t i = 0; i < component_list.size(); i++) {
479 const TargetComponent &a = component_list[i];
480 for (size_t j = 0; j < i; j++) {
481 bool target_valid = false;
482 Target new_target;
483 const TargetComponent &b = component_list[j];
484
Parker Schuh2a1447c2019-02-17 00:25:29 -0800485 if (a.is_right && !b.is_right) {
486 if (a.top.x() > b.top.x()) {
487 new_target.right = a;
488 new_target.left = b;
489 target_valid = true;
490 }
491 } else if (!a.is_right && b.is_right) {
492 if (b.top.x() > a.top.x()) {
493 new_target.right = b;
494 new_target.left = a;
495 target_valid = true;
496 }
Alex Perrybac3d3f2019-03-10 14:26:51 -0700497 } else if (verbose) {
498 printf("Found same side components: %s.\n",
499 a.is_right ? "right" : "left");
Parker Schuh2a1447c2019-02-17 00:25:29 -0800500 }
501 if (target_valid) {
502 target_list.emplace_back(new_target);
503 }
504 }
505 }
506 if (verbose) printf("Possible Target: %zu.\n", target_list.size());
507 return target_list;
508}
509
Austin Schuhe6cfbe32019-03-10 18:05:57 -0700510bool TargetFinder::MaybePickAndUpdateResult(IntermediateResult *result,
511 bool verbose) {
512 // Based on a linear regression between error and distance to target.
513 // Closer targets can have a higher error because they are bigger.
514 const double acceptable_error =
515 std::max(2 * (21 - 12 * result->extrinsics.z), 50.0);
516 if (result->solver_error < acceptable_error) {
517 if (verbose) {
518 printf("Using an 8 point solve: %f < %f \n", result->solver_error,
519 acceptable_error);
520 }
521 return true;
522 } else if (result->backup_solver_error < acceptable_error) {
523 if (verbose) {
524 printf("Using a 4 point solve: %f < %f \n", result->backup_solver_error,
525 acceptable_error);
526 }
527 IntermediateResult backup;
528 result->extrinsics = result->backup_extrinsics;
529 result->solver_error = result->backup_solver_error;
530 return true;
531 } else if (verbose) {
532 printf("Rejecting a target with errors: (%f, %f) > %f \n",
533 result->solver_error, result->backup_solver_error, acceptable_error);
534 }
535 return false;
536}
537
Parker Schuh2a1447c2019-02-17 00:25:29 -0800538std::vector<IntermediateResult> TargetFinder::FilterResults(
Alex Perrybac3d3f2019-03-10 14:26:51 -0700539 const std::vector<IntermediateResult> &results, uint64_t print_rate,
540 bool verbose) {
Parker Schuh2a1447c2019-02-17 00:25:29 -0800541 std::vector<IntermediateResult> filtered;
542 for (const IntermediateResult &res : results) {
Austin Schuhe6cfbe32019-03-10 18:05:57 -0700543 IntermediateResult updatable_result = res;
544 if (MaybePickAndUpdateResult(&updatable_result, verbose)) {
545 filtered.emplace_back(updatable_result);
Parker Schuh2a1447c2019-02-17 00:25:29 -0800546 }
547 }
Ben Fredricksona8c3d552019-03-03 14:14:53 -0800548 frame_count_++;
549 if (!filtered.empty()) {
550 valid_result_count_++;
551 }
552 if (print_rate > 0 && frame_count_ > print_rate) {
553 LOG(INFO, "Found (%zu / %zu)(%.2f) targets.\n", valid_result_count_,
554 frame_count_, (double)valid_result_count_ / (double)frame_count_);
555 frame_count_ = 0;
556 valid_result_count_ = 0;
557 }
558
Parker Schuh2a1447c2019-02-17 00:25:29 -0800559 return filtered;
560}
561
562} // namespace vision
563} // namespace y2019