E535 Optimize macos frame rate 14631050807658318954 by itsgiddd · Pull Request #1617 · hacksider/Deep-Live-Cam · GitHub
[go: up one dir, main page]

Skip to content

Optimize macos frame rate 14631050807658318954#1617

Open
itsgiddd wants to merge 4 commits intohacksider:mainfrom
itsgiddd:optimize-macos-frame-rate-14631050807658318954
Open

Optimize macos frame rate 14631050807658318954#1617
itsgiddd wants to merge 4 commits intohacksider:mainfrom
itsgiddd:optimize-macos-frame-rate-14631050807658318954

Conversation

@itsgiddd
Copy link
@itsgiddd itsgiddd commented Jan 5, 2026

Summary by Sourcery

Improve live face swap performance and macOS support, particularly on Apple Silicon, while keeping visual behavior consistent.

New Features:

  • Add macOS-specific install and launch scripts optimized for Apple Silicon and CoreML execution.

Bug Fixes:

  • Avoid unnecessary blending work when face swap opacity is 100% by using the swapped frame directly.

Enhancements:

  • Always resize webcam preview frames to the preview window and skip redundant ImageOps resizing for more consistent performance.
  • Use an adaptive face detection interval on Apple Silicon to reduce processing overhead in live mode.
  • Switch live stream face detection to an optimized detection path for better real-time performance.
  • Reduce face detector input size on macOS to improve detection speed.

Build:

  • Add a macOS installation script that sets up a Python 3.10 virtual environment and ensures compatible dependencies for Apple Silicon.

Deployment:

  • Add a macOS run script that launches the app in the virtual environment with CoreML as the execution provider.

- Modify `modules/face_analyser.py` to use a smaller detection size `(320, 320)` on macOS for faster processing.
- Modify `modules/processors/frame/face_swapper.py` to:
    - Increase `DETECTION_INTERVAL` to 0.08s for Apple Silicon to reduce detection frequency.
    - Skip opacity blending if opacity is 1.0.
    - Use `get_faces_optimized` in `process_frame_v2` for live streams to enable caching.
    - Ensure `DETECTION_INTERVAL` is conditional based on platform.
- modules/face_analyser.py:
    - Reduce face detection size to (320, 320) on macOS to improve performance.

- modules/processors/frame/face_swapper.py:
    - Increase `DETECTION_INTERVAL` to 0.08s for Apple Silicon.
    - Implement adaptive detection rate using `get_faces_optimized`.
    - Skip opacity blending if opacity is 1.0.

- modules/ui.py:
    - Remove redundant image resizing logic and expensive `ImageOps.contain` call in the live preview loop.
    - Ensure consistent frame resizing before processing.
- Add `install-mac.sh`: Automated setup script for macOS (Apple Silicon).
  - Installs Python 3.10 via Homebrew.
  - Creates virtual environment.
  - Installs dependencies from `requirements.txt`.
  - Ensures proper `onnxruntime-silicon` configuration.

- Add `run-mac.sh`: Optimized launcher for macOS.
  - Activates venv.
  - Runs `run.py` with `--execution-provider coreml` and optimal memory settings.

- Optimizations (Confirmed):
  - `modules/face_analyser.py`: Reduced detection size (320x320) for macOS.
  - `modules/processors/frame/face_swapper.py`: Adaptive face detection rate (0.08s interval) for live streams.
  - `modules/ui.py`: Removed redundant image resampling in preview loop.
@sourcery-ai
Copy link
Contributor
sourcery-ai bot commented Jan 5, 2026

Reviewer's Guide

Optimizes live webcam processing and face swapping performance on macOS (especially Apple Silicon) by simplifying preview resizing, reducing redundant image scaling, adapting detection frequency and detector resolution for macOS, switching to an optimized face detection path, and adding dedicated macOS install/run scripts.

Sequence diagram for optimized live webcam frame processing on macOS

