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.kt
Kotlin 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 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
}