内存泄漏来自迭代Opencv帧

Mat*_*ias 14 java opencv

我正在使用OpenCV的java包装器.我试图在电影的帧上写一个迭代器.我的问题是迭代器是一个巨大的内存泄漏.这是迭代器的一个非常简化的版本,它有这个漏洞:

public static final class SimpleIt implements Iterator<Mat> {

    private final VideoCapture capture;
    boolean hasNext;

    public SimpleIt(final VideoCapture capture) {
        this.capture = capture;
        hasNext = capture.grab();
    }

    @Override
    public boolean hasNext() {
        return hasNext;
    }

    @Override
    public Mat next() {
        final Mat mat = new Mat();
        capture.retrieve(mat);
        hasNext = capture.grab();
        return mat;
    }
}
Run Code Online (Sandbox Code Playgroud)

我使用这个循环迭代这个代码:

    final VideoCapture vc = new VideoCapture("/path/to/file");
    final SimpleIt it = new SimpleIt(vc);
    while (it.hasNext) {
        it.next();
    }
Run Code Online (Sandbox Code Playgroud)

只是迭代会增加线性内存消耗.我看到问题是next() - Method中的第一行.它总是创造一个新的垫子.但是只谈到java,只要迭代代码迭代到下一个图像,这个Mat就会超出范围.

我可以通过不每次使用新的Mat来克服这个问题,但是总是覆盖相同的Mat-Object,如下所示:

    private final VideoCapture capture;
    private final Mat mat = new Mat();
    boolean hasNext;

    @Override
    public Mat next() {
        capture.retrieve(mat);
        hasNext = capture.grab();
        return mat;
    }
Run Code Online (Sandbox Code Playgroud)

但是现在迭代器给出的最后一帧将被覆盖.因此,如果我对这个单帧感兴趣,我不能把它放在外面供以后使用.当然,我可以复制它,但这也很昂贵.

我假设问题是垃圾收集器不会破坏Mat对象,因为它不识别内存消耗,因为它不是java堆空间.在循环中调用mat.release()会有所帮助,但当然在实际代码中这意味着我的Mat对象没有垃圾收集.

有人知道怎么做吗?

编辑:

由于似乎不清楚我的第二个解决方案的问题是什么,我更明确地写下来了.使用迭代器考虑以下代码:

    final VideoCapture vc = new VideoCapture("/path/to/file");
    final SimpleIt it = new SimpleIt(vc);
    int i = 0;
    Mat save = null;
    while (it.hasNext) {
        final Mat next = it.next();
        if (i == 10) {
            save = next;
            Highgui.imwrite("/path/to/10.png", save);
        } else if (i == 30) {
            Highgui.imwrite("/path/to/30.png", save);
        }
        i++;
    }
Run Code Online (Sandbox Code Playgroud)

使用迭代器的第二个版本,10.png和30.png将是不同的图像.但这显然不是预期的.

luk*_*ukk 15

你应该打电话mat.release().

在我的应用程序中,我遇到了类似的问题.帧速率非常高,以至于java堆长大到可用的总系统内存,这有时会导致JVM崩溃.GC太慢了,我没有任何机制来检查可用内存并等待,如果这还不够.

一种解决方案是通过简单地使用来降低帧速率,Thread.sleep()这当然似乎是不可接受的.但它帮助GC按时完成了它的工作.

最后使用mat.release()修复问题.

您不必担心Mat对象的垃圾收集,因为此调用仅释放基础数据.当正确的时间到来时,Java对象包装器将由GC处理.


Poy*_*yan 15

当我编写一个运行很长时间的应用程序时,我只想添加我的0.02美元.

当Java Mat-wrapper被垃圾收集时,会自动调用Mat.release().但是,由于Java包装器与本机分配的对象相比非常小,因此可能不会足够快地进行垃圾收集.

因此,您可以Mat.release()在知道自己已完成某个对象或System.gc()定期调用以强制删除未使用的对象时执行此操作.

  • 这个策略似乎对我有用.Java认为它的分配方式类似于1个对象引用,每个mat分配1个.但是,C++方面做得更多.由于Java无法看到该内存,因此它不知道有多少内存可供使用.你必须强制调用`Mat.finalize()`.`System.gc()`会这样做. (2认同)

Sea*_*ayn 6

的System.gc(); 不适合我.

我添加了这一行:

System.runFinalization();

代码邮箱:

    startGC--;
    if (startGC==0) {
        System.gc();
        System.runFinalization();
        startGC=100;
    }
Run Code Online (Sandbox Code Playgroud)


Mat*_*ias 5

看来,没有好的解决方案.我现在已经试验了好几个小时.我想出的最好的是定期调用垃圾收集器,如下所示:

    int count = 0;

    @Override
    public Mat next() {
        final Mat result = mat;
        mat = new Mat();
        capture.retrieve(mat);
        hasNext = capture.grab();
        if (++count % 200 == 0) {
            System.gc();
        }
        return result;
Run Code Online (Sandbox Code Playgroud)

由于这有效,它表明我的假设是正确的,java无法识别从C分配的RAM,因此即使机器的RAM用完,也不会调用GC.

这不是一个很好的解决方案,因为它可能不是很稳定.如果其他人有更好的想法,我感兴趣.


小智 1

我会将你的.hasNext方法修改为:

public boolean hasNext() {
    return hasNext;
}
Run Code Online (Sandbox Code Playgroud)

然后您描述的方法(在下面复制)应该可以正常工作...您将迭代直到什么都没有剩下,此时您可以将最后一个图像分配给一个新的 Mat 对象...

public Mat next() {
    capture.retrieve(mat);
    hasNext = capture.grab();
    return mat;
}
Run Code Online (Sandbox Code Playgroud)

进而:

final VideoCapture vc = new VideoCapture("/path/to/file");
final SimpleIt it = new SimpleIt(vc);
final Mat lastFrame = new Mat();
while (it.hasNext) {
    lastFrame = it.next();
}
Run Code Online (Sandbox Code Playgroud)

我确实意识到这会产生额外的内存使用量。可能有一种方法可以解决这个问题,但它应该可以正常工作......