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