迭代 Opencv 帧导致内存泄漏

2022-09-03 03:42:26

我正在使用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;
    }
}

我使用以下循环迭代此代码:

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

只是迭代就会线性增加内存消耗。我看到问题是下一个()-方法中的第一行。它总是创建一个新的垫子。但是仅说到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;
    }

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

我假设问题是垃圾回收器不会破坏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++;
    }

使用迭代器的第二个版本,10.png和30.png将是不同的图像。但这显然不是我们想要的。


答案 1

你真的应该调用 mat.release()。

在我的申请中,我有与您的问题非常相似的问题。帧速率如此之高,以至于java堆增长到总可用系统内存,这有时会导致JVM崩溃。GC太慢了,我没有任何机制来检查可用内存并等待是否足够。

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

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

您不必担心 Mat 对象的垃圾回收,因为此调用仅解除分配基础数据。Java对象包装器将在合适的时间到来时由GC释放。


答案 2

我只想添加我的0.02美元,因为我在编写将运行很长时间的应用程序时遇到了这个问题。

Mat.release() 在垃圾回收 Java Mat 包装器时自动调用。但是,由于 Java 包装器与本机分配的对象相比非常小,因此它的垃圾回收速度可能不够快。

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


推荐