Some additions to aos/vision/blob (transpose and move_scale).
Change-Id: I927144e27d0494f3ea879ebbd1647741f82e3c7b
diff --git a/aos/vision/blob/BUILD b/aos/vision/blob/BUILD
index f823cc0..cf728c5 100644
--- a/aos/vision/blob/BUILD
+++ b/aos/vision/blob/BUILD
@@ -94,6 +94,43 @@
],
)
+cc_library(
+ name = 'move_scale',
+ hdrs = ['move_scale.h'],
+ srcs = ['move_scale.cc'],
+ deps = [
+ ':range_image',
+ ],
+)
+
+cc_library(
+ name = 'test_utils',
+ hdrs = ['test_utils.h'],
+ srcs = ['test_utils.cc'],
+ deps = [
+ ':range_image',
+ ],
+)
+
+cc_library(
+ name = 'transpose',
+ hdrs = ['transpose.h'],
+ srcs = ['transpose.cc'],
+ deps = [
+ ':range_image',
+ ],
+)
+
+cc_test(
+ name = 'transpose_test',
+ srcs = ['transpose_test.cc'],
+ deps = [
+ ':transpose',
+ ':test_utils',
+ '//aos/testing:googletest',
+ ],
+)
+
gtk_dependent_cc_library(
name = 'stream_view',
hdrs = ['stream_view.h'],
diff --git a/aos/vision/blob/codec.h b/aos/vision/blob/codec.h
index c2bc30d..b8a8089 100644
--- a/aos/vision/blob/codec.h
+++ b/aos/vision/blob/codec.h
@@ -12,11 +12,12 @@
struct IntCodec {
static constexpr size_t kSize = sizeof(T);
static inline char *Write(char *data, T ival) {
- *(reinterpret_cast<T *>(data)) = ival;
+ memcpy(data, &ival, sizeof(T));
return data + kSize;
}
static inline T Read(const char **data) {
- auto datum = *(reinterpret_cast<const T *>(*data));
+ T datum;
+ memcpy(&datum, *data, sizeof(T));
*data += kSize;
return datum;
}
diff --git a/aos/vision/blob/find_blob.cc b/aos/vision/blob/find_blob.cc
index 0e2586b..3153fcb 100644
--- a/aos/vision/blob/find_blob.cc
+++ b/aos/vision/blob/find_blob.cc
@@ -52,7 +52,7 @@
};
// Uses disjoint set class to track range images.
-// Joins in the disjoint set are done at the same time as joins in the
+// Joins in the disjoint set are done at the same time as joins in the
// range image.
class BlobDisjointSet {
public:
diff --git a/aos/vision/blob/hierarchical_contour_merge.cc b/aos/vision/blob/hierarchical_contour_merge.cc
index b3d9f41..ec617ca 100644
--- a/aos/vision/blob/hierarchical_contour_merge.cc
+++ b/aos/vision/blob/hierarchical_contour_merge.cc
@@ -22,7 +22,7 @@
// This is an exclusive range lookup into a modulo ring.
// The integral is precomputed in items_ and is inclusive even though
- // the input is [a, b).
+ // the input is [a, b).
T Get(int a, int b) {
a = Mod(a, items_.size());
b = Mod(b, items_.size());
diff --git a/aos/vision/blob/move_scale.cc b/aos/vision/blob/move_scale.cc
new file mode 100644
index 0000000..1caa479
--- /dev/null
+++ b/aos/vision/blob/move_scale.cc
@@ -0,0 +1,42 @@
+#include "aos/vision/blob/move_scale.h"
+
+namespace aos {
+namespace vision {
+
+RangeImage MoveScale(const RangeImage &img, int dx, int dy, int scale) {
+ std::vector<std::vector<ImageRange>> out_range_list;
+ for (const auto &range_list : img) {
+ std::vector<ImageRange> ranges;
+ for (const auto &range : range_list) {
+ ranges.emplace_back((range.st + dx) * scale, (range.ed + dx) * scale);
+ }
+ for (int i = 1; i < scale; ++i) {
+ out_range_list.push_back(ranges);
+ }
+ out_range_list.emplace_back(std::move(ranges));
+ }
+ return RangeImage((img.mini() + dy) * scale, std::move(out_range_list));
+}
+
+std::vector<RangeImage> MoveScale(const std::vector<RangeImage> &imgs, int dx,
+ int dy, int scale) {
+ std::vector<RangeImage> out;
+ for (const auto &img : imgs) {
+ out.emplace_back(MoveScale(img, dx, dy, scale));
+ }
+ return out;
+}
+
+void GetBBox(const RangeImage &img, ImageBBox *bbox) {
+ bbox->miny = std::min(img.min_y(), bbox->miny);
+ bbox->maxy = std::max(img.min_y() + img.size(), bbox->maxy);
+ for (const auto &range_lst : img) {
+ if (range_lst.size() > 0) {
+ bbox->maxx = std::max(range_lst[range_lst.size() - 1].ed, bbox->maxx);
+ bbox->minx = std::min(range_lst[0].st, bbox->minx);
+ }
+ }
+}
+
+} // namespace vision
+} // namespace aos
diff --git a/aos/vision/blob/move_scale.h b/aos/vision/blob/move_scale.h
new file mode 100644
index 0000000..c113bca
--- /dev/null
+++ b/aos/vision/blob/move_scale.h
@@ -0,0 +1,35 @@
+#ifndef AOS_VISION_BLOB_MOVE_SCALE_H_
+#define AOS_VISION_BLOB_MOVE_SCALE_H_
+
+#include <vector>
+#include <limits>
+
+#include "aos/vision/blob/range_image.h"
+
+namespace aos {
+namespace vision {
+
+// Bounding box for a RangeImage.
+struct ImageBBox {
+ int minx = std::numeric_limits<int>::max();
+ int maxx = std::numeric_limits<int>::min();
+ int miny = std::numeric_limits<int>::max();
+ int maxy = std::numeric_limits<int>::min();
+};
+
+// Sums img into bbox. bbox is constructed empty and grows with each call
+// to GetBBox.
+void GetBBox(const RangeImage &img, ImageBBox *bbox);
+inline void GetBBox(const std::vector<RangeImage> &imgs, ImageBBox *bbox) {
+ for (const auto &img : imgs) GetBBox(img, bbox);
+}
+
+std::vector<RangeImage> MoveScale(const std::vector<RangeImage> &imgs, int dx,
+ int dy, int scale);
+
+RangeImage MoveScale(const RangeImage &img, int dx, int dy, int scale);
+
+} // namespace vision
+} // namespace aos
+
+#endif // AOS_VISION_BLOB_MOVE_SCALE_H_
diff --git a/aos/vision/blob/range_image.h b/aos/vision/blob/range_image.h
index 109a100..3647890 100644
--- a/aos/vision/blob/range_image.h
+++ b/aos/vision/blob/range_image.h
@@ -53,6 +53,8 @@
int mini() const { return min_y_; }
+ int height() const { return min_y_ + ranges_.size(); }
+
private:
// minimum index in y where the blob starts
int min_y_ = 0;
diff --git a/aos/vision/blob/stream_view.h b/aos/vision/blob/stream_view.h
index bee1dc9..2ae56ac 100644
--- a/aos/vision/blob/stream_view.h
+++ b/aos/vision/blob/stream_view.h
@@ -61,6 +61,26 @@
}
}
+ inline void DrawSecondBlobList(const BlobList &blob_list, PixelRef color1,
+ PixelRef color2, PixelRef prev_color) {
+ ImagePtr ptr = img();
+ for (const auto &blob : blob_list) {
+ for (int i = 0; i < (int)blob.ranges().size(); ++i) {
+ for (const auto &range : blob.ranges()[i]) {
+ for (int j = range.st; j < range.ed; ++j) {
+ auto px = ptr.get_px(j, i + blob.min_y());
+ if (px.r == prev_color.r && px.g == prev_color.g &&
+ px.b == prev_color.b) {
+ ptr.get_px(j, i + blob.min_y()) = color2;
+ } else {
+ ptr.get_px(j, i + blob.min_y()) = color1;
+ }
+ }
+ }
+ }
+ }
+ }
+
// Backwards compatible.
DebugViewer *view() { return this; }
diff --git a/aos/vision/blob/test_utils.cc b/aos/vision/blob/test_utils.cc
new file mode 100644
index 0000000..7664f8a
--- /dev/null
+++ b/aos/vision/blob/test_utils.cc
@@ -0,0 +1,43 @@
+#include "aos/vision/blob/test_utils.h"
+
+namespace aos {
+namespace vision {
+
+RangeImage LoadFromTestData(int mini, const char *data) {
+ // Consume initial return.
+ if (*data) ++data;
+ std::vector<std::vector<ImageRange>> rows;
+ int x = 0;
+ bool p_score = false;
+ int pstart = -1;
+ std::vector<ImageRange> out_ranges;
+
+ for (; *data; ++data) {
+ char cell = *data;
+
+ if (cell == '\n') {
+ if (p_score) {
+ out_ranges.emplace_back(ImageRange(pstart, x));
+ }
+ rows.emplace_back(out_ranges);
+ out_ranges = {};
+ x = 0;
+ pstart = -1;
+ p_score = false;
+ } else {
+ if ((cell != ' ') != p_score) {
+ if (p_score) {
+ out_ranges.emplace_back(ImageRange(pstart, x));
+ } else {
+ pstart = x;
+ }
+ p_score = !p_score;
+ }
+ ++x;
+ }
+ }
+ return RangeImage(mini, std::move(rows));
+}
+
+} // namespace vision
+} // namespace aos
diff --git a/aos/vision/blob/test_utils.h b/aos/vision/blob/test_utils.h
new file mode 100644
index 0000000..e0c706c
--- /dev/null
+++ b/aos/vision/blob/test_utils.h
@@ -0,0 +1,24 @@
+#ifndef AOS_VISION_BLOB_TEST_UTILS_H_
+#define AOS_VISION_BLOB_TEST_UTILS_H_
+
+#include "aos/vision/blob/range_image.h"
+
+namespace aos {
+namespace vision {
+
+// For tests. Loads a RangeImage from a constant string.
+//
+// For example this should look something like this:
+// first and final returns will be stripped.
+//
+// R"(
+// --- ---
+// -----
+// --
+// )"
+RangeImage LoadFromTestData(int mini, const char *data);
+
+} // namespace vision
+} // namespace aos
+
+#endif // AOS_VISION_BLOB_TEST_UTILS_H_
diff --git a/aos/vision/blob/transpose.cc b/aos/vision/blob/transpose.cc
new file mode 100644
index 0000000..19ac965
--- /dev/null
+++ b/aos/vision/blob/transpose.cc
@@ -0,0 +1,87 @@
+#include "aos/vision/blob/transpose.h"
+
+#include <algorithm>
+
+namespace aos {
+namespace vision {
+
+RangeImage Transpose(const RangeImage &img) {
+ enum EventT {
+ // Must happen before point adds and deletes.
+ kRangeStart = 0,
+ kRangeEnd = 1,
+ // Non-overlapping
+ kPointAdd = 3,
+ kPointDel = 2,
+ };
+ std::vector<std::vector<std::pair<int, EventT>>> events;
+ int y = img.min_y();
+ for (const std::vector<ImageRange> &row : img) {
+ for (const ImageRange &range : row) {
+ if (range.ed >= static_cast<int>(events.size()))
+ events.resize(range.ed + 1);
+ events[range.st].emplace_back(y, kPointAdd);
+ events[range.ed].emplace_back(y, kPointDel);
+ }
+ ++y;
+ }
+
+ int min_y = 0;
+ while (min_y < (int)events.size() && events[min_y].empty()) ++min_y;
+
+ std::vector<ImageRange> prev_ranges;
+ std::vector<ImageRange> cur_ranges;
+
+ std::vector<std::vector<ImageRange>> rows;
+ for (int y = min_y; y < static_cast<int>(events.size()) - 1; ++y) {
+ auto row_events = std::move(events[y]);
+ for (const auto &range : prev_ranges) {
+ row_events.emplace_back(range.st, kRangeStart);
+ row_events.emplace_back(range.ed, kRangeEnd);
+ }
+ std::sort(row_events.begin(), row_events.end());
+ cur_ranges.clear();
+
+ bool has_cur_range = false;
+ ImageRange cur_range{0, 0};
+ auto add_range = [&](ImageRange range) {
+ if (range.st == range.ed) return;
+ if (has_cur_range) {
+ if (cur_range.ed == range.st) {
+ range = ImageRange{cur_range.st, range.ed};
+ } else {
+ cur_ranges.emplace_back(cur_range);
+ }
+ }
+ cur_range = range;
+ has_cur_range = true;
+ };
+
+ int prev_start;
+ for (const auto &pt : row_events) {
+ switch (pt.second) {
+ case kRangeStart:
+ prev_start = pt.first;
+ break;
+ case kPointAdd:
+ add_range(ImageRange{pt.first, pt.first + 1});
+ break;
+ case kRangeEnd:
+ add_range(ImageRange{prev_start, pt.first});
+ break;
+ case kPointDel:
+ add_range(ImageRange{prev_start, pt.first});
+ prev_start = pt.first + 1;
+ break;
+ }
+ }
+
+ if (has_cur_range) cur_ranges.emplace_back(cur_range);
+ rows.emplace_back(cur_ranges);
+ std::swap(cur_ranges, prev_ranges);
+ }
+ return RangeImage(min_y, std::move(rows));
+}
+
+} // namespace vision
+} // namespace aos
diff --git a/aos/vision/blob/transpose.h b/aos/vision/blob/transpose.h
new file mode 100644
index 0000000..632f270
--- /dev/null
+++ b/aos/vision/blob/transpose.h
@@ -0,0 +1,20 @@
+#ifndef _y2017_VISION_BLOB_TRANSPOSE_H_
+#define _y2017_VISION_BLOB_TRANSPOSE_H_
+
+#include "aos/vision/blob/range_image.h"
+
+namespace aos {
+namespace vision {
+
+RangeImage Transpose(const RangeImage &img);
+inline std::vector<RangeImage> Transpose(const std::vector<RangeImage> &imgs) {
+ std::vector<RangeImage> out;
+ out.reserve(imgs.size());
+ for (const auto &img : imgs) out.push_back(Transpose(img));
+ return out;
+}
+
+} // namespace vision
+} // namespace aos
+
+#endif // _y2017_VISION_ROT90_H_
diff --git a/aos/vision/blob/transpose_test.cc b/aos/vision/blob/transpose_test.cc
new file mode 100644
index 0000000..a5c2964
--- /dev/null
+++ b/aos/vision/blob/transpose_test.cc
@@ -0,0 +1,29 @@
+#include "aos/vision/blob/transpose.h"
+#include "aos/vision/blob/test_utils.h"
+
+#include <algorithm>
+#include <string>
+#include "gtest/gtest.h"
+
+namespace aos {
+namespace vision {
+
+TEST(TransposeTest, Tranpspose) {
+ RangeImage img = LoadFromTestData(20, R"(
+ -----------
+ ----- ----
+ ------------
+ -------------
+ ------------
+ ----------
+ ------------
+ ---------
+)");
+
+ auto b = Transpose(img);
+ auto c = Transpose(b);
+ EXPECT_EQ(ShortDebugPrint({img}), ShortDebugPrint({c}));
+}
+
+} // namespace vision
+} // namespace aos