Section 6/161 menit

6. Menjalankan Inferensi di Device

6. Menjalankan Inferensi di Device

Synchronous Prediction (Paling Sederhana)

swift
import CoreML
import UIKit

// Generated class dari Xcode setelah drag .mlmodel
// Xcode generate: ImageClassifier.swift, ImageClassifierInput.swift, ImageClassifierOutput.swift

func classifyImage(_ image: UIImage) throws -> String {
    // 1. Pastikan model di-cache — jangan init per-request
    let model = try ImageClassifier(configuration: MLModelConfiguration())
    
    // 2. Siapkan input — ImageClassifier butuh CVPixelBuffer
    guard let cgImage = image.cgImage else {
        throw ClassificationError.invalidImage
    }
    
    let input = try ImageClassifierInput(imageWith: cgImage)
    
    // 3. Jalankan prediksi
    let output = try model.prediction(input: input)
    
    // 4. Baca hasil
    return output.classLabel
}

// Contoh dengan probabilitas
func classifyImageWithProbs(_ image: UIImage) throws -> [(label: String, confidence: Double)] {
    let model = try ImageClassifier(configuration: MLModelConfiguration())
    guard let cgImage = image.cgImage else {
        throw ClassificationError.invalidImage
    }
    
    let input = try ImageClassifierInput(imageWith: cgImage)
    let output = try model.prediction(input: input)
    
    // classLabelProbs: [String: Double]
    let sorted = output.classLabelProbs
        .sorted { $0.value > $1.value }
        .prefix(5)
        .map { (label: $0.key, confidence: $0.value) }
    
    return Array(sorted)
}

Batch Prediction untuk Multiple Inputs

swift
func classifyBatch(images: [UIImage]) throws -> [String] {
    let model = try ImageClassifier(configuration: MLModelConfiguration())
    
    // Buat array input
    let inputs: [ImageClassifierInput] = try images.compactMap { image in
        guard let cgImage = image.cgImage else { return nil }
        return try ImageClassifierInput(imageWith: cgImage)
    }
    
    // Batch prediction — lebih efisien daripada loop satu-satu
    let batchProvider = MLArrayBatchProvider(array: inputs)
    let outputProvider = try model.predictions(fromBatch: batchProvider)
    
    return (0..<outputProvider.count).map { i in
        let output = outputProvider.features(at: i)
        return output.featureValue(for: "classLabel")!.stringValue
    }
}

Manual MLFeatureProvider (Untuk Model Tanpa Code Generation)

swift
// Ketika model tidak di-generate (misal: model di-download runtime)
func predictWithFeatureProvider(modelURL: URL, inputData: MLMultiArray) throws -> String {
    let model = try MLModel(contentsOf: modelURL)
    
    // Buat input manual
    let inputFeatures = try MLDictionaryFeatureProvider(dictionary: [
        "input": MLFeatureValue(multiArray: inputData)
    ])
    
    let outputFeatures = try model.prediction(from: inputFeatures)
    
    guard let labelValue = outputFeatures.featureValue(for: "classLabel") else {
        throw PredictionError.missingOutput
    }
    
    return labelValue.stringValue
}

Membuat CVPixelBuffer dari UIImage Secara Manual

swift
// Beberapa model membutuhkan CVPixelBuffer, bukan CGImage
extension UIImage {
    func toCVPixelBuffer(size: CGSize) -> CVPixelBuffer? {
        let attributes: [CFString: Any] = [
            kCVPixelBufferCGImageCompatibilityKey: true,
            kCVPixelBufferCGBitmapContextCompatibilityKey: true
        ]
        
        var pixelBuffer: CVPixelBuffer?
        let status = CVPixelBufferCreate(
            kCFAllocatorDefault,
            Int(size.width),
            Int(size.height),
            kCVPixelFormatType_32ARGB,
            attributes as CFDictionary,
            &pixelBuffer
        )
        
        guard status == kCVReturnSuccess, let buffer = pixelBuffer else { return nil }
        
        CVPixelBufferLockBaseAddress(buffer, [])
        defer { CVPixelBufferUnlockBaseAddress(buffer, []) }
        
        let context = CGContext(
            data: CVPixelBufferGetBaseAddress(buffer),
            width: Int(size.width),
            height: Int(size.height),
            bitsPerComponent: 8,
            bytesPerRow: CVPixelBufferGetBytesPerRow(buffer),
            space: CGColorSpaceCreateDeviceRGB(),
            bitmapInfo: CGImageAlphaInfo.noneSkipFirst.rawValue
        )
        
        context?.draw(cgImage!, in: CGRect(origin: .zero, size: size))
        return buffer
    }
}