У меня есть приложение, которое выполняет фильтрацию в реальном времени по каналу камеры, я получаю каждый кадр с камеры, затем выполняю некоторую фильтрацию с помощью 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()
}
}