在 CoreModel 文件之间切换

Switch between CoreModel file

我几乎尝试了所有方法,@EnvironmentObject@StateObject、全局变量等

我有一个要跟踪的枚举和 return 我拥有的模型的名称:

enum MLNameModels: String, CaseIterable {
    case audi = "Audi"
    case bmw = "BMW"
    var mlModel: MLModel {
        switch self {
        case .audi:
            return try! Audi(configuration: MLModelConfiguration()).model
        case .bmw:
            return try! BMW(configuration: MLModelConfiguration()).model
        }
    }
}

我正在使用,并且在环顾四周时尝试使用实时检测,并且有一个 CameraView 来处理视图的所有设置,甚至是 Vision 框架。

struct CameraView : UIViewControllerRepresentable {
    func makeUIViewController(context: UIViewControllerRepresentableContext<CameraView>) -> UIViewController {
        let controller = CameraViewController()

        return controller
    }
    
    func updateUIViewController(_ uiViewController: CameraView.UIViewControllerType, context: UIViewControllerRepresentableContext<CameraView>) { }
}

我的 CameraViewController 中有一行设置对象检测:

 var objectDetector = Object_Detector(modelWithName: "audi")

我知道它说的是奥迪,因为我刚刚恢复到正常工作状态。

对象检测器内部 class 如下:

class Object_Detector {
    
    // MARK: Properties
    var requests = [VNRequest]()
    
    var boundingBox = CGRect()
    var objectType: ObservationTypeEnum?
    var firstObservation = VNRecognizedObjectObservation()
    
    init(modelWithName modelName: String) {
        self.setupModel(withFilename: modelName)
    }
    
    // MARK: Methods
    private func setupModel(withFilename modelName: String) {
        // Get model URL
        guard let modelURL = Bundle.main.url(forResource: modelName, withExtension: "mlmodelc") else {
            NSLog("Error: Unable to find model with name\(modelName), in \(Bundle.main.bundlePath)")
            
            return
        }
        
        // Create desired model
        guard let model = try? VNCoreMLModel(for: MLModel(contentsOf: modelURL)) else {
            NSLog("Error: Failed to create model->line:\(#line)")
            
            return
        }
        
        // Perform a request using ML Model
        let objectRecognizerRequests = VNCoreMLRequest(model: model) { (request, err) in
            if let error = err {
                NSLog("Error: \(error.localizedDescription)")
                
                return
            }
            
            // Get observation results
            guard let results = request.results as? [VNRecognizedObjectObservation] else {
                NSLog("Error: Failed to extract request results as [VNRecognizedObjectObservation]")
                
                return
            }
            
            // Get first observation result (one with the greatest confidence)
            guard let firstResult = results.first else { return }
            
            self.firstObservation = firstResult
            self.objectType = ObservationTypeEnum(fromRawValue: firstResult.labels.first!.identifier)
            self.boundingBox = firstResult.boundingBox
        }
        
        // Save requests
        self.requests = [objectRecognizerRequests]
    }
}

我已经测试过是否发生变化,甚至在 select 一个选项后调用了 setUpModel() 函数,这确实表明 modelName 参数由于某种原因已经更新了我的应用程序似乎立即转到最后一个型号,在本例中为 BMW。

我首先提示用户select一个制造商,然后之后,似乎没有任何效果。仅当我对值进行硬编码时才有效,如上所示。

澄清一下,我不想合并结果等。我只是想让应用程序知道用户使用了哪个制造商select,然后抓取相应的型号并继续识别。

编辑 - 用户提示

加载应用程序时,会出现 sheet 遍历所有模型案例,希望它对您有所帮助:

public var selectedModelName = String()
struct ContentView: View {
    @State private var isPresented: Bool = false
    var model = MLNameModels.allCases
    var body: some View {
        ZStack(alignment: .top) {
            if selectedModelName.isEmpty {
            CameraView().edgesIgnoringSafeArea(.all)
            }
            VStack(alignment: .leading){
                Spacer()
                HStack {
                Button {
                    isPresented = true
                    print("Tapped")
                } label: {
                    Image(systemName: "square.and.arrow.up")
                        .resizable()
                        .frame(width: 24, height: 30)
                        .padding()
                        .background(Color.secondary.clipShape(Circle()))
                        
                }
                    Spacer()
                }
            }.padding()
        }
        .slideOverCard(isPresented: $isPresented) {
            VStack {
                ForEach(model, id: \.self) { modelName in
                    Text(modelName.rawValue)
                        .onTapGesture {
               
                            selectedModelName = modelName.rawValue
                            isPresented = false
                        }
                }

            }
            .frame(width: UIScreen.main.bounds.width * 0.85)
        }
        .onAppear {
            if selectedModelName.isEmpty {
                isPresented = true
            }
        }
    }
}

因此,我声明:

var objectDetector = Object_Detector(modelWithName: selectedModelName)

但是好像只能加载另一个模型,根本无法切换模型。

这是我的 CameraViewController:

class CameraViewController : UIViewController, AVCaptureVideoDataOutputSampleBufferDelegate {
    var bufferSize: CGSize = .zero
    var rootLayer: CALayer! = nil
    
    private var detectionOverlay: CALayer! = nil
    
    private let session = AVCaptureSession()
    private let videoDataOutput = AVCaptureVideoDataOutput()
    private let videoOutputQueue = DispatchQueue(label: "Video_Output")
    
    private var previewLayer: AVCaptureVideoPreviewLayer! = nil
    
    // Initializing Model
    var objectDetector = Object_Detector(modelWithName: selectedModelName)
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        loadCamera()
        setupLayers()
        updateLayerGeometry()
        
        self.session.startRunning()
    }
    
    func loadCamera() {
        
        guard let videoDevice = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: .video, position: .back).devices.first else { return }
        
        guard let videoDeviceInput = try? AVCaptureDeviceInput(device: videoDevice) else {
            print("NO CAMERA DETECTED")
            return
        }
        
        // Begin session config
        self.session.beginConfiguration()
        self.session.sessionPreset = .hd1920x1080
        
        guard self.session.canAddInput(videoDeviceInput) else {
            NSLog("Could not add video device input to the session")
            self.session.commitConfiguration()
            return
        }
        // Add video input
        self.session.addInput(videoDeviceInput)
        
        if session.canAddOutput(self.videoDataOutput) {
            // Add a video data output
            self.session.addOutput(videoDataOutput)
            videoDataOutput.alwaysDiscardsLateVideoFrames = true
            videoDataOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: Int(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange)]
            videoDataOutput.setSampleBufferDelegate(self, queue: self.videoOutputQueue)
        } else {
            NSLog("Could not add video data output to the session")
            session.commitConfiguration()
            return
        }
        
        guard let captureConnection = videoDataOutput.connection(with: .video) else { return }
        
        // Always process the frames
        captureConnection.isEnabled = true
        
        do {
            try videoDevice.lockForConfiguration()
            
            let dimensions = CMVideoFormatDescriptionGetDimensions((videoDevice.activeFormat.formatDescription))
            // Read frame dimensions
            self.bufferSize.width = CGFloat(dimensions.width)
            self.bufferSize.height = CGFloat(dimensions.height)
            
            videoDevice.unlockForConfiguration()
        } catch {
            NSLog(error.localizedDescription)
        }
        
        // Save session config
        session.commitConfiguration()
        
        previewLayer = AVCaptureVideoPreviewLayer(session: session)
        previewLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
        rootLayer = view.layer
        previewLayer.frame = rootLayer.bounds
        rootLayer.addSublayer(previewLayer)
    }
    
    func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
        // Get buffer with image data
        guard let buffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
            NSLog("Error: Failed to get image buffer->\(#line)")
            
            return
        }
        
        // Get device orientation
        let deviceOrientation = self.exifOrientationFromDeviceOrientation()
        
        // Create an image request handler
        let requestHandler = VNImageRequestHandler(cvPixelBuffer: buffer, orientation: deviceOrientation, options: [:])
        
        do {
            try requestHandler.perform(self.objectDetector.requests)
            
            // Adding bounding box
            let boundingBox = self.objectDetector.boundingBox
            let objectType = self.objectDetector.objectType
        
            if !boundingBox.isEmpty && objectType != nil {
                DispatchQueue.main.async {
                    CATransaction.begin()
                    CATransaction.setValue(kCFBooleanTrue, forKey: kCATransactionDisableActions)
                    self.detectionOverlay.sublayers = nil
                    
                    let objectBounds = VNImageRectForNormalizedRect(boundingBox, Int(self.bufferSize.width), Int(self.bufferSize.height))
                    
                    let shapeLayer = self.createBoundingBox(withBounds: objectBounds)
                    
                    let textLayer = self.createTextBox(withBounds: objectBounds)
                    
                    shapeLayer.addSublayer(textLayer)
                    self.detectionOverlay.addSublayer(shapeLayer)
                    
                    self.updateLayerGeometry()
                    CATransaction.commit()
                }
            }
        } catch {
            NSLog("Error: Unable to perform requests")
        }
    }
    
    func createBoundingBox(withBounds bounds: CGRect) -> CALayer {
        let shapeLayer = CALayer()
        let borderColor = self.objectDetector.objectType?.getColor()
        
        shapeLayer.bounds = bounds
        shapeLayer.position = CGPoint(x: bounds.midX, y: bounds.midY)
        shapeLayer.name = "Found Object"
        shapeLayer.borderColor = borderColor
        shapeLayer.borderWidth = 2.5
        shapeLayer.cornerRadius = 5.0
        
        return shapeLayer
    }
    
    func createTextBox(withBounds bounds: CGRect) -> CATextLayer {
        let textLayer = CATextLayer()
        textLayer.name = "Object Label"
        let formattedString = NSMutableAttributedString(string: String(format: "\(self.objectDetector.firstObservation.labels[0].identifier)"))
        let backgroundColor = UIColor(cgColor: self.objectDetector.objectType!.getColor())
        let largeFont = UIFont(name: "AvenirNext-Medium", size: 40.0)!
        
        formattedString.addAttributes([NSAttributedString.Key.font: largeFont, NSAttributedString.Key.foregroundColor: UIColor.white, NSAttributedString.Key.backgroundColor: backgroundColor], range: NSRange(location: 0, length: self.objectDetector.firstObservation.labels[0].identifier.count))
        
        textLayer.string = formattedString
        textLayer.bounds = CGRect(x: 0, y: 0, width: bounds.size.height, height: 50)
        textLayer.position = CGPoint(x: bounds.minX - 25, y: bounds.maxY)
        textLayer.contentsScale = 2.0
        textLayer.cornerRadius = 5.0
        
        textLayer.setAffineTransform(CGAffineTransform(rotationAngle: CGFloat(.pi / 2.0)).scaledBy(x: 1.0, y: -1.0))
        
        return textLayer
    }
    
    func setupLayers() {
        detectionOverlay = CALayer()
        detectionOverlay.name = "DetectionOverlay"
        detectionOverlay.bounds = CGRect(x: 0.0, y: 0.0, width: bufferSize.width, height: bufferSize.height)
        detectionOverlay.position = CGPoint(x: rootLayer.bounds.midX, y: rootLayer.bounds.midY)
        rootLayer.addSublayer(detectionOverlay)
    }
    
    func updateLayerGeometry() {
        let bounds = rootLayer.bounds
        var scale: CGFloat
        
        let xScale: CGFloat = bounds.size.width / bufferSize.height
        let yScale: CGFloat = bounds.size.height / bufferSize.width
        
        scale = fmax(xScale, yScale)
        if scale.isInfinite {
            scale = 1.0
        }
        
        CATransaction.begin()
        CATransaction.setValue(kCFBooleanTrue, forKey: kCATransactionDisableActions)
        
        // Rotate the layer into screen orientation and scale and mirror
        detectionOverlay.setAffineTransform(CGAffineTransform(rotationAngle: CGFloat(.pi / 2.0)).scaledBy(x: scale, y: -scale))
        
        // Center the layer
        detectionOverlay.position = CGPoint(x: bounds.midX, y: bounds.midY)
        
        CATransaction.commit()
    }
    
    // Specify device orientation
    private func exifOrientationFromDeviceOrientation() -> CGImagePropertyOrientation {
        let curDeviceOrientation = UIDevice.current.orientation
        let exifOrientation: CGImagePropertyOrientation
        
        switch curDeviceOrientation {
        case UIDeviceOrientation.portraitUpsideDown:  // Device oriented vertically, home button on the top
            exifOrientation = .left
        case UIDeviceOrientation.landscapeLeft:       // Device oriented horizontally, home button on the right
            exifOrientation = .upMirrored
        case UIDeviceOrientation.landscapeRight:      // Device oriented horizontally, home button on the left
            exifOrientation = .down
        case UIDeviceOrientation.portrait:            // Device oriented vertically, home button on the bottom
            exifOrientation = .up
        default:
            exifOrientation = .up
        }
        return exifOrientation
    }
}

