我使用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()
。” (由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_handler
之间的共享图片数据导致m_pirate
内的m_treasure
完全停电。 :)所以为了避免意外修改内部m_treasure
,,你需要clone
它。
另一方面,这个解决方案也存在缺陷:
const Mat m_pirate = res_handler.emit_mat();
因为m_treasure
也可以被修改,所以m_pirate
的内容在后台发生了变化,给海盗的程序员带来了极大的麻烦。 :)
答案 0 :(得分:12)
是的,这是一个糟糕的设计。因为Mat
在内部实现共享所有权,所以它与选择所有权策略的标准方式不兼容,即智能指针。基本问题是数据和所有权是正交的,应该分开。
因为它是可变的,即使const Mat
更像是const shared_ptr<Mat>
,也无法描述包含的Mat
应该是可变的,即shared_ptr<const Mat>
。如果您熟悉,这与Java中final
的问题非常相似。
我相信你可以通过将Mat
包装在一个暴露与Mat
相同的接口的类中来解决这些问题,但它在默认的共享实现上实现了写时复制行为。
答案 1 :(得分:11)
[无耻的广告] 我们现在有answers.opencv.org,这是针对OpenCV特定问题的StackOverflow。
现在回答问题:
没有。我们没有看到实现这一点的有效方法。如果你这样做,我们来讨论吧。
是。除非您计划修改数据,否则请勿使用Mat::clone()
。当数据不再使用时,引用计数会正确地重新分配数据。
答案 2 :(得分:6)
回答OP问题:
是的,绝对是!正如一些人指出的那样: OpenCV使您无法描述对图像的const引用。这确实是一个缺陷。 &#34; const cv :: Mat&amp;&#34;并不是c ++程序员所期望的那样,而且我经常发现自己在我的代码中遍布clone()调用,以至于我开始失去数据共享的好处。
回答Vadims关于如何有效地做到这一点的问题:
尽管没有API更改,但绝对有可能有效地执行此操作。看看Qt如何放弃Qt 4之前的显式共享模型(类似于OpenCV&#39;当前模型),以及它当前的隐式共享(复制到写)非常成功。基本上所有改变对象的函数调用,或者返回一个稍后可以改变对象的引用都必须&#34; deref&#34;它。如果有多个引用,那就是复制。
与图像操作的平均成本相比,这个成本微乎其微。如果必须按像素执行,则变得禁止。这就是为什么课程需要分成两部分的原因。很像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更改的窗口已关闭,但我不明白为什么它不可能添加一个新的改进界面。
答案 3 :(得分:1)
添加和扩展Vadim的答案,以下是关于该主题的一些想法。
我在很多方面也广泛使用过cv :: Mat,并享受其好处。
编程的一般事实是你必须平衡项目的不同对立需求。其中之一是性能与可维护性。这一点一劳永逸地解决了“过早优化是邪恶的”。这种方法很棒,但许多程序员只是盲目地遵循它。
对于图像处理,性能至关重要。没有它,许多项目根本不可行。因此,处理图像时优化永远不会为时过早。它是毫秒计数的极少数领域之一,您所做的一切都是通过质量和速度来衡量的。而且,如果您来自C#,Java或用户界面设计,可能很难消化它,但是为了提高速度,值得牺牲面向对象设计的一些既定实践。
如果您浏览OpenCV的源代码,您将看到非常强调优化:基于SSE的函数,NEON函数,指针技巧,各种算法好奇心,图形处理器实现,OpenCL实现,查找表等等,其他许多在其他类型的项目中被认为是矫枉过正,难以保留或“过早优化”。
您的应用程序架构中的一个小变化(如cv :: Mat分配策略)在性能方面可以产生巨大的差异。在嵌入式设备上共享图像而不是克隆可能会在一个伟大的小工具和一个死胡同的概念验证之间产生差异。
因此,当Vadim表示他们没有看到实施建议更改的有效方法时,他建议这些更改的性能损失不会涵盖这些好处。
这样的项目 难以编写和维护,但它是好的。通常,成像项目的难点在于编写正确的算法。封装它只是最后的1%工作。