C++ 缺陷使用findContours()时增加每次迭代的内存使用率

C++ 缺陷使用findContours()时增加每次迭代的内存使用率,c++,opencv,C++,Opencv,我已经试着调试这个一个月了,当然这是我糟糕的编程实践,但我认为这可能是一个bug,所以在我报告之前,我先在这里问一下 考虑以下代码: #include <sys/resource.h> // memory management. #include <stdio.h> #include <iostream> #include <iomanip> #include "opencv2/highgui/highgui.hpp" #in

我已经试着调试这个一个月了,当然这是我糟糕的编程实践,但我认为这可能是一个bug,所以在我报告之前,我先在这里问一下

考虑以下代码:

#include <sys/resource.h> // memory management.
#include <stdio.h>
#include <iostream>
#include <iomanip>

#include "opencv2/highgui/highgui.hpp"
#include "opencv2/core/core.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/video/background_segm.hpp"

using namespace std;
using namespace cv;

// Load frame from disk.
void readFrame(int frameNum, Mat &frame) {
    // Construct filenames
    Mat image;
    stringstream number, filename;

    number << setw(7) << setfill('0') << frameNum; // expecting over 1e10 images over the installation period.
    filename << "../images/store-" << number.str() << ".jpg"; // assumes jpegs!//
    cout << "Loading filename: " << filename.str() << endl;

    image = imread( filename.str() );

    if (image.empty() or !image.data) {
        cout << "Input image empty:\n";
    }

    frame = image.clone();
}

// Class to hold the perceptual chunks.
class percepUnit {

    public:
        cv::Mat image; // percept itself
        cv::Mat mask; // alpha channel

        // constructor method
        percepUnit(cv::Mat &ROI, cv::Mat &alpha, int ix, int iy, int iw, int ih, int area)  {
            image = ROI.clone();
            mask = alpha.clone();
        }
};

// Segment foreground from background
void segmentForeground(list<percepUnit*> &percepUnitsForeground, Mat &foreground, Mat &frame) {
    Mat contourImage = Mat(foreground.rows, foreground.cols, CV_8UC1, Scalar::all(0));
    vector<vector<Point>> contours;
    int area;

    // The following causes strange spikes in memory usage:
    // find contours
    findContours(foreground, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);

    for (int idx = 0; idx < contours.size(); idx++) {

        area = contourArea(contours[idx]);

        if (area > 100) {

            percepUnit *thisUnit = new percepUnit(frame, contourImage, 0, 0, 100,100, area);
            percepUnitsForeground.push_back(thisUnit); // Append to percepUnits
        }
    }

    /* The following does not:
    findContours(foreground, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);

    for (int idx = 0; idx < contours.size(); idx++) {
        area = contourArea(contours[idx]);
    }*/

    /* Neither does this:
    for (int idx = 0; idx < 10; idx++) {
        percepUnit *thisUnit = new percepUnit(frame, contourImage, 0, 0, 100,100, area);
        percepUnitsForeground.push_back(thisUnit); // Append to percepUnits
    }*/
}

int main(int argc, const char** argv)
{
    int frameCount = 78298; 
    Mat frame, foreground;
    BackgroundSubtractorMOG2 MOG2model;
    list<percepUnit*> scratchPercepUnitsForeground;

    // add rusage stuff
    struct rusage usage; // memory usage.

    for(int i=0; i<= 75; i++)
    {
        // run full segmenter here.  (background disabled)

        readFrame(frameCount, frame); // was frame = readFrame();

        // Only process if this frame actually loaded (non empty)
        if ( not frame.empty() ) {

            MOG2model(frame,foreground); // Update MOG2 model, downscale?

            // before we segment again clear scratch
            // TODO how to delete the actual memory allocated? Run delete on everything?
            for (list<percepUnit*>::iterator percepIter = scratchPercepUnitsForeground.begin(); 
                 percepIter != scratchPercepUnitsForeground.end();
                 percepIter++) {

                delete *percepIter; // delete what we point to.
                //percepIter = scratchPercepUnitsForeground.erase(percepIter); // remove the pointer itself, and update the iterator.
            }
            // Added with EDIT1
            scratchPercepUnitsForeground.clear();

            // Segment the foreground regions and generate boolImage to extract from background.
            segmentForeground(scratchPercepUnitsForeground, foreground, frame);

        }

        frameCount++;

        getrusage(RUSAGE_SELF, &usage);
        cout << "DEBUG leakTest_bug_report " << i << " " << usage.ru_maxrss/1024.0 << endl;
    }

    return 0;
}
然而,内存仍在莫名其妙地增加:(与上一个图相比,总体增加是由于在valgrind中运行了此测试。)


