Zpět na domů

Face swap na Rustu: 60 FPS bez Pythonu | Technický rozbor

Článek popisuje implementaci aplikace pro výměnu obličejů v reálném čase na jazyce Rust. Podrobně se rozebírá architektura vícevláknového zpracování bez zámků, optimalizace paměti a integrace neuronových sítí prostřednictvím ONNX Runtime. Jsou uvedeny příklady kódu a technická řešení pro dosažení 60 snímků za sekundu.

Face swap na Rustu: 60 snímků za sekundu bez Pythonu a CUDA
Advertisement 728x90

# Výměna obličejů v reálném čase na Rustu: 60 FPS bez Pythonu a lock-free architektura

Stávající řešení pro výměnu obličejů v reálném čase jsou často svázána s Pythonem a jeho ekosystémem, což vede k zbytečným závislostem a problémům s výkonem. V tomto článku rozebereme implementaci aplikace v čistém Rustu, která dosahuje 60 snímků za sekundu bez použití Pythonu. Klíčové komponenty: ONNX Runtime pro inferenci, lock-free vlákna a plná optimalizace paměti.

Řetězec zpracování snímků

Každý snímek prochází čtyřmi neuronovými sítěmi v přísném pořadí. RetinaFace detekuje obličeje a extrahuje pět klíčových bodů. ArcFace generuje 512-rozměrný embedding zdrojového obličeje. InSwapper přijímá oblast cílového obličeje a embedding zdroje, vrací vyměněný obličej. GFPGAN volitelně zlepšuje kvalitu výsledku. Všechny modely pracují přes ONNX Runtime bez vlastních CUDA jader — pouze tenzory na vstupu a výstupu.

Peculiarita architektury: absence meziúložištních alokací. Buffery pixelů se alokují jednou při startu aplikace. Konverze RGBA do RGB, vyplňování tenzorů a affinní transformace se provádějí bez dalších alokací paměti. Jediná alokace na snímek — vytvoření Arc pro finální snapshot, což je nevyhnutelné při použití ArcSwap.

Google AdInline article slot

Smyčky zpracování jsou optimalizovány pro paralelizmus na úrovni instrukcí. Například konverze barevného prostoru zpracovává 4 pixely za iteraci:

for i in 0..chunks {
    let si = i * 16;
    let di = i * 12;
    rgb[di] = rgba[si];
    rgb[di + 1] = rgba[si + 1];
    rgb[di + 2] = rgba[si + 2];
    rgb[di + 3] = rgba[si + 4];
    rgb[di + 4] = rgba[si + 5];
    rgb[di + 5] = rgba[si + 6];
    // ... ještě 2 pixely
}

Architektura vícevláknovosti bez zámků

Systém využívá tři vlákna s nulovými zámky na horké cestě. Vlákno zachycení získává snímky přes nokhwa a publikuje je přes ArcSwap. Vlákno řetězce zpracovává snímky a předává výsledek přes druhý ArcSwap. UI vlákno renderuje aktuální buffer přes egui. Kritická optimalizace — zarovnání struktur podle cache line (64 bajtů) pro prevenci false sharing:

const CL: usize = 64;
#[repr(C, align(64))]
struct ProducerCell {
    frame: ArcSwap<FrameSnapshot>,
    generation: AtomicU64,
    _pad: [u8; CL - 16],
}
const _: () = {
    assert!(std::mem::size_of::<ProducerCell>() == CL);
};

Compile-time asserty zaručují dodržení velikosti. Atomové čítače generací nahrazují mutexy, vylučují overhead synchronizace. Odmítnutí async a kanálů zjednodušuje logiku a snižuje režii.

Google AdInline article slot

Makra pro minimalizaci duplikace kódu

Čtyři neuronové sítě vyžadují stejné operace: konverze RGBA do BGR planar formátu, normalizace. Místo kopírování kódu jsou použita makra. Makro fill_tensor_planar! je parametrizováno normalizátorem:

// Pro ArcFace: (v - 127.5) / 127.5
fill_tensor_planar!(
    self.tensor,
    self.aligned,
    PLANE,
    FS,
    FS,
    FS * 3,
    bgr_norm_arcface
);

// Pro InSwapper: v / 255.0
fill_tensor_planar!(
    self.tensor,
    self.aligned,
    PLANE,
    SS,
    SS,
    SS * 3,
    bgr_inv255
);

Analogně build_session! eliminuje trojnásobné vnořené volání ort_err! při inicializaci ONNX Runtime:

macro_rules! build_session {
    ($path:expr, $threads:expr) => {{
        let builder = ort_err!(Session::builder())?;
        let mut builder = ort_err!(builder.with_intra_threads($threads))?;
        ort_err!(builder.commit_from_file($path))?
    }};
}

Geometrické transformace a UI

Zarovnání obličejů je realizováno vlastní similarity transformací. Pět klíčových bodů RetinaFace se mapuje na šablonu ArcFace řešením systému 4x4 metodou Gauss-Seidel s částečným pivotováním. Inverzní transformace se aplikuje pro bilinear warp a paste-back s alfa-blendingem:

Google AdInline article slot
let edge_dist = ax.min(ay).min(fs - ax).min(fs - ay) - margin;
let alpha = (edge_dist * inv_blend).clamp(0.0, 1.0);

To zajišťuje plynulý přechod po okrajích bez artefaktů. UI je postaveno na egui v immediate mode, což je ideální pro live video. Vlastní frameless téma s macOS-style traffic lights a průhledným pozadím umožňuje táhnutí okna bez retained state. Rendering každého snímku bez zpoždění podporuje stabilních 60 FPS.

Co je důležité

  • Ownership model Rustu vyloučil data race a use-after-free i v složité vícevláknové architektuře.
  • ONNX Runtime přes ort se ukázal být připravený pro produkci: stabilní načítání modelů a inferenci bez CUDA závislostí.
  • egui pro real-time — immediate mode rendering bez retained state minimalizuje zpoždění při práci s videopotokem.
  • Nula alokací na horké cestě dosažena předem alokovanými buffery a optimalizací smyček.
  • Lock-free přes ArcSwap s cache zarovnáním eliminovalo overhead synchronizace mezi vlákny.

Řešení dokazuje, že Rust je efektivní pro high-performance úlohy počítačového vidění. Jediný binárník bez Python závislostí se spouští „z krabice“ — stačí stáhnout release z GitHubu. Pro další růst výkonu je perspektivní přechod na multisession ONNX Runtime, umožňující paralelní spouštění modelů místo sekvenčního zpracování.

— Editorial Team

Advertisement 728x90

Číst dál