blob: d39764a1cb5d8cdf5fa92ea7e3603ff856620a17 [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
10TargetFinder::TargetFinder() { target_template_ = Target::MakeTemplate(); }
11
12aos::vision::RangeImage TargetFinder::Threshold(aos::vision::ImagePtr image) {
13 const uint8_t threshold_value = GetThresholdValue();
14 return aos::vision::DoThreshold(image, [&](aos::vision::PixelRef &px) {
15 if (px.g > threshold_value && px.b > threshold_value &&
16 px.r > threshold_value) {
17 return true;
18 }
19 return false;
20 });
21}
22
23// Filter blobs on size.
24void TargetFinder::PreFilter(BlobList *imgs) {
25 imgs->erase(
26 std::remove_if(imgs->begin(), imgs->end(),
27 [](RangeImage &img) {
28 // We can drop images with a small number of
29 // pixels, but images
30 // must be over 20px or the math will have issues.
31 return (img.npixels() < 100 || img.height() < 25);
32 }),
33 imgs->end());
34}
35
Ben Fredricksonf7b68522019-03-02 21:19:42 -080036ContourNode* TargetFinder::GetContour(const RangeImage &blob) {
37 alloc_.reset();
38 return RangeImgToContour(blob, &alloc_);
39}
40
Ben Fredricksonec575822019-03-02 22:03:20 -080041// TODO(ben): These values will be moved into the constants.h file.
Ben Fredricksonf7b68522019-03-02 21:19:42 -080042namespace {
43
Ben Fredricksonec575822019-03-02 22:03:20 -080044constexpr double f_x = 481.4957;
45constexpr double c_x = 341.215;
46constexpr double f_y = 484.314;
47constexpr double c_y = 251.29;
Ben Fredricksonf7b68522019-03-02 21:19:42 -080048
Ben Fredricksonec575822019-03-02 22:03:20 -080049constexpr double f_x_prime = 363.1424;
50constexpr double c_x_prime = 337.9895;
51constexpr double f_y_prime = 366.4837;
52constexpr double c_y_prime = 240.0702;
Ben Fredricksonf7b68522019-03-02 21:19:42 -080053
Ben Fredricksonec575822019-03-02 22:03:20 -080054constexpr double k_1 = -0.2739;
55constexpr double k_2 = 0.01583;
56constexpr double k_3 = 0.04201;
Ben Fredricksonf7b68522019-03-02 21:19:42 -080057
58constexpr int iterations = 7;
59
60}
61
62Point UnWarpPoint(const Point &point, int iterations) {
Ben Fredricksonec575822019-03-02 22:03:20 -080063 const double x0 = ((double)point.x - c_x) / f_x;
64 const double y0 = ((double)point.y - c_y) / f_y;
Ben Fredricksonf7b68522019-03-02 21:19:42 -080065 double x = x0;
66 double y = y0;
67 for (int i = 0; i < iterations; i++) {
68 const double r_sqr = x * x + y * y;
69 const double coeff =
Ben Fredricksonec575822019-03-02 22:03:20 -080070 1.0 + r_sqr * (k_1 + k_2 * r_sqr * (1.0 + k_3 * r_sqr));
Ben Fredricksonf7b68522019-03-02 21:19:42 -080071 x = x0 / coeff;
72 y = y0 / coeff;
73 }
Ben Fredricksonec575822019-03-02 22:03:20 -080074 double nx = x * f_x_prime + c_x_prime;
75 double ny = y * f_y_prime + c_y_prime;
Ben Fredricksonf7b68522019-03-02 21:19:42 -080076 Point p = {static_cast<int>(nx), static_cast<int>(ny)};
77 return p;
78}
79
80void TargetFinder::UnWarpContour(ContourNode *start) const {
81 ContourNode *node = start;
82 while (node->next != start) {
83 node->set_point(UnWarpPoint(node->pt, iterations));
84 node = node->next;
85 }
86 node->set_point(UnWarpPoint(node->pt, iterations));
87}
88
Parker Schuh2a1447c2019-02-17 00:25:29 -080089// TODO: Try hierarchical merge for this.
90// Convert blobs into polygons.
91std::vector<aos::vision::Segment<2>> TargetFinder::FillPolygon(
Ben Fredricksonf7b68522019-03-02 21:19:42 -080092 ContourNode* start, bool verbose) {
Parker Schuh2a1447c2019-02-17 00:25:29 -080093 if (verbose) printf("Process Polygon.\n");
Parker Schuh2a1447c2019-02-17 00:25:29 -080094
95 struct Pt {
96 float x;
97 float y;
98 };
Austin Schuh335eef12019-03-02 17:04:17 -080099 std::vector<Pt> points;
Parker Schuh2a1447c2019-02-17 00:25:29 -0800100
101 // Collect all slopes from the contour.
Austin Schuh335eef12019-03-02 17:04:17 -0800102 Point previous_point = start->pt;
103 for (ContourNode *node = start; node->next != start;) {
Parker Schuh2a1447c2019-02-17 00:25:29 -0800104 node = node->next;
105
Austin Schuh335eef12019-03-02 17:04:17 -0800106 Point current_point = node->pt;
Parker Schuh2a1447c2019-02-17 00:25:29 -0800107
Austin Schuh335eef12019-03-02 17:04:17 -0800108 points.push_back({static_cast<float>(current_point.x - previous_point.x),
109 static_cast<float>(current_point.y - previous_point.y)});
Parker Schuh2a1447c2019-02-17 00:25:29 -0800110
Austin Schuh335eef12019-03-02 17:04:17 -0800111 previous_point = current_point;
Parker Schuh2a1447c2019-02-17 00:25:29 -0800112 }
113
Austin Schuh335eef12019-03-02 17:04:17 -0800114 const int num_points = points.size();
115 auto get_pt = [&points, num_points](int i) {
116 return points[(i + num_points * 2) % num_points];
117 };
Parker Schuh2a1447c2019-02-17 00:25:29 -0800118
Austin Schuh335eef12019-03-02 17:04:17 -0800119 std::vector<Pt> filtered_points = points;
120 // Three box filter makith a guassian?
121 // Run gaussian filter over the slopes 3 times. That'll get us pretty close
122 // to running a gausian over it.
123 for (int k = 0; k < 3; ++k) {
124 const int window_size = 2;
125 for (size_t i = 0; i < points.size(); ++i) {
Parker Schuh2a1447c2019-02-17 00:25:29 -0800126 Pt a{0.0, 0.0};
127 for (int j = -window_size; j <= window_size; ++j) {
128 Pt p = get_pt(j + i);
129 a.x += p.x;
130 a.y += p.y;
131 }
132 a.x /= (window_size * 2 + 1);
133 a.y /= (window_size * 2 + 1);
134
Austin Schuh335eef12019-03-02 17:04:17 -0800135 const float scale = 1.0 + (i / float(points.size() * 10));
Parker Schuh2a1447c2019-02-17 00:25:29 -0800136 a.x *= scale;
137 a.y *= scale;
Austin Schuh335eef12019-03-02 17:04:17 -0800138 filtered_points[i] = a;
Parker Schuh2a1447c2019-02-17 00:25:29 -0800139 }
Austin Schuh335eef12019-03-02 17:04:17 -0800140 points = filtered_points;
141 }
Parker Schuh2a1447c2019-02-17 00:25:29 -0800142
143 // Heuristic which says if a particular slope is part of a corner.
144 auto is_corner = [&](size_t i) {
Austin Schuh335eef12019-03-02 17:04:17 -0800145 const Pt a = get_pt(i - 3);
146 const Pt b = get_pt(i + 3);
147 const double dx = (a.x - b.x);
148 const double dy = (a.y - b.y);
Parker Schuh2a1447c2019-02-17 00:25:29 -0800149 return dx * dx + dy * dy > 0.25;
150 };
151
152 bool prev_v = is_corner(-1);
153
154 // Find all centers of corners.
155 // Because they round, multiple points may be a corner.
156 std::vector<size_t> edges;
Austin Schuh335eef12019-03-02 17:04:17 -0800157 size_t kBad = points.size() + 10;
Parker Schuh2a1447c2019-02-17 00:25:29 -0800158 size_t prev_up = kBad;
159 size_t wrapped_n = prev_up;
160
Austin Schuh335eef12019-03-02 17:04:17 -0800161 for (size_t i = 0; i < points.size(); ++i) {
Parker Schuh2a1447c2019-02-17 00:25:29 -0800162 bool v = is_corner(i);
163 if (prev_v && !v) {
164 if (prev_up == kBad) {
165 wrapped_n = i;
166 } else {
167 edges.push_back((prev_up + i - 1) / 2);
168 }
169 }
170 if (v && !prev_v) {
171 prev_up = i;
172 }
173 prev_v = v;
174 }
175
176 if (wrapped_n != kBad) {
Austin Schuh335eef12019-03-02 17:04:17 -0800177 edges.push_back(((prev_up + points.size() + wrapped_n - 1) / 2) % points.size());
Parker Schuh2a1447c2019-02-17 00:25:29 -0800178 }
179
180 if (verbose) printf("Edge Count (%zu).\n", edges.size());
181
182 // Get all CountourNodes from the contour.
183 using aos::vision::PixelRef;
184 std::vector<ContourNode *> segments;
185 {
186 std::vector<ContourNode *> segments_all;
187
Austin Schuh335eef12019-03-02 17:04:17 -0800188 for (ContourNode *node = start; node->next != start;) {
Parker Schuh2a1447c2019-02-17 00:25:29 -0800189 node = node->next;
190 segments_all.push_back(node);
191 }
192 for (size_t i : edges) {
193 segments.push_back(segments_all[i]);
194 }
195 }
196 if (verbose) printf("Segment Count (%zu).\n", segments.size());
197
198 // Run best-fits over each line segment.
199 std::vector<Segment<2>> seg_list;
200 if (segments.size() == 4) {
201 for (size_t i = 0; i < segments.size(); ++i) {
Austin Schuh335eef12019-03-02 17:04:17 -0800202 ContourNode *segment_end = segments[(i + 1) % segments.size()];
203 ContourNode *segment_start = segments[i];
Parker Schuh2a1447c2019-02-17 00:25:29 -0800204 float mx = 0.0;
205 float my = 0.0;
206 int n = 0;
Austin Schuh335eef12019-03-02 17:04:17 -0800207 for (ContourNode *node = segment_start; node != segment_end;
208 node = node->next) {
Parker Schuh2a1447c2019-02-17 00:25:29 -0800209 mx += node->pt.x;
210 my += node->pt.y;
211 ++n;
212 // (x - [x] / N) ** 2 = [x * x] - 2 * [x] * [x] / N + [x] * [x] / N / N;
213 }
214 mx /= n;
215 my /= n;
216
217 float xx = 0.0;
218 float xy = 0.0;
219 float yy = 0.0;
Austin Schuh335eef12019-03-02 17:04:17 -0800220 for (ContourNode *node = segment_start; node != segment_end;
221 node = node->next) {
222 const float x = node->pt.x - mx;
223 const float y = node->pt.y - my;
Parker Schuh2a1447c2019-02-17 00:25:29 -0800224 xx += x * x;
225 xy += x * y;
226 yy += y * y;
227 }
228
229 // TODO: Extract common to hierarchical merge.
Austin Schuh335eef12019-03-02 17:04:17 -0800230 const float neg_b_over_2 = (xx + yy) / 2.0;
231 const float c = (xx * yy - xy * xy);
Parker Schuh2a1447c2019-02-17 00:25:29 -0800232
Austin Schuh335eef12019-03-02 17:04:17 -0800233 const float sqr = sqrt(neg_b_over_2 * neg_b_over_2 - c);
Parker Schuh2a1447c2019-02-17 00:25:29 -0800234
235 {
Austin Schuh335eef12019-03-02 17:04:17 -0800236 const float lam = neg_b_over_2 + sqr;
Parker Schuh2a1447c2019-02-17 00:25:29 -0800237 float x = xy;
238 float y = lam - xx;
239
Austin Schuh335eef12019-03-02 17:04:17 -0800240 const float norm = hypot(x, y);
Parker Schuh2a1447c2019-02-17 00:25:29 -0800241 x /= norm;
242 y /= norm;
243
244 seg_list.push_back(
245 Segment<2>(Vector<2>(mx, my), Vector<2>(mx + x, my + y)));
246 }
247
248 /* Characteristic polynomial
249 1 lam^2 - (xx + yy) lam + (xx * yy - xy * xy) = 0
250
251 [a b]
252 [c d]
253
254 // covariance matrix.
255 [xx xy] [nx]
256 [xy yy] [ny]
257 */
258 }
259 }
260 if (verbose) printf("Poly Count (%zu).\n", seg_list.size());
261 return seg_list;
262}
263
264// Convert segments into target components (left or right)
265std::vector<TargetComponent> TargetFinder::FillTargetComponentList(
266 const std::vector<std::vector<Segment<2>>> &seg_list) {
267 std::vector<TargetComponent> list;
268 TargetComponent new_target;
Austin Schuh335eef12019-03-02 17:04:17 -0800269 for (const std::vector<Segment<2>> &poly : seg_list) {
Parker Schuh2a1447c2019-02-17 00:25:29 -0800270 // Reject missized pollygons for now. Maybe rectify them here in the future;
Austin Schuh9f859ca2019-03-06 20:46:01 -0800271 if (poly.size() != 4) {
272 continue;
273 }
Parker Schuh2a1447c2019-02-17 00:25:29 -0800274 std::vector<Vector<2>> corners;
275 for (size_t i = 0; i < 4; ++i) {
Austin Schuh9f859ca2019-03-06 20:46:01 -0800276 Vector<2> corner = poly[i].Intersect(poly[(i + 1) % 4]);
277 if (::std::isnan(corner.x()) || ::std::isnan(corner.y())) {
278 break;
279 }
280 corners.push_back(corner);
281 }
282 if (corners.size() != 4) {
283 continue;
Parker Schuh2a1447c2019-02-17 00:25:29 -0800284 }
285
286 // Select the closest two points. Short side of the rectangle.
287 double min_dist = -1;
288 std::pair<size_t, size_t> closest;
289 for (size_t i = 0; i < 4; ++i) {
290 size_t next = (i + 1) % 4;
291 double nd = corners[i].SquaredDistanceTo(corners[next]);
292 if (min_dist == -1 || nd < min_dist) {
293 min_dist = nd;
294 closest.first = i;
295 closest.second = next;
296 }
297 }
298
299 // Verify our top is above the bottom.
300 size_t bot_index = closest.first;
301 size_t top_index = (closest.first + 2) % 4;
302 if (corners[top_index].y() < corners[bot_index].y()) {
303 closest.first = top_index;
304 closest.second = (top_index + 1) % 4;
305 }
306
307 // Find the major axis.
308 size_t far_first = (closest.first + 2) % 4;
309 size_t far_second = (closest.second + 2) % 4;
310 Segment<2> major_axis(
311 (corners[closest.first] + corners[closest.second]) * 0.5,
312 (corners[far_first] + corners[far_second]) * 0.5);
313 if (major_axis.AsVector().AngleToZero() > M_PI / 180.0 * 120.0 ||
314 major_axis.AsVector().AngleToZero() < M_PI / 180.0 * 60.0) {
315 // Target is angled way too much, drop it.
316 continue;
317 }
318
319 // organize the top points.
320 Vector<2> topA = corners[closest.first] - major_axis.B();
321 new_target.major_axis = major_axis;
322 if (major_axis.AsVector().AngleToZero() > M_PI / 2.0) {
323 // We have a left target since we are leaning positive.
324 new_target.is_right = false;
325 if (topA.AngleTo(major_axis.AsVector()) > 0.0) {
326 // And our A point is left of the major axis.
327 new_target.inside = corners[closest.second];
328 new_target.top = corners[closest.first];
329 } else {
330 // our A point is to the right of the major axis.
331 new_target.inside = corners[closest.first];
332 new_target.top = corners[closest.second];
333 }
334 } else {
335 // We have a right target since we are leaning negative.
336 new_target.is_right = true;
337 if (topA.AngleTo(major_axis.AsVector()) > 0.0) {
338 // And our A point is left of the major axis.
339 new_target.inside = corners[closest.first];
340 new_target.top = corners[closest.second];
341 } else {
342 // our A point is to the right of the major axis.
343 new_target.inside = corners[closest.second];
344 new_target.top = corners[closest.first];
345 }
346 }
347
348 // organize the top points.
349 Vector<2> botA = corners[far_first] - major_axis.A();
350 if (major_axis.AsVector().AngleToZero() > M_PI / 2.0) {
351 // We have a right target since we are leaning positive.
352 if (botA.AngleTo(major_axis.AsVector()) < M_PI) {
353 // And our A point is left of the major axis.
354 new_target.outside = corners[far_second];
355 new_target.bottom = corners[far_first];
356 } else {
357 // our A point is to the right of the major axis.
358 new_target.outside = corners[far_first];
359 new_target.bottom = corners[far_second];
360 }
361 } else {
362 // We have a left target since we are leaning negative.
363 if (botA.AngleTo(major_axis.AsVector()) < M_PI) {
364 // And our A point is left of the major axis.
365 new_target.outside = corners[far_first];
366 new_target.bottom = corners[far_second];
367 } else {
368 // our A point is to the right of the major axis.
369 new_target.outside = corners[far_second];
370 new_target.bottom = corners[far_first];
371 }
372 }
373
374 // This piece of the target should be ready now.
375 list.emplace_back(new_target);
376 }
377
378 return list;
379}
380
381// Match components into targets.
382std::vector<Target> TargetFinder::FindTargetsFromComponents(
383 const std::vector<TargetComponent> component_list, bool verbose) {
384 std::vector<Target> target_list;
385 using namespace aos::vision;
386 if (component_list.size() < 2) {
387 // We don't enough parts for a target.
388 return target_list;
389 }
390
391 for (size_t i = 0; i < component_list.size(); i++) {
392 const TargetComponent &a = component_list[i];
393 for (size_t j = 0; j < i; j++) {
394 bool target_valid = false;
395 Target new_target;
396 const TargetComponent &b = component_list[j];
397
398 // Reject targets that are too far off vertically.
399 Vector<2> a_center = a.major_axis.Center();
400 if (a_center.y() > b.bottom.y() || a_center.y() < b.top.y()) {
401 continue;
402 }
403 Vector<2> b_center = b.major_axis.Center();
404 if (b_center.y() > a.bottom.y() || b_center.y() < a.top.y()) {
405 continue;
406 }
407
408 if (a.is_right && !b.is_right) {
409 if (a.top.x() > b.top.x()) {
410 new_target.right = a;
411 new_target.left = b;
412 target_valid = true;
413 }
414 } else if (!a.is_right && b.is_right) {
415 if (b.top.x() > a.top.x()) {
416 new_target.right = b;
417 new_target.left = a;
418 target_valid = true;
419 }
420 }
421 if (target_valid) {
422 target_list.emplace_back(new_target);
423 }
424 }
425 }
426 if (verbose) printf("Possible Target: %zu.\n", target_list.size());
427 return target_list;
428}
429
430std::vector<IntermediateResult> TargetFinder::FilterResults(
Ben Fredricksona8c3d552019-03-03 14:14:53 -0800431 const std::vector<IntermediateResult> &results, uint64_t print_rate) {
Parker Schuh2a1447c2019-02-17 00:25:29 -0800432 std::vector<IntermediateResult> filtered;
433 for (const IntermediateResult &res : results) {
434 if (res.solver_error < 75.0) {
435 filtered.emplace_back(res);
436 }
437 }
Ben Fredricksona8c3d552019-03-03 14:14:53 -0800438 frame_count_++;
439 if (!filtered.empty()) {
440 valid_result_count_++;
441 }
442 if (print_rate > 0 && frame_count_ > print_rate) {
443 LOG(INFO, "Found (%zu / %zu)(%.2f) targets.\n", valid_result_count_,
444 frame_count_, (double)valid_result_count_ / (double)frame_count_);
445 frame_count_ = 0;
446 valid_result_count_ = 0;
447 }
448
Parker Schuh2a1447c2019-02-17 00:25:29 -0800449 return filtered;
450}
451
452} // namespace vision
453} // namespace y2019