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
102 changes: 102 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,103 @@ 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.

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();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type check is required here through CV_CheckTypeEQ(mat.type(), traits::Type<_Tp>::value, "")

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The suggestion almost worked. I had to modify to account for multiple channels.

CV_CheckTypeEQ(mat.type(), CV_MAKETYPE(traits::Type<_Tp>::value, mat.channels()), "");

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jgbradley1 You are right!

Perhaps this one should be clear:
CV_CheckDepthEQ(mat.depth(), traits::Depth<_Tp>::value, "");

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, checking the depth would have been clear (and less verbose code). It doesn’t matter to me which approach we use. Since you already approved the merge, I’ll let you decide if it’s worth opening up another PR to update the code.

return Eigen::TensorMap<Eigen::Tensor<_Tp, 3, Eigen::RowMajor>>((_Tp *)mat.data, mat.rows, mat.cols, mat.channels());
alalek marked this conversation as resolved.
Show resolved Hide resolved
}
#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