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])
}
}