(来源:)

对于相同的代码,此处是体量输出: 未调用FindOnTours()的非泄漏情况下的massif输出,只有percepUnit构造函数:

编辑3 在cross-thread()中有人建议我阅读proc而不是使用rusage方法,看看这个,内存并没有稳定地增加:(!)


(来源:)


这确实与体量输出类似!!所以我想我需要重做所有的单元测试。任何人都有理由不放弃这里并考虑问题Ruffs:< /P> < p>您的PTR列表清理的循环是跳过项目,因为它既有增量子句(<代码>感知器++/COD>)和循环体本身中的迭代器重新分配(来自<代码> EraseE()/代码>调用的返回值)。 换句话说,您将迭代器加倍,跳过每一个偶数开槽项

我在下面做了标记:

        for (list<percepUnit*>::iterator percepIter = scratchPercepUnitsForeground.begin(); 
             percepIter != scratchPercepUnitsForeground.end();
             percepIter++) { // ADVANCES ITERATOR

            delete *percepIter; // delete what we point to.
            percepIter = scratchPercepUnitsForeground.erase(percepIter); // ADVANCES ITERATOR
        }
就个人而言,如果我必须选择其中一个,我更喜欢后者。除其他外,它速度更快,可读性更强

但是如果我编写这个代码,我会使用智能指针,这将使它完全不相关,因为您可以简单地触发
scratchpercepunitsforground.clear()并完成它。当列表内容被清除时,所有的智能指针析构函数都会启动,它们会依次为您删除它们的对象。这个概念被称为资源获取是初始化,简称RAII,它简单地意味着所有事情,包括动态分配,都有基于范围的生命周期,在范围退出时自动回收资源


不管怎么说,这肯定是一个漏洞,从外观上看,这是一个很大的漏洞。

对我来说,使用
rusage
的图形分析单调增加的事实值得怀疑

rusage
的文档说明:

使用的最大常驻集大小,以KB为单位。也就是说,同时使用的物理内存的最大KB数

由于您可能只在多个图像中收集同一进程的统计数据,
rusage
报告所有图像的最大内存使用量的结论是有意义的

实际上,使用不同的分析工具(OS X上的仪器)会生成显示当前内存使用情况的图表:


这与使用
proc
得到的结果非常相似。我的结论是,
rusage
不是你想要的评测工具。

我在谷歌搜索了似乎是同样的问题后发现了这个问题(valgrind报告“仍然可以访问”的内存,包括_dl_init和libpixman-1)。我用的是Fedora 18 64位。我设法用一个绝对最小的C可执行文件重现了这个问题,所以库。。。C程序只有一个返回0的主函数,而.so只有一个函数(从未实际调用),该函数也会立即以0退出。然而,valgrind仍然报告了5个未缠绕的块,总计10360字节。我决定发布这个帖子,以防其他可怜的人花了一个月的时间调试同样的问题!无论缺陷是什么,它似乎不太可能是由可执行文件和库引起的,两者都不起任何作用。

