blob: 1d991ff7d398ae7a52e618badcd2840bd413e26b [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();
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
Austin Schuhe5015972019-03-09 17:47:34 -080062::Eigen::Vector2f UnWarpPoint(const Point point) {
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 }
Austin Schuhe5015972019-03-09 17:47:34 -080074 const double nx = x * f_x_prime + c_x_prime;
75 const double ny = y * f_y_prime + c_y_prime;
76 return ::Eigen::Vector2f(nx, ny);
Ben Fredricksonf7b68522019-03-02 21:19:42 -080077}
78
Austin Schuhe5015972019-03-09 17:47:34 -080079::std::vector<::Eigen::Vector2f> TargetFinder::UnWarpContour(
80 ContourNode *start) const {
81 ::std::vector<::Eigen::Vector2f> result;
Ben Fredricksonf7b68522019-03-02 21:19:42 -080082 ContourNode *node = start;
83 while (node->next != start) {
Austin Schuhe5015972019-03-09 17:47:34 -080084 result.push_back(UnWarpPoint(node->pt));
Ben Fredricksonf7b68522019-03-02 21:19:42 -080085 node = node->next;
86 }
Austin Schuhe5015972019-03-09 17:47:34 -080087 result.push_back(UnWarpPoint(node->pt));
88 return result;
Ben Fredricksonf7b68522019-03-02 21:19:42 -080089}
90
Parker Schuh2a1447c2019-02-17 00:25:29 -080091// TODO: Try hierarchical merge for this.
92// Convert blobs into polygons.
93std::vector<aos::vision::Segment<2>> TargetFinder::FillPolygon(
Austin Schuhe5015972019-03-09 17:47:34 -080094 const ::std::vector<::Eigen::Vector2f> &contour, bool verbose) {
Parker Schuh2a1447c2019-02-17 00:25:29 -080095 if (verbose) printf("Process Polygon.\n");
Parker Schuh2a1447c2019-02-17 00:25:29 -080096
Austin Schuhe5015972019-03-09 17:47:34 -080097 ::std::vector<::Eigen::Vector2f> slopes;
Parker Schuh2a1447c2019-02-17 00:25:29 -080098
99 // Collect all slopes from the contour.
Austin Schuhe5015972019-03-09 17:47:34 -0800100 ::Eigen::Vector2f previous_point = contour[0];
101 for (size_t i = 0; i < contour.size(); ++i) {
102 ::Eigen::Vector2f next_point = contour[(i + 1) % contour.size()];
Parker Schuh2a1447c2019-02-17 00:25:29 -0800103
Austin Schuhe5015972019-03-09 17:47:34 -0800104 slopes.push_back(next_point - previous_point);
Parker Schuh2a1447c2019-02-17 00:25:29 -0800105
Austin Schuhe5015972019-03-09 17:47:34 -0800106 previous_point = next_point;
Parker Schuh2a1447c2019-02-17 00:25:29 -0800107 }
108
Austin Schuhe5015972019-03-09 17:47:34 -0800109 const int num_points = slopes.size();
110 auto get_pt = [&slopes, num_points](int i) {
111 return slopes[(i + num_points * 2) % num_points];
Austin Schuh335eef12019-03-02 17:04:17 -0800112 };
Parker Schuh2a1447c2019-02-17 00:25:29 -0800113
Austin Schuhe5015972019-03-09 17:47:34 -0800114 ::std::vector<::Eigen::Vector2f> filtered_slopes = slopes;
Austin Schuh335eef12019-03-02 17:04:17 -0800115 // Three box filter makith a guassian?
116 // Run gaussian filter over the slopes 3 times. That'll get us pretty close
117 // to running a gausian over it.
118 for (int k = 0; k < 3; ++k) {
119 const int window_size = 2;
Austin Schuhe5015972019-03-09 17:47:34 -0800120 for (size_t i = 0; i < slopes.size(); ++i) {
121 ::Eigen::Vector2f a = ::Eigen::Vector2f::Zero();
Parker Schuh2a1447c2019-02-17 00:25:29 -0800122 for (int j = -window_size; j <= window_size; ++j) {
Austin Schuhe5015972019-03-09 17:47:34 -0800123 ::Eigen::Vector2f p = get_pt(j + i);
124 a += p;
Parker Schuh2a1447c2019-02-17 00:25:29 -0800125 }
Austin Schuhe5015972019-03-09 17:47:34 -0800126 a /= (window_size * 2 + 1);
Parker Schuh2a1447c2019-02-17 00:25:29 -0800127
Austin Schuhe5015972019-03-09 17:47:34 -0800128 const float scale = 1.0 + (i / float(slopes.size() * 10));
129 a *= scale;
130 filtered_slopes[i] = a;
Parker Schuh2a1447c2019-02-17 00:25:29 -0800131 }
Austin Schuhe5015972019-03-09 17:47:34 -0800132 slopes = filtered_slopes;
Austin Schuh335eef12019-03-02 17:04:17 -0800133 }
Parker Schuh2a1447c2019-02-17 00:25:29 -0800134
135 // Heuristic which says if a particular slope is part of a corner.
136 auto is_corner = [&](size_t i) {
Austin Schuhe5015972019-03-09 17:47:34 -0800137 const ::Eigen::Vector2f a = get_pt(i - 3);
138 const ::Eigen::Vector2f b = get_pt(i + 3);
139 const double dx = (a.x() - b.x());
140 const double dy = (a.y() - b.y());
Parker Schuh2a1447c2019-02-17 00:25:29 -0800141 return dx * dx + dy * dy > 0.25;
142 };
143
144 bool prev_v = is_corner(-1);
145
146 // Find all centers of corners.
Austin Schuhe5015972019-03-09 17:47:34 -0800147 // Because they round, multiple slopes may be a corner.
148 ::std::vector<size_t> edges;
149 const size_t kBad = slopes.size() + 10;
Parker Schuh2a1447c2019-02-17 00:25:29 -0800150 size_t prev_up = kBad;
151 size_t wrapped_n = prev_up;
152
Austin Schuhe5015972019-03-09 17:47:34 -0800153 for (size_t i = 0; i < slopes.size(); ++i) {
Parker Schuh2a1447c2019-02-17 00:25:29 -0800154 bool v = is_corner(i);
155 if (prev_v && !v) {
156 if (prev_up == kBad) {
157 wrapped_n = i;
158 } else {
159 edges.push_back((prev_up + i - 1) / 2);
160 }
161 }
162 if (v && !prev_v) {
163 prev_up = i;
164 }
165 prev_v = v;
166 }
167
168 if (wrapped_n != kBad) {
Austin Schuhe5015972019-03-09 17:47:34 -0800169 edges.push_back(((prev_up + slopes.size() + wrapped_n - 1) / 2) % slopes.size());
Parker Schuh2a1447c2019-02-17 00:25:29 -0800170 }
171
172 if (verbose) printf("Edge Count (%zu).\n", edges.size());
173
Parker Schuh2a1447c2019-02-17 00:25:29 -0800174 // Run best-fits over each line segment.
Austin Schuhe5015972019-03-09 17:47:34 -0800175 ::std::vector<Segment<2>> seg_list;
176 if (edges.size() == 4) {
177 for (size_t i = 0; i < edges.size(); ++i) {
178 // Include the corners in both line fits.
179 const size_t segment_start_index = edges[i];
180 const size_t segment_end_index =
181 (edges[(i + 1) % edges.size()] + 1) % contour.size();
Parker Schuh2a1447c2019-02-17 00:25:29 -0800182 float mx = 0.0;
183 float my = 0.0;
184 int n = 0;
Austin Schuhe5015972019-03-09 17:47:34 -0800185 for (size_t j = segment_start_index; j != segment_end_index;
186 (j = (j + 1) % contour.size())) {
187 mx += contour[j].x();
188 my += contour[j].y();
Parker Schuh2a1447c2019-02-17 00:25:29 -0800189 ++n;
190 // (x - [x] / N) ** 2 = [x * x] - 2 * [x] * [x] / N + [x] * [x] / N / N;
191 }
192 mx /= n;
193 my /= n;
194
195 float xx = 0.0;
196 float xy = 0.0;
197 float yy = 0.0;
Austin Schuhe5015972019-03-09 17:47:34 -0800198 for (size_t j = segment_start_index; j != segment_end_index;
199 (j = (j + 1) % contour.size())) {
200 const float x = contour[j].x() - mx;
201 const float y = contour[j].y() - my;
Parker Schuh2a1447c2019-02-17 00:25:29 -0800202 xx += x * x;
203 xy += x * y;
204 yy += y * y;
205 }
206
207 // TODO: Extract common to hierarchical merge.
Austin Schuh335eef12019-03-02 17:04:17 -0800208 const float neg_b_over_2 = (xx + yy) / 2.0;
209 const float c = (xx * yy - xy * xy);
Parker Schuh2a1447c2019-02-17 00:25:29 -0800210
Austin Schuh335eef12019-03-02 17:04:17 -0800211 const float sqr = sqrt(neg_b_over_2 * neg_b_over_2 - c);
Parker Schuh2a1447c2019-02-17 00:25:29 -0800212
213 {
Austin Schuh335eef12019-03-02 17:04:17 -0800214 const float lam = neg_b_over_2 + sqr;
Parker Schuh2a1447c2019-02-17 00:25:29 -0800215 float x = xy;
216 float y = lam - xx;
217
Austin Schuh335eef12019-03-02 17:04:17 -0800218 const float norm = hypot(x, y);
Parker Schuh2a1447c2019-02-17 00:25:29 -0800219 x /= norm;
220 y /= norm;
221
222 seg_list.push_back(
223 Segment<2>(Vector<2>(mx, my), Vector<2>(mx + x, my + y)));
224 }
225
226 /* Characteristic polynomial
227 1 lam^2 - (xx + yy) lam + (xx * yy - xy * xy) = 0
228
229 [a b]
230 [c d]
231
232 // covariance matrix.
233 [xx xy] [nx]
234 [xy yy] [ny]
235 */
236 }
237 }
238 if (verbose) printf("Poly Count (%zu).\n", seg_list.size());
239 return seg_list;
240}
241
242// Convert segments into target components (left or right)
243std::vector<TargetComponent> TargetFinder::FillTargetComponentList(
244 const std::vector<std::vector<Segment<2>>> &seg_list) {
245 std::vector<TargetComponent> list;
246 TargetComponent new_target;
Austin Schuh335eef12019-03-02 17:04:17 -0800247 for (const std::vector<Segment<2>> &poly : seg_list) {
Parker Schuh2a1447c2019-02-17 00:25:29 -0800248 // Reject missized pollygons for now. Maybe rectify them here in the future;
Austin Schuh9f859ca2019-03-06 20:46:01 -0800249 if (poly.size() != 4) {
250 continue;
251 }
Parker Schuh2a1447c2019-02-17 00:25:29 -0800252 std::vector<Vector<2>> corners;
253 for (size_t i = 0; i < 4; ++i) {
Austin Schuh9f859ca2019-03-06 20:46:01 -0800254 Vector<2> corner = poly[i].Intersect(poly[(i + 1) % 4]);
255 if (::std::isnan(corner.x()) || ::std::isnan(corner.y())) {
256 break;
257 }
258 corners.push_back(corner);
259 }
260 if (corners.size() != 4) {
261 continue;
Parker Schuh2a1447c2019-02-17 00:25:29 -0800262 }
263
264 // Select the closest two points. Short side of the rectangle.
265 double min_dist = -1;
266 std::pair<size_t, size_t> closest;
267 for (size_t i = 0; i < 4; ++i) {
268 size_t next = (i + 1) % 4;
269 double nd = corners[i].SquaredDistanceTo(corners[next]);
270 if (min_dist == -1 || nd < min_dist) {
271 min_dist = nd;
272 closest.first = i;
273 closest.second = next;
274 }
275 }
276
277 // Verify our top is above the bottom.
278 size_t bot_index = closest.first;
279 size_t top_index = (closest.first + 2) % 4;
280 if (corners[top_index].y() < corners[bot_index].y()) {
281 closest.first = top_index;
282 closest.second = (top_index + 1) % 4;
283 }
284
285 // Find the major axis.
286 size_t far_first = (closest.first + 2) % 4;
287 size_t far_second = (closest.second + 2) % 4;
288 Segment<2> major_axis(
289 (corners[closest.first] + corners[closest.second]) * 0.5,
290 (corners[far_first] + corners[far_second]) * 0.5);
291 if (major_axis.AsVector().AngleToZero() > M_PI / 180.0 * 120.0 ||
292 major_axis.AsVector().AngleToZero() < M_PI / 180.0 * 60.0) {
293 // Target is angled way too much, drop it.
294 continue;
295 }
296
297 // organize the top points.
298 Vector<2> topA = corners[closest.first] - major_axis.B();
299 new_target.major_axis = major_axis;
300 if (major_axis.AsVector().AngleToZero() > M_PI / 2.0) {
301 // We have a left target since we are leaning positive.
302 new_target.is_right = false;
303 if (topA.AngleTo(major_axis.AsVector()) > 0.0) {
304 // And our A point is left of the major axis.
305 new_target.inside = corners[closest.second];
306 new_target.top = corners[closest.first];
307 } else {
308 // our A point is to the right of the major axis.
309 new_target.inside = corners[closest.first];
310 new_target.top = corners[closest.second];
311 }
312 } else {
313 // We have a right target since we are leaning negative.
314 new_target.is_right = true;
315 if (topA.AngleTo(major_axis.AsVector()) > 0.0) {
316 // And our A point is left of the major axis.
317 new_target.inside = corners[closest.first];
318 new_target.top = corners[closest.second];
319 } else {
320 // our A point is to the right of the major axis.
321 new_target.inside = corners[closest.second];
322 new_target.top = corners[closest.first];
323 }
324 }
325
326 // organize the top points.
327 Vector<2> botA = corners[far_first] - major_axis.A();
328 if (major_axis.AsVector().AngleToZero() > M_PI / 2.0) {
329 // We have a right target since we are leaning positive.
330 if (botA.AngleTo(major_axis.AsVector()) < M_PI) {
331 // And our A point is left of the major axis.
332 new_target.outside = corners[far_second];
333 new_target.bottom = corners[far_first];
334 } else {
335 // our A point is to the right of the major axis.
336 new_target.outside = corners[far_first];
337 new_target.bottom = corners[far_second];
338 }
339 } else {
340 // We have a left target since we are leaning negative.
341 if (botA.AngleTo(major_axis.AsVector()) < M_PI) {
342 // And our A point is left of the major axis.
343 new_target.outside = corners[far_first];
344 new_target.bottom = corners[far_second];
345 } else {
346 // our A point is to the right of the major axis.
347 new_target.outside = corners[far_second];
348 new_target.bottom = corners[far_first];
349 }
350 }
351
352 // This piece of the target should be ready now.
353 list.emplace_back(new_target);
354 }
355
356 return list;
357}
358
359// Match components into targets.
360std::vector<Target> TargetFinder::FindTargetsFromComponents(
361 const std::vector<TargetComponent> component_list, bool verbose) {
362 std::vector<Target> target_list;
363 using namespace aos::vision;
364 if (component_list.size() < 2) {
365 // We don't enough parts for a target.
366 return target_list;
367 }
368
369 for (size_t i = 0; i < component_list.size(); i++) {
370 const TargetComponent &a = component_list[i];
371 for (size_t j = 0; j < i; j++) {
372 bool target_valid = false;
373 Target new_target;
374 const TargetComponent &b = component_list[j];
375
376 // Reject targets that are too far off vertically.
377 Vector<2> a_center = a.major_axis.Center();
378 if (a_center.y() > b.bottom.y() || a_center.y() < b.top.y()) {
379 continue;
380 }
381 Vector<2> b_center = b.major_axis.Center();
382 if (b_center.y() > a.bottom.y() || b_center.y() < a.top.y()) {
383 continue;
384 }
385
386 if (a.is_right && !b.is_right) {
387 if (a.top.x() > b.top.x()) {
388 new_target.right = a;
389 new_target.left = b;
390 target_valid = true;
391 }
392 } else if (!a.is_right && b.is_right) {
393 if (b.top.x() > a.top.x()) {
394 new_target.right = b;
395 new_target.left = a;
396 target_valid = true;
397 }
398 }
399 if (target_valid) {
400 target_list.emplace_back(new_target);
401 }
402 }
403 }
404 if (verbose) printf("Possible Target: %zu.\n", target_list.size());
405 return target_list;
406}
407
408std::vector<IntermediateResult> TargetFinder::FilterResults(
Ben Fredricksona8c3d552019-03-03 14:14:53 -0800409 const std::vector<IntermediateResult> &results, uint64_t print_rate) {
Parker Schuh2a1447c2019-02-17 00:25:29 -0800410 std::vector<IntermediateResult> filtered;
411 for (const IntermediateResult &res : results) {
412 if (res.solver_error < 75.0) {
413 filtered.emplace_back(res);
414 }
415 }
Ben Fredricksona8c3d552019-03-03 14:14:53 -0800416 frame_count_++;
417 if (!filtered.empty()) {
418 valid_result_count_++;
419 }
420 if (print_rate > 0 && frame_count_ > print_rate) {
421 LOG(INFO, "Found (%zu / %zu)(%.2f) targets.\n", valid_result_count_,
422 frame_count_, (double)valid_result_count_ / (double)frame_count_);
423 frame_count_ = 0;
424 valid_result_count_ = 0;
425 }
426
Parker Schuh2a1447c2019-02-17 00:25:29 -0800427 return filtered;
428}
429
430} // namespace vision
431} // namespace y2019