Skip to content
imaged

Multi-model image pipelines

Share a decoded frame, crop regions, and compare embeddings.

Use your own .slab model and a runtime license. Model files carry their input sizes, preprocessing, labels, and backend preferences.

Faces.swift

Swift setup →

Run inference off the main thread.

// Detect faces, crop each one without copying pixels, and compare the
// first two with a face embedding model.

import Foundation
import SlabSwift

func compareFaces(detectorPath: String, embedderPath: String, imagePath: String, license: String) throws -> Float? {
    let config = try SlabConfig(license: license)
    let detector = try SlabSession(config: config, path: detectorPath)
    let embedder = try SlabSession(config: config, path: embedderPath)

    // Preprocessing normally comes from the model file. Set it in code only
    // when a model file does not carry it, or to override it.
    detector.setResize(.letterbox, letterboxPad: 0)
    detector.setColorBGR(true)
    detector.setNormalization(scale: 1, mean: (127.5, 127.5, 127.5), std: (128, 128, 128))
    embedder.setNormalization(scale: 1, mean: (127.5, 127.5, 127.5), std: (127.5, 127.5, 127.5))

    // One decoded frame feeds both models.
    let frame = try SlabFrame(contentsOf: imagePath)
    let faces = try detector.run(SlabInput(frame: frame)).boxes(scoreThreshold: 0.5, iouThreshold: 0.4, maxBoxes: 32)
    for face in faces {
        print("face at \(face.rect), score \(face.score), \(face.landmarks.count) landmarks")
    }
    guard faces.count >= 2 else { return nil }

    // A crop is a view into the original pixels, not a copy.
    func embed(_ face: SlabBox) throws -> SlabOutput {
        let output = try embedder.run(SlabInput(frame: try frame.crop(face.rect)))
        output.normalizeL2()
        return output
    }
    return try embed(faces[0]).cosineSimilarity(with: try embed(faces[1]))
}