홈으로 돌아가기

Rust에서 얼굴 스왑: Python 없이 60 FPS | 기술 분석

이 글은 Rust에서 실시간 얼굴 스왑 애플리케이션 구현을 설명합니다. lock-free 멀티스레딩 아키텍처, 메모리 최적화, ONNX Runtime을 통한 신경망 모델 통합을 자세히 다룹니다. 초당 60 프레임을 달성하기 위한 코드 예제와 기술 솔루션이 제공됩니다.

Rust에서 얼굴 스왑: Python과 CUDA 없이 초당 60 프레임
Advertisement 728x90

# Rust로 구현한 실시간 얼굴 교체: Python 없이 60 FPS와 Lock-Free 아키텍처

기존 실시간 얼굴 교체 솔루션은 종종 Python과 그 생태계에 얽매여 과도한 의존성과 성능 문제를 일으킵니다. 이 글에서는 Python을 전혀 사용하지 않고 순수 Rust로 60 FPS를 달성하는 앱 구현을 자세히 분석합니다. 주요 구성 요소: 추론을 위한 ONNX Runtime, lock-free 스레드, 완전한 메모리 최적화입니다.

프레임 처리 파이프라인

각 프레임은 엄격한 순서로 네 개의 신경망 모델을 거칩니다. RetinaFace는 얼굴을 감지하고 다섯 개의 키포인트를 추출합니다. ArcFace는 소스 얼굴의 512차원 임베딩을 생성합니다. InSwapper는 타겟 얼굴 영역과 소스 임베딩을 받아 교체된 얼굴을 반환합니다. GFPGAN은 선택적으로 결과 품질을 개선합니다. 모든 모델은 사용자 정의 CUDA 커널 없이 ONNX Runtime를 통해 실행되며, 입력과 출력에서 텐서만 사용합니다.

아키텍처 특징: 중간 할당 없음. 픽셀 버퍼는 앱 시작 시 한 번만 할당됩니다. RGBA에서 RGB 변환, 텐서 채우기, 아핀 변환은 추가 메모리 할당 없이 수행됩니다. 프레임당 유일한 할당은 ArcSwap을 사용할 때 불가피한 최종 스냅샷을 위한 Arc 생성입니다.

Google AdInline article slot

처리 루프는 명령어 수준 병렬성을 위해 최적화되었습니다. 예를 들어 색상 공간 변환은 반복당 4픽셀을 처리합니다:

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];
    // ... also 2 pikselya
}

Lock-Free 멀티스레딩 아키텍처

시스템은 핫 패스에서 락을 전혀 사용하지 않는 세 개의 스레드를 사용합니다. 캡처 스레드는 nokhwa를 통해 프레임을 수신하고 ArcSwap을 통해 게시합니다. 파이프라인 스레드는 프레임을 처리하고 두 번째 ArcSwap을 통해 결과를 전달합니다. UI 스레드는 egui를 통해 현재 버퍼를 렌더링합니다. 핵심 최적화: false sharing을 방지하기 위해 구조체를 캐시 라인(64바이트)에 맞춥니다:

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);
};

컴파일 타임 어설트가 크기 준수를 보장합니다. 뮤텍스를 대체하는 원자적 세대 카운터는 동기화 오버헤드를 제거합니다. async와 채널을 피함으로써 로직이 단순해지고 오버헤드가 줄어듭니다.

Google AdInline article slot

코드 중복 최소화를 위한 매크로

네 개의 신경망은 동일한 작업이 필요합니다: RGBA에서 BGR 플래너 형식 변환과 정규화. 코드를 복사하는 대신 매크로를 사용합니다. fill_tensor_planar! 매크로는 정규화기를 매개변수로 받습니다:

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

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

마찬가지로 build_session!은 ONNX Runtime 초기화 중 세 번 중첩된 ort_err! 호출을 제거합니다:

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))?
    }};
}

기하학적 변환과 UI

얼굴 정렬은 사용자 정의 유사성 변환을 사용해 구현됩니다. RetinaFace의 다섯 개 키포인트는 Gauss 방법(부분 피벗팅)으로 4x4 시스템을 풀어 ArcFace 템플릿에 맞춥니다. 역변환은 빌리니어 워핑과 알파 블렌딩을 위한 페이스트백에 적용됩니다:

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);

이로 인해 가장자리에서 부드러운 전환이 아티팩트 없이 이뤄집니다. UI는 라이브 비디오에 이상적인 즉시 모드의 egui로 구축됩니다. macOS 스타일 트래픽 라이트와 투명 배경을 가진 프레임리스 테마는 유지 상태 없이 창을 드래그할 수 있게 합니다. 지연 없이 각 프레임을 렌더링해 안정적인 60 FPS를 유지합니다.

주요 요점

  • Rust의 소유권 모델은 복잡한 멀티스레딩 아키텍처에서도 데이터 레이스와 use-after-free를 제거했습니다.
  • ort를 통한 ONNX Runtime은 CUDA 의존성 없이 안정적인 모델 로딩과 추론으로 프로덕션 준비가 입증되었습니다.
  • 실시간 egui—유지 상태 없는 즉시 모드 렌더링이 비디오 스트림에서 지연을 최소화합니다.
  • 핫 패스에서 할당 제로는 버퍼 사전 할당과 루프 최적화로 달성되었습니다.
  • ArcSwap을 통한 Lock-Free와 캐시 정렬이 스레드 간 동기화 오버헤드를 제거했습니다.

이 솔루션은 Rust가 고성능 컴퓨터 비전 작업에 효과적임을 증명합니다. Python 의존성 없는 단일 바이너리는 GitHub에서 릴리스를 다운로드하면 바로 실행됩니다. 추가 성능 확장을 위해 순차 처리 대신 병렬 모델 실행을 가능하게 하는 멀티 세션 ONNX Runtime으로 전환하는 것이 유망해 보입니다.

— Editorial Team

Advertisement 728x90

다음 읽기