feat: capture resolution + detection size dropdowns by 5uck1ess · Pull Request #1833 · hacksider/Deep-Live-Cam · GitHub
Skip to content

feat: capture resolution + detection size dropdowns#1833

Open
5uck1ess wants to merge 4 commits into
hacksider:mainfrom
5uck1ess:pr/resolution-and-det-size
Open

feat: capture resolution + detection size dropdowns#1833
5uck1ess wants to merge 4 commits into
hacksider:mainfrom
5uck1ess:pr/resolution-and-det-size

Conversation

@5uck1ess

@5uck1ess 5uck1ess commented May 19, 2026

Copy link
Copy Markdown

Summary

Adds two dropdowns to the Camera card on the main window:

  • Resolution — requested webcam capture size (640×480 / 960×540 / 1280×720 / 1920×1080). Applied on next Live start.
  • Det size — face detection input size (160 / 320 / 640). Triggers face analyser re-init so the change takes effect immediately.

Also exposes --det-size as a CLI flag for headless runs.

Why

Resolution: Previously VideoCapturer.start() was hardcoded to PREVIEW_DEFAULT_WIDTH/HEIGHT which mixes "the preview window's pixel size" with "the camera's capture resolution" — they're separate concerns. With this PR they're decoupled and the capture side becomes user-configurable.

Why these choices specifically: 640×480 is native on virtually every USB 2.0 webcam and the safest 30fps mode (USB 2.0 isochronous bandwidth caps ~30fps on common YUYV at this size). Higher tiers are useful for users with capture cards or USB 3.0 webcams; the tooltip explains that 720p+ typically drops to ~10fps over USB 2.0 due to bandwidth, so users know what they're trading.

Det size: insightface defaults to 640×640 detection which is plenty accurate but expensive per frame. At 320 or 160 the detector runs much faster — a meaningful speedup when the face is large in frame (live webcam usage), with negligible accuracy loss for that distance regime. Currently the only way to change det_size is by editing the source.

What changed

  • modules/globals.py
    • New det_size: int = 640 and capture_resolution: tuple = (640, 480) globals with documentation.
  • modules/face_analyser.py
    • The hardcoded DET_SIZE = (640, 640) constant is replaced by _current_det_size() that reads modules.globals.det_size per call.
    • New reset_face_analyser() clears the cached FACE_ANALYSER so the next call re-prepares insightface with the new size.
  • modules/core.py — new --det-size argparse flag with choices=[160, 320, 640] for headless runs.
  • modules/ui.py
    • Camera card now uses a QGridLayout (was a single row) with the two new dropdowns on rows 1 and 2 below the camera selector + Live button.
    • _on_resolution_change writes the selected (w, h) tuple to modules.globals.capture_resolution.
    • _on_det_size_change writes the selected int and calls reset_face_analyser() so the new size kicks in on the next analyser call.
    • WebcamPreviewWindow reads modules.globals.capture_resolution instead of PREVIEW_DEFAULT_WIDTH/HEIGHT (the constants still drive preview-window sizing, separate concern).

Test plan

  • Defaults: launch, Camera card shows Resolution=640x480 and Det size=640 x 640. Live mode behaves as before
  • Switch Resolution to 1280 x 720, start Live → console shows [VideoCapturer] 1280x720 or the camera's nearest supported size
  • Switch Det size from 640 → 320 while no Live is running → next face-analysis call (e.g. starting Live with map_faces) uses the new size
  • Switch Det size mid-Live → analyser re-inits on the next frame; no crash
  • python run.py --det-size 320 headless honors the flag

No new dependencies.

Summary by Sourcery

Add configurable camera capture resolution and face detection size for both GUI and headless live mode.

New Features:

  • Introduce Resolution dropdown in the Camera card to choose webcam capture size for live mode.
  • Introduce Det size dropdown in the Camera card to configure face detection input resolution and reinitialize the analyser on change.
  • Expose a --det-size CLI flag to control face detection input size in headless runs.

Enhancements:

  • Make the face analyser read a configurable global det_size and allow forcing a re-init when it changes.
  • Have the webcam preview use the global capture_resolution instead of hardcoded preview dimensions.

