【问题标题】:Does std::vector's push_back create a deep copy of the argument?std::vector 的 push_back 是否会创建参数的深层副本?
【发布时间】:2015-09-17 14:24:05
【问题描述】:

我有一个图像列表,其中存储了几个 Mat 对象,我需要将它们推入 Mat 的向量中。

vector<Mat> images; 
Mat image;
for ( i = 0; i < n; i++)
{
   \\ importing the i-th image into a mat image; 
   images.push_back(image);
}

这会创建图像的深层副本吗?

当然

vector<Mat> images;
Mat image (100, 100, CV_8UC(1), Scalar::all(255));
images.push_back(image);
image.release(); 
Mat temp (100,100, CV_8UC(1), Scalar::all(0));
image =  temp;
images.push_back(image);
imshow("black", images[0]);
waitKey(0);
imshow("White",images[1]);
waitKey(0); 

这应该显示一张黑白图像。

另一个问题

Mat img;
vector<mat> images;
for (i = 1; i < 5, i++)
{
    img.create(h,w,type); // h,w and type are given correctly
    // input an image from somewhere to img correctly.
    images.push_back(img);
    img.release();
}
for (i = 1; i < 5; i++) images[i].release();

这仍然让我有内存泄漏,这可能是什么原因?

【问题讨论】:

  • .push_back 将复制对象。但在这里,该对象是一个cv::Mat 对象,它本身是一个矩阵头,类似于“智能指针”。所以你复制智能指针而不是矩阵数据值! imageimage 的副本都将共享相同的矩阵元素,直到其中一个被重新分配新内存(如果 openCV 函数需要更多数据,可能会发生这种情况)。所以这里没有执行(矩阵元素的)深拷贝,但你不能肯定地说两者永远引用相同的数据。
  • 那我就糊涂了。由于我总是.push_back(image),即具有不同值的相同矩阵,矢量图像如何仍然包含不同的矩阵(我检查过。)
  • 你能提供一些最小的简单代码来观察这种行为吗?
  • 当然。 vector&lt;Mat&gt; images Mat image;
  • 编辑了原帖并添加了比上面更好的代码。

标签: c++ opencv vector copy-constructor mat


【解决方案1】:

std::vector::push_back 使用对象的复制构造函数将元素插入向量中。因此,如果 Mat 复制构造函数创建了 Mat 对象的深层副本,您将得到一个深层副本。

