blob: a789b777824d0ad68e510ea0626f5438bd5f0b7e [file] [log] [blame]
Parker Schuh6691f192017-01-14 17:01:02 -08001#include "aos/vision/blob/contour.h"
2
Stephan Pleinescc500b92024-05-30 10:58:40 -07003#include <vector>
4
Stephan Pleinesf63bde82024-01-13 15:59:33 -08005namespace aos::vision {
Parker Schuh6691f192017-01-14 17:01:02 -08006
7namespace {
8// Half-loop of a contour.
9// This represents a range-line in a range-image
10// at the current sweep of the range image propagation.
11struct HalfContour {
12 // start
13 ContourNode *st;
14 // end
15 ContourNode *ed;
16};
17
18// The following helper functions handle different cases for extending
19// overlapping ranges (from the first line to the next line).
20
21// Constructs a half-contour for the first line in a range-image.
22// This happens for ranges that have no overlaps in the previous line.
23// hc.st ----- hc.ed
24// This is symmetric to CloseRange.
25// stx is start_x; edx is end_x; alloc is the region allocator.
26HalfContour FwdRange(int y, int stx, int edx, AnalysisAllocator *alloc) {
27 ContourNode *st = alloc->cons_obj<ContourNode>(stx, y);
28 ContourNode *ed = st;
29 st->next = st;
30 for (int x = stx + 1; x < edx; x++) {
31 ed = alloc->cons_obj<ContourNode>(x, y, ed);
32 }
33 return HalfContour{st, ed};
34}
35
36// Connects hc.st to hc.ed assuming intervening range.
37// hc.st ----- hc.ed
38// This closes out the contour.
39void CloseRange(HalfContour hc, AnalysisAllocator *alloc) {
40 auto p_end = hc.ed;
41 auto p_cur = hc.st;
42 while (p_cur->pt.x < p_end->pt.x - 1) {
43 p_cur = p_cur->append(p_cur->pt.x + 1, p_cur->pt.y, alloc);
44 }
45 if (p_end->pt.x == p_cur->pt.x) {
46 // Remove duplicated pixel for length 1 ranges.
47 p_cur->next = p_end->next;
48 } else {
49 p_cur->next = p_end;
50 }
51}
52
53// Connects pst to the return value (r).
54// pst------
55// r------
56// cst is the x position of r.
57ContourNode *ExtendStart(ContourNode *pst, int cst, AnalysisAllocator *alloc) {
58 while (pst->pt.x < cst) {
59 pst = pst->append(pst->pt.x + 1, pst->pt.y, alloc);
60 }
61 pst = pst->append(pst->pt.x, pst->pt.y + 1, alloc);
62 while (pst->pt.x > cst) {
63 pst = pst->append(pst->pt.x - 1, pst->pt.y, alloc);
64 }
65 return pst;
66}
67
68// Connects pst to the return value (r)
69// ------- pst
70// --- r
71// cst is the x position of r.
72ContourNode *ExtendEnd(ContourNode *pst, int cst, AnalysisAllocator *alloc) {
73 while (pst->pt.x > cst) {
74 pst = pst->pappend(pst->pt.x - 1, pst->pt.y, alloc);
75 }
76 pst = pst->pappend(pst->pt.x, pst->pt.y + 1, alloc);
77 while (pst->pt.x < cst) {
78 pst = pst->pappend(pst->pt.x + 1, pst->pt.y, alloc);
79 }
80 return pst;
81}
82
83// Connects concave contour like this:
84//
85// --pst est--
86// ----------------
87void CapRange(ContourNode *pst, ContourNode *est, AnalysisAllocator *alloc) {
88 est = est->append(est->pt.x, est->pt.y + 1, alloc);
89 while (est->pt.x > pst->pt.x) {
90 est = est->append(est->pt.x - 1, est->pt.y, alloc);
91 }
92 est->next = pst;
93}
94
95// Constructs the starting range like so
96// Return value (r)
97//
98// ----------------------
99// ---r.st r.ed----
100HalfContour MakeCavity(int i, int sti, int edi, AnalysisAllocator *alloc) {
101 ContourNode *st = alloc->cons_obj<ContourNode>(sti, i);
102 ContourNode *ed = st;
103 for (int x = sti + 1; x < edi; x++) {
104 ed = ed->append(x, i, alloc);
105 }
106 return HalfContour{st, ed};
107}
108} // namespace
109
110// Sweepline conversion from RangeImage to ContourNode loop.
111// Internal cavities are not returned.
112ContourNode *RangeImgToContour(const RangeImage &rimg,
113 AnalysisAllocator *alloc) {
114 alloc->reset();
115 // prev list and current list plst is mutated to include ranges from the
116 // current line
117 // becoming clst.
118 std::vector<HalfContour> clst;
119 std::vector<HalfContour> plst;
120
121 for (int x = 0; x < static_cast<int>(rimg.ranges()[0].size()); x++) {
122 ImageRange rng = rimg.ranges()[0][x];
123 plst.emplace_back(FwdRange(rimg.min_y(), rng.st, rng.ed, alloc));
124 }
125
126 for (int i = 1; i < static_cast<int>(rimg.size()); i++) {
127 const std::vector<ImageRange> &pranges = rimg.ranges()[i - 1];
128 const std::vector<ImageRange> &cranges = rimg.ranges()[i];
129 int y = i + rimg.min_y();
130 clst.clear();
131 // prev merge id and current merge id.
132 int mp = 0;
133 int mc = 0;
134
135 // This is a merge-sort of the previous list of ranges and the new-list
136 // of ranges. The HafContour list above have a one to one mapping with
137 // the range images here.
138 while (mp < static_cast<int>(pranges.size()) &&
139 mc < static_cast<int>(cranges.size())) {
140 ImageRange rprev = pranges[mp];
141 ImageRange rcur = cranges[mc];
142 if (rcur.last() < rprev.st) {
143 clst.emplace_back(FwdRange(y, rcur.st, rcur.ed, alloc));
144 mc++;
145 } else if (rprev.last() < rcur.st) {
146 CloseRange(plst[mp], alloc);
147 mp++;
148 } else {
149 ContourNode *within_pb = plst[mp].ed;
150 ContourNode *within_ca = ExtendStart(plst[mp].st, rcur.st, alloc);
151
152 while (true) {
153 if (mp + 1 < static_cast<int>(pranges.size()) &&
154 rcur.last() >= pranges[mp + 1].st) {
155 mp++;
156 CapRange(within_pb, plst[mp].st, alloc);
157 within_pb = plst[mp].ed;
158 rprev = pranges[mp];
159 } else if (mc + 1 < static_cast<int>(cranges.size()) &&
160 rprev.last() >= cranges[mc + 1].st) {
161 auto cav_t = MakeCavity(y, rcur.last(), cranges[mc + 1].st, alloc);
162 clst.emplace_back(HalfContour{within_ca, cav_t.st});
163 within_ca = cav_t.ed;
164 mc++;
165 rcur = cranges[mc];
166 } else {
167 within_pb = ExtendEnd(within_pb, rcur.last(), alloc);
168 clst.emplace_back(HalfContour{within_ca, within_pb});
169 mc++;
170 mp++;
171 break;
172 }
173 }
174 }
175 }
176 while (mc < static_cast<int>(cranges.size())) {
177 ImageRange rcur = cranges[mc];
178 clst.emplace_back(FwdRange(y, rcur.st, rcur.ed, alloc));
179 mc++;
180 }
181
182 while (mp < static_cast<int>(pranges.size())) {
183 CloseRange(plst[mp], alloc);
184 mp++;
185 }
186 std::swap(clst, plst);
187 }
188
189 for (int mp = 0; mp < static_cast<int>(plst.size()); mp++) {
190 CloseRange(plst[mp], alloc);
191 }
192 return plst[0].st;
193}
194
Stephan Pleinesf63bde82024-01-13 15:59:33 -0800195} // namespace aos::vision