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

Posted

技术标签:

【中文标题】如何在 C++ OpenCV 中编写表达式“img[markers == -1] = [255,0,0]”?【英文标题】:How to write the expression "img[markers == -1] = [255,0,0]" in C++ OpenCV? 【发布时间】:2017-07-03 02:09:53 【问题描述】:

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

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

imgmarkers 都是矩阵。

在 C++ OpenCV 中编写此代码的有效方法是什么?

【问题讨论】:

您链接的是 C++ 示例,而不是 Python。无论如何,它在链接 中的编写方式是 用 C++ 编写该代码的有效方式。虽然 python 代码 img[markers == -1] = [255,0,0] 更具可读性,但它必须在一天结束时循环通过 markers 的索引,检查值是什么,评估条件并分配值当该评估为True img.setTo(Scalar(255,0,0), markers==-1); @Miki 即使那是 100% 正确我认为它不是那么有效,据我所知,这将创建一个蒙版,然后检查每个像素 f 它不是 0,然后写入它.可能最有效的方法是遍历它们两者....但是,除非我真的需要压缩所有可能的性能,否则我可能会选择您的答案 @api55 除非测量将此操作识别为瓶颈(恕我直言,这在该算法中似乎不太可能),否则这似乎是过早的优化。比较和setTo 都已经过优化(据我所知,矢量化+ IPP 支持),所以主要是关于那个中间掩码。您必须使用指针以通过足够简单的实现至少取得一点进展。一些简单的测量表明,原始图像每 2^20 像素大约需要 3ms,所以问题是减少 1-2ms 是否值得麻烦。 @DanMašek 我完全同意你的看法。我认为在大多数情况下不值得。 【参考方案1】:

因为我已经写了一些代码来支持我的 cmets,不写就太浪费了。

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


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

cv::MatExpr operator== (const cv::Mat& a, double s) 创建一个面具 你在cv::Mat::setTo(...)中使用的

例如:

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 个字节,我们必须处理对齐等)——我们不要讨论这个,尽管对于好奇的读者来说这可能是一个很好的练习.然而,由于我们的每个像素大约有 10 个时钟周期,即使是最差的方法,改进的潜力也不大。

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

【讨论】:

很好的答案,谢谢你把它带到这里。要考虑的一件事是编译器中的发布、调试、优化模式。例如,在调试模式下,.at 中的断言会产生巨大的开销(但是,我们应该始终以发布模式下的性能为目标)。我仍然会选择第一个选项,或者可能是最后一个选项,因为它们简单易读,一目了然 谢谢,真的很有帮助

以上是关于如何在 C++ OpenCV 中编写表达式“img[markers == -1] = [255,0,0]”?的主要内容,如果未能解决你的问题,请参考以下文章

如何在opencv2中手动编写Roi函数

OpenCV(C++):如何显示 yuv 文件?

在 C++ 中扩展 OpenCV mat 对象的尺寸

OpenCV - 如何以 Mat 形式编写 IplImage 数组?

如何在 Visual C++ 2010 或 2008 中使用 OpenCV 2.1 访问 ip camera (compro IP50W)

如何在 Flutter 的原生 C++ 中使用 OpenCV 4(2021 年)(支持 Flutter 2.0)? [关闭]