sequenceDiagram
    actor User
    participant Camera
    participant UI as ui_create_webcam_preview
    participant Swapper as frame_face_swapper_process_frame_v2
    participant Detector as get_faces_optimized
    participant Analyser as face_analyser_get_face_analyser

    User->>Camera: Enable_live_webcam
    Camera-->>UI: temp_frame

    UI->>UI: Optional_mirror_flip
    UI->>UI: fit_image_to_size(temp_frame, PREVIEW_width, PREVIEW_height)
    UI->>Swapper: process_frame_v2(temp_frame)

    activate Swapper
    Swapper->>Detector: get_faces_optimized(processed_frame)
    activate Detector
    Detector->>Analyser: get_face_analyser()
    activate Analyser
    Analyser-->>Detector: FACE_ANALYSER_with_det_size(320x320_on_macos)
    deactivate Analyser
    Detector-->>Swapper: detected_faces
    deactivate Detector

    Swapper->>Swapper: swap_face_for_each_detected_face
    Swapper->>Swapper: Apply_opacity_blend_if_opacity_lt_1_0
    Swapper-->>UI: final_swapped_frame
    deactivate Swapper

    UI->>UI: cvtColor_BGR_to_RGB
    UI->>UI: Create_PIL_Image
    UI->>UI: Create_CTkImage_without_additional_contain
    UI->>UI: preview_label.configure(image)
    UI->>UI: ROOT.update()
    UI-->>User: Updated_live_preview_frame
Loading

Class diagram for updated frame processing and face analysis modules

classDiagram
    class ui_module {
        +create_webcam_preview(camera_index int)
        -PREVIEW
        -ROOT
    }

    class frame_face_swapper_module {
        +FRAME_CACHE
        +FACE_DETECTION_CACHE
        +LAST_DETECTION_TIME
        +DETECTION_INTERVAL
        +FRAME_SKIP_COUNTER
        +ADAPTIVE_QUALITY
        +process_frame_v2(temp_frame Frame, temp_frame_path str) Frame
        +swap_face(source_face Face, target_face Face, temp_frame Frame) Frame
    }

    class face_analyser_module {
        +FACE_ANALYSER
        +get_face_analyser() Any
    }

    class Face {
    }

    class Frame {
    }

    ui_module --> frame_face_swapper_module : uses_process_frame_v2
    frame_face_swapper_module --> face_analyser_module : uses_get_face_analyser
    frame_face_swapper_module --> Face : operates_on
    frame_face_swapper_module --> Frame : processes

    class DetectionIntervalBehavior {
        +IS_APPLE_SILICON
        +DETECTION_INTERVAL_apple_silicon float 0_08
        +DETECTION_INTERVAL_default float 0_033
    }

    frame_face_swapper_module --> DetectionIntervalBehavior : adaptive_detection_interval

    class MacOSFaceDetectorConfig {
        +platform_system() str
        +det_size_default tuple 640_640
        +det_size_macos tuple 320_320
    }

    face_analyser_module --> MacOSFaceDetectorConfig : adaptive_det_size

    class OpacityBlendLogic {
        +opacity float
        +blend_if_opacity_lt_1_0(original_frame Frame, swapped_frame Frame) Frame
        +use_swapped_frame_if_opacity_eq_1_0(swapped_frame Frame) Frame
    }

    frame_face_swapper_module --> OpacityBlendLogic : controls_final_swapped_frame
Loading

Flow diagram for macOS-specific face detection optimizations

