Assign a 3D vector to a 4d vector or how to cast a cv::Vec<double, 4> to cv::Vec<double, 3> using OpenCV

124 Views Asked by At

Is there a better or more efficient way to cast a cv::Vec<double, 4> to cv::Vec<double, 3>. The example shows my current solution using reinterpret_cast. Where I like to point out that the vector c points to the same data as vector a but as a cv::Vec<double,3>.

cv::Vec<double, 4> a(1,2,3,4);
cv::Vec<double, 3> b(5,6,7);
std::cout << "a: " << a << ", b: " << b << std::endl;
cv::Vec<double, 3> &c = reinterpret_cast<cv::Vec<double, 3>&>(a);
c = b;
std::cout << "a: " << a << ", b: " << b << std::endl;

output:

a: [1, 2, 3, 4], b: [5, 6, 7]
a: [5, 6, 7, 4], b: [5, 6, 7]

In order to make my point clear In my use case, I have to operate on a huge set of cv::Vec4d object instances. And I would like to avoid copping data to back and forth.

2

There are 2 best solutions below

7
Jan Schultke On
cv::Vec<double, 3> &c = reinterpret_cast<cv::Vec<double, 3>&>(a);

Using c would be undefined behavior. c is an lvlaue of a type Vec<double, 3> which refers to an object of type Vec<double, 4>. Using it is UB as per [basic.lval] p11. See also What is the Strict Aliasing Rule and Why do we care?

It doesn't matter that these two types are "vaguely similar", and that they both store a double[] of some size, although that similarity makes it "work" on your machine.

The proper way would be to create a new cv::Vec<double, 3>:

cv::Vec<double, 4> a(1,2,3,4);
cv::Vec<double, 3> c(a.val);

// or as an expression:
std::cout << cv::Vec<double, 3>(a.val) << std::endl;

This uses the constructor of cv::Vec which takes a const double*.

8
Ted Lyngmo On

Is there a better or more efficient way ... ?

Yes. The way you do it now has undefined behavior. Any way that has defined behavior is better.

What you've observed when compiling and running your current program is nothing you should rely on.

You'd also get exactly the same efficiency by copying directly from the source Vec into the destination Vec using the public member variable _Tp val [m*n];.

I would advice against using the constructor taking a pointer to an array without defined bounds because mistakes are just too easy to make.

cv::Vec<double, 3> a(1,2,3);
cv::Vec<double, 4> c(a.val); // boom

By defining a helper function that asserts that you are staying within the defined bounds, you can do these transformations in a controlled and safe way without worrying too much.

#include <algorithm>

template<std::size_t SrcPos, std::size_t DstPos, std::size_t Len,
         class T1, int N1, class T2, int N2>
void copy_from_to(const cv::Vec<T1, N1>& src, cv::Vec<T2, N2>& dst) {
    static_assert(SrcPos + Len <= N1, "Source out of bounds");
    static_assert(DstPos + Len <= N2, "Destination out of bounds");
    std::copy_n(src.val + SrcPos, Len, dst.val + DstPos);
}
int main() {
    cv::Vec<double, 4> a(1, 2, 3, 4);
    cv::Vec<double, 3> b(5, 6, 7);

    std::cout << "a: " << a << ", b: " << b << '\n';

    copy_from_to<0,0,3>(b, a);

    std::cout << "a: " << a << ", b: " << b << '\n';
}