низкая частота кадров при рендеринге cifiltered ciimage и MTKView при использовании распознавания лиц (Vision и CIDetection) - PullRequest
0 голосов
/ 05 марта 2020

У меня есть приложение, которое выполняет фильтрацию в реальном времени по каналу камеры, я получаю каждый кадр с камеры, затем выполняю некоторую фильтрацию с помощью CIFilter, а затем передаю последний кадр (CIImage) в MTKView, который будет отображаться в моем представлении swiftUI, это работает нормально, но когда я хочу сделать обнаружение лица / тела в режиме реального времени, при подаче камеры, частота кадров снижается до 8 кадров в секунду и супер лаги. я пробовал все, что я мог найти в inte rnet, используя vision, CIDetector, CoreML, все то же самое, что ж, я бы сделал это в глобальном потоке, который делает UI отзывчивым, но фид, который я показываю в основной вид все еще отстает, но такие вещи, как scrollview работают нормально. поэтому я попытался изменить представление с MTKView на UIImageView, Xcode показывает его рендеринг со скоростью 120FPS (что я не понимаю почему, его 30FPS, когда не используется распознавание лиц), но подача все еще запаздывает, не может каким-то образом соответствовать частоте кадров на выходе Я новичок в этом, я не понимаю, почему это так. Я также попытался просто передать поступающее изображение в MTKView (без какой-либо промежуточной фильтрации, с распознаванием лиц), и тот же самый медленный результат, без распознавания лиц, он достигает 30FPS (почему не 120?). это код, который я использую для преобразования sampleBuffer в ciImage

extension CICameraCapture: AVCaptureVideoDataOutputSampleBufferDelegate {
  func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {

    guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return }

    var ciImage = CIImage(cvImageBuffer: imageBuffer)

    if self.cameraPosition == AVCaptureDevice.Position.front {
        ciImage = ciImage.oriented(.downMirrored)
    }
    ciImage = ciImage.transformed(by: CGAffineTransform(rotationAngle: 3 * .pi / 2))
    ciImage = ciImage.transformToOrigin(withSize: ciImage.extent.size)
    detectFace(image: ciImage) // this is for detecting face realtime, i have done it in vision 
        //and also cidetector - cidetector is a little bit faster when setted to low accuracy 
       //but still not desired result(frame rate)

    DispatchQueue.main.async {
        self.callback(ciImage)
    }

  }
}  

, и это код MTKView, который очень прост и его базовая c реализация:

import MetalKit
import CoreImage

class MetalRenderView: MTKView {
    //var textureCache: CVMetalTextureCache?

  override init(frame frameRect: CGRect, device: MTLDevice?) {
    super.init(frame: frameRect, device: device)

    if super.device == nil {
      fatalError("No support for Metal. Sorry")
    }

    framebufferOnly = false
    preferredFramesPerSecond = 120
    sampleCount = 2
  }

  required init(coder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }

  private lazy var commandQueue: MTLCommandQueue? = {
    [unowned self] in
    return self.device!.makeCommandQueue()
  }()

  private lazy var ciContext: CIContext = {
    [unowned self] in
    return CIContext(mtlDevice: self.device!)
  }()

  var image: CIImage? {
    didSet {
        renderImage()
    }
  }

  private func renderImage() {
    guard var image = image else { return }
    image = image.transformToOrigin(withSize: drawableSize) // this is an extension to resize 
          //the image to the render size so i dont get the render error while rendering a frame

    let commandBuffer = commandQueue?.makeCommandBuffer()
    let destination = CIRenderDestination(width: Int(drawableSize.width),
                                          height: Int(drawableSize.height),
                                          pixelFormat: .bgra8Unorm,
                                          commandBuffer: commandBuffer) { () -> MTLTexture in
                                            return self.currentDrawable!.texture
    }

    try! ciContext.startTask(toRender: image, to: destination)

    commandBuffer?.present(currentDrawable!)
    commandBuffer?.commit()
    draw()

  }

}

и вот код для обнаружения лица с использованием CIDetector:

func detectFace (image: CIImage){
    //DispatchQueue.global().async {

            let options = [CIDetectorAccuracy: CIDetectorAccuracyHigh,
                           CIDetectorSmile: true, CIDetectorTypeFace: true] as [String : Any]

            let faceDetector = CIDetector(ofType: CIDetectorTypeFace, context: nil, 
                                          options: options)!

            let faces = faceDetector.features(in: image)

            if let face = faces.first as? CIFaceFeature {

                AppState.shared.mouth = face.mouthPosition
                AppState.shared.leftEye = face.leftEyePosition
                AppState.shared.rightEye = face.rightEyePosition
            }

    //}
}

то, что я пробовал

1) различные методы обнаружения лица, используя Vision, CIDetector, а также CoreML (этот не очень глубоко, так как у меня нет опыта в этом), я бы получил информацию об обнаружении, но частота кадров равна 8 или, в лучшем случае, 15 (что было бы задержанным обнаружением)

2) Я где-то читал, что это может быть результатом colorapce изображения, поэтому я пробовал разные настройки видео и другое цветовое пространство рендеринга, но без изменений частоты кадров.

3) Я почему-то уверен, что это может касаться времени освобождения пиксельного буфера, поэтому я глубоко скопировал imageBuffer и передал его на обнаружение, кроме некоторых проблем с памятью, он достиг 15 FPS, но стил Я не минимум 30 кадров в секунду. здесь я также попытался преобразовать imageBuffer в ciimage, а затем отобразить ciimage в cgimage и обратно в ciimage, чтобы просто освободить буфер, но также не смог получить более 15FPS (ну в среднем иногда до 17 или 19, но все еще отстает )

Я новичок в этом и все еще пытаюсь понять это, я был бы признателен за любые предложения, образцы или советы, которые могли бы направить меня на лучший путь решения этой проблемы.

обновление

это код настройки захвата камеры:

class CICameraCapture: NSObject {
    typealias Callback = (CIImage?) -> ()
    private var cameraPosition = AVCaptureDevice.Position.front
    var ciContext: CIContext?
    let callback: Callback
    private let session = AVCaptureSession()
    private let sampleBufferQueue = DispatchQueue(label: "buffer", qos: .userInitiated)//, attributes: [], autoreleaseFrequency: .workItem)
    // face detection
    //private var sequenceHandler = VNSequenceRequestHandler()
    //var request: VNCoreMLRequest!
    //var visionModel: VNCoreMLModel!
    //let detectionQ = DispatchQueue(label: "detectionQ", qos: .background)//, attributes: [], autoreleaseFrequency: .workItem)

    init(callback: @escaping Callback) {
        self.callback = callback
        super.init()
        prepareSession()
        ciContext = CIContext(mtlDevice: MTLCreateSystemDefaultDevice()!)
    }

    func start() {
        session.startRunning()
    }

    func stop() {
        session.stopRunning()
    }


  private func prepareSession() {
    session.sessionPreset = .high //.hd1920x1080
    let cameraDiscovery = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInDualCamera, .builtInWideAngleCamera], mediaType: .video, position: cameraPosition)
    guard let camera = cameraDiscovery.devices.first else { fatalError("Can't get hold of the camera") }
    //try! camera.lockForConfiguration()
    //camera.activeVideoMinFrameDuration = camera.formats[0].videoSupportedFrameRateRanges[0].minFrameDuration
    //camera.activeVideoMaxFrameDuration = camera.formats[0].videoSupportedFrameRateRanges[0].maxFrameDuration
    //camera.unlockForConfiguration()
    guard let input = try? AVCaptureDeviceInput(device: camera) else { fatalError("Can't get hold of the camera") }

    session.addInput(input)

    let output = AVCaptureVideoDataOutput()
    output.videoSettings = [:]
    //print(output.videoSettings.description)
    //[875704438, 875704422, 1111970369]
    //output.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String : Int(kCVPixelFormatType_32BGRA)]
    output.setSampleBufferDelegate(self, queue: sampleBufferQueue)

    session.addOutput(output)
    session.commitConfiguration()
  }
}
...