blob: 8ee8e925117ea8d9120b0bc8f86fe544e5214f36 [file] [log] [blame]
Parker Schuh6691f192017-01-14 17:01:02 -08001#include "aos/vision/blob/find_blob.h"
2
3#include "aos/vision/blob/disjoint_set.h"
4
Stephan Pleinesf63bde82024-01-13 15:59:33 -08005namespace aos::vision {
Parker Schuh6691f192017-01-14 17:01:02 -08006
7struct BlobBuilder {
8 BlobBuilder(int i) : min_y(i) {}
9 void Add(int i, ImageRange rng) {
10 while (static_cast<int>(ranges.size()) <= i - min_y) {
11 ranges.emplace_back();
12 }
13 ranges[i - min_y].push_back(rng);
14 }
15
16 void Merge(const BlobBuilder &o) {
17 // Always will be positive because of std::swap below in
18 // MergeInBlob maintains y order.
19 int diff = o.min_y - min_y;
20 for (int j = 0; j < static_cast<int>(o.ranges.size()); j++) {
21 int i = j + diff;
22 while (static_cast<int>(ranges.size()) <= i) {
23 ranges.emplace_back();
24 }
25
26 std::vector<ImageRange> cur = ranges[i];
27 std::vector<ImageRange> prev = o.ranges[j];
28
29 // Merge sort of cur and prev.
30 ranges[i].clear();
31 int a = 0;
32 int b = 0;
33 while (a < static_cast<int>(cur.size()) &&
34 b < static_cast<int>(prev.size())) {
35 if (cur[a].st < prev[b].st) {
36 ranges[i].push_back(cur[a++]);
37 } else {
38 ranges[i].push_back(prev[b++]);
39 }
40 }
41 while (a < static_cast<int>(cur.size())) {
42 ranges[i].push_back(cur[a++]);
43 }
44 while (b < static_cast<int>(prev.size())) {
45 ranges[i].push_back(prev[b++]);
46 }
47 }
48 }
49 std::vector<std::vector<ImageRange>> ranges;
50 int min_y;
51};
52
53// Uses disjoint set class to track range images.
Parker Schuh0ff777c2017-02-19 15:01:13 -080054// Joins in the disjoint set are done at the same time as joins in the
Parker Schuh6691f192017-01-14 17:01:02 -080055// range image.
56class BlobDisjointSet {
57 public:
58 int AddToBlob(int bid, int i, ImageRange img) {
59 bid = disjoint_set.Find(bid);
60 items[bid].Add(i, img);
61 return bid;
62 }
63 int AddBlob(int i, ImageRange img) {
64 int bid = disjoint_set.Add();
65 items.emplace_back(i);
66 items[bid].Add(i, img);
67 return bid;
68 }
69 void MergeInBlob(int cbid, int pbid) {
70 cbid = disjoint_set.Find(cbid);
71 pbid = disjoint_set.Find(pbid);
72 if (cbid != pbid) {
73 if (items[cbid].min_y > items[pbid].min_y) {
74 std::swap(cbid, pbid);
75 }
76 items[cbid].Merge(items[pbid]);
77 disjoint_set.ExplicitUnion(pbid, cbid);
78 }
79 }
80 BlobList MoveBlobs() {
81 std::vector<RangeImage> blobs;
82 for (int i = 0; i < static_cast<int>(items.size()); i++) {
83 if (disjoint_set.IsRoot(i)) {
84 blobs.emplace_back(items[i].min_y, std::move(items[i].ranges));
85 }
86 }
87 return blobs;
88 }
89
90 private:
91 DisjointSet disjoint_set;
92 std::vector<BlobBuilder> items;
93};
94
Brian Silvermanb27e6b72019-03-10 16:17:42 -070095BlobList FindBlobs(const RangeImage &input_image) {
Parker Schuh6691f192017-01-14 17:01:02 -080096 BlobDisjointSet blob_set;
Brian Silvermanb27e6b72019-03-10 16:17:42 -070097 std::vector<int> previous_ids;
98 std::vector<int> current_ids;
99 for (ImageRange input_range : input_image.ranges()[0]) {
100 previous_ids.push_back(blob_set.AddBlob(0, input_range));
Parker Schuh6691f192017-01-14 17:01:02 -0800101 }
102
Brian Silvermanb27e6b72019-03-10 16:17:42 -0700103 for (int input_row = 1; input_row < input_image.size(); input_row++) {
104 // The index of previous_ranges we're currently considering.
105 int previous_location = 0;
106 // The index of current_ranges we're currently considering.
107 int current_location = 0;
108 const std::vector<ImageRange> &previous_ranges =
109 input_image.ranges()[input_row - 1];
110 const std::vector<ImageRange> &current_ranges =
111 input_image.ranges()[input_row];
112 current_ids.clear();
Parker Schuh6691f192017-01-14 17:01:02 -0800113
Brian Silvermanb27e6b72019-03-10 16:17:42 -0700114 while (previous_location < static_cast<int>(previous_ranges.size()) &&
115 current_location < static_cast<int>(current_ranges.size())) {
116 const ImageRange previous_range = previous_ranges[previous_location];
117 const ImageRange current_range = current_ranges[current_location];
118 if (current_range.last() < previous_range.st) {
119 // If current_range ends before previous_range starts, then they don't
120 // overlap, so we might want to add current_range to a separate blob.
121 if (static_cast<int>(current_ids.size()) == current_location) {
122 // We only want to add it if we haven't already added this
123 // current_range to a blob.
124 current_ids.push_back(blob_set.AddBlob(input_row, current_range));
Parker Schuh6691f192017-01-14 17:01:02 -0800125 }
Brian Silvermanb27e6b72019-03-10 16:17:42 -0700126 current_location++;
127 } else if (previous_range.last() < current_range.st) {
128 // If previous_range ends before current_range starts, then they don't
129 // overlap. Definitely nothing to merge before current_range in the
130 // current row.
131 previous_location++;
Parker Schuh6691f192017-01-14 17:01:02 -0800132 } else {
Brian Silvermanb27e6b72019-03-10 16:17:42 -0700133 if (static_cast<int>(current_ids.size()) > current_location) {
134 // If we've already added current_range, and they still overlap, then
135 // we should merge the blobs.
136 blob_set.MergeInBlob(current_ids[current_location],
137 previous_ids[previous_location]);
Parker Schuh6691f192017-01-14 17:01:02 -0800138 } else {
Brian Silvermanb27e6b72019-03-10 16:17:42 -0700139 // If we haven't yet added current_range to a blob, then do that now.
140 current_ids.push_back(
141 blob_set.AddToBlob(previous_ids[previous_location], input_row,
142 current_ranges[current_location]));
Parker Schuh6691f192017-01-14 17:01:02 -0800143 }
Brian Silvermanb27e6b72019-03-10 16:17:42 -0700144 // Increment the furthest-left-ending range in either row. The
145 // further-right-ending one might merge with the next one in the other
146 // row, so we need to look at it again next iteration.
147 if (current_range.last() < previous_range.last()) {
148 current_location++;
Parker Schuh6691f192017-01-14 17:01:02 -0800149 } else {
Brian Silvermanb27e6b72019-03-10 16:17:42 -0700150 previous_location++;
Parker Schuh6691f192017-01-14 17:01:02 -0800151 }
152 }
153 }
Brian Silvermanb27e6b72019-03-10 16:17:42 -0700154 // Finish processing the current row. This is sometimes necessary if the
155 // previous row was fully processed first.
156 //
157 // Note that we don't care if the previous row didn't get fully iterated
158 // over.
159 while (current_location < static_cast<int>(current_ranges.size())) {
160 if (static_cast<int>(current_ids.size()) == current_location) {
161 // We only want to add it if we haven't already added this range to a
162 // blob.
163 current_ids.push_back(
164 blob_set.AddBlob(input_row, current_ranges[current_location]));
Parker Schuh6691f192017-01-14 17:01:02 -0800165 }
Brian Silvermanb27e6b72019-03-10 16:17:42 -0700166 current_location++;
Parker Schuh6691f192017-01-14 17:01:02 -0800167 }
Brian Silvermanb27e6b72019-03-10 16:17:42 -0700168
169 // Update previous_ids for the next iteration.
170 std::swap(previous_ids, current_ids);
Parker Schuh6691f192017-01-14 17:01:02 -0800171 }
172 return blob_set.MoveBlobs();
173}
174
Stephan Pleinesf63bde82024-01-13 15:59:33 -0800175} // namespace aos::vision