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