Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add eigen tensor conversions #17320

Merged
merged 14 commits into from
May 23, 2020
Merged
106 changes: 106 additions & 0 deletions modules/core/include/opencv2/core/eigen.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@

#include "opencv2/core.hpp"

#if EIGEN_WORLD_VERSION == 3 && EIGEN_MAJOR_VERSION >= 3
#include <unsupported/Eigen/CXX11/Tensor>
#define OPENCV_EIGEN_TENSOR_SUPPORT
#endif // EIGEN_WORLD_VERSION == 3 && EIGEN_MAJOR_VERSION >= 3

#if defined _MSC_VER && _MSC_VER >= 1200
#pragma warning( disable: 4714 ) //__forceinline is not inlined
#pragma warning( disable: 4127 ) //conditional expression is constant
Expand All @@ -59,6 +64,107 @@ namespace cv
//! @addtogroup core_eigen
//! @{

#ifdef OPENCV_EIGEN_TENSOR_SUPPORT
/** @brief Converts an Eigen::Tensor to a cv::Mat.

The method converts an Eigen::Tensor with shape (H x W x C) to a cv::Mat where:
H = number of rows
W = number of columns
C = number of channels

Usage:
\code
Eigen::Tensor<float, 3, Eigen::RowMajor> a_tensor(...);
// populate tensor with values
Mat a_mat;
eigen2cv(a_tensor, a_mat);
\endcode
*/
template <typename _Tp, int _layout> static inline
void eigen2cv( const Eigen::Tensor<_Tp, 3, _layout> &src, OutputArray dst )
{
if( !(_layout & Eigen::RowMajorBit) )
{
const std::array<int, 3> shuffle{2, 1, 0};
Eigen::Tensor<_Tp, 3, !_layout> row_major_tensor = src.swap_layout().shuffle(shuffle);
Mat _src(src.dimension(0), src.dimension(1), CV_MAKETYPE(DataType<_Tp>::type, src.dimension(2)), row_major_tensor.data());
_src.copyTo(dst);
}
else
{
Mat _src(src.dimension(0), src.dimension(1), CV_MAKETYPE(DataType<_Tp>::type, src.dimension(2)), (void *)src.data());
_src.copyTo(dst);
}
}

/** @brief Converts a cv::Mat to an Eigen::Tensor.

The method converts a cv::Mat to an Eigen Tensor with shape (H x W x C) where:
H = number of rows
W = number of columns
C = number of channels

Usage:
\code
Mat a_mat(...);
// populate Mat with values
Eigen::Tensor<float, 3, Eigen::RowMajor> a_tensor(...);
cv2eigen(a_mat, a_tensor);
\endcode
*/
template <typename _Tp, int _layout> static inline
void cv2eigen( const Mat &src, Eigen::Tensor<_Tp, 3, _layout> &dst )
{
if( !(_layout & Eigen::RowMajorBit) )
{
Eigen::Tensor<_Tp, 3, !_layout> row_major_tensor(src.rows, src.cols, src.channels());
Mat _dst(src.rows, src.cols, CV_MAKETYPE(DataType<_Tp>::type, src.channels()), row_major_tensor.data());
if (src.type() == _dst.type())
src.copyTo(_dst);
else
src.convertTo(_dst, _dst.type());
const std::array<int, 3> shuffle{2, 1, 0};
dst = row_major_tensor.swap_layout().shuffle(shuffle);
}
else
{
dst.resize(src.rows, src.cols, src.channels());
Mat _dst(src.rows, src.cols, CV_MAKETYPE(DataType<_Tp>::type, src.channels()), dst.data());
if (src.type() == _dst.type())
src.copyTo(_dst);
else
src.convertTo(_dst, _dst.type());
}
}

/** @brief Maps cv::Mat data to an Eigen::TensorMap.

The method wraps an existing Mat data array with an Eigen TensorMap of shape (H x W x C) where:
H = number of rows
W = number of columns
C = number of channels

Explicit instantiation of the return type is required.

@note Caller should be aware of the lifetime of the cv::Mat instance and take appropriate safety measures.
The cv::Mat instance will retain ownership of the data and the Eigen::TensorMap will lose access when the cv::Mat data is deallocated.

The example below initializes a cv::Mat and produces an Eigen::TensorMap:
\code
float arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
Mat a_mat(2, 2, CV_32FC3, arr);
Eigen::TensorMap<Eigen::Tensor<float, 3, Eigen::RowMajor>> a_tensormap = cv2eigen_tensormap<float>(a_mat);
\endcode
*/
template <typename _Tp> static inline
Eigen::TensorMap<Eigen::Tensor<_Tp, 3, Eigen::RowMajor>> cv2eigen_tensormap(const cv::InputArray &src)
{
Mat mat = src.getMat();
CV_CheckTypeEQ(mat.type(), CV_MAKETYPE(traits::Type<_Tp>::value, mat.channels()), "");
return Eigen::TensorMap<Eigen::Tensor<_Tp, 3, Eigen::RowMajor>>((_Tp *)mat.data, mat.rows, mat.cols, mat.channels());
}
#endif // OPENCV_EIGEN_TENSOR_SUPPORT

