内存泄漏来自迭代Opencv帧

时间:2014-01-10 17:21:22

标签: 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;
    }
}

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

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

只需迭代就会增加线性内存消耗。我看到问题是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;
    }

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

我认为问题在于垃圾收集器不会破坏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将是不同的图像。但这显然不是预期的。

5 个答案:

答案 0 :(得分:15)

您应该致电mat.release()

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

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

最后使用mat.release()解决了问题。

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

答案 1 :(得分:15)

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

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

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

答案 2 :(得分:6)

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

我添加了一行:

System.runFinalization();

A Codesnippet:

    startGC--;
    if (startGC==0) {
        System.gc();
        System.runFinalization();
        startGC=100;
    }

答案 3 :(得分: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;

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

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

答案 4 :(得分:1)

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

public boolean hasNext() {
    return hasNext;
}

然后你所描述的方法,在下面复制,应该可以正常工作......你将迭代直到没有剩下任何东西,此时你可以将最后一个图像分配给一个新的Mat对象......

public Mat next() {
    capture.retrieve(mat);
    hasNext = capture.grab();
    return mat;
}

然后:

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();
}

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