如何在 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]
img
和 markers
都是矩阵。
在 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 毫秒。
是时候放下手套了——让我们使用指向像素数据的指针。我们必须小心并考虑不连续的Mat
s(例如,当您从更大的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]”?的主要内容,如果未能解决你的问题,请参考以下文章
OpenCV - 如何以 Mat 形式编写 IplImage 数组?
如何在 Visual C++ 2010 或 2008 中使用 OpenCV 2.1 访问 ip camera (compro IP50W)
如何在 Flutter 的原生 C++ 中使用 OpenCV 4(2021 年)(支持 Flutter 2.0)? [关闭]