OpenCV cv::Mat的深度复制

33

复制 cv::Mat 的行为使我感到困惑。

据文档介绍,Mat::copyTo() 是深层复制,而赋值运算符不是。我的问题是:

  1. 如果我想从一个函数中返回一个 cv::Mat,例如: cv::Mat func(),该怎么办?

  2. 根据文档,如果我返回一个 cv::Mat,它将没有用处,因为在函数返回后,该函数内部的本地副本将被销毁,因此接受在函数外部返回值的变量将指向某个随机地址。奇怪的是(大多数情况下)它却能正常工作。例如,以下代码可以正常工作:

    cv::Mat CopyOneImage(const cv::Mat& orgImage)
    {
    
        cv::Mat image;
        orgImage.copyTo(image);
        return image;
    
    }
    
    int main()
    {
    
        std::string orgImgName("a.jpg");        
        cv::Mat orgImage;
        orgImage = cv::imread(orgImgName);
    
        cv::Mat aCopy;
        aCopy = CopyOneImage(orgImage);
    
        return 1;
    }
    

但是为什么呢?这不是深拷贝。

问题3. 有时候,赋值运算符似乎也是深拷贝:

    int main()
    {

        std::string orgImgName("a.jpg");        
        cv::Mat orgImage;
        orgImage = cv::imread(orgImgName);

        cv::Mat aCopy;
        orgImage.copyTo(aCopy);

        cv::Mat copyCopy1;
        copyCopy1 = aCopy;

        cv::namedWindow("smallTest", 1);
        cv::imshow("smallTest", copyCopy1);
        uchar key = (uchar)cv::waitKey();

        cv::Mat orgImage2 = cv::imread("b.jpg");
        orgImage2.copyTo(aCopy);

        cv::imshow("smallTest", copyCopy1);
        return 1;
    }

那么这两个显示器将显示相同的图像,即a.jpg。为什么?有时候又不起作用。(原始代码太长,但可以简化为上述情况)。在这些情况下,赋值运算符似乎实际上是“浅”复制。为什么?

非常感谢!


3
  1. 这取决于所需的语义。
  2. 不会,cv::Mat 使用某种引用计数,因此调用方收到的对象将是有效的。
- juanchopanza
第三个问题说“有时候”某些事情“似乎”发生了。我的有限大脑无法计算这样的问题 :-) - juanchopanza
3个回答

34

我认为,使用赋值并不是复制矩阵的最佳方式。如果想要获得新的完整矩阵副本,请使用:

Mat a=b.clone(); 

如果您想复制矩阵以替换另一个矩阵中的数据(以避免内存重新分配),请使用以下方法:
Mat a(b.size(),b.type());
b.copyTo(a);

当您将一个矩阵分配给另一个矩阵时,智能指针对矩阵数据的引用计数会增加1。当您释放矩阵(可以在离开代码块时隐式完成)时,引用计数会减少1。当引用计数为0时,分配的内存被释放。

如果您想从函数中获取结果,请使用引用,速度更快:

void Func(Mat& input,Mat& output)
{
 somefunc(input,output);
}

int main(void)
{
...
  Mat a=Mat(.....);
  Mat b=Mat(.....);
  Func(a,b);
...
}

8
我已经使用OpenCV有一段时间了,但是cv::Mat也让我感到困惑,因此我做了一些研究。 cv::Mat是指向实际图像数据的*data指针的头文件。它还实现了引用计数。它保存了指向该*data指针的cv::Mat头文件的数量。因此,当您执行常规复制操作时,例如:
cv::Mat b; 
cv::Mat a = b;

a将指向b的数据,并且它的引用计数将增加。同时,b以前指向的数据的引用计数将减少(如果在减少后为0,则内存将被释放)。

问题1:这取决于您的程序。有关更多详细信息,请参阅此问题:is-cvmat-class-flawed-by-design

问题2:该函数通过值返回。这意味着return image将复制Mat并增加ref_count(现在ref_count = 2),并返回新的Mat。当函数结束时,图像将被销毁,并且ref_count将减少一个。但是,由于ref_count不为0,因此不会释放内存。因此返回的cv::Mat不指向随机内存位置。

问题3:类似的事情也会发生。当您说orgImage2.copyTo(aCopy);时,将减少指向aCopy所指向的数据的引用计数。然后分配新内存以存储将要复制的新数据。这就是为什么在执行此操作时未修改copyCopy1的原因。


2
看看c++11的std::shared_ptr是如何有效地使用引用计数器,cv::Mat聪明地记住了每次指针被引用的次数,一旦计数达到0,它就会自动释放,即内存被释放,cv::Mat不再可用。这实际上是一种"浅复制",节省了在分配/释放大量内存方面的资源。
另一方面,cv::Mat::clone将提供一个"深拷贝",为矩阵分配一个全新的内存块,这在您需要撤销图像变换时可能很有用。然而,更多的内存分配/释放增加了所需的资源量。
希望这能帮助某些人。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接