Home > Enterprise >  C OpenCV: Convert vector<vector<Point>> to vector<vector<Point2f>>
C OpenCV: Convert vector<vector<Point>> to vector<vector<Point2f>>

Time:01-04

I get a vector<vector<Point>> data by OpenCV. For some reasons (for example offset/scaling), I need to convert the data Point to Point2f. How can I do that?

For example:

std::vector<std::vector<Point> > contours;
std::vector<std::Vec4i> hierarchy;
cv::findContours(edges, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE);


std::vector<std::vector<Point2f> > new_contour;

I need new_contour which is vector of vector of Point2f. Is there a simple way that convert it to float type? Then I can do some modification (for example offset/scaling) by replacing each Point2f data.

I try it using push_back. But still get error when building...

CodePudding user response:

You can use 2 nested loops: one for the outer vector and one for the inner. In the code below you can replace the trivial conversion between int and float and apply any transformation you need.

Note that for the output contours, I allocated the outer vector using std::vector::resize and then in a similar way allocated all the inner vectors in the loop.

Alternatively you could use std::vector::reserve to do all the allocations together with std::vector::push_back for adding the elements.

std::vector<std::vector<cv::Point>> contours;

// ... obtain the contours

std::vector<std::vector<cv::Point2f>> contours_float;
contours_float.resize(contours.size());  // allocate the outer vector
for (size_t i = 0; i < contours.size();   i)
{
    auto const & cur_contour = contours[i];
    auto & cur_contour_float = contours_float[i];
    cur_contour_float.resize(cur_contour.size()); // allocate the current inner vector
    for (size_t j = 0; j < cur_contour.size();   j)
    {
        auto const & cur_point = cur_contour[j];
        // Here you can apply any transformation you need:
        float x = static_cast<float>(cur_point.x);
        float y = static_cast<float>(cur_point.y);
        cur_contour_float[j] = cv::Point2f{ x,y };
    }
}

Another way to implement this is using std::transform (here I found it convenient to allocate the vectors by using the appropriate std::vector constructor):

std::vector<std::vector<cv::Point>> contours;

// ... obtain the contours

std::vector<std::vector<cv::Point2f>> contours_float(contours.size());  // allocate the outer vector
std::transform(
        contours.begin(), 
        contours.end(), 
        contours_float.begin(), 
        [](auto const & cur_contour) -> auto
        {
            std::vector<cv::Point2f> cur_contour_float(cur_contour.size());  // allocate the current inner vector
            std::transform(
                    cur_contour.begin(), 
                    cur_contour.end(), 
                    cur_contour_float.begin(),
                    [](auto const & cur_point) -> auto
                    {
                        // Here you can apply any transformation you need:
                        float x = static_cast<float>(cur_point.x);
                        float y = static_cast<float>(cur_point.y);
                        return cv::Point2f{ x,y };
                    });
            return cur_contour_float;
        });

The 2nd version actually implements the exact operation that you require (transformation from one representation to another).

CodePudding user response:

I'll first focus on your actual question, which is just the type conversion. Let's say we have some basic type aliases to make our life easier:

using pi_vec = std::vector<cv::Point2i>; // NB: Point is an alias for Point2i
using pf_vec = std::vector<cv::Point2f>;
using pi_vec_vec = std::vector<pi_vec>;
using pf_vec_vec = std::vector<pf_vec>;

To solve this problem, let's use the "divide and conquer" principle:

  • In order to convert a vector of vectors of points, we need to be able to convert a single vector of points
  • In order to convert a vector of points, we need to be able to convert a single point

Converting Single Points

This is actually trivial, since cv::Point_ provides a cast operator allowing implicit conversion to points of a different data type. Hence, conversion is done by a simple assignment:

cv::Point2i p1 = { 1,2 };
cv::Point2f p2 = p1;

Converting Vectors of Points

Since implicit conversion of points is possible, this is just as easy -- we simply construct the new vector initializing it using an iterator pair:

pi_vec v1 = { {1,2}, {3,4}, {5,6} };
pf_vec v2{ v1.begin(), v1.end() };

Converting Vectors of Vectors of Points

We do the above in a loop. To improve performance, we can reserve space in the destination vector to avoid reallocations. Then we use emplace_back to construct vectors of points in-place, using an iterator pair to initialize them as above.

pi_vec_vec vv1 = {
    { {1,2}, {3,4}, {5,6} }
    , { {7, 8}, {9,10} }
};

pf_vec_vec vv2;
vv2.reserve(vv1.size());
for (auto const& pts : vv1) {
    vv2.emplace_back(pts.begin(), pts.end());
}

Additional Operations During Conversion

The answer by @wohlstad already provides some possible approaches. However, looking at the second piece of code using std::transform made me wonder whether there was a way to make it a bit less verbose, perhaps taking advantage of features provided by more recent standard (like c 20). Here is my approach using std::views::transform.

First we create a "range adapter closure" wrapping our conversion lambda function, which applies some constant scaling and offset:

auto const tf = std::views::transform(
    [](cv::Point2i const& pt) -> cv::Point2f
    {
        return cv::Point2f(pt) * 0.5   cv::Point2f{ 1, -1 };
    });

Next, we create an outer transform view of the input vector of vectors of integer points. The lambda function will create an inner transform view using the previously created "range adapter closure", and use this view to construct a vector of float points:

auto const tf_view2 = std::views::transform(vv1
    , [&tf](pi_vec const& v) -> pf_vec
    {
        auto const tf_view = v | tf;
        return { tf_view.begin(), tf_view.end() };
    });

Finally, we'll construct a vector of vectors of float points, using an iterator pair of this view:

pf_vec_vec vv3{ tf_view2.begin(), tf_view2.end()};

Example Code

#include <opencv2/opencv.hpp>

#include <algorithm>
#include <ranges>

template <typename T>
void dump_v(std::vector<T> const& v)
{
    for (auto const& e : v) {
        std::cout << e << ' ';
    }
    std::cout << '\n';
}

template <typename T>
void dump_vv(std::vector<std::vector<T>> const& vv)
{
    for (auto const& v : vv) {
        dump_v(v);
    }
    std::cout << '\n';
}

int main()
{
    using pi_vec = std::vector<cv::Point2i>;
    using pf_vec = std::vector<cv::Point2f>;
    using pi_vec_vec = std::vector<pi_vec>;
    using pf_vec_vec = std::vector<pf_vec>;

    pi_vec_vec vv1 = {
        { {1,2}, {3,4}, {5,6} }
        , { {7, 8}, {9,10} }
    };

    dump_vv(vv1);

    pf_vec_vec vv2;
    vv2.reserve(vv1.size());
    for (auto const& pts : vv1) {
        vv2.emplace_back(pts.begin(), pts.end());
    }

    dump_vv(vv2);

    auto const tf = std::views::transform(
        [](cv::Point2i const& pt) -> cv::Point2f
        {
            return cv::Point2f(pt) * 0.5   cv::Point2f{ 1, -1 };
        });
    auto const tf_view2 = std::views::transform(vv1
        , [&tf](pi_vec const& v) -> pf_vec
        {
            auto const tf_view = v | tf;
            return { tf_view.begin(), tf_view.end() };
        });
    pf_vec_vec vv3{ tf_view2.begin(), tf_view2.end()};

    dump_vv(vv3);

    return 0;
}

Example Output

[1, 2] [3, 4] [5, 6]
[7, 8] [9, 10]

[1, 2] [3, 4] [5, 6]
[7, 8] [9, 10]

[1.5, 0] [2.5, 1] [3.5, 2]
[4.5, 3] [5.5, 4]
  • Related