进一步调查后,当我在Ubuntu 13.04上重新编译可执行文件和.so文件时,内存泄漏消失了(与Fedora 18的GCC 4.7.2相比,GCC 4.7.3)。我没有做其他更改,所以问题肯定不是我的代码。修复了。所以即使在我把它转移回软呢帽机后,valgrind仍然是完美的。我建议使用更新版本的gcc重新编译您正在使用的任何/所有共享对象。至于内存使用问题随着时间的推移而增加,我不知道,因为我的项目很小,分配的内存很少,而且很快退出。

你应该真正考虑使用查找内存泄漏,你会看到漏洞在哪里,并保存自己一个月的调试。我将添加我的ValGRD输出以上。我一直在使用它,但发现它毫无用处,因为大多数输出都是由被确认没有泄漏的代码生成的,而且我看不出有什么办法可以告诉我实际的问题出在哪里。当然,上一次我用它的时候,程序要大5倍。我可以在Centos6上看到更多的评论。尝试升级到libpixman-1.so.0.32.4,但发生了相同的泄漏。从Valgrind:1个块中的2064字节仍然可以通过0x4A069EE:malloc(vg_replace_malloc.c:270)=15199==0x5BA8B7A:??在丢失记录6中访问???(在/usr/lib64/libpixman-1.so.0.32.4中)谢谢。我在这里交叉发布了这个问题:请看我对类似答案的评论。不幸的是,这不是问题的原因。事实上,我看到的漏洞比我应用您建议的修复方案所节省的要大得多。(见上文第1版)。如果你能重现我在图表中看到的东西,我们就可以前进了。@b很遗憾,这并没有解决你的问题,因为海报和我都认为这是一个完全的内存泄漏,这是非常正确的。不管你寻找的是不是漏洞,你要么实例级的向量,使用智能指针,要么按照他/她和我的建议修复漏洞。如果我有机会运行这个,我会的,但因为我既没有你的测试数据,也没有在这个平台上安装OpenCV,“复制”它将远远不是一个快速的转变。我真的很感谢你在这里花时间。我并不着急,我在一个多月前的一个大得多的程序中发现了这些问题,直到现在,我才煞费苦心地把它变成了一个简单的测试用例,我可以使用它
#include <sys/resource.h> // memory management.
#include <stdio.h>
#include <iostream>
#include <iomanip>

#include "opencv2/highgui/highgui.hpp"
#include "opencv2/core/core.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/video/background_segm.hpp"

using namespace std;
using namespace cv;

// Load frame from disk.
void readFrame(int frameNum, Mat &frame) {
    // Construct filenames
    Mat image;
    stringstream number, filename;

    number << setw(7) << setfill('0') << frameNum; // expecting over 1e10 images over the installation period.
    filename << "../images/store-" << number.str() << ".jpg"; // assumes jpegs!//
    cout << "Loading filename: " << filename.str() << endl;

    image = imread( filename.str() );

    if (image.empty() or !image.data) {
        cout << "Input image empty:\n";
    }

    frame = image.clone();
}

// Class to hold the perceptual chunks.
class percepUnit {
    
    public:
        cv::Mat image; // percept itself
        cv::Mat mask; // alpha channel
        
        // constructor method
        percepUnit(cv::Mat &ROI, cv::Mat &alpha, int ix, int iy, int iw, int ih, int area)  {
            image = ROI.clone();
            mask = alpha.clone();
        }
};

// Segment foreground from background
void segmentForeground(list<percepUnit> &percepUnitsForeground, Mat &foreground, Mat &frame) {
    Mat contourImage = Mat(foreground.rows, foreground.cols, CV_8UC1, Scalar::all(0));
    vector<vector<Point>> contours;
    int area;

    // The following causes strange spikes in memory usage:
    // find contours
    findContours(foreground, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);

    for (int idx = 0; idx < contours.size(); idx++) {

        area = contourArea(contours[idx]);

        if (area > 100) {

            percepUnit thisUnit = percepUnit(frame, contourImage, 0, 0, 100,100, area);
            percepUnitsForeground.push_back(thisUnit); // Append to percepUnits
        }
    }

    /* The following does not:
    findContours(foreground, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);

    for (int idx = 0; idx < contours.size(); idx++) {
        area = contourArea(contours[idx]);
    }*/

    /* Neither does this:
    for (int idx = 0; idx < 10; idx++) {
        percepUnit thisUnit = percepUnit(frame, contourImage, 0, 0, 100,100, area);
        percepUnitsForeground.push_back(thisUnit); // Append to percepUnits
    }*/
}

