Add a basic test of the thresholding code
This in preparation for adding a new implementation.
Change-Id: I8ef7a25607b48a1649cb2c30e121a72a600eb1e6
diff --git a/aos/vision/blob/BUILD b/aos/vision/blob/BUILD
index d22aa4d..2e77079 100644
--- a/aos/vision/blob/BUILD
+++ b/aos/vision/blob/BUILD
@@ -49,6 +49,19 @@
],
)
+cc_test(
+ name = "threshold_test",
+ srcs = [
+ "threshold_test.cc",
+ ],
+ deps = [
+ ":range_image",
+ ":threshold",
+ "//aos/testing:googletest",
+ "//aos/vision/image:image_types",
+ ],
+)
+
cc_library(
name = "hierarchical_contour_merge",
srcs = ["hierarchical_contour_merge.cc"],
diff --git a/aos/vision/blob/range_image.cc b/aos/vision/blob/range_image.cc
index c01a919..613d2ca 100644
--- a/aos/vision/blob/range_image.cc
+++ b/aos/vision/blob/range_image.cc
@@ -88,6 +88,10 @@
}
}
+void PrintTo(const ImageRange &range, std::ostream *os) {
+ *os << "{" << range.st << ", " << range.ed << "}";
+}
+
void RangeImage::Flip(int image_width, int image_height) {
std::reverse(ranges_.begin(), ranges_.end());
for (std::vector<ImageRange> &range : ranges_) {
@@ -102,6 +106,31 @@
min_y_ = image_height - static_cast<int>(ranges_.size()) - min_y_;
}
+void PrintTo(const RangeImage &range, std::ostream *os) {
+ *os << "{min_y=" << range.min_y()
+ << ", ranges={";
+ bool first_row = true;
+ for (const auto &row : range) {
+ if (first_row) {
+ first_row = false;
+ } else {
+ *os << ", ";
+ }
+ *os << "{";
+ bool first_value = true;
+ for (const auto &value : row) {
+ if (first_value) {
+ first_value = false;
+ } else {
+ *os << ", ";
+ }
+ *os << "{" << value.st << ", " << value.ed << "}";
+ }
+ *os << "}";
+ }
+ *os << "}}";
+}
+
int RangeImage::npixels() {
if (npixelsc_ > 0) {
return npixelsc_;
diff --git a/aos/vision/blob/range_image.h b/aos/vision/blob/range_image.h
index 3647890..201ffb2 100644
--- a/aos/vision/blob/range_image.h
+++ b/aos/vision/blob/range_image.h
@@ -21,8 +21,14 @@
int calc_width() const { return ed - st; }
bool operator<(const ImageRange &o) const { return st < o.st; }
+ bool operator==(const ImageRange &other) const {
+ return st == other.st && ed == other.ed;
+ }
+ bool operator!=(const ImageRange &other) const { return !(*this == other); }
};
+void PrintTo(const ImageRange &range, std::ostream *os);
+
// Image in pre-thresholded run-length encoded format.
class RangeImage {
public:
@@ -31,6 +37,13 @@
explicit RangeImage(int l) { ranges_.reserve(l); }
RangeImage() {}
+ bool operator==(const RangeImage &other) const {
+ if (min_y_ != other.min_y_) { return false; }
+ if (ranges_ != other.ranges_) { return false; }
+ return true;
+ }
+ bool operator!=(const RangeImage &other) const { return !(*this == other); }
+
int size() const { return ranges_.size(); }
int npixels();
@@ -59,13 +72,16 @@
// minimum index in y where the blob starts
int min_y_ = 0;
- // ranges are always sorted in y then x order
+ // Each vector<ImageRange> represents all the matched ranges in a given row.
+ // Each ImageRange within that row represents a run of pixels which matches.
std::vector<std::vector<ImageRange>> ranges_;
// Cached pixel count.
int npixelsc_ = -1;
};
+void PrintTo(const RangeImage &range, std::ostream *os);
+
typedef std::vector<RangeImage> BlobList;
typedef std::vector<const RangeImage *> BlobLRef;
diff --git a/aos/vision/blob/threshold_test.cc b/aos/vision/blob/threshold_test.cc
new file mode 100644
index 0000000..96a2a22
--- /dev/null
+++ b/aos/vision/blob/threshold_test.cc
@@ -0,0 +1,100 @@
+#include "aos/vision/blob/threshold.h"
+
+#include <vector>
+
+#include "aos/vision/blob/range_image.h"
+#include "aos/vision/image/image_types.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace aos {
+namespace vision {
+namespace testing {
+
+class YuyvYThresholdTest : public ::testing::Test {
+};
+
+// Verifies that a simple image is thresholded correctly.
+//
+// Specifically, we want to get this result from the thresholding:
+// --+--
+// +---+
+// -+++-
+// +++-+
+// -----
+// ++-++
+// +++++
+// +-+-+
+TEST_F(YuyvYThresholdTest, SimpleImage) {
+ ImageFormat format;
+ format.w = 5;
+ format.h = 8;
+
+ std::vector<std::vector<ImageRange>> expected_ranges;
+ std::vector<char> image;
+ image.resize(5 * 8 * 2);
+ // --+--
+ image[0 * 2 + 0 * 10] = 0;
+ image[1 * 2 + 0 * 10] = 0;
+ image[2 * 2 + 0 * 10] = 128;
+ image[3 * 2 + 0 * 10] = 127;
+ image[4 * 2 + 0 * 10] = 0;
+ expected_ranges.push_back({{{2, 3}}});
+ // +---+
+ image[0 * 2 + 1 * 10] = 128;
+ image[1 * 2 + 1 * 10] = 0;
+ image[2 * 2 + 1 * 10] = 0;
+ image[3 * 2 + 1 * 10] = 10;
+ image[4 * 2 + 1 * 10] = 255;
+ expected_ranges.push_back({{{0, 1}, {4, 5}}});
+ // -+++-
+ image[0 * 2 + 2 * 10] = 73;
+ image[1 * 2 + 2 * 10] = 250;
+ image[2 * 2 + 2 * 10] = 251;
+ image[3 * 2 + 2 * 10] = 252;
+ image[4 * 2 + 2 * 10] = 45;
+ expected_ranges.push_back({{{1, 4}}});
+ // +++-+
+ image[0 * 2 + 3 * 10] = 128;
+ image[1 * 2 + 3 * 10] = 134;
+ image[2 * 2 + 3 * 10] = 250;
+ image[3 * 2 + 3 * 10] = 0;
+ image[4 * 2 + 3 * 10] = 230;
+ expected_ranges.push_back({{{0, 3}, {4, 5}}});
+ // -----
+ image[0 * 2 + 4 * 10] = 7;
+ image[1 * 2 + 4 * 10] = 120;
+ image[2 * 2 + 4 * 10] = 127;
+ image[3 * 2 + 4 * 10] = 0;
+ image[4 * 2 + 4 * 10] = 50;
+ expected_ranges.push_back({{}});
+ // ++-++
+ image[0 * 2 + 5 * 10] = 140;
+ image[1 * 2 + 5 * 10] = 140;
+ image[2 * 2 + 5 * 10] = 0;
+ image[3 * 2 + 5 * 10] = 140;
+ image[4 * 2 + 5 * 10] = 140;
+ expected_ranges.push_back({{{0, 2}, {3, 5}}});
+ // +++++
+ image[0 * 2 + 6 * 10] = 128;
+ image[1 * 2 + 6 * 10] = 128;
+ image[2 * 2 + 6 * 10] = 128;
+ image[3 * 2 + 6 * 10] = 128;
+ image[4 * 2 + 6 * 10] = 128;
+ expected_ranges.push_back({{{0, 5}}});
+ // +-+-+
+ image[0 * 2 + 7 * 10] = 200;
+ image[1 * 2 + 7 * 10] = 0;
+ image[2 * 2 + 7 * 10] = 200;
+ image[3 * 2 + 7 * 10] = 0;
+ image[4 * 2 + 7 * 10] = 200;
+ expected_ranges.push_back({{{0, 1}, {2, 3}, {4, 5}}});
+ const RangeImage expected_result(0, std::move(expected_ranges));
+
+ const auto slow_result = SlowYuyvYThreshold(format, image.data(), 127);
+ ASSERT_EQ(expected_result, slow_result);
+}
+
+} // namespace testing
+} // namespace vision
+} // namespace aos