blob: a789b777824d0ad68e510ea0626f5438bd5f0b7e [file] [log] [blame]
#include "aos/vision/blob/contour.h"
#include <vector>
namespace aos::vision {
namespace {
// Half-loop of a contour.
// This represents a range-line in a range-image
// at the current sweep of the range image propagation.
struct HalfContour {
// start
ContourNode *st;
// end
ContourNode *ed;
};
// The following helper functions handle different cases for extending
// overlapping ranges (from the first line to the next line).
// Constructs a half-contour for the first line in a range-image.
// This happens for ranges that have no overlaps in the previous line.
// hc.st ----- hc.ed
// This is symmetric to CloseRange.
// stx is start_x; edx is end_x; alloc is the region allocator.
HalfContour FwdRange(int y, int stx, int edx, AnalysisAllocator *alloc) {
ContourNode *st = alloc->cons_obj<ContourNode>(stx, y);
ContourNode *ed = st;
st->next = st;
for (int x = stx + 1; x < edx; x++) {
ed = alloc->cons_obj<ContourNode>(x, y, ed);
}
return HalfContour{st, ed};
}
// Connects hc.st to hc.ed assuming intervening range.
// hc.st ----- hc.ed
// This closes out the contour.
void CloseRange(HalfContour hc, AnalysisAllocator *alloc) {
auto p_end = hc.ed;
auto p_cur = hc.st;
while (p_cur->pt.x < p_end->pt.x - 1) {
p_cur = p_cur->append(p_cur->pt.x + 1, p_cur->pt.y, alloc);
}
if (p_end->pt.x == p_cur->pt.x) {
// Remove duplicated pixel for length 1 ranges.
p_cur->next = p_end->next;
} else {
p_cur->next = p_end;
}
}
// Connects pst to the return value (r).
// pst------
// r------
// cst is the x position of r.
ContourNode *ExtendStart(ContourNode *pst, int cst, AnalysisAllocator *alloc) {
while (pst->pt.x < cst) {
pst = pst->append(pst->pt.x + 1, pst->pt.y, alloc);
}
pst = pst->append(pst->pt.x, pst->pt.y + 1, alloc);
while (pst->pt.x > cst) {
pst = pst->append(pst->pt.x - 1, pst->pt.y, alloc);
}
return pst;
}
// Connects pst to the return value (r)
// ------- pst
// --- r
// cst is the x position of r.
ContourNode *ExtendEnd(ContourNode *pst, int cst, AnalysisAllocator *alloc) {
while (pst->pt.x > cst) {
pst = pst->pappend(pst->pt.x - 1, pst->pt.y, alloc);
}
pst = pst->pappend(pst->pt.x, pst->pt.y + 1, alloc);
while (pst->pt.x < cst) {
pst = pst->pappend(pst->pt.x + 1, pst->pt.y, alloc);
}
return pst;
}
// Connects concave contour like this:
//
// --pst est--
// ----------------
void CapRange(ContourNode *pst, ContourNode *est, AnalysisAllocator *alloc) {
est = est->append(est->pt.x, est->pt.y + 1, alloc);
while (est->pt.x > pst->pt.x) {
est = est->append(est->pt.x - 1, est->pt.y, alloc);
}
est->next = pst;
}
// Constructs the starting range like so
// Return value (r)
//
// ----------------------
// ---r.st r.ed----
HalfContour MakeCavity(int i, int sti, int edi, AnalysisAllocator *alloc) {
ContourNode *st = alloc->cons_obj<ContourNode>(sti, i);
ContourNode *ed = st;
for (int x = sti + 1; x < edi; x++) {
ed = ed->append(x, i, alloc);
}
return HalfContour{st, ed};
}
} // namespace
// Sweepline conversion from RangeImage to ContourNode loop.
// Internal cavities are not returned.
ContourNode *RangeImgToContour(const RangeImage &rimg,
AnalysisAllocator *alloc) {
alloc->reset();
// prev list and current list plst is mutated to include ranges from the
// current line
// becoming clst.
std::vector<HalfContour> clst;
std::vector<HalfContour> plst;
for (int x = 0; x < static_cast<int>(rimg.ranges()[0].size()); x++) {
ImageRange rng = rimg.ranges()[0][x];
plst.emplace_back(FwdRange(rimg.min_y(), rng.st, rng.ed, alloc));
}
for (int i = 1; i < static_cast<int>(rimg.size()); i++) {
const std::vector<ImageRange> &pranges = rimg.ranges()[i - 1];
const std::vector<ImageRange> &cranges = rimg.ranges()[i];
int y = i + rimg.min_y();
clst.clear();
// prev merge id and current merge id.
int mp = 0;
int mc = 0;
// This is a merge-sort of the previous list of ranges and the new-list
// of ranges. The HafContour list above have a one to one mapping with
// the range images here.
while (mp < static_cast<int>(pranges.size()) &&
mc < static_cast<int>(cranges.size())) {
ImageRange rprev = pranges[mp];
ImageRange rcur = cranges[mc];
if (rcur.last() < rprev.st) {
clst.emplace_back(FwdRange(y, rcur.st, rcur.ed, alloc));
mc++;
} else if (rprev.last() < rcur.st) {
CloseRange(plst[mp], alloc);
mp++;
} else {
ContourNode *within_pb = plst[mp].ed;
ContourNode *within_ca = ExtendStart(plst[mp].st, rcur.st, alloc);
while (true) {
if (mp + 1 < static_cast<int>(pranges.size()) &&
rcur.last() >= pranges[mp + 1].st) {
mp++;
CapRange(within_pb, plst[mp].st, alloc);
within_pb = plst[mp].ed;
rprev = pranges[mp];
} else if (mc + 1 < static_cast<int>(cranges.size()) &&
rprev.last() >= cranges[mc + 1].st) {
auto cav_t = MakeCavity(y, rcur.last(), cranges[mc + 1].st, alloc);
clst.emplace_back(HalfContour{within_ca, cav_t.st});
within_ca = cav_t.ed;
mc++;
rcur = cranges[mc];
} else {
within_pb = ExtendEnd(within_pb, rcur.last(), alloc);
clst.emplace_back(HalfContour{within_ca, within_pb});
mc++;
mp++;
break;
}
}
}
}
while (mc < static_cast<int>(cranges.size())) {
ImageRange rcur = cranges[mc];
clst.emplace_back(FwdRange(y, rcur.st, rcur.ed, alloc));
mc++;
}
while (mp < static_cast<int>(pranges.size())) {
CloseRange(plst[mp], alloc);
mp++;
}
std::swap(clst, plst);
}
for (int mp = 0; mp < static_cast<int>(plst.size()); mp++) {
CloseRange(plst[mp], alloc);
}
return plst[0].st;
}
} // namespace aos::vision