如何在 C++ OpenCV 中编写表达式 "img[markers == -1] = [255,0,0]"?

How to write the expression "img[markers == -1] = [255,0,0]" in C++ OpenCV?

我正在尝试将 OpenCV Python 示例 here 转换为 C++。 我被困在这一行:

img[markers == -1] = [255,0,0]

其中 imgmarkers 都是矩阵。

用 C++ OpenCV 编写这个的有效方法是什么?

既然我已经写了一些代码来支持我的评论,那么不写就浪费了。

注意:在 i7-4930k 上使用 MSVC 2013、OpenCV 3.1、64 位对其进行测试。使用随机生成的输入图像和蒙版(~9% 设置为 -1)。


Miki 所述,在 C++ 中执行此操作的最简单方法是使用:

例如:

void set_where_markers_match(cv::Mat3b img
    , cv::Vec3b value
    , cv::Mat1i markers
    , int32_t target)
{
    img.setTo(value, markers == target);
}

即使这创建了一个中间掩码 Mat,它对于绝大多数情况仍然足够有效(每 2^20 像素大约 2.9 毫秒)。


如果你觉得这真的不够好,想尝试更快地写点东西怎么办?

让我们从简单的事情开始 -- 迭代行和列并使用 cv::Mat::at.

void set_where_markers_match(cv::Mat3b img
    , cv::Vec3b value
    , cv::Mat1i markers
    , int32_t target)
{
    CV_Assert(img.size == markers.size);

    for (int32_t r(0); r < img.rows; ++r) {
        for (int32_t c(0); c < img.cols; ++c) {
            if (markers.at<int32_t>(r, c) == target) {
                img.at<cv::Vec3b>(r, c) = value;
            }
        }
    }
}

好一点,每次迭代约 2.4 毫秒。


让我们尝试使用 Mat iterators

void set_where_markers_match(cv::Mat3b img
    , cv::Vec3b value
    , cv::Mat1i markers
    , int32_t target)
{
    CV_Assert(img.size == markers.size);

    cv::Mat3b::iterator it_img(img.begin());
    cv::Mat1i::const_iterator it_mark(markers.begin());
    cv::Mat1i::const_iterator it_mark_end(markers.end());

    for (; it_mark != it_mark_end; ++it_mark, ++it_img) {
        if (*it_mark == target) {
            *it_img = value;
        }
    }
}

这对我来说似乎没有帮助,每次迭代约 3.1 毫秒。


是时候放下手套了——让我们使用指向像素数据的指针。我们必须小心并考虑不连续的 Mats(例如,当您从较大的 Mat 获得投资回报率时)——让我们一次处理行。

void set_where_markers_match(cv::Mat3b img
    , cv::Vec3b value
    , cv::Mat1i markers
    , int32_t target)
{
    CV_Assert(img.size == markers.size);

    for (int32_t r(0); r < img.rows; ++r) {
        uint8_t* it_img(img.ptr<uint8_t>(r));
        int32_t const* it_mark(markers.ptr<int32_t>(r));
        int32_t const* it_mark_end(it_mark + markers.cols);

        for (; it_mark != it_mark_end; ++it_mark, it_img += 3) {
            if (*it_mark == target) {
                it_img[0] = value[0];
                it_img[1] = value[1];
                it_img[2] = value[2];
            }
        }
    }
}

这是向前迈出的一步,每次迭代约 1.9 毫秒。


使用 OpenCV 的下一个最简单的步骤可能是将其并行化——我们可以利用 cv::parallel_for_。让我们按行拆分工作,以便我们可以重用以前的算法。

class ParallelSWMM : public cv::ParallelLoopBody
{
public:
    ParallelSWMM(cv::Mat3b& img
        , cv::Vec3b value
        , cv::Mat1i const& markers
        , int32_t target)
        : img_(img)
        , value_(value)
        , markers_(markers)
        , target_(target)
    {
        CV_Assert(img.size == markers.size);
    }

    virtual void operator()(cv::Range const& range) const
    {
        for (int32_t r(range.start); r < range.end; ++r) {
            uint8_t* it_img(img_.ptr<uint8_t>(r));
            int32_t const* it_mark(markers_.ptr<int32_t>(r));
            int32_t const* it_mark_end(it_mark + markers_.cols);

            for (; it_mark != it_mark_end; ++it_mark, it_img += 3) {
                if (*it_mark == target_) {
                    it_img[0] = value_[0];
                    it_img[1] = value_[1];
                    it_img[2] = value_[2];
                }
            }
        }
    }

    ParallelSWMM& operator=(ParallelSWMM const&)
    {
        return *this;
    };

private:
    cv::Mat3b& img_;
    cv::Vec3b value_;
    cv::Mat1i const& markers_;
    int32_t target_;
};

void set_where_markers_match(cv::Mat3b img
    , cv::Vec3b value
    , cv::Mat1i markers
    , int32_t target)
{
    ParallelSWMM impl(img, value, markers, target);
    cv::parallel_for_(cv::Range(0, img.rows), impl);
}

这个运行时间为 0.5 毫秒。


让我们退后一步——在我的例子中,原始方法运行单线程。如果我们将其并行化怎么办?我们可以将上面代码中的 operator() 替换为以下内容:

    virtual void operator()(cv::Range const& range) const
    {
        img_.rowRange(range).setTo(value_, markers_.rowRange(range) == target_);
    }

运行时间约为 0.9 毫秒。


这似乎是合理的实施。我们可以尝试将其矢量化,但这远非微不足道(像素是 3 个字节,我们必须处理对齐等)——我们不谈这个,尽管对于好奇的人来说这可能是一个很好的练习 reader。然而,由于即使是最差的方法,我们每个像素也有大约 10 个时钟周期,因此没有太大的改进潜力。

做出你的选择。一般来说,我会采用第一种方法,并且只有在测量将此特定操作识别为瓶颈时才担心它。