@sourcery-ai

sourcery-ai Bot commented May 19, 2026

Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • The new update_status messages and some tooltips added in _build_camera_card are hardcoded English strings; consider wrapping them in _() like the other UI text so they participate in localization.
  • In _on_det_size_change, importing reset_face_analyser inside the slot will run on every change; it would be cleaner and slightly more efficient to move this import to the module level unless you are explicitly avoiding a circular import.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new `update_status` messages and some tooltips added in `_build_camera_card` are hardcoded English strings; consider wrapping them in `_()` like the other UI text so they participate in localization.
- In `_on_det_size_change`, importing `reset_face_analyser` inside the slot will run on every change; it would be cleaner and slightly more efficient to move this import to the module level unless you are explicitly avoiding a circular import.

## Individual Comments

### Comment 1
<location path="modules/ui.py" line_range="759" />
<code_context>
+        self._det_size_options = [160, 320, 640]
+        for v in self._det_size_options:
+            self.cb_det_size.addItem(f"{v} x {v}")
+        cur_det = int(getattr(modules.globals, 'det_size', 640))
+        idx = self._det_size_options.index(cur_det) if cur_det in self._det_size_options else 2
+        self.cb_det_size.setCurrentIndex(idx)
</code_context>
<issue_to_address>
**suggestion:** Centralize the default det_size handling to avoid repeating literal defaults in multiple places.

The default `640` for `det_size` now lives here, in `face_analyser._current_det_size`, and in the CLI defaults. If one changes and the others don’t, the CLI, backend, and UI could diverge. Consider defining a single default (e.g., a constant in `modules.globals` or `face_analyser`) and referencing it everywhere instead of repeating the literal.

Suggested implementation:

```python
        self.cb_det_size = QComboBox()
        self._det_size_options = [160, 320, 640]

        for v in self._det_size_options:
            self.cb_det_size.addItem(f"{v} x {v}")

        # Use a centralized default detection size so UI/CLI/backend stay in sync.
        default_det_size = getattr(
            modules.globals,
            "DEFAULT_DET_SIZE",
            self._det_size_options[-1],
        )
        cur_det = int(
            getattr(
                modules.globals,
                "det_size",
                default_det_size,
            )
        )

        if cur_det in self._det_size_options:
            idx = self._det_size_options.index(cur_det)
        elif default_det_size in self._det_size_options:
            idx = self._det_size_options.index(default_det_size)
        else:
            idx = len(self._det_size_options) - 1

        self.cb_det_size.setCurrentIndex(idx)

```

To fully implement the centralization you suggested, the following should also be done elsewhere in the codebase:

1. In `modules/globals` (or a similar configuration module), define a single constant:
   ```python
   DEFAULT_DET_SIZE = 640  # or whatever default you want
   ```
2. Update `face_analyser._current_det_size` to use `modules.globals.DEFAULT_DET_SIZE` instead of an inline literal (e.g. `640`).
3. Update CLI default handling for `det_size` to also use `modules.globals.DEFAULT_DET_SIZE` (e.g. via `default=DEFAULT_DET_SIZE` or equivalent).
4. Ensure `DEFAULT_DET_SIZE` remains consistent with `self._det_size_options` (e.g., is one of `[160, 320, 640]`), or adjust the options list to match.
</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 thread modules/ui.py Outdated
5uck1ess added a commit to 5uck1ess/Deep-Live-Cam that referenced this pull request May 19, 2026
Addresses sourcery-ai review on PR hacksider#1833: the default detection size
(640) was duplicated in UI cb_det_size init, face_analyser._current_det_size,
and CLI parse_args. Hoist a single DEFAULT_DET_SIZE constant in
modules.globals so UI/CLI/backend stay in sync if it ever needs to
change. UI fallback also picks the matching index instead of a
positional literal.
5uck1ess added a commit to 5uck1ess/Deep-Live-Cam that referenced this pull request Jun 24, 2026
…, i18n

Adversarial review of hacksider#1833 surfaced a BLOCKER the bots missed plus the
repo's recurring persistence gap:

- BLOCKER (race): the det_size dropdown calls reset_face_analyser() which
  nulls FACE_ANALYSER while the live worker is detecting. get_face_analyser()
  published the FaceAnalysis object to the global *before* prepare()/optimize(),
  so a lock-free reader could grab a half-built analyser → crash/garbage.
  Now built into a local and published only after prepare()/optimize(), so a
  concurrent reset/rebuild can never expose an unprepared analyser. This makes
  mid-Live det_size changes safe.
- Persist capture_resolution + det_size in save/load_switch_states (with
  validation: bad/corrupt values fall back to defaults), and save on change —
  previously both reset to defaults on every restart.
- Move 'from modules.face_analyser import reset_face_analyser' to module level
  (no circular import exists) instead of importing inside the slot each call.
- Localization: pre-translate the static part of the two new status messages
  and append the dynamic size, so they participate in i18n.
5uck1ess added 4 commits June 24, 2026 11:01
Ported behavior from April 2026 Fork:

- globals.det_size (160/320/640) — face detection input resolution.
  Lower = faster, less accurate at distance.
- globals.capture_resolution (tuple) — requested webcam resolution.
  Camera may negotiate down; actual size logged by VideoCapturer.

UI changes (camera card now uses QGridLayout):
- Resolution dropdown: 640x360, 640x480, 960x540, 1280x720, 1920x1080.
  Applies on next Live start.
- Det size dropdown: 160, 320, 640. Triggers FACE_ANALYSER re-init
  via reset_face_analyser() so the new size takes effect on next call.

Backend changes:
- face_analyser.py: DET_SIZE constant replaced with _current_det_size()
  that reads modules.globals.det_size. Added reset_face_analyser()
  helper for hot-swap.
- ui.py: VideoCapturer.start() now passes globals.capture_resolution
  instead of hardcoded PREVIEW_DEFAULT_WIDTH/HEIGHT (which are kept
  for preview window sizing, separate concern).
- core.py: --det-size CLI flag (choices: 160, 320, 640).
Addresses sourcery-ai review on PR hacksider#1833: the default detection size
(640) was duplicated in UI cb_det_size init, face_analyser._current_det_size,
and CLI parse_args. Hoist a single DEFAULT_DET_SIZE constant in
modules.globals so UI/CLI/backend stay in sync if it ever needs to
change. UI fallback also picks the matching index instead of a
positional literal.
…, i18n

Adversarial review of hacksider#1833 surfaced a BLOCKER the bots missed plus the
repo's recurring persistence gap:

- BLOCKER (race): the det_size dropdown calls reset_face_analyser() which
  nulls FACE_ANALYSER while the live worker is detecting. get_face_analyser()
  published the FaceAnalysis object to the global *before* prepare()/optimize(),
  so a lock-free reader could grab a half-built analyser → crash/garbage.
  Now built into a local and published only after prepare()/optimize(), so a
  concurrent reset/rebuild can never expose an unprepared analyser. This makes
  mid-Live det_size changes safe.
- Persist capture_resolution + det_size in save/load_switch_states (with
  validation: bad/corrupt values fall back to defaults), and save on change —
  previously both reset to defaults on every restart.
- Move 'from modules.face_analyser import reset_face_analyser' to module level
  (no circular import exists) instead of importing inside the slot each call.
- Localization: pre-translate the static part of the two new status messages
  and append the dynamic size, so they participate in i18n.
…view

- load_switch_states: reject zero/negative persisted capture_resolution before
  it reaches cv2 (only type/length was validated before)
- det_size dropdown: normalize a bad persisted size to the default (then the
  last option) so .index() can never raise ValueError
- get_face_analyser: capture det_size once and thread it into
  _optimize_det_model instead of re-reading the global, removing a latent TOCTOU
  if that step ever moves outside the lock
- drop unused numpy import (F401)
@5uck1ess 5uck1ess force-pushed the pr/resolution-and-det-size branch from a603dfb to 5d1c79d Compare June 24, 2026 15:05
@5uck1ess

Copy link
Copy Markdown
Author

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.

1 participant