19

我使用 OpenCV C++ 接口做了很多工作,并设计了许多使用 Mat 作为私有资源的类。

最近,我开始关注 Mat 类,因为它总是使用图像数据作为共享资源,除非我明确调用 clone。即使我写const Mat了,我也不能确定图像数据以后不会从外部改变。

所以我需要克隆以确保封装。但是需要显式克隆一个 Mat 的问题是它通常是不必要且昂贵的。另一方面,我知道对共享图像数据的需求源于 roi 选择器,并且能够编写如下内容: Mat m_small = m_big(my_roi).

我的问题是:

1.) cv::Mat 类不应该被懒惰地克隆吗?因此,用户不会从外部将 Mat 视为共享资源处理程序。SharedMat当需要真正的共享图像数据时,用户不应该显式地实例化一个类吗?2.)在将 cv::Mat 作为类的私有资源的情况下,
您有什么比总是克隆更好的策略吗?


Mat::clone() 更新:“除非您打算修改数据,否则不要使用。” (by Vadim Pisarevsky)
这个想法有问题。 考虑你有这个类的情况:

class Res_handler{
public:
  const Mat emit_mat(){ return m_treasure; } // I argue you are compelled to clone here.
private:
  Mat m_treasure;
};

如果你clone在这种情况下不这样做,你可以写

Mat m_pirate = res_handler.emit_mat(); m_pirate = Scalar(0,0,0);

这通过和之间的共享图像数据导致m_treasure内部完全停电。:) 所以为了避免意外修改内部,你需要res_handlerm_piratem_treasurem_treasureclone

另一方面,这个解决方案也有缺陷:

const Mat m_pirate = res_handler.emit_mat(); 

因为m_treasure也可以修改,所以m_pirate后台修改的内容,让盗版的程序员很头疼。:)

4

4 回答 4

13

是的,这是一个糟糕的设计。因为Mat在内部实现了共享所有权,所以它与选择所有权策略的标准方式,即智能指针不兼容。基本问题是数据和所有权是正交的,应该分开。

因为它是可变的,所以甚至 aconst Mat更像是 a const shared_ptr<Mat>,无法描述所包含的内容Mat应该是可变的,即shared_ptr<const Mat>final如果您熟悉的话,这与 Java 中的问题非常相似。

我相信你可以通过包装Mat一个类来解决这些问题,该类公开与 相同的接口Mat,但在默认共享实现之上实现写时复制行为。

于 2012-12-04T23:36:56.407 回答
12

[无耻广告]我们现在有了 answers.opencv.org,这是一种针对 OpenCV 特定问题的 StackOverflow。

现在的问题:

  1. 不,我们没有看到一种有效的方法来实现这一点。如果你这样做,让我们讨论一下。

  2. 是的。Mat::clone()除非您打算修改数据,否则请勿使用。引用计数负责在不再使用数据时对数据进行适当的释放。

于 2012-12-05T08:10:57.557 回答
6

要回答 OP 问题:

是的,绝对是!正如一些人指出的那样:OpenCV 使您无法描述对图像的 const 引用。这确实是一个缺陷。“const cv::Mat&”不是 C++ 程序员所期望的,我经常发现自己在我的代码中到处调用 clone(),以至于我失去了数据共享的好处。

要回答 Vadims 如何有效地做到这一点的问题:

绝对有可能有效地做到这一点,尽管并非没有 API 更改。看看 Qt 如何成功地放弃了它在 Qt 4 之前的显式共享模型(类似于 OpenCV 的当前模型)到它当前的隐式共享(写入时复制)。基本上所有改变对象的函数调用,或者返回一个以后可能改变对象的引用都必须“deref”它。如果有多个参考,那就制作一份副本。

与图像操作的平均成本相比,这样做的成本微不足道。如果必须按像素执行,它只会变得令人望而却步。这就是为什么类需要分成两个。很像 cv::Mat 和 cv::Mat_。一种负责隐式共享和复制,另一种只是 IplImage 的模板包装器。下面是 API 的示例为了清楚起见,我选择了过于明确的名称):

// The following makes no unnecessary copies. Only a 
// couple of atomic increments and decrements.
const cv::Image img = cv::Image("lenna.bmp").toGray().brighter(0.3).inverted();

cv::Image copy(img);// Still no deep copy.

cv::ConstImageRef<char> src = img.constRef<char>();// Still no deep copy.

// This is where the copy(detach) happens.
// "img" is left untouched
cv::MutableImageRef<char> dst = copy.ref<char>();

// The following is as efficient as it has ever been.
for(int y = 0; y<dst.height(); y++)
    for(int x = 0; x<dst.width(); x++)
        dst.at(x, y) += src.at(x, y);

我意识到有太多的 OpenCV 代码浮动,无法进行任何根本性的更改,并且使用 OpenCV 3 进行 API 更改的窗口已关闭,但我不明白为什么不能添加新的改进接口。

于 2014-10-02T11:37:30.790 回答
2

添加和扩展 Vadim 的答案,这里有一些关于这个话题的想法。

我还以多种方式广泛使用了 cv::Mat,并享受了它的好处。

编程中的一个普遍事实是,您必须平衡项目的不同相反需求。其中之一是性能与可维护性。并且通过“过早的优化是邪恶的”一劳永逸地解决了这个问题。这种方法很棒,但是很多程序员只是盲目地遵循它。

对于图像处理,性能至关重要。没有它,许多项目根本不可行。因此,在处理图像时进行优化永远不会为时过早。它是为数不多的以毫秒计的领域之一,您所做的一切都是通过质量和速度来衡量的。而且,如果您来自 C#、Java 或用户界面设计,可能很难消化它,但是为了提高速度,牺牲一些面向对象设计的既定实践是值得的。

如果您浏览 OpenCV 的源代码,您会看到对优化的难以置信的强调:基于 SSE 的函数、NEON 函数、指针技巧、各种算法奇观、图形处理器实现、OpenCL 实现、查找表等等在其他类型的项目中,这将被视为矫枉过正、难以维护或“过早优化”。

应用架构中的一个小改动(如 cv::Mat 分配策略)在性能方面会产生非常大的影响。在嵌入式设备上共享图像而不是克隆可能会在一个伟大的小工具和一个死胡同的概念验证之间产生差异。

因此,当 Vadim 说他们没有看到实施您建议的更改的有效方法时,他建议这些更改的性能损失不会涵盖收益。

这样的项目更难编写和维护,但它是好的。通常,成像项目的困难部分是编写正确的算法。封装它只是最后 1% 的工作。

于 2012-12-07T21:17:00.877 回答