template<typename _Tp, int _rows, int _cols, int _options, int _maxRows, int _maxCols> static inline
void eigen2cv( const Eigen::Matrix<_Tp, _rows, _cols, _options, _maxRows, _maxCols>& src, OutputArray dst )
{
Expand Down
80 changes: 80 additions & 0 deletions modules/core/test/test_mat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2084,6 +2084,86 @@ TEST(Core_Eigen, eigen2cv_check_Mat_type)
}
#endif // HAVE_EIGEN

#ifdef OPENCV_EIGEN_TENSOR_SUPPORT
TEST(Core_Eigen, cv2eigen_check_tensor_conversion)
{
Mat A(2, 3, CV_32FC3);
float value = 0;
for(int row=0; row<A.rows; row++)
for(int col=0; col<A.cols; col++)
for(int ch=0; ch<A.channels(); ch++)
A.at<Vec3f>(row,col)[ch] = value++;

Eigen::Tensor<float, 3, Eigen::RowMajor> row_tensor;
cv2eigen(A, row_tensor);

float* mat_ptr = (float*)A.data;
float* tensor_ptr = row_tensor.data();
for (int i=0; i< row_tensor.size(); i++)
ASSERT_FLOAT_EQ(mat_ptr[i], tensor_ptr[i]);

Eigen::Tensor<float, 3, Eigen::ColMajor> col_tensor;
cv2eigen(A, col_tensor);
value = 0;
for(int row=0; row<A.rows; row++)
for(int col=0; col<A.cols; col++)
for(int ch=0; ch<A.channels(); ch++)
ASSERT_FLOAT_EQ(value++, col_tensor(row,col,ch));
}
#endif // OPENCV_EIGEN_TENSOR_SUPPORT

#ifdef OPENCV_EIGEN_TENSOR_SUPPORT
TEST(Core_Eigen, eigen2cv_check_tensor_conversion)
{
Eigen::Tensor<float, 3, Eigen::RowMajor> row_tensor(2,3,3);
Eigen::Tensor<float, 3, Eigen::ColMajor> col_tensor(2,3,3);
float value = 0;
for(int row=0; row<row_tensor.dimension(0); row++)
for(int col=0; col<row_tensor.dimension(1); col++)
for(int ch=0; ch<row_tensor.dimension(2); ch++)
{
row_tensor(row,col,ch) = value;
col_tensor(row,col,ch) = value;
value++;
}

Mat A;
eigen2cv(row_tensor, A);

float* tensor_ptr = row_tensor.data();
float* mat_ptr = (float*)A.data;
for (int i=0; i< row_tensor.size(); i++)
ASSERT_FLOAT_EQ(tensor_ptr[i], mat_ptr[i]);

Mat B;
eigen2cv(col_tensor, B);

value = 0;
for(int row=0; row<B.rows; row++)
for(int col=0; col<B.cols; col++)
for(int ch=0; ch<B.channels(); ch++)
ASSERT_FLOAT_EQ(value++, B.at<Vec3f>(row,col)[ch]);
}
#endif // OPENCV_EIGEN_TENSOR_SUPPORT

#ifdef OPENCV_EIGEN_TENSOR_SUPPORT
TEST(Core_Eigen, cv2eigen_tensormap_check_tensormap_access)
{
float arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
Mat a_mat(2, 2, CV_32FC3, arr);
Eigen::TensorMap<Eigen::Tensor<float, 3, Eigen::RowMajor>> a_tensor = cv2eigen_tensormap<float>(a_mat);

for(int i=0; i<a_mat.rows; i++) {
for (int j=0; j<a_mat.cols; j++) {
for (int ch=0; ch<a_mat.channels(); ch++) {
ASSERT_FLOAT_EQ(a_mat.at<Vec3f>(i,j)[ch], a_tensor(i,j,ch));
ASSERT_EQ(&a_mat.at<Vec3f>(i,j)[ch], &a_tensor(i,j,ch));
}
}
}
}
#endif // OPENCV_EIGEN_TENSOR_SUPPORT

TEST(Mat, regression_12943) // memory usage: ~4.5 Gb
{
applyTestTag(CV_TEST_TAG_MEMORY_6GB);
Expand Down