IsometricPatternMatcher/Image.h (417 lines of code) (raw):

// Copyright (c) Facebook, Inc. and its affiliates. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. #pragma once #include <cstring> #include <glog/logging.h> #include <pangolin/image/image_io.h> #include <Eigen/Core> #include <Eigen/Geometry> namespace surreal_opensource { template <class T> using DefaultImageAllocator = std::allocator<T>; template <class T, class Enable = void> struct DefaultImageValTraits { static constexpr int max_value = 0; }; template <class T, class Enable> constexpr int DefaultImageValTraits<T, Enable>::max_value; template <class T> struct DefaultImageValTraits< T, typename std::enable_if<std::is_integral<T>::value>::type> { static constexpr int max_value = (sizeof(T) >= sizeof(int)) ? std::numeric_limits<int>::max() : static_cast<int>(std::numeric_limits<T>::max()); }; template <class T> struct DefaultImageValTraits< T, typename std::enable_if<std::is_floating_point<T>::value>::type> { static constexpr int max_value = 1; }; template <class T, int Rows, int Cols, int Options, int MaxRows, int MaxCols> struct DefaultImageValTraits< Eigen::Matrix<T, Rows, Cols, Options, MaxRows, MaxCols>> { static constexpr int max_value = DefaultImageValTraits<T>::max_value; }; struct ImageDimensions { size_t width; size_t height; }; namespace details { // Scalar case template <class T> struct Zero { static T val() { return T(0); } }; // Specialization for Eigen types template <class T, int M, int N, int Opts> struct Zero<Eigen::Matrix<T, M, N, Opts>> { static Eigen::Matrix<T, M, N, Opts> val() { Eigen::Matrix<T, M, N, Opts> o; o.setZero(); return o; } }; // underlying memcopy function inline void PitchedCopy(char* dst, size_t dst_pitch_bytes, const char* src, size_t src_pitch_bytes, size_t width_bytes, size_t height) { if (dst_pitch_bytes == width_bytes && src_pitch_bytes == width_bytes) { std::memcpy(dst, src, height * width_bytes); } else { for (size_t row = 0; row < height; ++row) { std::memcpy(dst, src, width_bytes); dst += dst_pitch_bytes; src += src_pitch_bytes; } } } } // namespace details // Image class that stores a weak ptr to it's memory. // // MaxValue is a compile-time hint, which indicates the maximum value, if this // structure represents an intensity image. This class is CUDA aware: it does // not require CUDA, but supports being compiled in CUDA. If HAVE_CUDA is // defined, then some operations will use CUDA functions. template <typename T, int MaxValue = DefaultImageValTraits<T>::max_value> struct Image { static constexpr int max_value = MaxValue; using BaseType = T; inline Image() noexcept : pitch(0), ptr(nullptr), w(0), h(0) {} inline Image(T* ptr) noexcept : pitch(0), ptr(ptr), w(0), h(0) {} inline Image(T* ptr, size_t w) noexcept : pitch(sizeof(T) * w), ptr(ptr), w(w), h(1) {} inline Image(T* ptr, size_t w, size_t h) noexcept : pitch(sizeof(T) * w), ptr(ptr), w(w), h(h) {} inline Image(T* ptr, size_t w, size_t h, size_t pitch) noexcept : pitch(pitch), ptr(ptr), w(w), h(h) {} inline Image(const Image<std::remove_cv_t<T>, MaxValue>& other) noexcept : pitch(other.pitch), ptr(other.ptr), w(other.w), h(other.h) {} ////////////////////////////////////////////////////// // Query dimensions ////////////////////////////////////////////////////// inline size_t Width() const { return w; } inline size_t Height() const { return h; } inline size_t Area() const { return w * h; } inline size_t SizeBytes() const { return pitch * h; } inline Eigen::Vector2i Dim() const { return Eigen::Vector2i(w, h); } ////////////////////////////////////////////////////// // Iterators ////////////////////////////////////////////////////// inline T* begin() { return ptr; } inline T* end() { return (T*)((unsigned char*)(ptr) + h * pitch); } inline const T* begin() const { return ptr; } inline const T* end() const { return (T*)((unsigned char*)(ptr) + h * pitch); } inline size_t size() const { return w * h; } ////////////////////////////////////////////////////// // Image set / copy ////////////////////////////////////////////////////// inline void Fill(const T& val) { CHECK(IsValid() && IsContiguous()); const T* end_element = end(); for (T* it = begin(); it != end_element; ++it) { *it = val; } } template <typename TOther, int OtherMaxValue, typename std::enable_if< std::is_same<T, typename std::remove_cv<TOther>::type>::value, int>::type = 0> inline void CopyFrom(const Image<TOther, OtherMaxValue>& img) { if (IsValid() && img.IsValid()) { CHECK(w >= img.w && h >= img.h); details::PitchedCopy((char*)ptr, pitch, (const char*)img.ptr, img.pitch, std::min(img.w, w) * sizeof(T), std::min(img.h, h)); } else if (img.IsValid() != IsValid()) { CHECK(false && "Cannot copy from / to an unasigned image."); } } // Converting one of the Eigen image types is not implemented template < typename R, typename std::enable_if<std::is_integral<BaseType>::value, typename R::Base::BaseType>::type = 0, typename std::enable_if< std::is_integral<typename R::Base::BaseType>::value, int>::type = 0> inline R ConvertTo() const { // Ensure for now that they are a powers of 2 (true for all current // use cases). This simplifies the arithmetic. static_assert(((R::Base::max_value + 1) & R::Base::max_value) == 0, "Must be power of 2"); static_assert(((max_value + 1) & max_value) == 0, "Must be power of 2"); // Only support converting from higher bpp to lower bpp. // since we're doing integer division. static_assert(R::Base::max_value < max_value, "Can only reduce"); // Sanity check static_assert((max_value + 1) % (R::Base::max_value + 1) == 0, "Should be divisble"); // It's not correct to divide by 4 for say going from 10bpp to 8bpp. // To be exact, we'd need to multiply by 255 / 1023 // However, this should be sufficiently accurate. static constexpr int CONVERSION_FACTOR = (max_value + 1) / (R::Base::max_value + 1); R dest_image{this->w, this->h}; for (size_t y = 0; y < this->h; ++y) { const BaseType* src_pixel_ptr = this->RowPtr(y); typename R::Base::BaseType* dest_pixel_ptr = dest_image.RowPtr(y); for (size_t x = 0; x < this->w; ++x) { *dest_pixel_ptr = *src_pixel_ptr / CONVERSION_FACTOR; ++src_pixel_ptr; ++dest_pixel_ptr; } } return dest_image; } ////////////////////////////////////////////////////// // Direct Pixel Access ////////////////////////////////////////////////////// inline bool IsValid() const { return ptr != 0; } inline bool IsContiguous() const { return w * sizeof(T) == pitch; } inline T* RowPtr(size_t y) { CHECK(YInBounds(y)); return (T*)((unsigned char*)(ptr) + y * pitch); } inline const T* RowPtr(size_t y) const { CHECK(YInBounds(y)); return (T*)((unsigned char*)(ptr) + y * pitch); } inline T& operator()(size_t x, size_t y) { CHECK(InBounds(x, y)); return RowPtr(y)[x]; } inline const T& operator()(size_t x, size_t y) const { CHECK(InBounds(x, y)); return RowPtr(y)[x]; } inline T& operator()(const Eigen::Vector2i& p) { CHECK(InBounds(p[0], p[1])); return RowPtr(p[1])[p[0]]; } inline const T& operator()(const Eigen::Vector2i& p) const { CHECK(InBounds(p[0], p[1])); return RowPtr(p[1])[p[0]]; } inline T& operator[](size_t ix) { CHECK(InImage(ptr + ix)); return ptr[ix]; } inline const T& operator[](size_t ix) const { CHECK(InImage(ptr + ix)); return ptr[ix]; } inline T& Get(int x, int y) { CHECK(InBounds(x, y)); return RowPtr(y)[x]; } inline const T& Get(int x, int y) const { CHECK(InBounds(x, y)); return RowPtr(y)[x]; } ////////////////////////////////////////////////////// // Bounds Checking ////////////////////////////////////////////////////// bool InImage(const T* ptest) const { return ptr <= ptest && ptest < end(); } inline bool YInBounds(int y) const { return 0 <= y && y < (int)h; } inline bool InBounds(int x, int y) const { return 0 <= x && x < (int)w && 0 <= y && y < (int)h; } inline bool InBounds(float x, float y, float border) const { return border <= x && x < (w - border) && border <= y && y < (h - border); } template <typename Derived> inline bool InBounds( const Eigen::MatrixBase<Derived>& p, const typename Eigen::MatrixBase<Derived>::Scalar border) const { EIGEN_STATIC_ASSERT_FIXED_SIZE(Derived); EIGEN_STATIC_ASSERT(Derived::RowsAtCompileTime == 2, THIS_METHOD_IS_ONLY_FOR_MATRICES_OF_A_SPECIFIC_SIZE); return border <= p[0] && p[0] < ((int)w - border) && border <= p[1] && p[1] < ((int)h - border); } template <typename Derived> inline bool InBounds(const Eigen::MatrixBase<Derived>& p) const { EIGEN_STATIC_ASSERT_FIXED_SIZE(Derived); EIGEN_STATIC_ASSERT(Derived::RowsAtCompileTime == 2, THIS_METHOD_IS_ONLY_FOR_MATRICES_OF_A_SPECIFIC_SIZE); return 0 <= p[0] && p[0] < (int)w && 0 <= p[1] && p[1] < (int)h; } ////////////////////////////////////////////////////// // Obtain slices / subimages ////////////////////////////////////////////////////// inline const Image<const T, MaxValue> SubImage(size_t x, size_t y, size_t width, size_t height) const { CHECK((x + width) <= w && (y + height) <= h); return Image<const T, MaxValue>(RowPtr(y) + x, width, height, pitch); } inline Image<T, MaxValue> SubImage(size_t x, size_t y, size_t width, size_t height) { CHECK((x + width) <= w && (y + height) <= h); return Image<T, MaxValue>(RowPtr(y) + x, width, height, pitch); } inline const Image<const T, MaxValue> SubImage( const Eigen::AlignedBox2i& b) const { return SubImage(b.min()(0), b.min()(1), b.sizes()(0), b.sizes()(1)); } inline Image<T> SubImage(const Eigen::AlignedBox2i& b) { return SubImage(b.min()(0), b.min()(1), b.sizes()(0), b.sizes()(1)); } inline Image<const T, MaxValue> Row(int y) const { return SubImage(0, y, w, 1); } inline Image<const T, MaxValue> Col(int x) const { return SubImage(x, 0, 1, h); } inline Image<T, MaxValue> Row(int y) { return SubImage(0, y, w, 1); } inline Image<T, MaxValue> Col(int x) { return SubImage(x, 0, 1, h); } inline Image<T, MaxValue> SubImage(int width, int height) { CHECK(width <= (int)w && height <= (int)h); return Image<T, MaxValue>(ptr, width, height, pitch); } inline Eigen::Map<Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic>> GetEigenMap() { // to enable operations on images using eigen: e.g. im_map = im_map + // im_map*2; Eigen::Map<Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic>> im_map(ptr, h, w); return im_map; } inline Eigen::Map<const Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic>> GetEigenMap() const { // to enable operations on images using eigen: e.g. im_map = im_map + // im_map*2; Eigen::Map<const Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic>> im_map( ptr, h, w); return im_map; } inline Eigen::Map<Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic>, 0, Eigen::Stride<Eigen::Dynamic, 1>> GetEigenMapPitched() { // to enable operations on images using eigen: e.g. im_map = im_map + // im_map*2; use Eigen::Stride to work on subimages as well. Eigen::Map<Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic>, 0, Eigen::Stride<Eigen::Dynamic, 1>> im_map(ptr, h, w, Eigen::Stride<Eigen::Dynamic, 1>(pitch / sizeof(T), 1)); return im_map; } inline Eigen::Map<const Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic>, 0, Eigen::Stride<Eigen::Dynamic, 1>> GetEigenMapPitched() const { // to enable operations on images using eigen: e.g. im_map = im_map + // im_map*2; use Eigen::Stride to work on subimages as well. Eigen::Map<const Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic>, 0, Eigen::Stride<Eigen::Dynamic, 1>> im_map(ptr, h, w, Eigen::Stride<Eigen::Dynamic, 1>(pitch / sizeof(T), 1)); return im_map; } template <typename Tout = T> Tout Sum() const { Tout sum = details::Zero<Tout>::val(); for (size_t r = 0; r < h; ++r) { const T* row_ptr = RowPtr(r); const T* end = row_ptr + w; while (row_ptr != end) { sum += Tout(*row_ptr); ++row_ptr; } } return sum; } template <typename Tout = T> Tout Mean() const { return Sum<Tout>() / Area(); } size_t pitch; T* ptr; size_t w; size_t h; }; #if defined __AVX2__ && !defined __NVCC__ // TODO: break this into vector utility inline float hsum(__m256 v) { __m128 v2 = _mm256_extractf128_ps(v, 1); __m128 v1 = _mm256_castps256_ps128(v); v1 = _mm_add_ps(v1, v2); v2 = _mm_movehdup_ps(v1); // broadcast elements 3,1 to 2,0 v1 = _mm_add_ps(v1, v2); v2 = _mm_movehl_ps(v2, v1); // high half -> low half v1 = _mm_add_ss(v1, v2); return _mm_cvtss_f32(v1); } template <> template <> inline float Image<float>::Sum<float>() const { const size_t w32loops = w / 32; const size_t w8loops = (w - w32loops * 32) / 8; const size_t w1loops = w & 7; __m256 sum = _mm256_setzero_ps(); alignas(32) float arr[8]; const float* rowStart = RowPtr(0); const size_t rowElts = pitch / sizeof(float); _mm256_store_ps(arr, sum); for (size_t r = 0; r < h; ++r, rowStart += rowElts) { const float* rowOffset = rowStart; for (size_t i = 0; i < w32loops; ++i, rowOffset += 32) { sum = _mm256_add_ps(sum, _mm256_loadu_ps(rowOffset)); sum = _mm256_add_ps(sum, _mm256_loadu_ps(rowOffset + 8)); sum = _mm256_add_ps(sum, _mm256_loadu_ps(rowOffset + 16)); sum = _mm256_add_ps(sum, _mm256_loadu_ps(rowOffset + 24)); } for (size_t i = 0; i < w8loops; ++i, rowOffset += 8) { sum = _mm256_add_ps(sum, _mm256_loadu_ps(rowOffset)); } std::copy_n(rowOffset, w1loops, arr); sum = _mm256_add_ps(sum, _mm256_load_ps(arr)); } return hsum(sum); } #endif //__AVX2__ template <typename T, int MaxValue> constexpr int Image<T, MaxValue>::max_value; static_assert(Image<uint8_t>::max_value == 255, "Compile time sanity check"); static_assert(Image<float>::max_value == 1, "Compile time sanity check"); static_assert(Image<uint16_t, 1023>::max_value == 1023, "Compile time sanity check"); static_assert(Image<float, 0>::max_value == 0, "Compile time sanity check"); static_assert(Image<Eigen::Matrix<uint8_t, 3, 3>>::max_value == 255, "Compile time sanity check"); static_assert(Image<Eigen::Matrix<float, 1, 2>>::max_value == 1, "Compile time sanity check"); static_assert(Image<Eigen::Matrix<uint16_t, 1, 3>, 1023>::max_value == 1023, "Compile time sanity check"); static_assert(Image<Eigen::Vector3d, -1>::max_value == -1, "Compile time sanity check"); // Image that manages it's own memory, storing a strong pointer to it's memory template <typename T, class Allocator_ = DefaultImageAllocator<T>, int MaxValue = DefaultImageValTraits<T>::max_value> class ManagedImage : public Image<T, MaxValue> { public: using Base = Image<T, MaxValue>; using Allocator = Allocator_; // Destructor inline ~ManagedImage() { Deallocate(); } // Null image inline ManagedImage() {} // Row image // // Precondition: w must not be 0. inline ManagedImage(size_t w) : Base(Allocator().allocate(w), w, 1, w * sizeof(T)) { CHECK(w != 0); } // Precondition: Neither w nor h should be 0. inline ManagedImage(size_t w, size_t h) : Base(Allocator().allocate(w * h), w, h, w * sizeof(T)) { CHECK(w != 0 && h != 0); } // Precondition: Neither dim.width nor dim.height should be 0. inline ManagedImage(ImageDimensions dim) : ManagedImage(dim.width, dim.height) {} // Precondition: Neither dim.x() nor dim.y() should be 0. inline ManagedImage(Eigen::Vector2i dim) : ManagedImage(dim.x(), dim.y()) {} // Not copy constructable inline ManagedImage(const ManagedImage& other) = delete; // Move constructor inline ManagedImage(ManagedImage&& img) noexcept : Base(img.ptr, img.w, img.h, img.pitch) { img.ptr = nullptr; } // Move asignment inline void operator=(ManagedImage&& img) { Deallocate(); Base::pitch = img.pitch; Base::ptr = img.ptr; Base::w = img.w; Base::h = img.h; img.ptr = nullptr; } inline void Swap(ManagedImage& img) { std::swap(img.pitch, Image<T>::pitch); std::swap(img.ptr, Image<T>::ptr); std::swap(img.w, Image<T>::w); std::swap(img.h, Image<T>::h); } template <typename TOther, int OtherMaxValue> inline void CopyFrom(const Image<TOther, OtherMaxValue>& img) { if (!Base::IsValid() || Base::w != img.w || Base::h != img.h) { Reinitialise(img.w, img.h); } Base::CopyFrom(img); } inline void Reinitialise(size_t width, size_t height) { if (width == 0 || height == 0) { *this = ManagedImage(); } else if (!Base::ptr || Base::Width() != width || Base::Height() != height) { *this = ManagedImage(width, height); } } inline void Reinitialise(const Eigen::Vector2i& dim) { Reinitialise(dim(0), dim(1)); } protected: inline void Deallocate() { if (Base::ptr) { Allocator().deallocate(Base::ptr, Base::Area()); Base::ptr = nullptr; } } }; template <typename T, template <typename...> class Alloc> using ManagedImageAlloc = ManagedImage<T, Alloc<T>>; // Image Processing / IO functions template <typename Tout, typename Tin> ManagedImage<Tout> Own(pangolin::Image<Tin>& im) { return std::move(reinterpret_cast<ManagedImage<Tout>&>(im)); } template <typename T> ManagedImage<T> LoadImage(const std::string& filename) { pangolin::TypedImage img = pangolin::LoadImage(filename); CHECK(img.fmt.bpp == sizeof(T) * 8); return Own<T, uint8_t>(img); } } // namespace surreal_opensource