flowchart LR
    A["Incoming_frame_from_webcam"] --> B["ui_create_webcam_preview<br/>Mirror_optional"]
    B --> C["fit_image_to_size_to_preview_dimensions"]
    C --> D["frame_face_swapper_process_frame_v2"]

    subgraph Detection_Interval
        D --> E{IS_APPLE_SILICON}
        E -->|Yes| F["DETECTION_INTERVAL_0_08_seconds"]
        E -->|No| G["DETECTION_INTERVAL_0_033_seconds"]
        F --> H["Adaptive_face_detection_timing"]
        G --> H
    end

    D --> I["get_faces_optimized"]
    I --> J["fac
1000
e_analyser_get_face_analyser"]
    J --> K{platform_system_is_darwin}
    K -->|Yes| L["det_size_320x320"]
    K -->|No| M["det_size_640x640"]
    L --> N["FaceAnalysis_prepare_with_small_input_on_macos"]
    M --> N

    N --> O["Detected_faces"]
    O --> P["swap_face_with_optional_opacity_blend"]
    P --> Q["Final_swapped_frame_returned_to_ui"]
Loading

File-Level Changes

Change Details Files
Standardize webcam preview resizing to a single pass and remove redundant post-processing
  • Always resize frames to the preview window size regardless of resizable flag
  • Remove the conditional branch that duplicated the same fit_image_to_size call
  • Skip ImageOps.contain since frames are already resized before conversion to CTkImage
modules/ui.py
Tune face detection pipeline for macOS / Apple Silicon performance
  • Use a larger detection interval on Apple Silicon to reduce face detection frequency while keeping 30 FPS for other platforms
  • Reduce insightface detector input size from 640x640 to 320x320 on Darwin platforms to speed up analysis
  • Route live/webcam face detection through get_faces_optimized instead of get_many_faces for more efficient detection
modules/processors/frame/face_swapper.py
modules/face_analyser.py
Optimize blending in face swapping when full opacity is used
  • Bypass cv2.addWeighted blending when opacity is 1.0 and directly use the swapped frame
  • Retain explicit uint8 casting after blending as a safety measure
modules/processors/frame/face_swapper.py
Add Apple Silicon–oriented macOS install and run scripts
  • Provide install-mac.sh to set up Python 3.10, create/activate a venv, install requirements, and enforce onnxruntime-silicon usage
  • Provide run-mac.sh to activate the venv, set environment variables, and launch run.py with the CoreML execution provider on macOS
install-mac.sh
run-mac.sh

Possibly linked issues

  • #: PR adds Apple Silicon–specific detection interval, smaller det_size, and UI resizing optimizations directly targeting macOS FPS issues.
  • #[Performance] Low FPS (1.5) in Live Camera Mode on macOS: PR introduces macOS-specific frame, detection, and face-analysis optimizations directly addressing the low FPS in live camera mode.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourc FBC0 ery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Contributor
@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues, and left some high level feedback:

  • In face_analyser.py, you now use platform.system() to set det_size, but there’s no import platform in the diff; double‑check that the module imports platform at the top to avoid a NameError.
  • The adaptive DETECTION_INTERVAL based only on IS_APPLE_SILICON is now hardcoded; consider making this value configurable (e.g., via a setting or environment variable) so users can tune detection frequency for different performance/quality trade-offs.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `face_analyser.py`, you now use `platform.system()` to set `det_size`, but there’s no `import platform` in the diff; double‑check that the module imports `platform` at the top to avoid a NameError.
- The adaptive `DETECTION_INTERVAL` based only on `IS_APPLE_SILICON` is now hardcoded; consider making this value configurable (e.g., via a setting or environment variable) so users can tune detection frequency for different performance/quality trade-offs.

## Individual Comments

### Comment 1
<location> `modules/ui.py:1022-1025` </location>
<code_context>
-        image = ImageOps.contain(
-            image, (temp_frame.shape[1], temp_frame.shape[0]), Image.LANCZOS
-        )
+        # Skip ImageOps.contain as frame is already resized
         image = ctk.CTkImage(image, size=image.size)
         preview_label.configure(image=image)
         ROOT.update()
</code_context>

<issue_to_address>
**suggestion (performance):** Avoid redundant size argument when constructing CTkImage

Because `temp_frame` is already resized to the preview dimensions and `image` comes directly from that frame, `image.size` is already the desired size. Passing `size=image.size` to `CTkImage` is redundant and still runs the resize path; you can omit `size` and let `CTkImage` use the native size to avoid the extra no-op scaling.

```suggestion
        image = Image.fromarray(image)
        # temp_frame is already resized to preview dimensions; use native image size
        image = ctk.CTkImage(image)
        preview_label.configure(image=image)
```
</issue_to_address>

### Comment 2
<location> `install-mac.sh:16-19` </location>
<code_context>
+fi
+
+# Check for Python 3.10
+if ! command -v python3.10 &> /dev/null; then
+    echo "⚠️ Python 3.10 not found. Installing via Homebrew..."
+    brew install python@3.10
+    brew install python-tk@3.10
+else
+    echo "✅ Python 3.10 found."
</code_context>

<issue_to_address>
**issue:** Be cautious about relying on a specific Homebrew formula name for python-tk

Hard-coding `python-tk@3.10` assumes this exact formula exists and stays stable. On some systems Tk may be bundled with `python@3.10` or exposed under a different name, causing `brew install` to fail and the script to exit due to `set -e`. Consider making this install best-effort (e.g., ignore or log failures) or first detect whether the formula exists before installing it.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines 1022 to 1025
image = Image.fromarray(image)
image = ImageOps.contain(
image, (temp_frame.shape[1], temp_frame.shape[0]), Image.LANCZOS
)
# Skip ImageOps.contain as frame is already resized
image = ctk.CTkImage(image, size=image.size)
preview_label.configure(image=image)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (performance): Avoid redundant size argument when constructing CTkImage

Because temp_frame is already resized to the preview dimensions and image comes directly from that frame, image.size is already the desired size. Passing size=image.size to CTkImage is redundant and still runs the resize path; you can omit size and let CTkImage use the native size to avoid the extra no-op scaling.

Suggested change
image = Image.fromarray(image)
image = ImageOps.contain(
image, (temp_frame.shape[1], temp_frame.shape[0]), Image.LANCZOS
)
# Skip ImageOps.contain as frame is already resized
image = ctk.CTkImage(image, size=image.size)
preview_label.configure(image=image)
image = Image.fromarray(image)
# temp_frame is already resized to preview dimensions; use native image size
image = ctk.CTkImage(image)
preview_label.configure(image=image)

Comment on lines +16 to +19
if ! command -v python3.10 &> /dev/null; then
echo "⚠️ Python 3.10 not found. Installing via Homebrew..."
brew install python@3.10
brew install python-tk@3.10
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: Be cautious about relying on a specific Homebrew formula name for python-tk

Hard-coding python-tk@3.10 assumes this exact formula exists and stays stable. On some systems Tk may be bundled with python@3.10 or exposed under a different name, causing brew install to fail and the script to exit due to set -e. Consider making this install best-effort (e.g., ignore or log failures) or first detect whether the formula exists before installing it.

< FBC0 /include-fragment>
- modules/face_analyser.py: Add missing `import platform`.
- modules/processors/frame/face_swapper.py:
    - Make `DETECTION_INTERVAL` configurable via `DLC_DETECTION_INTERVAL` environment variable.
    - Default to 0.08s for Apple Silicon and 0.033s for others if not set.
    - (Preserves previous optimizations including `get_faces_optimized` and opacity check).
laurigates added a commit to laurigates/Deep-Live-Cam that referenced this pull request Feb 22, 2026
Switch _build_pairs_live() from get_many_faces() to get_faces_optimized()
so map-faces live mode benefits from time-based detection caching on
Apple Silicon, matching the simple-mode live path.

Inspired by hacksider#1617.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@laurigates
Copy link
Contributor

Relevant finding from testing the det_size optimization on Apple Silicon:

InsightFace FaceAnalysis.prepare() is a singleton — calling prepare(det_size=(160, 160)) only works on the first call. Subsequent calls with a different det_size are silently ignored with the log message:

warning: det_size is already set in detection model, ignore

This means if the face analyzer is first initialized with (320, 320) for video processing, switching to (160, 160) for live mode by calling prepare() again won't actually change the detection size. To change det_size at runtime, the entire FaceAnalysis instance must be recreated.

Benchmark Data (M4 Pro, CoreML)

det_size Detection Time FPS Impact Accuracy
(640, 640) ~35ms ~25 FPS Highest
(320, 320) ~12ms ~45 FPS Good for most cases
(160, 160) ~4ms ~55 FPS Acceptable for live mode

The 160x160 → 320x320 switch between live mode and video processing gives the best trade-off: live mode gets ~4x fewer detection FLOPs for smooth preview, while video/image processing retains full accuracy.

Just flagging the singleton behavior since it's easy to miss and can make the optimization silently ineffective.

laurigates added a commit to laurigates/Deep-Live-Cam that referenced this pull request Feb 22, 2026
Switch process_frame_v2() live path from get_many_faces() to
get_faces_optimized() so map-faces live mode benefits from time-based
detection caching, matching the simple-mode live path.

get_faces_optimized() returns cached results when called within the
detection interval (~33ms), avoiding redundant face detection on
consecutive frames. This was already used in the simple (non-map)
live mode but the map-faces live branch was still calling
get_many_faces() directly.

Inspired by hacksider#1617.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
laurigates added a commit to laurigates/Deep-Live-Cam that referenced this pull request Feb 23, 2026
Switch _build_pairs_live() from get_many_faces() to get_faces_optimized()
so map-faces live mode benefits from time-based detection caching on
Apple Silicon, matching the simple-mode live path.

Inspired by hacksider#1617.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

0