【讨论】:

    【解决方案2】:

    这里有一个小测试程序,专门演示 cv::Mat 对象(即矩阵头)的数据共享属性!

    int main()
    {
        // create input of size 512x512
        cv::Mat input = cv::imread("../inputData/Lenna.png");
    
        // create a second input of size 256x256
        cv::Mat modifiedInput;
        cv::resize(input, modifiedInput, cv::Size(256,256));
    
        std::vector<cv::Mat> images;
    
        // first element will be a "deep copy" where the matrix elements will be copied to a new memory location and a new header will be created, referecing those matrix elements.
        images.push_back(input.clone());
    
        // 6 times copy the "input" to "images". 
        // All the copies will (deep) copy the matrix header but they will share the matrix elements (because their memory LOCATION will be copied)
        for(unsigned int i=0; i<6; ++i)
            images.push_back(input);
    
        // now some experiments:
        // draw a circle to input variable. At this point it should share it's matrix elements with images[1-5]
        cv::circle(input, cv::Point(100,100), 30, cv::Scalar(0,0,0), -1);
    
        // draw a circle to a vector element:
        cv::circle(images[5], cv::Point(300,100), 30, cv::Scalar(0,0,0), -1);
    
        // use a openCV function that will allocate new memory, if the destination dimensions don't fit:
        // to a mat whose dimensions fit:
        // remember that input.size() == vector[0..5].size
        // compute median blur and target one of the matrices that share their data at the moment:
        cv::medianBlur(input, images[3], 11);
    
        cv::imshow("0", images[0]);
        cv::imshow("1", images[1]);
        cv::imshow("2", images[2]);
        cv::imshow("3", images[3]);
        cv::imshow("4", images[4]);
        cv::imshow("5", images[5]);
        cv::waitKey(0);
    

    此时它看起来像这样:除了第一个矩阵之外,所有矩阵都共享其元素的数据,因为使用.clone() 强制进行了深层复制。

    现在继续:

        // to a mat whose dimensions don't fit (new memory will be allocated, not shared by the other matrix headers anymore):
        // images[3] will not share the data with other matrix headers afterwards
        cv::medianBlur(modifiedInput, images[3], 11);
    
        // now images[3] and images[4] will share matrix elements
        images[4] = images[3];
        cv::circle(images[4], cv::Point(128,128), 20, cv::Scalar(255,255,255), 3);
    
        // create a deep-copy of 256x256 input to overwrite images[5] (not modifying any other image's matrix elements)
        images[5] = modifiedInput.clone();
        cv::circle(images[5], cv::Point(0,0), 30, cv::Scalar(0,255,0), -1);
    
        cv::imshow("0", images[0]);
        cv::imshow("1", images[1]);
        cv::imshow("2", images[2]);
        cv::imshow("3", images[3]);
        cv::imshow("4", images[4]);
        cv::imshow("5", images[5]);
    
        //cv::imshow("input", input);
        //cv::imwrite("../outputData/MainBase.png", input);
        cv::waitKey(0);
        return 0;
    }
    

    看起来像这样:

    这一次,medianBlur 的调用没有与所有其他矩阵共享数据,因为目标图像的尺寸不适合,因此必须在 mediumBlur 方法中为images[3] 分配新内存。所以 images[3] 引用了不同的数据元素!

    所有这一切可能有点棘手,因为用户可能无法直接看到,哪些函数调用会分配新数据,哪些不会,所以如果你想确保分配新数据,你应该在开始时这样做对于每个垫子,或者使用一个空垫子作为目的地(或者在开始时不共享任何数据)。

    还有一件事:

    cv::Mat emptyMat;
    std::vector<cv::Mat> images(n, emptyMat); // insert n copies of emptyMat header
    // or
    for(unsigned int i=0; i<n; ++i)
        images.push_back(emptyMat) // same result
    

    这既是save to use,所以不共享数据,因为所有emptyMat一开始都没有数据,所以不能共享数据。每当将任何数据分配给任何向量元素时,其他人都不知道它,因此他们不会共享该数据。

    // BUT:
    cv::Mat notEmptyMat = cv::Mat::zeros(height, width, type);
    std::vector<cv::Mat> images(n, notEmptyMat ); // insert n copies of emptyMat header which references the assigned zeroes data of size width x height
    // or
    for(unsigned int i=0; i<n; ++i)
        images.push_back(notEmptyMat ) // same result
    

    在这里,数据是共享的,每当您更改其中一个矩阵的 DATA 时,其他矩阵也会更改。但显然,如果您将新的数据内存分配给其中一个矩阵,其他矩阵仍会引用它们的其他数据内存。

    【讨论】:

    • 所以析构函数 Mat::release 实际上破坏了“标题”。我如何真正销毁 Mat 对象的值?
    • OpenCV 对数据使用引用计数。所以当一个矩阵数据的最后一个header被销毁时,内存就会被释放。
    • 嗨 Micka,我还有一个关于此设置的问题。我正在将 mat 对象推入向量中,然后将它们一个一个释放,但似乎仍然存在内存泄漏。我认为当 refcount = 0 时,内存会被释放。
    • @VahagnTumanyan 你能用这种行为发布一个完整的工作(最小化)代码示例(可能是一个新问题)吗?如果你这样做了,请在此处发布一个链接,以便我找到它 =) 例如发布一个主函数,其中将 Mats 推送到一个向量并在发生内存泄漏后释放。
    • Mat img;矢量 图像; for (i = 1; i
    【解决方案3】:

    它不会进行深拷贝,因为cv::Mat 是共享指针。添加到向量images 时,您必须使用clone() 或类似名称。

    【讨论】:

      【解决方案4】:

      std::vector::push_back 会将对象复制或移动到向量中,这意味着将调用 Mat 的复制 ctor 或移动 ctor。所以这取决于Mat

      请参阅 CopyInsertableMoveInsertable

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-03-13
        • 2012-05-16
        • 1970-01-01
        • 2014-05-25
        • 1970-01-01
        • 2023-03-05
        • 1970-01-01
        相关资源
        最近更新 更多