int main(int argc, const char** argv)
{
    int frameCount = 78298; 
    Mat frame, foreground;
    BackgroundSubtractorMOG2 MOG2model;
    list<percepUnit> scratchPercepUnitsForeground;

    // add rusage stuff
    struct rusage usage; // memory usage.

    for(int i=0; i<= 75; i++)
    {
        // run full segmenter here.  (background disabled)
        
        readFrame(frameCount, frame); // was frame = readFrame();

        // Only process if this frame actually loaded (non empty)
        if ( not frame.empty() ) {

            MOG2model(frame,foreground); // Update MOG2 model, downscale?

            // before we segment again clear scratch
            scratchPercepUnitsForeground.clear();
    
            // Segment the foreground regions and generate boolImage to extract from background.
            segmentForeground(scratchPercepUnitsForeground, foreground, frame);

        }

        frameCount++;

        getrusage(RUSAGE_SELF, &usage);
        cout << "DEBUG leakTest_bug_report " << i << " " << usage.ru_maxrss/1024.0 << endl;
    }

    return 0;
}
==3562== Memcheck, a memory error detector
==3562== Copyright (C) 2002-2011, and GNU GPL'd, by Julian Seward et al.
==3562== Using Valgrind-3.7.0 and LibVEX; rerun with -h for copyright info
==3562== Command: ./leakTest
==3562== 
==3562== 
==3562== HEAP SUMMARY:
==3562==     in use at exit: 15,024 bytes in 7 blocks
==3562==   total heap usage: 795,556 allocs, 795,549 frees, 29,269,731,785 bytes allocated
==3562== 
==3562== 568 bytes in 1 blocks are still reachable in loss record 1 of 7
==3562==    at 0x4C2B6CD: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==3562==    by 0x63A720A: __fopen_internal (iofopen.c:76)
==3562==    by 0xA8BC050: libjpeg_general_init (in /usr/lib/x86_64-linux-gnu/libjpeg.so.8.0.2)
==3562==    by 0x400F305: call_init.part.0 (dl-init.c:85)
==3562==    by 0x400F3DE: _dl_init (dl-init.c:52)
==3562==    by 0x40016E9: ??? (in /lib/x86_64-linux-gnu/ld-2.15.so)
==3562== 
==3562== 2,072 bytes in 1 blocks are still reachable in loss record 2 of 7
==3562==    at 0x4C2B6CD: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==3562==    by 0x1495F675: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.24.4)
==3562==    by 0x1495E4AE: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.24.4)
==3562==    by 0x14950888: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.24.4)
==3562==    by 0x149253B8: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.24.4)
==3562==    by 0x400F305: call_init.part.0 (dl-init.c:85)
==3562==    by 0x400F3DE: _dl_init (dl-init.c:52)
==3562==    by 0x40016E9: ??? (in /lib/x86_64-linux-gnu/ld-2.15.so)
==3562== 
==3562== 2,072 bytes in 1 blocks are still reachable in loss record 3 of 7
==3562==    at 0x4C2B6CD: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==3562==    by 0x1495F675: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.24.4)
==3562==    by 0x1495E0EF: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.24.4)
==3562==    by 0x14950890: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.24.4)
==3562==    by 0x149253B8: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.24.4)
==3562==    by 0x400F305: call_init.part.0 (dl-init.c:85)
==3562==    by 0x400F3DE: _dl_init (dl-init.c:52)
==3562==    by 0x40016E9: ??? (in /lib/x86_64-linux-gnu/ld-2.15.so)
==3562== 
==3562== 2,072 bytes in 1 blocks are still reachable in loss record 4 of 7
==3562==    at 0x4C2B6CD: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==3562==    by 0x1495F675: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.24.4)
==3562==    by 0x14971A6F: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.24.4)
==3562==    by 0x14950898: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.24.4)
==3562==    by 0x149253B8: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.24.4)
==3562==    by 0x400F305: call_init.part.0 (dl-init.c:85)
==3562==    by 0x400F3DE: _dl_init (dl-init.c:52)
==3562==    by 0x40016E9: ??? (in /lib/x86_64-linux-gnu/ld-2.15.so)
==3562== 
==3562== 2,072 bytes in 1 blocks are still reachable in loss record 5 of 7
==3562==    at 0x4C2B6CD: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==3562==    by 0x1495F675: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.24.4)
==3562==    by 0x1499024F: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.24.4)
==3562==    by 0x149508A0: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.24.4)
==3562==    by 0x149253B8: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.24.4)
==3562==    by 0x400F305: call_init.part.0 (dl-init.c:85)
==3562==    by 0x400F3DE: _dl_init (dl-init.c:52)
==3562==    by 0x40016E9: ??? (in /lib/x86_64-linux-gnu/ld-2.15.so)
==3562== 
==3562== 2,072 bytes in 1 blocks are still reachable in loss record 6 of 7
==3562==    at 0x4C2B6CD: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==3562==    by 0x1495F675: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.24.4)
==3562==    by 0x149610EF: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.24.4)
==3562==    by 0x149253B8: ??? (in /usr/lib/x86_64-linux-gnu/libpixman-1.so.0.24.4)
==3562==    by 0x400F305: call_init.part.0 (dl-init.c:85)
==3562==    by 0x400F3DE: _dl_init (dl-init.c:52)
==3562==    by 0x40016E9: ??? (in /lib/x86_64-linux-gnu/ld-2.15.so)
==3562== 
==3562== 4,096 bytes in 1 blocks are still reachable in loss record 7 of 7
==3562==    at 0x4C2B6CD: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==3562==    by 0xA8BC067: libjpeg_general_init (in /usr/lib/x86_64-linux-gnu/libjpeg.so.8.0.2)
==3562==    by 0x400F305: call_init.part.0 (dl-init.c:85)
==3562==    by 0x400F3DE: _dl_init (dl-init.c:52)
==3562==    by 0x40016E9: ??? (in /lib/x86_64-linux-gnu/ld-2.15.so)
==3562== 
==3562== LEAK SUMMARY:
==3562==    definitely lost: 0 bytes in 0 blocks
==3562==    indirectly lost: 0 bytes in 0 blocks
==3562==      possibly lost: 0 bytes in 0 blocks
==3562==    still reachable: 15,024 bytes in 7 blocks
==3562==         suppressed: 0 bytes in 0 blocks
==3562== 
==3562== For counts of detected and suppressed errors, rerun with: -v
==3562== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 2 from 2)
        for (list<percepUnit*>::iterator percepIter = scratchPercepUnitsForeground.begin(); 
             percepIter != scratchPercepUnitsForeground.end();
             percepIter++) { // ADVANCES ITERATOR

            delete *percepIter; // delete what we point to.
            percepIter = scratchPercepUnitsForeground.erase(percepIter); // ADVANCES ITERATOR
        }
       for (list<percepUnit*>::iterator percepIter = scratchPercepUnitsForeground.begin(); 
             percepIter != scratchPercepUnitsForeground.end();) {

            delete *percepIter; // delete what we point to.
            percepIter = scratchPercepUnitsForeground.erase(percepIter);
        }
       for (list<percepUnit*>::iterator percepIter = scratchPercepUnitsForeground.begin(); 
             percepIter != scratchPercepUnitsForeground.end();
             ++percepIter) 
        {
            delete *percepIter;
        }
        scratchPercepUnitsForeground.clear();