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;
}
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
andto_color_image
functions.
at
for each pixel. Take a pointer to the data, and do amemcpy
(orstd::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\$