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.

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 dev.slab.Slab
import dev.slab.SlabConfig
import dev.slab.SlabFrame
import dev.slab.SlabInput
import dev.slab.SlabOutput
import dev.slab.SlabSession

fun compareFaces(config: SlabConfig, detectorPath: String, embedderPath: String, imagePath: String): Float? {
    val detector = SlabSession(config, detectorPath)
    val embedder = SlabSession(config, 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(Slab.RESIZE_LETTERBOX, 0).setColorBgr(true)
        .setNormalization(1f, floatArrayOf(127.5f, 127.5f, 127.5f), floatArrayOf(128f, 128f, 128f))
    embedder.setNormalization(1f, floatArrayOf(127.5f, 127.5f, 127.5f), floatArrayOf(127.5f, 127.5f, 127.5f))

    // One decoded frame feeds both models.
    val frame = SlabFrame.fromFile(imagePath)
    val faces = detector.run(SlabInput().setFrame(frame)).boxes(0.5f, 0.4f, 32)
    for (face in faces) {
        println("face [${face.left}, ${face.top}, ${face.width()}x${face.height()}] score ${face.score}, ${face.landmarks.size / 2} landmarks")
    }
    if (faces.size < 2) return null

    // A crop is a view into the original pixels, not a copy.
    fun embed(face: SlabOutput.Box): SlabOutput {
        val crop = frame.crop(face.left.toInt(), face.top.toInt(), face.width().toInt(), face.height().toInt())
        return embedder.run(SlabInput().setFrame(crop)).also { it.normalizeL2(0) }
    }
    val similarity = embed(faces[0]).cosineSimilarity(embed(faces[1]))

    detector.close()
    embedder.close()
    return similarity
}