正如您在 question/comments 中提到的那样,您目前只是获取初始值并基于它设置模型,以后不会响应或监听任何更改。

我无法重新创建您的所有代码,因为缺少很多类型等,但以下内容应该让您了解如何通过视图和对象完成传播状态更改。查看内联评论。


enum MLNameModels: String, CaseIterable { //simplified just for the example
    case audi = "Audi"
    case bmw = "BMW"
}

struct ContentView : View {
    @State var model : String //@State, so that the view knows to update
    
    var body: some View {
        VStack {
            CameraView(modelName: model) //note that it gets passed to CameraView here
            VStack {
                ForEach(MLNameModels.allCases, id: \.self) { modelName in
                    Text(modelName.rawValue)
                        .onTapGesture {
                            model = modelName.rawValue
                        }
                }
            }
        }
    }
}

struct CameraView : UIViewControllerRepresentable {
    var modelName : String //gets updated when the parent state changes
    
    func makeUIViewController(context: Context) -> CameraViewController {
        return CameraViewController(modelName: modelName) //initial value
    }
    
    func updateUIViewController(_ uiViewController: CameraViewController, context: Context) {
        uiViewController.modelName = modelName //gets called when modelName changes or the parent re-renders
    }
}

class CameraViewController : UIViewController {
    var objectDetector : Object_Detector
    
    var modelName : String = "" {
        didSet {
            objectDetector.modelName = modelName //update modelName on the objectDetector when a new modelName is passed through
        }
    }
    
    init(modelName: String) {
        self.objectDetector = Object_Detector(modelWithName: modelName)
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

class Object_Detector {
    var modelName : String = "" {
        didSet {
            self.setupModel(withFilename: modelName) //call setupModel when there is a new modelName
        }
    }
    
    init(modelWithName modelName: String) {
        self.modelName = modelName
    }
    
    private func setupModel(withFilename modelName: String) {
        //do setup
    }
}