2
\$\begingroup\$

This is a follow-up question for An Updated Multi-dimensional Image Data Structure with Variadic Template Functions in C++. I am trying to build the bridge to perform the conversion between the implemented Image class and OpenCV's mat data structure in C++ in this post.

The experimental implementation

  • to_cv_mat function implementation

    // to_cv_mat function implementation
    constexpr auto to_cv_mat(const Image<RGB>& input)
    {
     cv::Mat output = cv::Mat::zeros(cv::Size(input.getWidth(), input.getHeight()), CV_8UC3);
     #pragma omp parallel for collapse(2)
     for (int y = 0; y < output.rows; ++y)
     {
     for (int x = 0; x < output.cols; ++x)
     {
     output.at<cv::Vec3b>(output.rows - y - 1, x)[0] = input.at(x, y).channels[2];
     output.at<cv::Vec3b>(output.rows - y - 1, x)[1] = input.at(x, y).channels[1];
     output.at<cv::Vec3b>(output.rows - y - 1, x)[2] = input.at(x, y).channels[0];
     }
     }
     return output;
    }
    
  • to_color_image function implementation

    // to_color_image function implementation
    constexpr auto to_color_image(const cv::Mat input)
    {
     auto output = Image<RGB>(input.cols, input.rows);
     #pragma omp parallel for collapse(2)
     for (int y = 0; y < input.rows; ++y)
     {
     for (int x = 0; x < input.cols; ++x)
     {
     output.at(x, y).channels[0] = input.at<cv::Vec3b>(input.rows - y - 1, x)[2];
     output.at(x, y).channels[1] = input.at<cv::Vec3b>(input.rows - y - 1, x)[1];
     output.at(x, y).channels[2] = input.at<cv::Vec3b>(input.rows - y - 1, x)[0];
     }
     }
     return output;
    }
    

Full Testing Code

The full testing code:

// Convert Image Class to OpenCV Mat Data Structure in C++
#include <algorithm>
#include <cassert>
#include <cctype>
#include <chrono>
#include <cmath>
#include <complex>
#include <concepts>
#include <execution>
#include <format>
#include <iostream>
#include <limits>
#include <map>
#include <numeric>
#include <optional>
#include <queue>
#include <ranges>
#include <stack>
#include <string>
#include <opencv2/opencv.hpp>
struct RGB
{
 std::uint8_t channels[3];
};
// From https://stackoverflow.com/a/37264642/6667035
#ifndef NDEBUG
# define M_Assert(Expr, Msg) \
 M_Assert_Helper(#Expr, Expr, __FILE__, __LINE__, Msg)
#else
# define M_Assert(Expr, Msg) ;
#endif
void M_Assert_Helper(const char* expr_str, bool expr, const char* file, int line, std::string msg)
{
 if (!expr)
 {
 std::cerr << "Assert failed:\t" << msg << "\n"
 << "Expected:\t" << expr_str << "\n"
 << "Source:\t\t" << file << ", line " << line << "\n";
 abort();
 }
}
struct recursive_print_fn
{
 template<std::ranges::input_range T>
 constexpr auto operator()(const T& input, const int level = 0) const
 {
 T output = input;
 std::cout << std::string(level, ' ') << "Level " << level << ":" << std::endl;
 std::ranges::transform(std::ranges::cbegin(input), std::ranges::cend(input), std::ranges::begin(output),
 [&](auto&& x)
 {
 std::cout << std::string(level, ' ') << x << std::endl;
 return x;
 }
 );
 return output;
 }
 template<std::ranges::input_range T>
 requires (std::ranges::input_range<std::ranges::range_value_t<T>>)
 constexpr auto operator()(const T& input, const int level = 0) const
 {
 T output = input;
 std::cout << std::string(level, ' ') << "Level " << level << ":" << std::endl;
 std::ranges::transform(std::ranges::cbegin(input), std::ranges::cend(input), std::ranges::begin(output),
 [&](auto&& element)
 {
 return operator()(element, level + 1);
 }
 );
 return output;
 }
};
inline constexpr recursive_print_fn recursive_print;
namespace TinyDIP
{
 template <typename ElementT>
 class Image
 {
 public:
 Image() = default;
 template<std::same_as<std::size_t>... Sizes>
 Image(Sizes... sizes): size{sizes...}, image_data((1 * ... * sizes))
 {}
 template<std::same_as<int>... Sizes>
 Image(Sizes... sizes)
 {
 size.reserve(sizeof...(sizes));
 (size.push_back(sizes), ...);
 image_data.resize(
 std::reduce(
 std::ranges::cbegin(size),
 std::ranges::cend(size),
 std::size_t{1},
 std::multiplies<>()
 )
 );
 }
 template<std::ranges::input_range Range,
 std::same_as<std::size_t>... Sizes>
 Image(const Range& input, Sizes... sizes):
 size{sizes...}, image_data(begin(input), end(input))
 {
 if (image_data.size() != (1 * ... * sizes)) {
 throw std::runtime_error("Image data input and the given size are mismatched!");
 }
 }
 template<std::ranges::input_range Range>
 requires(std::same_as<std::ranges::range_value_t<Range>, ElementT>)
 Image(Range&& input, std::size_t newWidth, std::size_t newHeight)
 {
 size.reserve(2);
 size.emplace_back(newWidth);
 size.emplace_back(newHeight);
 if (input.size() != newWidth * newHeight)
 {
 throw std::runtime_error("Image data input and the given size are mismatched!");
 }
 image_data = std::move(input); // Reference: https://stackoverflow.com/a/51706522/6667035
 }
 Image(const std::vector<std::vector<ElementT>>& input)
 {
 size.reserve(2);
 size.emplace_back(input[0].size());
 size.emplace_back(input.size());
 for (auto& rows : input)
 {
 image_data.insert(image_data.end(), std::ranges::begin(input), std::ranges::end(input)); // flatten
 }
 return;
 }
 // at template function implementation
 template<typename... Args>
 constexpr ElementT& at(const Args... indexInput)
 {
 return const_cast<ElementT&>(static_cast<const Image &>(*this).at(indexInput...));
 }
 // at template function implementation
 // Reference: https://codereview.stackexchange.com/a/288736/231235
 template<typename... Args>
 constexpr ElementT const& at(const Args... indexInput) const
 {
 checkBoundary(indexInput...);
 constexpr std::size_t n = sizeof...(Args);
 if(n != size.size())
 {
 throw std::runtime_error("Dimensionality mismatched!");
 }
 std::size_t i = 0;
 std::size_t stride = 1;
 std::size_t position = 0;
 auto update_position = [&](auto index) {
 position += index * stride;
 stride *= size[i++];
 };
 (update_position(indexInput), ...);
 return image_data[position];
 }
 // at without boundary check
 template<typename... Args>
 constexpr ElementT& at_without_boundary_check(const Args... indexInput)
 {
 return const_cast<ElementT&>(static_cast<const Image &>(*this).at_without_boundary_check(indexInput...));
 }
 template<typename... Args>
 constexpr ElementT const& at_without_boundary_check(const Args... indexInput) const
 {
 std::size_t i = 0;
 std::size_t stride = 1;
 std::size_t position = 0;
 auto update_position = [&](auto index) {
 position += index * stride;
 stride *= size[i++];
 };
 (update_position(indexInput), ...);
 return image_data[position];
 }
 constexpr std::size_t count() const noexcept
 {
 return std::reduce(std::ranges::cbegin(size), std::ranges::cend(size), 1, std::multiplies());
 }
 constexpr std::size_t getDimensionality() const noexcept
 {
 return size.size();
 }
 constexpr std::size_t getWidth() const noexcept
 {
 return size[0];
 }
 constexpr std::size_t getHeight() const noexcept
 {
 return size[1];
 }
 constexpr auto getSize() noexcept
 {
 return size;
 }
 std::vector<ElementT> const& getImageData() const noexcept { return image_data; } // expose the internal data
 void print(std::string separator = "\t", std::ostream& os = std::cout) const
 {
 if(size.size() == 1)
 {
 for(std::size_t x = 0; x < size[0]; ++x)
 {
 // Ref: https://isocpp.org/wiki/faq/input-output#print-char-or-ptr-as-number
 os << +at(x) << separator;
 }
 os << "\n";
 }
 else if(size.size() == 2)
 {
 for (std::size_t y = 0; y < size[1]; ++y)
 {
 for (std::size_t x = 0; x < size[0]; ++x)
 {
 // Ref: https://isocpp.org/wiki/faq/input-output#print-char-or-ptr-as-number
 os << +at(x, y) << separator;
 }
 os << "\n";
 }
 os << "\n";
 }
 else if (size.size() == 3)
 {
 for(std::size_t z = 0; z < size[2]; ++z)
 {
 for (std::size_t y = 0; y < size[1]; ++y)
 {
 for (std::size_t x = 0; x < size[0]; ++x)
 {
 // Ref: https://isocpp.org/wiki/faq/input-output#print-char-or-ptr-as-number
 os << +at(x, y, z) << separator;
 }
 os << "\n";
 }
 os << "\n";
 }
 os << "\n";
 }
 }
 Image<ElementT>& setAllValue(const ElementT input)
 {
 std::fill(std::ranges::begin(image_data), std::ranges::end(image_data), input);
 return *this;
 }
 friend std::ostream& operator<<(std::ostream& os, const Image<ElementT>& rhs)
 {
 const std::string separator = "\t";
 rhs.print(separator, os);
 return os;
 }
 Image<ElementT>& operator+=(const Image<ElementT>& rhs)
 {
 check_size_same(rhs, *this);
 std::transform(std::ranges::cbegin(image_data), std::ranges::cend(image_data), std::ranges::cbegin(rhs.image_data),
 std::ranges::begin(image_data), std::plus<>{});
 return *this;
 }
 Image<ElementT>& operator-=(const Image<ElementT>& rhs)
 {
 check_size_same(rhs, *this);
 std::transform(std::ranges::cbegin(image_data), std::ranges::cend(image_data), std::ranges::cbegin(rhs.image_data),
 std::ranges::begin(image_data), std::minus<>{});
 return *this;
 }
 Image<ElementT>& operator*=(const Image<ElementT>& rhs)
 {
 check_size_same(rhs, *this);
 std::transform(std::ranges::cbegin(image_data), std::ranges::cend(image_data), std::ranges::cbegin(rhs.image_data),
 std::ranges::begin(image_data), std::multiplies<>{});
 return *this;
 }
 Image<ElementT>& operator/=(const Image<ElementT>& rhs)
 {
 check_size_same(rhs, *this);
 std::transform(std::ranges::cbegin(image_data), std::ranges::cend(image_data), std::ranges::cbegin(rhs.image_data),
 std::ranges::begin(image_data), std::divides<>{});
 return *this;
 }
 friend bool operator==(Image<ElementT> const&, Image<ElementT> const&) = default;
 friend bool operator!=(Image<ElementT> const&, Image<ElementT> const&) = default;
 friend Image<ElementT> operator+(Image<ElementT> input1, const Image<ElementT>& input2)
 {
 return input1 += input2;
 }
 friend Image<ElementT> operator-(Image<ElementT> input1, const Image<ElementT>& input2)
 {
 return input1 -= input2;
 }
 friend Image<ElementT> operator*(Image<ElementT> input1, ElementT input2)
 {
 return multiplies(input1, input2);
 }
 friend Image<ElementT> operator*(ElementT input1, Image<ElementT> input2)
 {
 return multiplies(input2, input1);
 }
 
 #ifdef USE_BOOST_SERIALIZATION
 void Save(std::string filename)
 {
 const std::string filename_with_extension = filename + ".dat";
 // Reference: https://stackoverflow.com/questions/523872/how-do-you-serialize-an-object-in-c
 std::ofstream ofs(filename_with_extension, std::ios::binary);
 boost::archive::binary_oarchive ArchiveOut(ofs);
 // write class instance to archive
 ArchiveOut << *this;
 // archive and stream closed when destructors are called
 ofs.close();
 }
 
 #endif
 private:
 std::vector<std::size_t> size;
 std::vector<ElementT> image_data;
 template<typename... Args>
 void checkBoundary(const Args... indexInput) const
 {
 constexpr std::size_t n = sizeof...(Args);
 if(n != size.size())
 {
 throw std::runtime_error("Dimensionality mismatched!");
 }
 std::size_t parameter_pack_index = 0;
 auto function = [&](auto index) {
 if (index >= size[parameter_pack_index])
 throw std::out_of_range("Given index out of range!");
 parameter_pack_index = parameter_pack_index + 1;
 };
 (function(indexInput), ...);
 }
 #ifdef USE_BOOST_SERIALIZATION
 friend class boost::serialization::access;
 template<class Archive>
 void serialize(Archive& ar, const unsigned int version)
 {
 ar& size;
 ar& image_data;
 }
 
 #endif
 };
 template<typename T, typename ElementT>
 concept is_Image = std::is_same_v<T, Image<ElementT>>;
 // to_cv_mat function implementation
 constexpr auto to_cv_mat(const Image<RGB>& input)
 {
 cv::Mat output = cv::Mat::zeros(cv::Size(input.getWidth(), input.getHeight()), CV_8UC3);
 #pragma omp parallel for collapse(2)
 for (int y = 0; y < output.rows; ++y)
 {
 for (int x = 0; x < output.cols; ++x)
 {
 output.at<cv::Vec3b>(output.rows - y - 1, x)[0] = input.at(x, y).channels[2];
 output.at<cv::Vec3b>(output.rows - y - 1, x)[1] = input.at(x, y).channels[1];
 output.at<cv::Vec3b>(output.rows - y - 1, x)[2] = input.at(x, y).channels[0];
 }
 }
 return output;
 }
 // to_color_image function implementation
 constexpr auto to_color_image(const cv::Mat input)
 {
 auto output = Image<RGB>(input.cols, input.rows);
 #pragma omp parallel for collapse(2)
 for (int y = 0; y < input.rows; ++y)
 {
 for (int x = 0; x < input.cols; ++x)
 {
 output.at(x, y).channels[0] = input.at<cv::Vec3b>(input.rows - y - 1, x)[2];
 output.at(x, y).channels[1] = input.at<cv::Vec3b>(input.rows - y - 1, x)[1];
 output.at(x, y).channels[2] = input.at<cv::Vec3b>(input.rows - y - 1, x)[0];
 }
 }
 return output;
 }
}
int main()
{
 auto start = std::chrono::system_clock::now();
 std::string file_path = "InputImages/1";
 auto bmp1 = TinyDIP::bmp_read(file_path.c_str(), false);
 auto cv_mat = TinyDIP::to_cv_mat(bmp1);
 cv::imshow("Image", cv_mat);
 cv::waitKey(0);
 auto end = std::chrono::system_clock::now();
 std::chrono::duration<double> elapsed_seconds = end - start;
 std::time_t end_time = std::chrono::system_clock::to_time_t(end);
 std::cout << "Computation finished at " << std::ctime(&end_time) << "elapsed time: " << elapsed_seconds.count() << '\n';
 return 0;
}

TinyDIP on GitHub

All suggestions are welcome.

The summary information:

  • Which question it is a follow-up to?

    An Updated Multi-dimensional Image Data Structure with Variadic Template Functions in C++

  • What changes has been made in the code since last question?

    I am trying to build the bridge to perform the conversion between the implemented Image class and OpenCV's mat data structure in C++ in this post.

  • Why a new review is being asked for?

    Please review the implementation of to_cv_mat and to_color_image functions.

asked Jun 14, 2024 at 8:53
\$\endgroup\$
3
  • \$\begingroup\$ Very interesting topic. \$\endgroup\$ Commented Jun 14, 2024 at 9:37
  • 1
    \$\begingroup\$ I’ve mentioned before that you shouldn’t call at for each pixel. Take a pointer to the data, and do a memcpy (or std::copy in C++). But also, you can create an OpenCV object that points at the data array in your object, then no copy is necessary. \$\endgroup\$ Commented Jun 14, 2024 at 14:43
  • \$\begingroup\$ It takes time to adopt new habits and get rid of old ones :) @CrisLuengo \$\endgroup\$ Commented Jun 15, 2024 at 17:41

0

Know someone who can answer? Share a link to this question via email, Twitter, or Facebook.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.