3

I am trying to make converting between a custom class and OpenCV easier.

Converting constructors allow us to declare a class without the explicit keyword and construct from an alternate data type.

For example, we could declare two classes:

class Foo
{
public:
    std::vector<int32_t> data;

    Foo(int k) : data(k) {}
};

class FooProxy : public cv::Mat
{
public:
    FooProxy(const Foo & foo) :  cv::Mat(foo.data.size(), 1, CV_32SC1, (void*)foo.data.data()) {}
};

And then we can create a FooProxy by setting it equal and then then using any OpenCV function we want:

Foo myFoo(3);
myFoo.data = { 1,2,3 };

FooProxy fp = myFoo;
fp.setTo(1);
float myNorm = cv::norm(fp);

std::cout <<  "myFoo: " << myFoo.data.at(0) << ", " << myFoo.data.at(1) << ", " << myFoo.data.at(2) << "\n";
std::cout << "fp: " << fp.at<int32_t>(0) << ", " << fp.at<int32_t>(1) << ", " << fp.at<int32_t>(2) << "\n";
std::cout << "The Norm is: " << myNorm << "\n";

with output:

myFoo: 1, 1, 1
fp: 1, 1, 1
The Norm is: 1.73205

However, I would much rather write:

float myNorm = cv::norm(myFoo);

and have c++ automatically convert myFoo. Is this possible?

This works:

 float myNorm2 = cv::norm(FooProxy(myFoo));

but not the simpler version.

MSalters
  • 173,980
  • 10
  • 155
  • 350
Steve
  • 3,957
  • 2
  • 26
  • 50

2 Answers2

3

You could create a conversion operator

class Foo
{
  ...

  operator cv::Mat()
  {
    return cv::Mat(data.size(), 1, CV_32SC1, (void*)data.data());
  }
};

Edit cv::norm has the following overloads (notice no cv::Mat):

cv::norm(const Matx< _Tp, m, n > &M)
cv::norm(const Matx< _Tp, m, n > &M, int normType)
cv::norm(InputArray src1, int normType=NORM_L2, InputArray mask=noArray()) // this is the one we want
cv::norm(InputArray src1, InputArray src2, int normType=NORM_L2, InputArray mask=noArray())
cv::norm(const SparseMat &src, int normType)

A cv::InputArray can be constructed from a cv::Mat, however you're only allowed 1 user-defined conversion implicitly. So you'll actually need a conversion operator for cv::InputArray if you want Foo to work with cv::norm.

kmdreko
  • 42,554
  • 6
  • 57
  • 106
  • This is a great solution. Is there a way to not have the conversion operator be a member of class `Foo` so that I don't absolutely REQUIRE a dependency to OpenCV `cv::Mat` for ALL uses of `Foo`? – Steve Jan 17 '18 at 01:49
  • @Steve unfortunately no, the only way to do implicit conversions are with constructors and conversion operators – kmdreko Jan 17 '18 at 01:51
  • 1
    I try to run this code, but failed! Error: no matching function for call to ‘norm(Foo&)’ `float myNorm3 = cv::norm(myFoo);` – Kinght 金 Jan 17 '18 at 02:06
  • I was afraid of that, I suspect it might not work because `cv::norm` doesn't have a signature for `cv::Mat`, it has overloads for `InputArray` but I think thats also a user-defined conversion... (can't have more than 1 implicitly) – kmdreko Jan 17 '18 at 02:11
2

Then you should define a function in namespace cv like this:

namespace cv{
float norm(const Foo& foo){
    return cv::norm(FooProxy(foo));
}
}

Now you can call:

float res =  cv::norm(myFoo);

The code:

//! 2018.01.17 09:42:16 CST

#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;

class Foo
{
    public:
        std::vector<int32_t> data;

        Foo(int k) : data(k) {}
};

class FooProxy : public cv::Mat
{
    public:
        FooProxy(const Foo & foo) :  cv::Mat(foo.data.size(), 1, CV_32SC1, (void*)foo.data.data()) {}
};

namespace cv {
float norm(const Foo& foo) {
    return cv::norm(FooProxy(foo));
}
}

int main() {
    Foo myFoo(3);
    myFoo.data = { 1,2,3 };

    FooProxy fp = myFoo;
    fp.setTo(1);
    float myNorm = cv::norm(fp);

    std::cout <<  "myFoo: " << myFoo.data.at(0) << ", " << myFoo.data.at(1) << ", " << myFoo.data.at(2) << "\n";
    std::cout << "fp: " << fp.at<int32_t>(0) << ", " << fp.at<int32_t>(1) << ", " << fp.at<int32_t>(2) << "\n";
    std::cout << "The Norm is: " << myNorm << "\n";


    float myNorm2 = cv::norm(FooProxy(myFoo));
    std::cout << "The Norm2 is:"<<myNorm2<<endl;

    float myNorm3 = cv::norm(myFoo);
    std::cout << "The Norm3 is:"<<myNorm2<<endl;
}

The result:

myFoo: 1, 1, 1
fp: 1, 1, 1
The Norm is: 1.73205
The Norm2 is:1.73205
The Norm3 is:1.73205
Kinght 金
  • 17,681
  • 4
  • 60
  • 74
  • This is an interesting solution. I was hoping to have it do the conversion since there are hundreds of OpenCV functions that would need such a conversation. – Steve Jan 17 '18 at 01:47