【问题标题】:Memory Leak from iterating Opencv frames迭代 Opencv 帧的内存泄漏
【发布时间】:2014-01-29 18:59:13
【问题描述】:

我正在使用 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 的第一行。它总是创建一个新的 Mat。但是单就 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 将是不同的图像。但这显然不是本意。

【问题讨论】:

  • 内存不足了吗?如果没有,那么这不是泄漏 - GC 将在需要时运行。
  • 是的,我这样做了。几秒钟后,一些 Gigabyte 已满。

标签: java opencv


【解决方案1】:

你真的应该打电话给mat.release()

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

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

最终使用mat.release() 解决了这个问题。

您不必担心 Mat 对象的垃圾回收,因为此调用仅释放底层数据。 Java 对象包装器会在适当的时候被 GC 处理掉。

【讨论】:

  • 目前我的解决方案适用于我,但我对此感到非常不安。 release() 的问题在于,很难确定某个垫子何时停止使用。我对它们进行了很多处理,将它们传递给系统的各个部分进行不同的分析。许多部分不知道其他部分是否也使用相同的 Mat。这可能就是 Java 开发人员设计他们的系统的方式。虽然 > 99% 的帧在第一个分析步骤之后直接被丢弃并被丢弃 - 所以也许我至少应该释放那些。
  • 我刚刚想出了一个想法,您可以将Mat 对象包装在某种引用计数器对象中。与例如acquire()realese() 方法可以处理对 Mat 的活动引用计数。当此计数归零时,您将调用 Mat.release() 以释放内存。
  • @lukk 我在纠正下说,但需要注意的是,Mat 已经使用引用计数器来管理底层内存,因为 opencv 非常强烈地共享 Mat 引用:docs.opencv.org/java/2.4.11/org/opencv/core/Mat.html - 我认为你可以创建一个新的 Mat 引用旧的 new Mat(m.getNativeObjAddr());
【解决方案2】:

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

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

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

【讨论】:

  • 这个策略似乎对我有用。 Java 认为它为每个垫子分配了 1 个对象引用和 1 个 long 。然而,C++ 方面做得更多。由于 Java 看不到该内存,它不知道可以使用的内存有多少。您必须强制致电Mat.finalize()System.gc() 会这样做。
  • 当我自定义 openCV 3 教程时,我必须添加行“modified.release();”在“Utils.matToBitmap(modified, mCacheBitmap);”之后在 CameraBridgeViewBase.java 文件中,现在所有运行稳定。我还按照建议在 onCameraFrame 中调用 System.gc()。
【解决方案3】:

System.gc();对我不起作用。

我已经添加了这一行:

System.runFinalization();

A Codesn-p:

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

【讨论】:

    【解决方案4】:

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

        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 已用完。

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

    【讨论】:

      【解决方案5】:

      我会将您的.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();
      }
      

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

      【讨论】:

      • 这不是真正的解决方案,我担心。这样,您就无法保存使用迭代器检索到的 Mat。查看我对问题的编辑。
      猜你喜欢
      • 2012-09-12
      • 1970-01-01
      • 2020-08-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-03-14
      • 2015-12-05
      • 2018-05-20
      相关资源
      最近更新 更多