From db57afeaff84bd511d77141562a085105ec307f9 Mon Sep 17 00:00:00 2001 From: Edward Rosten Date: Wed, 1 Dec 2021 16:00:10 +0000 Subject: [PATCH] Flipping and transposing --- Makefile.in | 2 +- cvd/image.h | 13 +++++ cvd/image_ref.h | 4 ++ cvd/vision.h | 70 +++++++++++++++++++++---- cvd_src/exceptions.cc | 6 +++ tests/CMakeLists.txt | 4 ++ tests/flips.cc | 116 ++++++++++++++++++++++++++++-------------- 7 files changed, 165 insertions(+), 50 deletions(-) diff --git a/Makefile.in b/Makefile.in index a60a0db1..4f7d073e 100644 --- a/Makefile.in +++ b/Makefile.in @@ -290,7 +290,7 @@ printlibs: .PHONY: test -REGRESSIONS=distance_transform_test fast_corner_test load_and_save image_ref convolution +REGRESSIONS=distance_transform_test fast_corner_test load_and_save image_ref convolution flips REGRESSION_OUT=$(patsubst %,tests/%.out, $(REGRESSIONS)) test:$(REGRESSION_OUT) diff --git a/cvd/image.h b/cvd/image.h index ee98f4c7..7ada5ece 100644 --- a/cvd/image.h +++ b/cvd/image.h @@ -62,9 +62,22 @@ namespace Internal struct ImagePromise { }; + + [[noreturn]] void error_abort(const char* f, int l, const char* code); }; #endif + +#ifdef CVD_DEBUG +#define CVD_ASSERT(X) do{if(!(X))CVD::Internal::error_abort(__FILE__, __LINE__, #X);}while(0) +#elif defined(_MSC_VER) +#define CVD_ASSERT(X) __assume(X) +#elif defined(__GNUC__) || defined(__clang__) +#define CVD_ASSERT(X) do{if(!(X)) __builtin_unreachable();}while(0) +#elif +#define CVD_ASSERT(X) do{}while(0) +#endif + #ifdef CVD_IMAGE_DEBUG #define CVD_IMAGE_ASSERT(X, Y) \ if(!(X)) \ diff --git a/cvd/image_ref.h b/cvd/image_ref.h index 3e092322..92736246 100644 --- a/cvd/image_ref.h +++ b/cvd/image_ref.h @@ -163,6 +163,10 @@ class ImageRef ///@overload constexpr ImageRef shiftr(int i) const; + constexpr ImageRef transpose() const{ + return ImageRef(y,x); + } + // and now the data members (which are public!) int x; ///< The x co-ordinate int y; ///< The y co-ordinate diff --git a/cvd/vision.h b/cvd/vision.h index ca29c4f5..58eddf38 100644 --- a/cvd/vision.h +++ b/cvd/vision.h @@ -556,27 +556,77 @@ Image warp(const BasicImage& in, const CAM1& cam_in, const CAM2& cam_out) #endif -namespace Internal{ +namespace Internal +{ + template + void simpleTranspose(const SubImage& in, SubImage out) + { + CVD_ASSERT(in.size().transpose() == out.size()); + for(int r = 0; r < in.size().y; r++) + for(int c = 0; c < in.size().x; c++) + out[c][r] = in[r][c]; + } + template - Image simpleTranspose(const SubImage& in) + void recursiveTranspose(const SubImage& in, SubImage out, const int bytes = 2048) { - Image out(ImageRef(in.size().y, in.size().x)); - for(int r=0; r < in.size().y; r++) - for(int c=0; c < in.size().x; c++) - out[c][r] = in[r][c]; - return out; + CVD_ASSERT(in.size().transpose() == out.size()); + + if(in.size().area() * static_cast(sizeof(T)) < bytes || in.size().x == 1 || in.size().y == 1) + simpleTranspose(in, out); + else if(in.size().x >= in.size().y) + { + //The image is very wide, so the strategy of picking largest-sqare-and-remainder + //can lead to linear recursion depth, so instead split it in half + + const int width_left = in.size().x / 2; + const int width_right = in.size().x - width_left; + const ImageRef left_chunk { width_left, in.size().y }; + const ImageRef right_chunk { width_right, in.size().y }; + const ImageRef right_start { width_left, 0 }; + + recursiveTranspose(in.sub_image(ImageRef(0, 0), left_chunk), out.sub_image(ImageRef(0, 0), left_chunk.transpose())); + recursiveTranspose(in.sub_image(right_start, right_chunk), out.sub_image(right_start.transpose(), right_chunk.transpose())); + } + else + { + const int height_top = in.size().y / 2; + const int height_bottom = in.size().y - height_top; + ImageRef top_chunk { in.size().x, height_top }; + ImageRef bottom_chunk { in.size().x, height_bottom }; + ImageRef bottom_start { 0, height_top }; + + recursiveTranspose(in.sub_image(ImageRef(0, 0), top_chunk), out.sub_image(ImageRef(0, 0), top_chunk.transpose())); + recursiveTranspose(in.sub_image(bottom_start, bottom_chunk), out.sub_image(bottom_start.transpose(), bottom_chunk.transpose())); + } } + } +template +void transpose(const SubImage& in, SubImage&& out) +{ + CVD_ASSERT(in.size().transpose() == out.size()); + Internal::recursiveTranspose(in, out); +} + +template +Image transpose(const SubImage& in) +{ + Image out(in.size().transpose()); + Internal::recursiveTranspose(in, out); + return out; +} /// flips an image vertically in place. template void flipVertical(SubImage&& in) { - for(int r=0; r < in.size().y/2; r++) - for(int c=0; c < in.size().x; c++){ - std::swap(in[r][c], in[in.size().y-1-r][c]); + for(int r = 0; r < in.size().y / 2; r++) + for(int c = 0; c < in.size().x; c++) + { + std::swap(in[r][c], in[in.size().y - 1 - r][c]); } } diff --git a/cvd_src/exceptions.cc b/cvd_src/exceptions.cc index 7fcb536f..ac3892a1 100644 --- a/cvd_src/exceptions.cc +++ b/cvd_src/exceptions.cc @@ -16,3 +16,9 @@ CVD::Exceptions::VideoBuffer::BadColourSpace::BadColourSpace(const std::string& buffer + " can not grab video in the " + c + "colourspace on the specified device.") { } + +[[noreturn]] void CVD::Internal::error_abort(const char* f, int l, const char* code){ + std::cerr << "Assertion failed at " << f << ": " << l << " " << code << "\n"; + std::abort(); +} + diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index f66f531e..ead11f7a 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -18,3 +18,7 @@ add_test(NAME load_and_save COMMAND load_and_save) add_executable(convolution convolution.cc) target_link_libraries(convolution PRIVATE CVD) add_test(NAME convolution COMMAND convolution) + +add_executable(flips flips.cc) +target_link_libraries(flips PRIVATE CVD) +add_test(NAME flips COMMAND flips) diff --git a/tests/flips.cc b/tests/flips.cc index fb062af9..130902e9 100644 --- a/tests/flips.cc +++ b/tests/flips.cc @@ -1,69 +1,107 @@ +#include #include #include +#include using CVD::Image; -using CVD::SubImage; using CVD::ImageRef; +using CVD::SubImage; -Image im(int x, int y, const std::initializer_list& data){ +Image im(int x, int y, const std::initializer_list& data) +{ if(ImageRef(x, y).area() != (int)data.size()) abort(); return SubImage(const_cast(std::data(data)), ImageRef(x, y)); } -int main(){ - +int main() +{ + Image a; - a = im(2,2, - { - 1, 2, - 3, 4 - }); + a = im(2, 2, + { 1, 2, + 3, 4 }); flipVertical(a); - if(!std::equal(a.begin(), a.end(), im(2,2, - { - 3, 4, - 1, 2 - }).begin())) + if(!std::equal(a.begin(), a.end(), im(2, 2, { 3, 4, 1, 2 }).begin())) throw std::logic_error("Even sized flipV failed"); - - a = im(2,3, - { - 1, 2, - 3, 4, - 5, 6 - }); + //////////////////////////////////////////////////////////////////////////////// + a = im(2, 3, + { 1, 2, + 3, 4, + 5, 6 }); flipVertical(a); - if(!std::equal(a.begin(), a.end(), im(2,3, - { - 5, 6, - 3, 4, - 1, 2 - }).begin())) + if(!std::equal(a.begin(), a.end(), im(2, 3, { 5, 6, 3, 4, 1, 2 }).begin())) throw std::logic_error("Odd sized flipV failed"); + //////////////////////////////////////////////////////////////////////////////// - a = im(2,3, - { - 1, 2, - 3, 4, - 5, 6 - }); + a = im(2, 3, + { 1, 2, + 3, 4, + 5, 6 }); - a = CVD::Internal::simpleTranspose(a); + Image b(a.size().transpose()); + CVD::Internal::simpleTranspose(a, b); - if(!std::equal(a.begin(), a.end(), im(2,3, - { - 1, 3, 5, - 2, 4, 6 - }).begin())) + if(!std::equal(b.begin(), b.end(), im(2, 3, { 1, 3, 5, 2, 4, 6 }).begin())) throw std::logic_error("Simple transpose failed"); + //////////////////////////////////////////////////////////////////////////////// + + a = im(2, 3, + { 1, 2, + 3, 4, + 5, 6 }); + + b.resize(a.size().transpose()); + CVD::Internal::recursiveTranspose(a, b, 1); + + if(!std::equal(b.begin(), b.end(), im(2, 3, { 1, 3, 5, 2, 4, 6 }).begin())) + throw std::logic_error("Recursive transpose failed (small)"); + + //////////////////////////////////////////////////////////////////////////////// + const int N = 100; + const int R = 10; + + std::mt19937 eng; + for(int i = 0; i < N; i++) + { + std::uniform_int_distribution<> rng(1, 1024); + std::uniform_int_distribution<> byte(0, 255); + + ImageRef size; + size.x = rng(eng); + size.y = rng(eng); + + double s_simple = 0, s_recursive = 0; + + for(int j = 0; j < R; j++) + { + Image im1(size), im2; + for(auto& p : im1) + p = static_cast(byte(eng)); + + auto t1 = std::chrono::steady_clock::now(); + Image recurs = CVD::transpose(im1); + auto t2 = std::chrono::steady_clock::now(); + + Image simple(recurs.size()); + CVD::Internal::simpleTranspose(im1, simple); + auto t3 = std::chrono::steady_clock::now(); + + if(!std::equal(simple.begin(), simple.end(), recurs.begin())) + throw std::logic_error("Recursive transpose failed"); + + s_recursive += std::chrono::duration(t2 - t1).count(); + s_simple += std::chrono::duration(t3 - t2).count(); + } + std::cout << size << " " << size.area() << " " << s_simple / R << " " << s_recursive / R << "\n"; + } }