Section 7/161 menit

7. Integrasi dengan Vision Framework

7. Integrasi dengan Vision Framework

Vision adalah high-level framework di atas Core ML, khusus untuk computer vision tasks. Vision menangani resize, color conversion, dan buffer management secara otomatis.

Image Classification dengan Vision

swift
import Vision
import UIKit

class ImageClassificationService {
    // Lazy load request — VNCoreMLRequest di-cache untuk efisiensi
    private lazy var classificationRequest: VNCoreMLRequest = {
        let config = MLModelConfiguration()
        config.computeUnits = .all
        
        // VNCoreMLModel wraps Core ML model untuk Vision
        let coreMLModel = try! ImageClassifier(configuration: config)
        let visionModel = try! VNCoreMLModel(for: coreMLModel.model)
        
        let request = VNCoreMLRequest(model: visionModel) { [weak self] request, error in
            self?.handleClassificationResults(request: request, error: error)
        }
        
        // .scaleFill, .scaleFit, .centerCrop
        request.imageCropAndScaleOption = .centerCrop
        return request
    }()
    
    var onResult: (([VNClassificationObservation]) -> Void)?
    
    func classify(image: UIImage) {
        guard let cgImage = image.cgImage else { return }
        
        let handler = VNImageRequestHandler(
            cgImage: cgImage,
            orientation: .up,
            options: [:]
        )
        
        // Jalankan di background thread
        DispatchQueue.global(qos: .userInitiated).async {
            do {
                try handler.perform([self.classificationRequest])
            } catch {
                print("Vision error: \(error)")
            }
        }
    }
    
    private func handleClassificationResults(request: VNRequest, error: Error?) {
        guard let results = request.results as? [VNClassificationObservation] else { return }
        
        // Hasil sudah diurutkan by confidence, tertinggi pertama
        let topResults = results.prefix(3)
        onResult?(Array(topResults))
    }
}

// Penggunaan
let service = ImageClassificationService()
service.onResult = { observations in
    for obs in observations {
        print("\(obs.identifier): \(obs.confidence * 100)%")
    }
}
service.classify(image: capturedImage)

Object Detection dengan Vision

swift
class ObjectDetectionService {
    private lazy var detectionRequest: VNCoreMLRequest = {
        let model = try! YOLOv3(configuration: MLModelConfiguration())
        let visionModel = try! VNCoreMLModel(for: model.model)
        
        let request = VNCoreMLRequest(model: visionModel)
        request.imageCropAndScaleOption = .scaleFill
        return request
    }()
    
    struct Detection {
        let label: String
        let confidence: Float
        let boundingBox: CGRect  // Normalized (0–1), origin di bottom-left
    }
    
    func detect(in image: UIImage) throws -> [Detection] {
        guard let cgImage = image.cgImage else { return [] }
        
        let handler = VNImageRequestHandler(cgImage: cgImage, options: [:])
        try handler.perform([detectionRequest])
        
        guard let results = detectionRequest.results as? [VNRecognizedObjectObservation] else {
            return []
        }
        
        return results.compactMap { observation in
            guard let label = observation.labels.first else { return nil }
            
            // Konversi bounding box: Vision pakai bottom-left origin, UIKit pakai top-left
            let flippedBox = CGRect(
                x: observation.boundingBox.minX,
                y: 1 - observation.boundingBox.maxY,
                width: observation.boundingBox.width,
                height: observation.boundingBox.height
            )
            
            return Detection(
                label: label.identifier,
                confidence: label.confidence,
                boundingBox: flippedBox
            )
        }
    }
    
    // Konversi normalized rect ke koordinat image
    func denormalize(boundingBox: CGRect, imageSize: CGSize) -> CGRect {
        return CGRect(
            x: boundingBox.minX * imageSize.width,
            y: boundingBox.minY * imageSize.height,
            width: boundingBox.width * imageSize.width,
            height: boundingBox.height * imageSize.height
        )
    }
}

Real-Time Detection di Camera (AVFoundation + Vision)

swift
import AVFoundation
import Vision

class CameraDetectionViewController: UIViewController {
    private let session = AVCaptureSession()
    private let videoOutput = AVCaptureVideoDataOutput()
    private let processingQueue = DispatchQueue(label: "com.app.vision", qos: .userInteractive)
    
    private lazy var detectionRequest: VNCoreMLRequest = {
        let model = try! MobileNetV2(configuration: MLModelConfiguration())
        let visionModel = try! VNCoreMLModel(for: model.model)
        
        let request = VNCoreMLRequest(model: visionModel) { [weak self] req, _ in
            guard let results = req.results as? [VNClassificationObservation],
                  let top = results.first else { return }
            
            DispatchQueue.main.async {
                self?.updateLabel(text: "\(top.identifier) (\(Int(top.confidence * 100))%)")
            }
        }
        request.imageCropAndScaleOption = .centerCrop
        return request
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupCamera()
    }
    
    private func setupCamera() {
        session.sessionPreset = .hd1280x720
        
        guard let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back),
              let input = try? AVCaptureDeviceInput(device: device) else { return }
        
        session.addInput(input)
        
        videoOutput.setSampleBufferDelegate(self, queue: processingQueue)
        videoOutput.alwaysDiscardsLateVideoFrames = true
        session.addOutput(videoOutput)
        
        DispatchQueue.global(qos: .userInitiated).async {
            self.session.startRunning()
        }
    }
    
    private func updateLabel(text: String) { /* update UI */ }
}

extension CameraDetectionViewController: AVCaptureVideoDataOutputSampleBufferDelegate {
    func captureOutput(_ output: AVCaptureOutput,
                      didOutput sampleBuffer: CMSampleBuffer,
                      from connection: AVCaptureConnection) {
        guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return }
        
        let handler = VNImageRequestHandler(cvPixelBuffer: pixelBuffer, options: [:])
        try? handler.perform([detectionRequest])
    }
}