关于OpenCV中内存消耗的问题

时间:2017-11-22 20:32:01

标签: c++ opencv memory memory-management

交叉发布here

我有这么简单的代码:

//OpenCV 3.3.1 project
#include<opencv.hpp>
#include<iostream>
using namespace std;
using namespace cv;


Mat removeBlackEdge(Mat img) {
    if (img.channels() != 1)
        cvtColor(img, img, COLOR_BGR2GRAY);
    Mat bin, whiteEdge;
    threshold(img, bin, 0, 255, THRESH_BINARY_INV + THRESH_OTSU);
    rectangle(bin, Rect(0, 0, bin.cols, bin.rows), Scalar(255));
    whiteEdge = bin.clone();
    floodFill(bin, Point(0, 0), Scalar(0));
    dilate(whiteEdge - bin, whiteEdge, Mat());
    return whiteEdge + img;
}
int main() {               //13.0M
    Mat emptyImg = imread("test.png", 0);   //14.7M
    emptyImg = removeBlackEdge(emptyImg);   //33.0M
    waitKey();
    return 0;
}

这是我的test.png。正如Windows所示,它的大小约为1.14M。当然我知道在内存中读取它会更大。但我填写不能接受巨大的内存消耗。 如果我使用F10,我可以知道project.exe占用13.0M。我无话可说 当我运行Mat emptyImg = imread("test.png", 0);时,内存消耗为14.7M。这也很正常。 但是为什么当我运行emptyImg = removeBlackEdge(emptyImg);时,内存消耗最多为33.0M?它意味着函数removeBlackEdge花费了额外的18.3M内存。谁能告诉我一些事情?我无法完全接受如此巨大的内存消耗。我有什么想念吗?我使用新的emptyImg替换旧的emptyImg。我想我不需要额外的内存来支付费用吗?

Ps:如果我希望工具通过我的removeBlackEdge做同样的事情(删除图像中的黑边),如何调整它以降低内存消耗?

1 个答案:

答案 0 :(得分:1)

下面 - 猜测和提示。这是一个很大的评论。请不要将其标记为答案,即使它有助于找到/解决问题。而不是那样,如果你设法找到原因,请编写自己的答案。我可能会在以后删除这个。

最有可能的是,像threshold(img, bin, ..)这样的图像/位图操作会创建原始图像的副本。很难说多少,但肯定会创建至少一个,正如你可以看到的第二个bin变量。然后是clonewhiteEdge,所以第三个副本。此外,whiteEdge - bin之类的操作也可能至少创建一个副本(结果)。 rectangledilatefloodfill可能就地工作,并使用其中一个作为工作区传递的Mat变量并同时输出。

这意味着你至少有4份图像副本,可能还要多一点,但是假设它是4 * 1.7mb,所以你观察到的增加了19mb,增加了7mb。

所以,我们已经得到了12mb的解释,或者看起来如此。

首先,图像操作可能会分配一些额外的缓冲区并保留以供以后重用。这会花费内存,但它会“摊销”,所以如果再次执行这些操作,它不会再花费你。

当前调用这些图像操作时,也可能导致某些动态库被加载。这也是一次性操作,如果再次使用,将不会花费你的内存。

接下来要注意的是,Windows通过简单工具报告的进程和内存使用情况是不准确的。当进程分配并重新关联内存时,Windows报告的内存消耗通常只会增加,并且在发布时不会减少。至少不是立竿见影的。该过程可以保持一些“空气”或“真空”。造成这种情况的原因有很多,但重点是当程序试图再次分配内存时,这种“空气”通常是可重用的。 Windows可以显示一个基本内存使用量为30mb,定期分配20mb并释放20mb的进程,总是占用50mb。

话虽如此,我不得不说我实际上怀疑你的记忆测量方法,或者至少是观察结果。单次运行removeBlackEdge之后,您无法说它消耗了大量的内存。你观察到的只是进程的工作内存池增长了那么多。这本身并没有告诉你多少。

如果您怀疑图像的临时副本占用太多空间,请尝试删除它们。这可能意味着编写代码以使用较少的Mat变量和临时值,或重用/解除分配不再需要的位图,等待函数结束,或者选择不同算法或编写拥有。您也可以尝试通过使用不同大小的输入图像多次运行程序来确认这种情况,并绘制观察记忆图表 - 输入 - 尺寸。如果它线性增长(即“内存消耗”总是大约10倍输入大小)那么可能真的是一些副本。

另一方面,如果您怀疑内存泄漏,请多次运行removeBlackEdge。数百或数千或数百万次。观察“记忆消耗”是否随着时间的推移稳步增长。如果确实如此,则可能存在泄漏。另一方面,如果它在开始时只增长一次然后保持稳定在同一水平,则可能没有任何错误,它只是一些一次性初始化或摊销的缓存/工作空间/等。

我建议你现在进行这两项测试。进一步的工作和分析取决于您在长期运行中观察到的内容。另外,我必须注意这段代码相对简短。你不是太快优化了吗?旁注 - 确保在编译器中打开正确的优化(速度/内存)。如果您碰巧使用并观察“调试”版本,那么您可以立即解除速度/记忆观察。