Search code examples
c++opencvfor-loopstliterator

range-based for loop and std::transform with input/output iterators not equivalent when applied to rows of opencv Mat(rix)


In a larger program, I came across the following problem, which I do not understand.

Basically, I am processing an opencv matrix of type float (cv::Mat1f) (docs here). OpenCV provides iterators to iterate over a matrix. When I iterate over the matrix using range-based iterators and a reference to the matrix members, the code works (test1 in the example below).

When I use std::transform and apply it to the entire matrix, the code below (test3) also works.

However, when I do the iteration row-wise, then the version with std::transform fails (it never (?) terminates).

(The reason I work on rows is that in the real application rows are processed in different parallel threads, but that is of no concern here)

#include <iostream>
#include "opencv2/imgproc.hpp"

void test1(cv::Mat1f& img) {
    for (int r = 0; r < img.rows; r++)
        for (auto& v : img.row(r))
            v = v+1;
}

void test2(cv::Mat1f& img) {
    for (int r = 0; r < img.rows; r++)
        std::transform(
            img.row(r).begin(),
            img.row(r).end(),
            img.row(r).begin(),
            [](float v)->float { return v+1; });
}

void test3(cv::Mat1f& img) {
        std::transform(
            img.begin(),
            img.end(),
            img.begin(),
            [](float v)->float { return v+1; });
}

int main() {
    auto img = cv::Mat1f(5, 5, 0.0);

    int i = 0;
    for (auto &v : img)
        v = i++;

    std::cerr << img << "\n";

    test1(img);

    std::cerr << img << "\n";

    // test2(img); does not work
    test3(img); // works !

    std::cerr << img << "\n";
}

On my system (with opencv installed in /usr/local) I run the example code above with

g++ -std=c++17 -I/usr/local/include/opencv4 mwe.cpp -L/usr/local/lib  -lopencv_core && ./a.out

What is wrong with the code in test2 ?


Solution

  • Each call to row creates a new object, and these objects have distinct iterators.
    So, row(r).begin() will never be equal to row(r).end().

    Get the iterators from the same object instead:

    void test2(cv::Mat1f& img) {
        for (int r = 0; r < img.rows; r++)
            auto row = img.row(r);
            std::transform(
                row.begin(),
                row.end(),
                row.begin(),
                [](float v)->float { return v+1; });
    }