21

在我的应用程序中,我使用 VNImageRequestHandler 和自定义 MLModel 进行对象检测。

该应用程序适用于 14.5 之前的 iOS 版本。

当 iOS 14.5 到来时,它打破了一切。

  1. 每当try handler.perform([visionRequest])抛出错误(Error Domain=com.apple.vis Code=11 "encountered unknown exception" UserInfo={NSLocalizedDescription=encountered unknown exception}),pixelBuffer内存被持有并且永远不会释放,它使 AVCaptureOutput 的缓冲区满了然后新帧没来。
  2. 我必须更改代码如下,通过将pixelBuffer复制到另一个var,我解决了新帧不来的问题,但内存泄漏问题仍然发生。

在此处输入图像描述

在此处输入图像描述

在此处输入图像描述

由于内存泄漏,应用程序在一段时间后崩溃。

请注意,在 iOS 版本 14.5 之前,检测工作完美,try handler.perform([visionRequest])从不抛出任何错误。

这是我的代码:

private func predictWithPixelBuffer(sampleBuffer: CMSampleBuffer) {
  guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
    return
  }
  
  // Get additional info from the camera.
  var options: [VNImageOption : Any] = [:]
  if let cameraIntrinsicMatrix = CMGetAttachment(sampleBuffer, kCMSampleBufferAttachmentKey_CameraIntrinsicMatrix, nil) {
    options[.cameraIntrinsics] = cameraIntrinsicMatrix
  }
  
  autoreleasepool {
    // Because of iOS 14.5, there is a bug that when perform vision request failed, pixel buffer memory leaked so the AVCaptureOutput buffers is full, it will not output new frame any more, this is a temporary work around to copy pixel buffer to a new buffer, this currently make the memory increased a lot also. Need to find a better way
    var clonePixelBuffer: CVPixelBuffer? = pixelBuffer.copy()
    let handler = VNImageRequestHandler(cvPixelBuffer: clonePixelBuffer!, orientation: orientation, options: options)
    print("[DEBUG] detecting...")
    
    do {
      try handler.perform([visionRequest])
    } catch {
      delegate?.detector(didOutputBoundingBox: [])
      failedCount += 1
      print("[DEBUG] detect failed \(failedCount)")
      print("Failed to perform Vision request: \(error)")
    }
    clonePixelBuffer = nil
  }
}

有没有人遇到过同样的问题?如果是这样,你是如何解决的?

4

3 回答 3

4

开发者门户上提供的 iOS 14.7 Beta 似乎已经解决了这个问题。

于 2021-05-20T17:43:13.300 回答
3

我使用@Matthijs Hollemans CoreMLHelpers 库对此进行了部分修复。

我使用的模型有 300 个类和 2363 个锚点。我使用了很多这里提供的代码 Matthijs将模型转换为 MLModel。

在最后一步中,使用 3 个子模型构建管道:raw_ssd_output、解码器和 nms。对于此解决方法,您需要nms从管道中删除模型,然后输出raw_confidenceraw_coordinates.

在您的应用程序中,您需要添加来自CoreMLHelpers的代码。

然后添加这个函数来解码你的 MLModel 的输出:

    func decodeResults(results:[VNCoreMLFeatureValueObservation]) -> [BoundingBox] {
        let raw_confidence: MLMultiArray = results[0].featureValue.multiArrayValue!
        let raw_coordinates: MLMultiArray = results[1].featureValue.multiArrayValue!
        print(raw_confidence.shape, raw_coordinates.shape)
        var boxes = [BoundingBox]()
        let startDecoding = Date()
        for anchor in 0..<raw_confidence.shape[0].int32Value {
            var maxInd:Int = 0
            var maxConf:Float = 0
            for score in 0..<raw_confidence.shape[1].int32Value {
                let key = [anchor, score] as [NSNumber]
                let prob = raw_confidence[key].floatValue
                if prob > maxConf {
                    maxInd = Int(score)
                    maxConf = prob
                }
            }
            let y0 = raw_coordinates[[anchor, 0] as [NSNumber]].doubleValue
            let x0 = raw_coordinates[[anchor, 1] as [NSNumber]].doubleValue
            let y1 = raw_coordinates[[anchor, 2] as [NSNumber]].doubleValue
            let x1 = raw_coordinates[[anchor, 3] as [NSNumber]].doubleValue
            let width = x1-x0
            let height = y1-y0
            let x = x0 + width/2
            let y = y0 + height/2
            let rect = CGRect(x: x, y: y, width: width, height: height)
            let box = BoundingBox(classIndex: maxInd, score: maxConf, rect: rect)
            boxes.append(box)
        }
        let finishDecoding = Date()
        let keepIndices = nonMaxSuppressionMultiClass(numClasses: raw_confidence.shape[1].intValue, boundingBoxes: boxes, scoreThreshold: 0.5, iouThreshold: 0.6, maxPerClass: 5, maxTotal: 10)
        let finishNMS = Date()
        var keepBoxes = [BoundingBox]()
        
        for index in keepIndices {
            keepBoxes.append(boxes[index])
        }
        print("Time Decoding", finishDecoding.timeIntervalSince(startDecoding))
        print("Time Performing NMS", finishNMS.timeIntervalSince(finishDecoding))
        return keepBoxes
    }

然后,当您从 Vision 收到结果时,您可以像这样调用函数:

if let rawResults = vnRequest.results as? [VNCoreMLFeatureValueObservation] {
   let boxes = self.decodeResults(results: rawResults)
   print(boxes)
}

由于我移动数据和制定BoundingBox类型列表的方式,这个解决方案很慢。使用底层指针处理 MLMultiArray 数据会更有效,并且可能使用 Accelerate 来找到每个锚框的最大分数和最佳类别。

于 2021-05-14T10:44:14.723 回答
1

就我而言,它通过强制 CoreML 仅在 CPU 和 GPU 上运行来帮助禁用神经引擎。这通常较慢,但不会引发异常(至少在我们的例子中)。最后,我们实施了一项政策,强制我们的一些模型不在某些 iOS 设备的神经引擎上运行。

请参阅MLModelConfiguration.computeUntis以约束可以使用的硬件 coreml 模型。

于 2021-05-11T06:17:34.867 回答