You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

OpenCV.js内存溢出问题:Ionic Angular相机图像处理报错求助

解决OpenCV.js在Ionic Angular项目中的OutOfMemoryError问题

针对你遇到的内存泄漏问题,以下是具体的修复步骤和优化建议:

1. 释放未清理的轮廓资源

在findRectforImage函数中,你创建了MatVector类型的contours,但未释放它;同时循环中获取的每个cnt对象也需要手动释放,否则会快速堆积内存:

findRectforImage(imageMAT) {
    try {
      let rectCoordinates = [];
      let contours = new cv.MatVector();
      let hierarchy = new cv.Mat();
      let gray = new cv.Mat();
      cv.cvtColor(imageMAT, gray, cv.COLOR_RGBA2GRAY, 0);
      cv.threshold(gray, gray, 100, 255, cv.THRESH_BINARY);
      cv.findContours(gray, contours, hierarchy, cv.RETR_LIST, cv.CHAIN_APPROX_SIMPLE);
      
      for (let i = 0; i < contours.size(); ++i) {
        let cnt = contours.get(i);
        let rect = cv.boundingRect(cnt);
        let aspectRatio = rect.width / rect.height;

        if (aspectRatio >= 0.9 && aspectRatio <= 1.1 && rect.width >= 15) {
          cv.drawContours(imageMAT, contours, i, [0, 255, 0, 255], 2, cv.LINE_AA, hierarchy, 100);
        }
        cnt.delete(); // 释放单个轮廓的内存
      }
      
      cv.imshow(this.canvas, imageMAT);
      // 释放所有创建的资源
      hierarchy.delete();
      gray.delete();
      contours.delete(); // 释放整个轮廓集合

    } catch (error) {
      console.log("ERROR IN FINDCOUNTOUR>>>>>>>", error); 
      // 出错时也要确保资源释放
      if (contours) contours.delete();
      if (hierarchy) hierarchy.delete();
      if (gray) gray.delete();
      throw cv.exceptionFromPtr(error).msg;
    }
  }

2. 修复rotateImage中的内存泄漏

rotateImage中存在两处内存泄漏:重复分配this.output未释放旧对象、roi操作丢弃原Mat对象。修改如下:

rotateImage(image) {
    try {
      // 先释放之前的output(如果存在)
      if (this.output) {
        this.output.delete();
      }
      this.output = new cv.Mat();
      let size = new cv.Size();

      size.width = image.cols;
      size.height = image.rows;

      let scalar = new cv.Scalar(0, 0, 0, 0);
      let center;
      let padding;
      let height = size.height;
      let width = size.width;

      if (height > width) {
        center = new cv.Point(height / 2, height / 2);
        padding = (height - width) / 2;
        cv.copyMakeBorder(image, this.output, 0, 0, padding, padding, cv.BORDER_CONSTANT, scalar);
        size.width = height;
      } else {
        center = new cv.Point(width / 2, width / 2);
        padding = (width - height) / 2;
        cv.copyMakeBorder(image, this.output, padding, padding, 0, 0, cv.BORDER_CONSTANT, scalar);
        size.height = width;
      }

      // 释放旧的旋转矩阵(如果存在)
      if (this.rotationMatrix) {
        this.rotationMatrix.delete();
      }
      this.rotationMatrix = cv.getRotationMatrix2D(center, -90, 1);

      cv.warpAffine(
        this.output,
        this.output,
        this.rotationMatrix,
        size,
        cv.INTER_LINEAR,
        cv.BORDER_CONSTANT,
        new cv.Scalar()
      );

      let rectangle;
      if (height > width) {
        rectangle = new cv.Rect(0, padding, height, width);
      } else {
        rectangle = new cv.Rect(padding, 0, height, width);
      }

      // 处理roi时,先保存原output,创建新对象后释放原内存
      let originalOutput = this.output;
      this.output = this.output.roi(rectangle);
      originalOutput.delete();

    } catch (error) {
      console.log("ERROR IN ROTATEMAGE>>>>>>>", error);
      // 出错时释放已创建的资源
      if (this.output) {
        this.output.delete();
        this.output = null;
      }
      if (this.rotationMatrix) {
        this.rotationMatrix.delete();
        this.rotationMatrix = null;
      }
      throw cv.exceptionFromPtr(error).msg;
    }
  }

3. 避免并发图像处理堆积

当前的requestAnimationFrame调用可能导致上一次图像处理未完成,下一次任务又启动,造成多份Mat同时占用内存。添加状态标记确保串行执行:

// 组件中添加状态变量
private isProcessing = false;

TestFunction() {
    if (this.isProcessing) return; // 避免并发执行
    this.isProcessing = true;
    try {
      this.cameraPreviewService.getCameraSize().then(cameraSize => {
        this.cameraSize = JSON.parse(cameraSize);
        this.captureImage().then(res => {
          console.log(res)
          this.isProcessing = false;
          requestAnimationFrame(this.TestFunction.bind(this));
        }).catch(error => {
          console.log(error);
          this.isProcessing = false;
          requestAnimationFrame(this.TestFunction.bind(this));
        })    
      }).catch(error => { 
        console.log(error);
        this.isProcessing = false;
        requestAnimationFrame(this.TestFunction.bind(this));
      });
    } catch (error) {
      console.log(error);
      this.isProcessing = false;
      requestAnimationFrame(this.TestFunction.bind(this));
    }
  }

4. 完善错误处理中的资源释放

在captureImage中,确保无论成功还是失败,所有创建的Mat都被释放:

captureImage(): Promise<string> {
    return new Promise((resolve, reject) => {
      let imageMAT = null;
      try {
        let data = JSON.parse(JSON.stringify(this.cameraPreviewOptions))
        this.cameraPreviewService.testFunction(data).subscribe(response => {
          try {
            this.imgUrl = 'data:image/png;base64,' + response.imageBytes;
            imageMAT = cv.imread(this.ImageElement);

            let desiredWidth = this.answerShadowDivWidth;
            let desiredHeight = this.answerShadowDivHeight;
            let dsize = new cv.Size(desiredWidth, desiredHeight);

            this.rotateImage(imageMAT);
            cv.resize(this.output, imageMAT, dsize, 0, 0, cv.INTER_LINEAR);
            this.findRectforImage(imageMAT);
          } catch (innerError) {
            console.log(innerError);
            resolve(innerError);
          } finally {
            // 确保资源释放
            if (imageMAT) imageMAT.delete();
            if (this.output) this.output.delete();
            if (this.rotationMatrix) this.rotationMatrix.delete();
            resolve("Image process is Finished");
          }
        }, error => {
          // 订阅错误时释放资源
          if (imageMAT) imageMAT.delete();
          if (this.output) this.output.delete();
          if (this.rotationMatrix) this.rotationMatrix.delete();
          resolve(error);
        });
      } catch (error) {
        if (imageMAT) imageMAT.delete();
        if (this.output) this.output.delete();
        if (this.rotationMatrix) this.rotationMatrix.delete();
        resolve(error);
      }
    })
  }

额外优化建议

  • 降低处理频率:如果业务允许,将处理间隔从100ms调整为200ms或更长,减少内存占用压力。
  • 缩小图像尺寸:在cv.imread后先将图像缩放到更小的尺寸,再进行旋转、轮廓检测等操作,减少单张图像的内存占用。
  • 复用Mat对象:提前初始化常用的Mat对象,每次使用前释放旧数据再重新赋值,避免频繁创建销毁对象。

内容的提问来源于stack exchange,提问作者Halil POLAT

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 16:05:53