如何在 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]
其中 img
和 markers
都是矩阵。
用 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 毫秒。
是时候放下手套了——让我们使用指向像素数据的指针。我们必须小心并考虑不连续的 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 个字节,我们必须处理对齐等)——我们不谈这个,尽管对于好奇的人来说这可能是一个很好的练习 reader。然而,由于即使是最差的方法,我们每个像素也有大约 10 个时钟周期,因此没有太大的改进潜力。
做出你的选择。一般来说,我会采用第一种方法,并且只有在测量将此特定操作识别为瓶颈时才担心它。
我正在尝试将 OpenCV Python 示例 here 转换为 C++。 我被困在这一行:
img[markers == -1] = [255,0,0]
其中 img
和 markers
都是矩阵。
用 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 毫秒。
是时候放下手套了——让我们使用指向像素数据的指针。我们必须小心并考虑不连续的 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 个字节,我们必须处理对齐等)——我们不谈这个,尽管对于好奇的人来说这可能是一个很好的练习 reader。然而,由于即使是最差的方法,我们每个像素也有大约 10 个时钟周期,因此没有太大的改进潜力。
做出你的选择。一般来说,我会采用第一种方法,并且只有在测量将此特定操作识别为瓶颈时才担心它。