Make the fast thresholding work
The only functional change was removing the block which did the same
algorithm non-flattened. It has tests now to verify it does the same
thing as the slow version.
Change-Id: I63da1a6374c2adfabf764b62d5c33f91897ce2a7
diff --git a/aos/vision/blob/BUILD b/aos/vision/blob/BUILD
index 2e77079..81afb93 100644
--- a/aos/vision/blob/BUILD
+++ b/aos/vision/blob/BUILD
@@ -45,6 +45,7 @@
],
deps = [
":range_image",
+ "//aos/logging",
"//aos/vision/image:image_types",
],
)
diff --git a/aos/vision/blob/threshold.cc b/aos/vision/blob/threshold.cc
index 4fc58eb..74809d1 100644
--- a/aos/vision/blob/threshold.cc
+++ b/aos/vision/blob/threshold.cc
@@ -1,210 +1,230 @@
#include "aos/vision/blob/threshold.h"
+#include "aos/logging/logging.h"
+
namespace aos {
namespace vision {
+// Expands to a unique value for each combination of values for 5 bools.
#define MASH(v0, v1, v2, v3, v4) \
((uint8_t(v0) << 4) | (uint8_t(v1) << 3) | (uint8_t(v2) << 2) | \
(uint8_t(v3) << 1) | (uint8_t(v4)))
+// At a high level, the algorithm is the same as the slow thresholding, except
+// it operates in 4-pixel chunks. The handling for each of these chunks is
+// manually flattened (via codegen) into a 32-case switch statement. There are
+// 2^4 cases for each pixel being in or out, along with another set of cases
+// depending on whether the start of the chunk is in a range or not.
RangeImage FastYuyvYThreshold(ImageFormat fmt, const char *data,
uint8_t value) {
- std::vector<std::vector<ImageRange>> ranges;
- ranges.reserve(fmt.h);
+ CHECK_EQ(0, fmt.w % 4);
+ std::vector<std::vector<ImageRange>> result;
+ result.reserve(fmt.h);
+
+ // Iterate through each row.
for (int y = 0; y < fmt.h; ++y) {
- const char *row = fmt.w * y * 2 + data;
- bool p_score = false;
- int pstart = -1;
- std::vector<ImageRange> rngs;
+ // The start of the data for the current row.
+ const char *const current_row = fmt.w * y * 2 + data;
+ bool in_range = false;
+ int current_range_start = -1;
+ std::vector<ImageRange> current_row_ranges;
+ // Iterate through each 4-pixel chunk
for (int x = 0; x < fmt.w / 4; ++x) {
- uint8_t v[8];
- memcpy(&v[0], row + x * 4 * 2, 8);
- uint8_t pattern =
- MASH(p_score, v[0] > value, v[2] > value, v[4] > value, v[6] > value);
+ // The per-channel (YUYV) values in the current chunk.
+ uint8_t chunk_channels[8];
+ memcpy(&chunk_channels[0], current_row + x * 4 * 2, 8);
+ const uint8_t pattern =
+ MASH(in_range, chunk_channels[0] > value, chunk_channels[2] > value,
+ chunk_channels[4] > value, chunk_channels[6] > value);
switch (pattern) {
- /*
+ // clang-format off
+/*
# Ruby code to generate the below code:
32.times do |v|
puts "case MASH(#{[v[4], v[3], v[2], v[1], v[0]].join(", ")}):"
- p_score = v[4]
- pstart = "pstart"
+ in_range = v[4]
+ current_range_start = "current_range_start"
4.times do |i|
- if v[3 - i] != p_score
- if (p_score == 1)
- puts " rngs.emplace_back(ImageRange(#{pstart},
-x * 4 + #{i}));"
+ if v[3 - i] != in_range
+ if (in_range == 1)
+ puts " current_row_ranges.emplace_back(ImageRange(#{current_range_start}, x * 4 + #{i}));"
else
- pstart = "x * 4 + #{i}"
+ current_range_start = "x * 4 + #{i}"
end
- p_score = v[3 - i]
+ in_range = v[3 - i]
end
end
- if (pstart != "pstart")
- puts " pstart = #{pstart};"
+ if (current_range_start != "current_range_start")
+ puts " current_range_start = #{current_range_start};"
end
- if (p_score != v[4])
- puts " p_score = #{["false", "true"][v[0]]};"
+ if (in_range != v[4])
+ puts " in_range = #{["false", "true"][v[0]]};"
end
puts " break;"
end
*/
+ // clang-format on
case MASH(0, 0, 0, 0, 0):
break;
case MASH(0, 0, 0, 0, 1):
- pstart = x * 4 + 3;
- p_score = true;
+ current_range_start = x * 4 + 3;
+ in_range = true;
break;
case MASH(0, 0, 0, 1, 0):
- rngs.emplace_back(ImageRange(x * 4 + 2, x * 4 + 3));
- pstart = x * 4 + 2;
+ current_row_ranges.emplace_back(ImageRange(x * 4 + 2, x * 4 + 3));
+ current_range_start = x * 4 + 2;
break;
case MASH(0, 0, 0, 1, 1):
- pstart = x * 4 + 2;
- p_score = true;
+ current_range_start = x * 4 + 2;
+ in_range = true;
break;
case MASH(0, 0, 1, 0, 0):
- rngs.emplace_back(ImageRange(x * 4 + 1, x * 4 + 2));
- pstart = x * 4 + 1;
+ current_row_ranges.emplace_back(ImageRange(x * 4 + 1, x * 4 + 2));
+ current_range_start = x * 4 + 1;
break;
case MASH(0, 0, 1, 0, 1):
- rngs.emplace_back(ImageRange(x * 4 + 1, x * 4 + 2));
- pstart = x * 4 + 3;
- p_score = true;
+ current_row_ranges.emplace_back(ImageRange(x * 4 + 1, x * 4 + 2));
+ current_range_start = x * 4 + 3;
+ in_range = true;
break;
case MASH(0, 0, 1, 1, 0):
- rngs.emplace_back(ImageRange(x * 4 + 1, x * 4 + 3));
- pstart = x * 4 + 1;
+ current_row_ranges.emplace_back(ImageRange(x * 4 + 1, x * 4 + 3));
+ current_range_start = x * 4 + 1;
break;
case MASH(0, 0, 1, 1, 1):
- pstart = x * 4 + 1;
- p_score = true;
+ current_range_start = x * 4 + 1;
+ in_range = true;
break;
case MASH(0, 1, 0, 0, 0):
- rngs.emplace_back(ImageRange(x * 4 + 0, x * 4 + 1));
- pstart = x * 4 + 0;
+ current_row_ranges.emplace_back(ImageRange(x * 4 + 0, x * 4 + 1));
+ current_range_start = x * 4 + 0;
break;
case MASH(0, 1, 0, 0, 1):
- rngs.emplace_back(ImageRange(x * 4 + 0, x * 4 + 1));
- pstart = x * 4 + 3;
- p_score = true;
+ current_row_ranges.emplace_back(ImageRange(x * 4 + 0, x * 4 + 1));
+ current_range_start = x * 4 + 3;
+ in_range = true;
break;
case MASH(0, 1, 0, 1, 0):
- rngs.emplace_back(ImageRange(x * 4 + 0, x * 4 + 1));
- rngs.emplace_back(ImageRange(x * 4 + 2, x * 4 + 3));
- pstart = x * 4 + 2;
+ current_row_ranges.emplace_back(ImageRange(x * 4 + 0, x * 4 + 1));
+ current_row_ranges.emplace_back(ImageRange(x * 4 + 2, x * 4 + 3));
+ current_range_start = x * 4 + 2;
break;
case MASH(0, 1, 0, 1, 1):
- rngs.emplace_back(ImageRange(x * 4 + 0, x * 4 + 1));
- pstart = x * 4 + 2;
- p_score = true;
+ current_row_ranges.emplace_back(ImageRange(x * 4 + 0, x * 4 + 1));
+ current_range_start = x * 4 + 2;
+ in_range = true;
break;
case MASH(0, 1, 1, 0, 0):
- rngs.emplace_back(ImageRange(x * 4 + 0, x * 4 + 2));
- pstart = x * 4 + 0;
+ current_row_ranges.emplace_back(ImageRange(x * 4 + 0, x * 4 + 2));
+ current_range_start = x * 4 + 0;
break;
case MASH(0, 1, 1, 0, 1):
- rngs.emplace_back(ImageRange(x * 4 + 0, x * 4 + 2));
- pstart = x * 4 + 3;
- p_score = true;
+ current_row_ranges.emplace_back(ImageRange(x * 4 + 0, x * 4 + 2));
+ current_range_start = x * 4 + 3;
+ in_range = true;
break;
case MASH(0, 1, 1, 1, 0):
- rngs.emplace_back(ImageRange(x * 4 + 0, x * 4 + 3));
- pstart = x * 4 + 0;
+ current_row_ranges.emplace_back(ImageRange(x * 4 + 0, x * 4 + 3));
+ current_range_start = x * 4 + 0;
break;
case MASH(0, 1, 1, 1, 1):
- pstart = x * 4 + 0;
- p_score = true;
+ current_range_start = x * 4 + 0;
+ in_range = true;
break;
case MASH(1, 0, 0, 0, 0):
- rngs.emplace_back(ImageRange(pstart, x * 4 + 0));
- p_score = false;
+ current_row_ranges.emplace_back(
+ ImageRange(current_range_start, x * 4 + 0));
+ in_range = false;
break;
case MASH(1, 0, 0, 0, 1):
- rngs.emplace_back(ImageRange(pstart, x * 4 + 0));
- pstart = x * 4 + 3;
+ current_row_ranges.emplace_back(
+ ImageRange(current_range_start, x * 4 + 0));
+ current_range_start = x * 4 + 3;
break;
case MASH(1, 0, 0, 1, 0):
- rngs.emplace_back(ImageRange(pstart, x * 4 + 0));
- rngs.emplace_back(ImageRange(x * 4 + 2, x * 4 + 3));
- pstart = x * 4 + 2;
- p_score = false;
+ current_row_ranges.emplace_back(
+ ImageRange(current_range_start, x * 4 + 0));
+ current_row_ranges.emplace_back(ImageRange(x * 4 + 2, x * 4 + 3));
+ current_range_start = x * 4 + 2;
+ in_range = false;
break;
case MASH(1, 0, 0, 1, 1):
- rngs.emplace_back(ImageRange(pstart, x * 4 + 0));
- pstart = x * 4 + 2;
+ current_row_ranges.emplace_back(
+ ImageRange(current_range_start, x * 4 + 0));
+ current_range_start = x * 4 + 2;
break;
case MASH(1, 0, 1, 0, 0):
- rngs.emplace_back(ImageRange(pstart, x * 4 + 0));
- rngs.emplace_back(ImageRange(x * 4 + 1, x * 4 + 2));
- pstart = x * 4 + 1;
- p_score = false;
+ current_row_ranges.emplace_back(
+ ImageRange(current_range_start, x * 4 + 0));
+ current_row_ranges.emplace_back(ImageRange(x * 4 + 1, x * 4 + 2));
+ current_range_start = x * 4 + 1;
+ in_range = false;
break;
case MASH(1, 0, 1, 0, 1):
- rngs.emplace_back(ImageRange(pstart, x * 4 + 0));
- rngs.emplace_back(ImageRange(x * 4 + 1, x * 4 + 2));
- pstart = x * 4 + 3;
+ current_row_ranges.emplace_back(
+ ImageRange(current_range_start, x * 4 + 0));
+ current_row_ranges.emplace_back(ImageRange(x * 4 + 1, x * 4 + 2));
+ current_range_start = x * 4 + 3;
break;
case MASH(1, 0, 1, 1, 0):
- rngs.emplace_back(ImageRange(pstart, x * 4 + 0));
- rngs.emplace_back(ImageRange(x * 4 + 1, x * 4 + 3));
- pstart = x * 4 + 1;
- p_score = false;
+ current_row_ranges.emplace_back(
+ ImageRange(current_range_start, x * 4 + 0));
+ current_row_ranges.emplace_back(ImageRange(x * 4 + 1, x * 4 + 3));
+ current_range_start = x * 4 + 1;
+ in_range = false;
break;
case MASH(1, 0, 1, 1, 1):
- rngs.emplace_back(ImageRange(pstart, x * 4 + 0));
- pstart = x * 4 + 1;
+ current_row_ranges.emplace_back(
+ ImageRange(current_range_start, x * 4 + 0));
+ current_range_start = x * 4 + 1;
break;
case MASH(1, 1, 0, 0, 0):
- rngs.emplace_back(ImageRange(pstart, x * 4 + 1));
- p_score = false;
+ current_row_ranges.emplace_back(
+ ImageRange(current_range_start, x * 4 + 1));
+ in_range = false;
break;
case MASH(1, 1, 0, 0, 1):
- rngs.emplace_back(ImageRange(pstart, x * 4 + 1));
- pstart = x * 4 + 3;
+ current_row_ranges.emplace_back(
+ ImageRange(current_range_start, x * 4 + 1));
+ current_range_start = x * 4 + 3;
break;
case MASH(1, 1, 0, 1, 0):
- rngs.emplace_back(ImageRange(pstart, x * 4 + 1));
- rngs.emplace_back(ImageRange(x * 4 + 2, x * 4 + 3));
- pstart = x * 4 + 2;
- p_score = false;
+ current_row_ranges.emplace_back(
+ ImageRange(current_range_start, x * 4 + 1));
+ current_row_ranges.emplace_back(ImageRange(x * 4 + 2, x * 4 + 3));
+ current_range_start = x * 4 + 2;
+ in_range = false;
break;
case MASH(1, 1, 0, 1, 1):
- rngs.emplace_back(ImageRange(pstart, x * 4 + 1));
- pstart = x * 4 + 2;
+ current_row_ranges.emplace_back(
+ ImageRange(current_range_start, x * 4 + 1));
+ current_range_start = x * 4 + 2;
break;
case MASH(1, 1, 1, 0, 0):
- rngs.emplace_back(ImageRange(pstart, x * 4 + 2));
- p_score = false;
+ current_row_ranges.emplace_back(
+ ImageRange(current_range_start, x * 4 + 2));
+ in_range = false;
break;
case MASH(1, 1, 1, 0, 1):
- rngs.emplace_back(ImageRange(pstart, x * 4 + 2));
- pstart = x * 4 + 3;
+ current_row_ranges.emplace_back(
+ ImageRange(current_range_start, x * 4 + 2));
+ current_range_start = x * 4 + 3;
break;
case MASH(1, 1, 1, 1, 0):
- rngs.emplace_back(ImageRange(pstart, x * 4 + 3));
- p_score = false;
+ current_row_ranges.emplace_back(
+ ImageRange(current_range_start, x * 4 + 3));
+ in_range = false;
break;
case MASH(1, 1, 1, 1, 1):
break;
}
-
- for (int i = 0; i < 4; ++i) {
- if ((v[i * 2] > value) != p_score) {
- if (p_score) {
- rngs.emplace_back(ImageRange(pstart, x * 4 + i));
- } else {
- pstart = x * 4 + i;
- }
- p_score = !p_score;
- }
- }
}
- if (p_score) {
- rngs.emplace_back(ImageRange(pstart, fmt.w));
+ if (in_range) {
+ current_row_ranges.emplace_back(ImageRange(current_range_start, fmt.w));
}
- ranges.push_back(rngs);
+ result.push_back(current_row_ranges);
}
- return RangeImage(0, std::move(ranges));
+ return RangeImage(0, std::move(result));
}
#undef MASH
diff --git a/aos/vision/blob/threshold.h b/aos/vision/blob/threshold.h
index 441a058..9891722 100644
--- a/aos/vision/blob/threshold.h
+++ b/aos/vision/blob/threshold.h
@@ -17,28 +17,31 @@
static_assert(
std::is_convertible<PointTestFn, std::function<bool(int, int)>>::value,
"Invalid threshold function");
- std::vector<std::vector<ImageRange>> ranges;
- ranges.reserve(fmt.h);
+ std::vector<std::vector<ImageRange>> result;
+ result.reserve(fmt.h);
+ // Iterate through each row.
for (int y = 0; y < fmt.h; ++y) {
- bool p_score = false;
- int pstart = -1;
- std::vector<ImageRange> rngs;
+ // Whether we're currently in a range.
+ bool in_range = false;
+ int current_range_start = -1;
+ std::vector<ImageRange> current_row_ranges;
+ // Iterate through each pixel.
for (int x = 0; x < fmt.w; ++x) {
- if (fn(x, y) != p_score) {
- if (p_score) {
- rngs.emplace_back(ImageRange(pstart, x));
+ if (fn(x, y) != in_range) {
+ if (in_range) {
+ current_row_ranges.emplace_back(ImageRange(current_range_start, x));
} else {
- pstart = x;
+ current_range_start = x;
}
- p_score = !p_score;
+ in_range = !in_range;
}
}
- if (p_score) {
- rngs.emplace_back(ImageRange(pstart, fmt.w));
+ if (in_range) {
+ current_row_ranges.emplace_back(ImageRange(current_range_start, fmt.w));
}
- ranges.push_back(rngs);
+ result.push_back(current_row_ranges);
}
- return RangeImage(0, std::move(ranges));
+ return RangeImage(0, std::move(result));
}
} // namespace threshold_internal
@@ -72,7 +75,7 @@
}
// Thresholds an image in YUYV format, selecting pixels with a Y (luma) greater
-// than value.
+// than value. The width must be a multiple of 4.
//
// This is implemented via some tricky bit shuffling that goes fast.
RangeImage FastYuyvYThreshold(ImageFormat fmt, const char *data, uint8_t value);
diff --git a/aos/vision/blob/threshold_test.cc b/aos/vision/blob/threshold_test.cc
index 96a2a22..108da35 100644
--- a/aos/vision/blob/threshold_test.cc
+++ b/aos/vision/blob/threshold_test.cc
@@ -1,5 +1,6 @@
#include "aos/vision/blob/threshold.h"
+#include <random>
#include <vector>
#include "aos/vision/blob/range_image.h"
@@ -12,87 +13,179 @@
namespace testing {
class YuyvYThresholdTest : public ::testing::Test {
+ public:
+ std::vector<char> RandomImage(ImageFormat format) {
+ std::vector<char> result;
+ result.resize(format.w * format.h * 2);
+ std::uniform_int_distribution<char> distribution(
+ std::numeric_limits<char>::min(), std::numeric_limits<char>::max());
+ for (size_t i = 0; i < result.size(); ++i) {
+ result[i] = distribution(generator_);
+ }
+ return result;
+ }
+
+ private:
+ std::minstd_rand generator_;
};
// 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.w = 8;
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;
+ image.resize(8 * 8 * 2);
+ // --+-----
+ image[0 * 2 + 0 * 16] = 0;
+ image[1 * 2 + 0 * 16] = 0;
+ image[2 * 2 + 0 * 16] = 128;
+ image[3 * 2 + 0 * 16] = 127;
+ image[4 * 2 + 0 * 16] = 0;
+ image[5 * 2 + 0 * 16] = 0;
+ image[6 * 2 + 0 * 16] = 0;
+ image[7 * 2 + 0 * 16] = 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;
+ // +------+
+ image[0 * 2 + 1 * 16] = 128;
+ image[1 * 2 + 1 * 16] = 0;
+ image[2 * 2 + 1 * 16] = 0;
+ image[3 * 2 + 1 * 16] = 10;
+ image[4 * 2 + 1 * 16] = 30;
+ image[5 * 2 + 1 * 16] = 50;
+ image[6 * 2 + 1 * 16] = 70;
+ image[7 * 2 + 1 * 16] = 255;
+ expected_ranges.push_back({{{0, 1}, {7, 8}}});
+ // -++++++-
+ image[0 * 2 + 2 * 16] = 73;
+ image[1 * 2 + 2 * 16] = 246;
+ image[2 * 2 + 2 * 16] = 247;
+ image[3 * 2 + 2 * 16] = 248;
+ image[4 * 2 + 2 * 16] = 249;
+ image[5 * 2 + 2 * 16] = 250;
+ image[6 * 2 + 2 * 16] = 250;
+ image[7 * 2 + 2 * 16] = 45;
+ expected_ranges.push_back({{{1, 7}}});
+ // +++-++++
+ image[0 * 2 + 3 * 16] = 128;
+ image[1 * 2 + 3 * 16] = 134;
+ image[2 * 2 + 3 * 16] = 250;
+ image[3 * 2 + 3 * 16] = 0;
+ image[4 * 2 + 3 * 16] = 230;
+ image[5 * 2 + 3 * 16] = 230;
+ image[6 * 2 + 3 * 16] = 230;
+ image[7 * 2 + 3 * 16] = 210;
+ expected_ranges.push_back({{{0, 3}, {4, 8}}});
+ // --------
+ image[0 * 2 + 4 * 16] = 7;
+ image[1 * 2 + 4 * 16] = 120;
+ image[2 * 2 + 4 * 16] = 127;
+ image[3 * 2 + 4 * 16] = 0;
+ image[4 * 2 + 4 * 16] = 50;
+ image[5 * 2 + 4 * 16] = 80;
+ image[6 * 2 + 4 * 16] = 110;
+ image[7 * 2 + 4 * 16] = 25;
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}}});
+ // ++++-+++
+ image[0 * 2 + 5 * 16] = 140;
+ image[1 * 2 + 5 * 16] = 140;
+ image[2 * 2 + 5 * 16] = 140;
+ image[3 * 2 + 5 * 16] = 140;
+ image[4 * 2 + 5 * 16] = 0;
+ image[5 * 2 + 5 * 16] = 140;
+ image[6 * 2 + 5 * 16] = 140;
+ image[7 * 2 + 5 * 16] = 140;
+ expected_ranges.push_back({{{0, 4}, {5, 8}}});
+ // ++++++++
+ image[0 * 2 + 6 * 16] = 128;
+ image[1 * 2 + 6 * 16] = 128;
+ image[2 * 2 + 6 * 16] = 128;
+ image[3 * 2 + 6 * 16] = 128;
+ image[4 * 2 + 6 * 16] = 128;
+ image[5 * 2 + 6 * 16] = 128;
+ image[6 * 2 + 6 * 16] = 128;
+ image[7 * 2 + 6 * 16] = 128;
+ expected_ranges.push_back({{{0, 8}}});
+ // +-+-+--+
+ image[0 * 2 + 7 * 16] = 200;
+ image[1 * 2 + 7 * 16] = 0;
+ image[2 * 2 + 7 * 16] = 200;
+ image[3 * 2 + 7 * 16] = 0;
+ image[4 * 2 + 7 * 16] = 200;
+ image[5 * 2 + 7 * 16] = 0;
+ image[6 * 2 + 7 * 16] = 0;
+ image[7 * 2 + 7 * 16] = 200;
+ expected_ranges.push_back({{{0, 1}, {2, 3}, {4, 5}, {7, 8}}});
const RangeImage expected_result(0, std::move(expected_ranges));
const auto slow_result = SlowYuyvYThreshold(format, image.data(), 127);
ASSERT_EQ(expected_result, slow_result);
+ const auto fast_result = FastYuyvYThreshold(format, image.data(), 127);
+ ASSERT_EQ(expected_result, fast_result);
+}
+
+// Verifies that a couple of completely random images match.
+TEST_F(YuyvYThresholdTest, Random) {
+ for (int i = 0; i < 10; ++i) {
+ ImageFormat small_format;
+ small_format.w = 16;
+ small_format.h = 16;
+ const auto small_image = RandomImage(small_format);
+ const auto slow_result =
+ SlowYuyvYThreshold(small_format, small_image.data(), 127);
+ const auto fast_result =
+ FastYuyvYThreshold(small_format, small_image.data(), 127);
+ ASSERT_EQ(slow_result, fast_result);
+ }
+ for (int i = 0; i < 10; ++i) {
+ ImageFormat large_format;
+ large_format.w = 1024;
+ large_format.h = 512;
+ const auto large_image = RandomImage(large_format);
+ const auto slow_result =
+ SlowYuyvYThreshold(large_format, large_image.data(), 127);
+ const auto fast_result =
+ FastYuyvYThreshold(large_format, large_image.data(), 127);
+ ASSERT_EQ(slow_result, fast_result);
+ }
+}
+
+// Verifies that changing the U and V values doesn't affect the result.
+TEST_F(YuyvYThresholdTest, UVIgnored) {
+ ImageFormat format;
+ format.w = 32;
+ format.h = 20;
+ const auto baseline_image = RandomImage(format);
+ const auto baseline_result =
+ SlowYuyvYThreshold(format, baseline_image.data(), 127);
+ for (int i = 0; i < 5; ++i) {
+ auto tweaked_image = RandomImage(format);
+ for (int y = 0; y < format.h; ++y) {
+ for (int x = 0; x < format.w; ++x) {
+ tweaked_image[x * 2 + y * format.w * 2] =
+ baseline_image[x * 2 + y * format.w * 2];
+ }
+ }
+
+ const auto slow_result =
+ SlowYuyvYThreshold(format, tweaked_image.data(), 127);
+ ASSERT_EQ(baseline_result, slow_result);
+ const auto fast_result =
+ FastYuyvYThreshold(format, tweaked_image.data(), 127);
+ ASSERT_EQ(baseline_result, fast_result);
+ }
}
} // namespace testing