blob: b59792cf413cdbe71bf0f11c172e45c3618ba447 [file] [log] [blame]
Parker Schuh2a1447c2019-02-17 00:25:29 -08001#include "y2019/vision/target_finder.h"
2
Brian Silverman63236772019-03-23 22:02:44 -07003#include "ceres/ceres.h"
Parker Schuh2a1447c2019-02-17 00:25:29 -08004
Philipp Schrader790cb542023-07-05 21:06:52 -07005#include "aos/vision/blob/hierarchical_contour_merge.h"
6
Parker Schuh2a1447c2019-02-17 00:25:29 -08007using namespace aos::vision;
8
9namespace y2019 {
10namespace vision {
11
Brian Silverman63236772019-03-23 22:02:44 -070012TargetFinder::TargetFinder()
13 : target_template_(Target::MakeTemplate()),
14 ceres_context_(ceres::Context::Create()) {}
15
16TargetFinder::~TargetFinder() {}
Parker Schuh2a1447c2019-02-17 00:25:29 -080017
18aos::vision::RangeImage TargetFinder::Threshold(aos::vision::ImagePtr image) {
19 const uint8_t threshold_value = GetThresholdValue();
Brian Silverman37b15b32019-03-10 13:30:18 -070020 return aos::vision::ThresholdImageWithFunction(
21 image, [&](aos::vision::PixelRef px) {
22 if (px.g > threshold_value && px.b > threshold_value &&
23 px.r > threshold_value) {
24 return true;
25 }
26 return false;
27 });
Parker Schuh2a1447c2019-02-17 00:25:29 -080028}
29
Austin Schuh3b1586a2019-05-02 13:46:52 -070030int TargetFinder::PixelCount(BlobList *imgs) {
31 int num_pixels = 0;
32 for (RangeImage &img : *imgs) {
33 num_pixels += img.npixels();
34 }
35 return num_pixels;
36}
37
Parker Schuh2a1447c2019-02-17 00:25:29 -080038// Filter blobs on size.
39void TargetFinder::PreFilter(BlobList *imgs) {
40 imgs->erase(
41 std::remove_if(imgs->begin(), imgs->end(),
42 [](RangeImage &img) {
43 // We can drop images with a small number of
44 // pixels, but images
45 // must be over 20px or the math will have issues.
46 return (img.npixels() < 100 || img.height() < 25);
47 }),
48 imgs->end());
49}
50
Austin Schuh7d2ef032019-03-10 14:59:34 -070051ContourNode *TargetFinder::GetContour(const RangeImage &blob) {
Ben Fredricksonf7b68522019-03-02 21:19:42 -080052 alloc_.reset();
53 return RangeImgToContour(blob, &alloc_);
54}
55
Ben Fredricksonec575822019-03-02 22:03:20 -080056// TODO(ben): These values will be moved into the constants.h file.
Ben Fredricksonf7b68522019-03-02 21:19:42 -080057namespace {
58
Austin Schuh7d2ef032019-03-10 14:59:34 -070059::Eigen::Vector2f AosVectorToEigenVector(Vector<2> in) {
60 return ::Eigen::Vector2f(in.x(), in.y());
61}
62
Ben Fredricksonec575822019-03-02 22:03:20 -080063constexpr double f_x = 481.4957;
64constexpr double c_x = 341.215;
65constexpr double f_y = 484.314;
66constexpr double c_y = 251.29;
Ben Fredricksonf7b68522019-03-02 21:19:42 -080067
Ben Fredricksonec575822019-03-02 22:03:20 -080068constexpr double f_x_prime = 363.1424;
69constexpr double c_x_prime = 337.9895;
70constexpr double f_y_prime = 366.4837;
71constexpr double c_y_prime = 240.0702;
Ben Fredricksonf7b68522019-03-02 21:19:42 -080072
Ben Fredricksonec575822019-03-02 22:03:20 -080073constexpr double k_1 = -0.2739;
74constexpr double k_2 = 0.01583;
75constexpr double k_3 = 0.04201;
Ben Fredricksonf7b68522019-03-02 21:19:42 -080076
77constexpr int iterations = 7;
78
Philipp Schrader790cb542023-07-05 21:06:52 -070079} // namespace
Ben Fredricksonf7b68522019-03-02 21:19:42 -080080
Austin Schuhe5015972019-03-09 17:47:34 -080081::Eigen::Vector2f UnWarpPoint(const Point point) {
Ben Fredricksonec575822019-03-02 22:03:20 -080082 const double x0 = ((double)point.x - c_x) / f_x;
83 const double y0 = ((double)point.y - c_y) / f_y;
Ben Fredricksonf7b68522019-03-02 21:19:42 -080084 double x = x0;
85 double y = y0;
86 for (int i = 0; i < iterations; i++) {
87 const double r_sqr = x * x + y * y;
Alex Perry2ca15742019-03-10 20:38:40 -070088 const double coeff = 1.0 + r_sqr * (k_1 + r_sqr * (k_2 + r_sqr * (k_3)));
Ben Fredricksonf7b68522019-03-02 21:19:42 -080089 x = x0 / coeff;
90 y = y0 / coeff;
91 }
Austin Schuhe5015972019-03-09 17:47:34 -080092 const double nx = x * f_x_prime + c_x_prime;
93 const double ny = y * f_y_prime + c_y_prime;
94 return ::Eigen::Vector2f(nx, ny);
Ben Fredricksonf7b68522019-03-02 21:19:42 -080095}
96
Austin Schuhe5015972019-03-09 17:47:34 -080097::std::vector<::Eigen::Vector2f> TargetFinder::UnWarpContour(
98 ContourNode *start) const {
99 ::std::vector<::Eigen::Vector2f> result;
Ben Fredricksonf7b68522019-03-02 21:19:42 -0800100 ContourNode *node = start;
101 while (node->next != start) {
Austin Schuhe5015972019-03-09 17:47:34 -0800102 result.push_back(UnWarpPoint(node->pt));
Ben Fredricksonf7b68522019-03-02 21:19:42 -0800103 node = node->next;
104 }
Austin Schuhe5015972019-03-09 17:47:34 -0800105 result.push_back(UnWarpPoint(node->pt));
106 return result;
Ben Fredricksonf7b68522019-03-02 21:19:42 -0800107}
108
Parker Schuh2a1447c2019-02-17 00:25:29 -0800109// TODO: Try hierarchical merge for this.
110// Convert blobs into polygons.
Austin Schuh6e56faf2019-03-10 14:04:57 -0700111Polygon TargetFinder::FindPolygon(::std::vector<::Eigen::Vector2f> &&contour,
112 bool verbose) {
Parker Schuh2a1447c2019-02-17 00:25:29 -0800113 if (verbose) printf("Process Polygon.\n");
Parker Schuh2a1447c2019-02-17 00:25:29 -0800114
Austin Schuhe5015972019-03-09 17:47:34 -0800115 ::std::vector<::Eigen::Vector2f> slopes;
Parker Schuh2a1447c2019-02-17 00:25:29 -0800116
117 // Collect all slopes from the contour.
Austin Schuhe5015972019-03-09 17:47:34 -0800118 ::Eigen::Vector2f previous_point = contour[0];
119 for (size_t i = 0; i < contour.size(); ++i) {
120 ::Eigen::Vector2f next_point = contour[(i + 1) % contour.size()];
Parker Schuh2a1447c2019-02-17 00:25:29 -0800121
Austin Schuhe5015972019-03-09 17:47:34 -0800122 slopes.push_back(next_point - previous_point);
Parker Schuh2a1447c2019-02-17 00:25:29 -0800123
Austin Schuhe5015972019-03-09 17:47:34 -0800124 previous_point = next_point;
Parker Schuh2a1447c2019-02-17 00:25:29 -0800125 }
126
Austin Schuhe5015972019-03-09 17:47:34 -0800127 const int num_points = slopes.size();
128 auto get_pt = [&slopes, num_points](int i) {
129 return slopes[(i + num_points * 2) % num_points];
Austin Schuh335eef12019-03-02 17:04:17 -0800130 };
Parker Schuh2a1447c2019-02-17 00:25:29 -0800131
Austin Schuh6a484962019-03-09 21:51:27 -0800132 // Bigger objects should be more filtered. Filter roughly proportional to the
133 // perimeter of the object.
134 const int range = slopes.size() / 50;
135 if (verbose) printf("Corner range: %d.\n", range);
136
Austin Schuhe5015972019-03-09 17:47:34 -0800137 ::std::vector<::Eigen::Vector2f> filtered_slopes = slopes;
Austin Schuh335eef12019-03-02 17:04:17 -0800138 // Three box filter makith a guassian?
139 // Run gaussian filter over the slopes 3 times. That'll get us pretty close
140 // to running a gausian over it.
141 for (int k = 0; k < 3; ++k) {
Austin Schuh6a484962019-03-09 21:51:27 -0800142 const int window_size = ::std::max(2, range);
Austin Schuhe5015972019-03-09 17:47:34 -0800143 for (size_t i = 0; i < slopes.size(); ++i) {
144 ::Eigen::Vector2f a = ::Eigen::Vector2f::Zero();
Parker Schuh2a1447c2019-02-17 00:25:29 -0800145 for (int j = -window_size; j <= window_size; ++j) {
Austin Schuhe5015972019-03-09 17:47:34 -0800146 ::Eigen::Vector2f p = get_pt(j + i);
147 a += p;
Parker Schuh2a1447c2019-02-17 00:25:29 -0800148 }
Austin Schuhe5015972019-03-09 17:47:34 -0800149 a /= (window_size * 2 + 1);
Parker Schuh2a1447c2019-02-17 00:25:29 -0800150
Austin Schuhe5015972019-03-09 17:47:34 -0800151 filtered_slopes[i] = a;
Parker Schuh2a1447c2019-02-17 00:25:29 -0800152 }
Austin Schuhe5015972019-03-09 17:47:34 -0800153 slopes = filtered_slopes;
Austin Schuh335eef12019-03-02 17:04:17 -0800154 }
Austin Schuh6a484962019-03-09 21:51:27 -0800155 if (verbose) printf("Point count: %zu.\n", slopes.size());
Parker Schuh2a1447c2019-02-17 00:25:29 -0800156
Austin Schuh6a484962019-03-09 21:51:27 -0800157 ::std::vector<float> corner_metric(slopes.size(), 0.0);
Parker Schuh2a1447c2019-02-17 00:25:29 -0800158
Austin Schuh6a484962019-03-09 21:51:27 -0800159 for (size_t i = 0; i < slopes.size(); ++i) {
160 const ::Eigen::Vector2f a = get_pt(i - ::std::max(3, range));
161 const ::Eigen::Vector2f b = get_pt(i + ::std::max(3, range));
162 corner_metric[i] = (a - b).squaredNorm();
163 }
164
165 // We want to find the Nth highest peaks.
166 // Clever algorithm: Find the highest point. Then, walk forwards and
167 // backwards to find the next valley each direction which is over x% lower
168 // than the peak.
169 // We want to ignore those points in the future. Set them to 0.
170 // Repeat until we've found the Nth highest peak.
Parker Schuh2a1447c2019-02-17 00:25:29 -0800171
172 // Find all centers of corners.
Austin Schuhe5015972019-03-09 17:47:34 -0800173 // Because they round, multiple slopes may be a corner.
174 ::std::vector<size_t> edges;
Parker Schuh2a1447c2019-02-17 00:25:29 -0800175
Austin Schuh6a484962019-03-09 21:51:27 -0800176 constexpr float peak_acceptance_ratio = 0.16;
177 constexpr float valley_ratio = 0.75;
178
179 float highest_peak_value = 0.0;
180
181 // Nth higest points.
Austin Schuh32ffac22019-03-09 22:42:02 -0800182 while (edges.size() < 5) {
Austin Schuh6a484962019-03-09 21:51:27 -0800183 const ::std::vector<float>::iterator max_element =
184 ::std::max_element(corner_metric.begin(), corner_metric.end());
185 const size_t highest_index =
186 ::std::distance(corner_metric.begin(), max_element);
187 const float max_value = *max_element;
Austin Schuh32ffac22019-03-09 22:42:02 -0800188 if (edges.size() == 0) {
Austin Schuh6a484962019-03-09 21:51:27 -0800189 highest_peak_value = max_value;
190 }
Austin Schuh32ffac22019-03-09 22:42:02 -0800191 if (max_value < highest_peak_value * peak_acceptance_ratio &&
192 edges.size() == 4) {
Austin Schuh6a484962019-03-09 21:51:27 -0800193 if (verbose)
194 printf("Rejecting index: %zu, %f (%f %%)\n", highest_index, max_value,
195 max_value / highest_peak_value);
196 break;
197 }
198 const float valley_value = max_value * valley_ratio;
199
200 if (verbose)
201 printf("Highest index: %zu, %f (%f %%)\n", highest_index, max_value,
202 max_value / highest_peak_value);
203
Austin Schuh32ffac22019-03-09 22:42:02 -0800204 bool foothill = false;
Austin Schuh6a484962019-03-09 21:51:27 -0800205 {
206 float min_value = max_value;
207 size_t fwd_index = (highest_index + 1) % corner_metric.size();
208 while (true) {
209 const float current_value = corner_metric[fwd_index];
Austin Schuh32ffac22019-03-09 22:42:02 -0800210
211 if (current_value == -1.0) {
212 if (min_value >= valley_value) {
213 if (verbose) printf("Foothill\n");
214 foothill = true;
215 }
216 break;
217 }
218
Austin Schuh6a484962019-03-09 21:51:27 -0800219 min_value = ::std::min(current_value, min_value);
220
Austin Schuh32ffac22019-03-09 22:42:02 -0800221 if (min_value < valley_value && current_value > min_value) {
Austin Schuh6a484962019-03-09 21:51:27 -0800222 break;
223 }
224 // Kill!!!
Austin Schuh32ffac22019-03-09 22:42:02 -0800225 corner_metric[fwd_index] = -1.0;
Austin Schuh6a484962019-03-09 21:51:27 -0800226
227 fwd_index = (fwd_index + 1) % corner_metric.size();
Parker Schuh2a1447c2019-02-17 00:25:29 -0800228 }
229 }
Austin Schuh6a484962019-03-09 21:51:27 -0800230
231 {
232 float min_value = max_value;
233 size_t rev_index =
234 (highest_index - 1 + corner_metric.size()) % corner_metric.size();
235 while (true) {
236 const float current_value = corner_metric[rev_index];
Austin Schuh32ffac22019-03-09 22:42:02 -0800237
238 if (current_value == -1.0) {
239 if (min_value >= valley_value) {
240 if (verbose) printf("Foothill\n");
241 foothill = true;
242 }
243 break;
244 }
245
Austin Schuh6a484962019-03-09 21:51:27 -0800246 min_value = ::std::min(current_value, min_value);
247
Austin Schuh32ffac22019-03-09 22:42:02 -0800248 if (min_value < valley_value && current_value > min_value) {
Austin Schuh6a484962019-03-09 21:51:27 -0800249 break;
250 }
251 // Kill!!!
Austin Schuh32ffac22019-03-09 22:42:02 -0800252 corner_metric[rev_index] = -1.0;
Austin Schuh6a484962019-03-09 21:51:27 -0800253
254 rev_index =
255 (rev_index - 1 + corner_metric.size()) % corner_metric.size();
256 }
Parker Schuh2a1447c2019-02-17 00:25:29 -0800257 }
Austin Schuh6a484962019-03-09 21:51:27 -0800258
Austin Schuh32ffac22019-03-09 22:42:02 -0800259 *max_element = -1.0;
260 if (!foothill) {
261 edges.push_back(highest_index);
262 }
Parker Schuh2a1447c2019-02-17 00:25:29 -0800263 }
264
Austin Schuh6a484962019-03-09 21:51:27 -0800265 ::std::sort(edges.begin(), edges.end());
Parker Schuh2a1447c2019-02-17 00:25:29 -0800266
267 if (verbose) printf("Edge Count (%zu).\n", edges.size());
268
Parker Schuh2a1447c2019-02-17 00:25:29 -0800269 // Run best-fits over each line segment.
Austin Schuh6e56faf2019-03-10 14:04:57 -0700270 Polygon polygon;
Austin Schuh6a484962019-03-09 21:51:27 -0800271 if (edges.size() >= 3) {
Austin Schuhe5015972019-03-09 17:47:34 -0800272 for (size_t i = 0; i < edges.size(); ++i) {
273 // Include the corners in both line fits.
274 const size_t segment_start_index = edges[i];
275 const size_t segment_end_index =
276 (edges[(i + 1) % edges.size()] + 1) % contour.size();
Parker Schuh2a1447c2019-02-17 00:25:29 -0800277 float mx = 0.0;
278 float my = 0.0;
279 int n = 0;
Austin Schuhe5015972019-03-09 17:47:34 -0800280 for (size_t j = segment_start_index; j != segment_end_index;
281 (j = (j + 1) % contour.size())) {
282 mx += contour[j].x();
283 my += contour[j].y();
Parker Schuh2a1447c2019-02-17 00:25:29 -0800284 ++n;
285 // (x - [x] / N) ** 2 = [x * x] - 2 * [x] * [x] / N + [x] * [x] / N / N;
286 }
287 mx /= n;
288 my /= n;
289
290 float xx = 0.0;
291 float xy = 0.0;
292 float yy = 0.0;
Austin Schuhe5015972019-03-09 17:47:34 -0800293 for (size_t j = segment_start_index; j != segment_end_index;
294 (j = (j + 1) % contour.size())) {
295 const float x = contour[j].x() - mx;
296 const float y = contour[j].y() - my;
Parker Schuh2a1447c2019-02-17 00:25:29 -0800297 xx += x * x;
298 xy += x * y;
299 yy += y * y;
300 }
301
302 // TODO: Extract common to hierarchical merge.
Austin Schuh335eef12019-03-02 17:04:17 -0800303 const float neg_b_over_2 = (xx + yy) / 2.0;
304 const float c = (xx * yy - xy * xy);
Parker Schuh2a1447c2019-02-17 00:25:29 -0800305
Austin Schuh335eef12019-03-02 17:04:17 -0800306 const float sqr = sqrt(neg_b_over_2 * neg_b_over_2 - c);
Parker Schuh2a1447c2019-02-17 00:25:29 -0800307
308 {
Austin Schuh335eef12019-03-02 17:04:17 -0800309 const float lam = neg_b_over_2 + sqr;
Parker Schuh2a1447c2019-02-17 00:25:29 -0800310 float x = xy;
311 float y = lam - xx;
312
Austin Schuh335eef12019-03-02 17:04:17 -0800313 const float norm = hypot(x, y);
Parker Schuh2a1447c2019-02-17 00:25:29 -0800314 x /= norm;
315 y /= norm;
316
Austin Schuh6e56faf2019-03-10 14:04:57 -0700317 polygon.segments.push_back(
Parker Schuh2a1447c2019-02-17 00:25:29 -0800318 Segment<2>(Vector<2>(mx, my), Vector<2>(mx + x, my + y)));
319 }
320
321 /* Characteristic polynomial
322 1 lam^2 - (xx + yy) lam + (xx * yy - xy * xy) = 0
323
324 [a b]
325 [c d]
326
327 // covariance matrix.
328 [xx xy] [nx]
329 [xy yy] [ny]
330 */
331 }
332 }
Austin Schuh6e56faf2019-03-10 14:04:57 -0700333 if (verbose) printf("Poly Count (%zu).\n", polygon.segments.size());
334 polygon.contour = ::std::move(contour);
335 return polygon;
Parker Schuh2a1447c2019-02-17 00:25:29 -0800336}
337
338// Convert segments into target components (left or right)
Austin Schuh6e56faf2019-03-10 14:04:57 -0700339::std::vector<TargetComponent> TargetFinder::FillTargetComponentList(
340 const ::std::vector<Polygon> &seg_list, bool verbose) {
341 ::std::vector<TargetComponent> list;
Parker Schuh2a1447c2019-02-17 00:25:29 -0800342 TargetComponent new_target;
Austin Schuh7d2ef032019-03-10 14:59:34 -0700343 for (const Polygon &polygon : seg_list) {
Parker Schuh2a1447c2019-02-17 00:25:29 -0800344 // Reject missized pollygons for now. Maybe rectify them here in the future;
Austin Schuh7d2ef032019-03-10 14:59:34 -0700345 if (polygon.segments.size() != 4) {
Austin Schuh9f859ca2019-03-06 20:46:01 -0800346 continue;
347 }
Austin Schuh32ffac22019-03-09 22:42:02 -0800348 ::std::vector<Vector<2>> corners;
Parker Schuh2a1447c2019-02-17 00:25:29 -0800349 for (size_t i = 0; i < 4; ++i) {
Austin Schuh7d2ef032019-03-10 14:59:34 -0700350 Vector<2> corner =
351 polygon.segments[i].Intersect(polygon.segments[(i + 1) % 4]);
Austin Schuh9f859ca2019-03-06 20:46:01 -0800352 if (::std::isnan(corner.x()) || ::std::isnan(corner.y())) {
353 break;
354 }
355 corners.push_back(corner);
356 }
357 if (corners.size() != 4) {
358 continue;
Parker Schuh2a1447c2019-02-17 00:25:29 -0800359 }
360
361 // Select the closest two points. Short side of the rectangle.
362 double min_dist = -1;
Austin Schuh32ffac22019-03-09 22:42:02 -0800363 ::std::pair<size_t, size_t> closest;
Parker Schuh2a1447c2019-02-17 00:25:29 -0800364 for (size_t i = 0; i < 4; ++i) {
365 size_t next = (i + 1) % 4;
366 double nd = corners[i].SquaredDistanceTo(corners[next]);
367 if (min_dist == -1 || nd < min_dist) {
368 min_dist = nd;
369 closest.first = i;
370 closest.second = next;
371 }
372 }
373
374 // Verify our top is above the bottom.
375 size_t bot_index = closest.first;
376 size_t top_index = (closest.first + 2) % 4;
377 if (corners[top_index].y() < corners[bot_index].y()) {
378 closest.first = top_index;
379 closest.second = (top_index + 1) % 4;
380 }
381
382 // Find the major axis.
383 size_t far_first = (closest.first + 2) % 4;
384 size_t far_second = (closest.second + 2) % 4;
385 Segment<2> major_axis(
386 (corners[closest.first] + corners[closest.second]) * 0.5,
387 (corners[far_first] + corners[far_second]) * 0.5);
388 if (major_axis.AsVector().AngleToZero() > M_PI / 180.0 * 120.0 ||
389 major_axis.AsVector().AngleToZero() < M_PI / 180.0 * 60.0) {
390 // Target is angled way too much, drop it.
391 continue;
392 }
393
394 // organize the top points.
395 Vector<2> topA = corners[closest.first] - major_axis.B();
396 new_target.major_axis = major_axis;
397 if (major_axis.AsVector().AngleToZero() > M_PI / 2.0) {
398 // We have a left target since we are leaning positive.
399 new_target.is_right = false;
400 if (topA.AngleTo(major_axis.AsVector()) > 0.0) {
401 // And our A point is left of the major axis.
402 new_target.inside = corners[closest.second];
403 new_target.top = corners[closest.first];
404 } else {
405 // our A point is to the right of the major axis.
406 new_target.inside = corners[closest.first];
407 new_target.top = corners[closest.second];
408 }
409 } else {
410 // We have a right target since we are leaning negative.
411 new_target.is_right = true;
412 if (topA.AngleTo(major_axis.AsVector()) > 0.0) {
413 // And our A point is left of the major axis.
414 new_target.inside = corners[closest.first];
415 new_target.top = corners[closest.second];
416 } else {
417 // our A point is to the right of the major axis.
418 new_target.inside = corners[closest.second];
419 new_target.top = corners[closest.first];
420 }
421 }
422
423 // organize the top points.
424 Vector<2> botA = corners[far_first] - major_axis.A();
425 if (major_axis.AsVector().AngleToZero() > M_PI / 2.0) {
426 // We have a right target since we are leaning positive.
427 if (botA.AngleTo(major_axis.AsVector()) < M_PI) {
428 // And our A point is left of the major axis.
429 new_target.outside = corners[far_second];
430 new_target.bottom = corners[far_first];
431 } else {
432 // our A point is to the right of the major axis.
433 new_target.outside = corners[far_first];
434 new_target.bottom = corners[far_second];
435 }
436 } else {
437 // We have a left target since we are leaning negative.
438 if (botA.AngleTo(major_axis.AsVector()) < M_PI) {
439 // And our A point is left of the major axis.
440 new_target.outside = corners[far_first];
441 new_target.bottom = corners[far_second];
442 } else {
443 // our A point is to the right of the major axis.
444 new_target.outside = corners[far_second];
445 new_target.bottom = corners[far_first];
446 }
447 }
448
Austin Schuh7d2ef032019-03-10 14:59:34 -0700449 // Take the vector which points from the bottom to the top of the target
450 // along the outside edge.
451 const ::Eigen::Vector2f outer_edge_vector =
452 AosVectorToEigenVector(new_target.top - new_target.outside);
453 // Now, dot each point in the perimeter along this vector. The one with the
454 // smallest component will be the one closest to the bottom along this
455 // direction vector.
456 ::Eigen::Vector2f smallest_point = polygon.contour[0];
457 float smallest_value = outer_edge_vector.transpose() * smallest_point;
Austin Schuhbb097ef2021-12-31 21:50:56 -0800458 for (const ::Eigen::Vector2f &point : polygon.contour) {
Austin Schuh7d2ef032019-03-10 14:59:34 -0700459 const float current_value = outer_edge_vector.transpose() * point;
460 if (current_value < smallest_value) {
461 smallest_value = current_value;
462 smallest_point = point;
463 }
464 }
465
466 // This piece of the target should be ready now.
467 new_target.bottom_point = smallest_point;
468 if (verbose) {
469 printf("Lowest point in the blob is (%f, %f)\n", smallest_point.x(),
470 smallest_point.y());
471 }
472
Parker Schuh2a1447c2019-02-17 00:25:29 -0800473 // This piece of the target should be ready now.
474 list.emplace_back(new_target);
Austin Schuh7d2ef032019-03-10 14:59:34 -0700475
Austin Schuh32ffac22019-03-09 22:42:02 -0800476 if (verbose) printf("Happy with a target\n");
Parker Schuh2a1447c2019-02-17 00:25:29 -0800477 }
478
479 return list;
480}
481
482// Match components into targets.
483std::vector<Target> TargetFinder::FindTargetsFromComponents(
484 const std::vector<TargetComponent> component_list, bool verbose) {
485 std::vector<Target> target_list;
486 using namespace aos::vision;
487 if (component_list.size() < 2) {
488 // We don't enough parts for a target.
489 return target_list;
490 }
491
492 for (size_t i = 0; i < component_list.size(); i++) {
493 const TargetComponent &a = component_list[i];
494 for (size_t j = 0; j < i; j++) {
495 bool target_valid = false;
496 Target new_target;
497 const TargetComponent &b = component_list[j];
498
Parker Schuh2a1447c2019-02-17 00:25:29 -0800499 if (a.is_right && !b.is_right) {
500 if (a.top.x() > b.top.x()) {
501 new_target.right = a;
502 new_target.left = b;
503 target_valid = true;
504 }
505 } else if (!a.is_right && b.is_right) {
506 if (b.top.x() > a.top.x()) {
507 new_target.right = b;
508 new_target.left = a;
509 target_valid = true;
510 }
Alex Perrybac3d3f2019-03-10 14:26:51 -0700511 } else if (verbose) {
512 printf("Found same side components: %s.\n",
513 a.is_right ? "right" : "left");
Parker Schuh2a1447c2019-02-17 00:25:29 -0800514 }
515 if (target_valid) {
516 target_list.emplace_back(new_target);
517 }
518 }
519 }
520 if (verbose) printf("Possible Target: %zu.\n", target_list.size());
521 return target_list;
522}
523
Austin Schuhe6cfbe32019-03-10 18:05:57 -0700524bool TargetFinder::MaybePickAndUpdateResult(IntermediateResult *result,
525 bool verbose) {
526 // Based on a linear regression between error and distance to target.
527 // Closer targets can have a higher error because they are bigger.
528 const double acceptable_error =
Alex Perryebc6dd52019-03-16 07:54:48 -0700529 std::max(2 * (75 - 12 * result->extrinsics.z), 75.0);
Austin Schuh45639882019-03-24 19:20:42 -0700530 if (!result->good_corners) {
531 if (verbose) {
532 printf("Rejecting a target with bad corners: (%f, %f)\n",
533 result->solver_error, result->backup_solver_error);
534 }
535 } else if (result->solver_error < acceptable_error) {
Austin Schuhe6cfbe32019-03-10 18:05:57 -0700536 if (verbose) {
537 printf("Using an 8 point solve: %f < %f \n", result->solver_error,
538 acceptable_error);
539 }
540 return true;
541 } else if (result->backup_solver_error < acceptable_error) {
542 if (verbose) {
543 printf("Using a 4 point solve: %f < %f \n", result->backup_solver_error,
544 acceptable_error);
545 }
546 IntermediateResult backup;
547 result->extrinsics = result->backup_extrinsics;
548 result->solver_error = result->backup_solver_error;
549 return true;
550 } else if (verbose) {
551 printf("Rejecting a target with errors: (%f, %f) > %f \n",
552 result->solver_error, result->backup_solver_error, acceptable_error);
553 }
554 return false;
555}
556
Parker Schuh2a1447c2019-02-17 00:25:29 -0800557std::vector<IntermediateResult> TargetFinder::FilterResults(
Alex Perrybac3d3f2019-03-10 14:26:51 -0700558 const std::vector<IntermediateResult> &results, uint64_t print_rate,
559 bool verbose) {
Parker Schuh2a1447c2019-02-17 00:25:29 -0800560 std::vector<IntermediateResult> filtered;
561 for (const IntermediateResult &res : results) {
Austin Schuhe6cfbe32019-03-10 18:05:57 -0700562 IntermediateResult updatable_result = res;
563 if (MaybePickAndUpdateResult(&updatable_result, verbose)) {
564 filtered.emplace_back(updatable_result);
Parker Schuh2a1447c2019-02-17 00:25:29 -0800565 }
566 }
Alex Perry5b1e8e32019-04-07 13:25:31 -0700567
568 // Sort the target list so that the widest (ie closest) target is first.
569 sort(filtered.begin(), filtered.end(),
Philipp Schrader790cb542023-07-05 21:06:52 -0700570 [](const IntermediateResult &a, const IntermediateResult &b) -> bool {
571 return a.target_width > b.target_width;
572 });
Alex Perry5b1e8e32019-04-07 13:25:31 -0700573
Ben Fredricksona8c3d552019-03-03 14:14:53 -0800574 frame_count_++;
575 if (!filtered.empty()) {
576 valid_result_count_++;
577 }
578 if (print_rate > 0 && frame_count_ > print_rate) {
Brian Silverman63236772019-03-23 22:02:44 -0700579 LOG(INFO) << "Found (" << valid_result_count_ << " / " << frame_count_
580 << ")(" << ((double)valid_result_count_ / (double)frame_count_)
581 << " targets.";
Ben Fredricksona8c3d552019-03-03 14:14:53 -0800582 frame_count_ = 0;
583 valid_result_count_ = 0;
584 }
585
Parker Schuh2a1447c2019-02-17 00:25:29 -0800586 return filtered;
587}
588
Alex Perry5b1e8e32019-04-07 13:25:31 -0700589bool TargetFinder::TestExposure(const std::vector<IntermediateResult> &results,
Austin Schuh3b1586a2019-05-02 13:46:52 -0700590 int pixel_count, int *desired_exposure) {
Alex Perry5b1e8e32019-04-07 13:25:31 -0700591 // TODO(ben): Add these values to config file.
592 constexpr double low_dist = 0.8;
Philipp Schrader790cb542023-07-05 21:06:52 -0700593 constexpr int low_exposure = 60;
594 constexpr int mid_exposure = 200;
Alex Perry5b1e8e32019-04-07 13:25:31 -0700595
596 bool needs_update = false;
597 if (results.size() > 0) {
598 // We are seeing a target so lets use an exposure
599 // based on the distance to that target.
600 // First result should always be the closest target.
601 if (results[0].extrinsics.z < low_dist) {
Austin Schuh3b1586a2019-05-02 13:46:52 -0700602 LOG(INFO) << "Low exposure";
Alex Perry5b1e8e32019-04-07 13:25:31 -0700603 *desired_exposure = low_exposure;
Austin Schuh3b1586a2019-05-02 13:46:52 -0700604 close_bucket_ = 4;
Alex Perry5b1e8e32019-04-07 13:25:31 -0700605 } else {
Austin Schuh3b1586a2019-05-02 13:46:52 -0700606 LOG(INFO) << "Mid exposure";
Alex Perry5b1e8e32019-04-07 13:25:31 -0700607 *desired_exposure = mid_exposure;
608 }
609 if (*desired_exposure != current_exposure_) {
610 needs_update = true;
611 current_exposure_ = *desired_exposure;
612 }
613 } else {
Austin Schuh3b1586a2019-05-02 13:46:52 -0700614 close_bucket_ = ::std::max(0, close_bucket_ - 1);
615 // It's been a while since we saw a target.
616 if (close_bucket_ == 0) {
617 if (pixel_count > 6000) {
618 if (low_exposure != current_exposure_) {
619 needs_update = true;
620 current_exposure_ = low_exposure;
621 *desired_exposure = low_exposure;
622 }
623 } else {
624 if (mid_exposure != current_exposure_) {
625 needs_update = true;
626 current_exposure_ = mid_exposure;
627 *desired_exposure = mid_exposure;
628 }
Alex Perry5b1e8e32019-04-07 13:25:31 -0700629 }
630 }
Alex Perry5b1e8e32019-04-07 13:25:31 -0700631 }
632 return needs_update;
633}
634
Parker Schuh2a1447c2019-02-17 00:25:29 -0800635} // namespace vision
636} // namespace y2019