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.cplus

C+ setup →
// Detect faces, crop each one without copying pixels, and compare the
// first two with a face embedding model.
//
// One decoded Frame feeds both models; each session prepares the image
// the way its own model expects.

import "stdlib/io" as io;
import "stdlib/env" as env;
import "stdlib/fs" as fs;
import "stdlib/result" as result;
import "stdlib/option" as option;
import "ffi/api" as slab;

fn embed(ref embedder: slab::Session, ref frame: slab::Frame, ref faces: slab::Output, i: i32, ref out: slab::Output) -> bool {
    // A crop is a view into the original pixels, not a copy.
    var face = slab::Frame::new();
    let x = faces.box_x1(i) as i32;
    let y = faces.box_y1(i) as i32;
    let w = (faces.box_x2(i) - faces.box_x1(i)) as i32;
    let h = (faces.box_y2(i) - faces.box_y1(i)) as i32;
    if !frame.crop_into(face, x, y, w, h) { return false; }

    var inp = slab::Input::new();
    inp.set_frame(face);
    if !embedder.run(inp, out) { return false; }
    return out.normalize_l2(0);
}

fn main() -> i32 {
    var cfg = slab::Config::new();
    cfg.set_models_dir("models");
    cfg.set_cache_dir("/tmp/slab_cache");
    guard let option::Option::Some(path) = env::var("SLAB_RUNTIME_LICENSE_FILE") else { return 1; };
    guard let result::Result::Ok(token) = fs::read_to_string(path.view()) else { return 1; };
    if !cfg.set_runtime_license(token.view()) {
        io::eprintln("License rejected: ${cfg.runtime_license_error_message()}");
        return 1;
    }

    var detector = slab::Session::new(cfg);
    var embedder = slab::Session::new(cfg);
    if !detector.open("face_detector.slab") || !embedder.open("face_embedder.slab") {
        io::eprintln("Cannot open the models: ${detector.error_message()} ${embedder.error_message()}");
        return 1;
    }

    // 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.set_resize_mode(slab::RESIZE_LETTERBOX);
    detector.set_letterbox_pad(0);
    detector.set_color_bgr(true);
    detector.set_normalization(1.0, 127.5, 127.5, 127.5, 128.0, 128.0, 128.0);
    embedder.set_normalization(1.0, 127.5, 127.5, 127.5, 127.5, 127.5, 127.5);

    var frame = slab::Frame::new();
    if !frame.load_file("images/group.jpg") {
        io::eprintln("Cannot load the image: ${frame.error_message()}");
        return 1;
    }

    var inp = slab::Input::new();
    var faces = slab::Output::new();
    inp.set_frame(frame);
    if !detector.run(inp, faces) {
        io::eprintln("Detection failed: ${detector.error_message()}");
        return 1;
    }

    // Boxes and landmarks come back in the original image's coordinates.
    let n = faces.decode_boxes(0.5f32, 0.4f32, 32);
    io::println("${n} faces");
    for i in 0..n {
        io::println("  [${faces.box_x1(i)}, ${faces.box_y1(i)}] - [${faces.box_x2(i)}, ${faces.box_y2(i)}] score ${faces.box_score(i)}, ${faces.box_landmark_count(i)} landmarks");
    }
    if n < 2 { return 0; }

    var a = slab::Output::new();
    var b = slab::Output::new();
    if embed(embedder, frame, faces, 0, a) && embed(embedder, frame, faces, 1, b) {
        io::println("Similarity of the first two faces: ${a.cosine_similarity(0, b, 0)}");
    }
    return 0;
}