mirror of
https://github.com/hacksider/Deep-Live-Cam.git
synced 2026-09-18 21:42:18 +02:00
Compare commits
23
Commits
2.7-RC1
...
2.7-ultimate
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
230217ec11 | ||
|
|
156321f7a3 | ||
|
|
8234965ee8 | ||
|
|
ab64c186ec | ||
|
|
b8e781e539 | ||
|
|
ff7ee0d219 | ||
|
|
8d727eba3e | ||
|
|
eba2a958d3 | ||
|
|
14ba4f9c0b | ||
|
|
7d2d7fb1f3 | ||
|
|
57c4c32377 | ||
|
|
d00b09f5d8 | ||
|
|
897dc21da4 | ||
|
|
834092c891 | ||
|
|
da0672ad6b | ||
|
|
834bc43768 | ||
|
|
3b69413d61 | ||
|
|
07e2e960c8 | ||
|
|
ba27b75265 | ||
|
|
cfa8123b67 | ||
|
|
08b2dd2526 | ||
|
|
886e64b320 | ||
|
|
aa6f2cbade |
@@ -0,0 +1,16 @@
|
|||||||
|
name: ruff
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
ruff:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: astral-sh/ruff-action@v4.0.0
|
||||||
|
with:
|
||||||
|
version: "0.15.7"
|
||||||
|
args: "check --output-format=github"
|
||||||
@@ -30,13 +30,39 @@ By using this software, you agree to these terms and commit to using it in a man
|
|||||||
|
|
||||||
Users are expected to use this software responsibly and legally. If using a real person's face, obtain their consent and clearly label any output as a deepfake when sharing online. We are not responsible for end-user actions.
|
Users are expected to use this software responsibly and legally. If using a real person's face, obtain their consent and clearly label any output as a deepfake when sharing online. We are not responsible for end-user actions.
|
||||||
|
|
||||||
## Exclusive v2.7 beta Quick Start - Pre-built (Windows/Mac Silicon/CPU)
|
## Pre-built Quickstart
|
||||||
|
|
||||||
<a href="https://deeplivecam.net/index.php/quickstart"> <img src="media/Download.png" width="285" height="77" />
|
<p align="center">
|
||||||
|
<a href="https://deeplivecam.net/index.php/quickstart">
|
||||||
|
<img src="https://github.com/user-attachments/assets/fa2cdf79-c933-4b93-844a-b087192261ed" width="100%" alt="Lite / Ultimate Download Banner">
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
##### This is the fastest build you can get if you have a discrete NVIDIA or AMD GPU, CPU or Mac Silicon, And you'll receive special priority support. 2.7 beta is the best you can have with 30+ extra features than the open source version.
|
<p align="center">
|
||||||
|
<img src="https://github.com/user-attachments/assets/56b61811-3a1e-4672-9b50-cf7f6e8e6852" width="40" alt="Windows">
|
||||||
###### These Pre-builts are perfect for non-technical users or those who don't have time to, or can't manually install all the requirements. Just a heads-up: this is an open-source project, so you can also install it manually.
|
|
||||||
|
<img src="https://github.com/user-attachments/assets/6538e3a6-c957-431a-b586-2d6abcf534dc" width="34" alt="Mac Silicon">
|
||||||
|
|
||||||
|
<img src="https://github.com/user-attachments/assets/ad45142e-426c-4364-a2a9-a512670cc62c" width="40" alt="CPU">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<strong>Windows • Mac Silicon • CPU • NVIDIA • AMD</strong>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
Builds optimized for your hardware.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://deeplivecam.net/index.php/quickstart">
|
||||||
|
<img src="media/Download.png" width="280" alt="Download">
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
> **Ultimate** includes **30+ exclusive features**, performance optimizations, and **priority support**.
|
||||||
|
|
||||||
|
Perfect if you want the fastest setup with **zero manual installation**, pre-configured dependencies, and optimized builds for every supported platform.
|
||||||
|
|
||||||
## TLDR; Live Deepfake in just 3 Clicks
|
## TLDR; Live Deepfake in just 3 Clicks
|
||||||

|

|
||||||
@@ -109,7 +135,7 @@ This is more likely to work on your computer but will be slower as it utilizes t
|
|||||||
|
|
||||||
**1. Set up Your Platform**
|
**1. Set up Your Platform**
|
||||||
|
|
||||||
- Python (3.11 recommended)
|
- Python (3.14 recommended; 3.11-3.14 supported)
|
||||||
- pip
|
- pip
|
||||||
- git
|
- git
|
||||||
- [ffmpeg](https://www.youtube.com/watch?v=OlNWCpFdVMA) - ```iex (irm ffmpeg.tc.ht)```
|
- [ffmpeg](https://www.youtube.com/watch?v=OlNWCpFdVMA) - ```iex (irm ffmpeg.tc.ht)```
|
||||||
@@ -118,7 +144,7 @@ This is more likely to work on your computer but will be slower as it utilizes t
|
|||||||
**2. Clone the Repository**
|
**2. Clone the Repository**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/hacksider/Deep-Live-Cam.git
|
git clone --depth 1 https://github.com/hacksider/Deep-Live-Cam.git
|
||||||
cd Deep-Live-Cam
|
cd Deep-Live-Cam
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -142,7 +168,7 @@ pip install -r requirements.txt
|
|||||||
```
|
```
|
||||||
For Linux:
|
For Linux:
|
||||||
```bash
|
```bash
|
||||||
# Ensure you use the installed Python 3.11
|
# Ensure you use the installed Python 3.14
|
||||||
python3 -m venv venv
|
python3 -m venv venv
|
||||||
source venv/bin/activate
|
source venv/bin/activate
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
@@ -150,17 +176,17 @@ pip install -r requirements.txt
|
|||||||
|
|
||||||
**For macOS:**
|
**For macOS:**
|
||||||
|
|
||||||
Apple Silicon (M1/M2/M3) requires specific setup:
|
Apple Silicon (M1 through M5) requires specific setup:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Install Python 3.11 (specific version is important)
|
# Install Python 3.14
|
||||||
brew install python@3.11
|
brew install python@3.14
|
||||||
|
|
||||||
# Install tkinter package (required for the GUI)
|
# Install tkinter package (required for the GUI)
|
||||||
brew install python-tk@3.11
|
brew install python-tk@3.14
|
||||||
|
|
||||||
# Create and activate virtual environment with Python 3.11
|
# Create and activate virtual environment with Python 3.14
|
||||||
python3.11 -m venv venv
|
python3.14 -m venv venv
|
||||||
source venv/bin/activate
|
source venv/bin/activate
|
||||||
|
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
@@ -212,26 +238,29 @@ python run.py --execution-provider cuda
|
|||||||
|
|
||||||
**CoreML Execution Provider (Apple Silicon)**
|
**CoreML Execution Provider (Apple Silicon)**
|
||||||
|
|
||||||
Apple Silicon (M1/M2/M3) specific installation:
|
Apple Silicon (M1 through M5) specific installation:
|
||||||
|
|
||||||
1. Make sure you've completed the macOS setup above using Python 3.11.
|
1. Make sure you've completed the macOS setup above using Python 3.14.
|
||||||
2. Install dependencies:
|
2. No extra install step is needed — `requirements.txt` pulls the official
|
||||||
|
`onnxruntime` build, whose macOS wheels ship the CoreML execution provider.
|
||||||
|
If you previously installed the unmaintained `onnxruntime-silicon` fork,
|
||||||
|
remove it first, as it shadows the real package:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip uninstall onnxruntime onnxruntime-silicon
|
pip uninstall onnxruntime-silicon
|
||||||
pip install onnxruntime-silicon==1.13.1
|
pip install -r requirements.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Usage:
|
3. Usage:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python3.11 run.py --execution-provider coreml
|
python3.14 run.py --execution-provider coreml
|
||||||
```
|
```
|
||||||
|
|
||||||
**Important Notes for macOS:**
|
**Important Notes for macOS:**
|
||||||
- You **must** use Python 3.11, not newer versions like 3.13
|
- Python 3.11 is the minimum (onnxruntime dropped 3.10); 3.14 is recommended
|
||||||
- Always run with `python3.11` command not just `python` if you have multiple Python versions installed
|
- Always run with `python3.14` command not just `python` if you have multiple Python versions installed
|
||||||
- If you get error about `_tkinter` missing, reinstall the tkinter package: `brew reinstall python-tk@3.11`
|
- If you get error about `_tkinter` missing, reinstall the tkinter package: `brew reinstall python-tk@3.14`
|
||||||
- If you get model loading errors, check that your models are in the correct folder
|
- If you get model loading errors, check that your models are in the correct folder
|
||||||
- If you encounter conflicts with other Python versions, consider uninstalling them:
|
- If you encounter conflicts with other Python versions, consider uninstalling them:
|
||||||
```bash
|
```bash
|
||||||
@@ -239,9 +268,9 @@ python3.11 run.py --execution-provider coreml
|
|||||||
brew list | grep python
|
brew list | grep python
|
||||||
|
|
||||||
# Uninstall conflicting versions if needed
|
# Uninstall conflicting versions if needed
|
||||||
brew uninstall --ignore-dependencies python@3.13
|
brew uninstall --ignore-dependencies python@3.11
|
||||||
|
|
||||||
# Keep only Python 3.11
|
# Keep only Python 3.14
|
||||||
brew cleanup
|
brew cleanup
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ if sys.platform == "win32":
|
|||||||
|
|
||||||
import insightface
|
import insightface
|
||||||
from insightface.app import FaceAnalysis
|
from insightface.app import FaceAnalysis
|
||||||
from insightface.utils import face_align
|
|
||||||
from modules.processors.frame.face_swapper import _fast_paste_back
|
from modules.processors.frame.face_swapper import _fast_paste_back
|
||||||
from modules import platform_info
|
from modules import platform_info
|
||||||
|
|
||||||
@@ -81,10 +80,14 @@ def capture_thread():
|
|||||||
try:
|
try:
|
||||||
capture_queue.put_nowait(frame)
|
capture_queue.put_nowait(frame)
|
||||||
except queue.Full:
|
except queue.Full:
|
||||||
try: capture_queue.get_nowait()
|
try:
|
||||||
except queue.Empty: pass
|
capture_queue.get_nowait()
|
||||||
try: capture_queue.put_nowait(frame)
|
except queue.Empty:
|
||||||
except queue.Full: pass
|
pass
|
||||||
|
try:
|
||||||
|
capture_queue.put_nowait(frame)
|
||||||
|
except queue.Full:
|
||||||
|
pass
|
||||||
|
|
||||||
cap_t = threading.Thread(target=capture_thread, daemon=True)
|
cap_t = threading.Thread(target=capture_thread, daemon=True)
|
||||||
cap_t.start()
|
cap_t.start()
|
||||||
|
|||||||
+38
-18
@@ -1,18 +1,38 @@
|
|||||||
import os
|
import os
|
||||||
import cv2
|
import cv2
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
# Utility function to support unicode characters in file paths for reading
|
|
||||||
def imread_unicode(path, flags=cv2.IMREAD_COLOR):
|
# Utility function to support unicode characters in file paths for reading.
|
||||||
return cv2.imdecode(np.fromfile(path, dtype=np.uint8), flags)
|
# OpenCV's cv2.imread() encodes the path with the locale ANSI code page on
|
||||||
|
# Windows, so it silently returns None for paths containing non-ASCII
|
||||||
# Utility function to support unicode characters in file paths for writing
|
# characters (Chinese, Japanese, Cyrillic, accents, ...). Reading the bytes
|
||||||
def imwrite_unicode(path, img, params=None):
|
# through NumPy (which uses Python's unicode-aware file I/O) and decoding them
|
||||||
root, ext = os.path.splitext(path)
|
# in memory sidesteps that limitation. Returns None on failure, matching
|
||||||
if not ext:
|
# cv2.imread() so it stays a drop-in replacement.
|
||||||
ext = ".png"
|
def imread_unicode(path, flags=cv2.IMREAD_COLOR):
|
||||||
result, encoded_img = cv2.imencode(ext, img, params if params else [])
|
try:
|
||||||
result, encoded_img = cv2.imencode(f".{ext}", img, params if params is not None else [])
|
data = np.fromfile(path, dtype=np.uint8)
|
||||||
encoded_img.tofile(path)
|
if data.size == 0:
|
||||||
return True
|
return None
|
||||||
return False
|
return cv2.imdecode(data, flags)
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# Utility function to support unicode characters in file paths for writing.
|
||||||
|
# cv2.imwrite() has the same ANSI-path limitation, so we encode the image in
|
||||||
|
# memory and write the bytes out with NumPy's unicode-aware file I/O. Returns
|
||||||
|
# True/False like cv2.imwrite() so it stays a drop-in replacement.
|
||||||
|
def imwrite_unicode(path, img, params=None):
|
||||||
|
try:
|
||||||
|
root, ext = os.path.splitext(path)
|
||||||
|
if not ext:
|
||||||
|
ext = ".png"
|
||||||
|
result, encoded_img = cv2.imencode(ext, img, params if params is not None else [])
|
||||||
|
if not result:
|
||||||
|
return False
|
||||||
|
encoded_img.tofile(path)
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|||||||
+7
-2
@@ -14,8 +14,13 @@ def get_video_frame(video_path: str, frame_number: int = 0) -> Any:
|
|||||||
if modules.globals.color_correction:
|
if modules.globals.color_correction:
|
||||||
capture.set(cv2.CAP_PROP_CONVERT_RGB, 1)
|
capture.set(cv2.CAP_PROP_CONVERT_RGB, 1)
|
||||||
|
|
||||||
frame_total = capture.get(cv2.CAP_PROP_FRAME_COUNT)
|
frame_total = int(capture.get(cv2.CAP_PROP_FRAME_COUNT))
|
||||||
capture.set(cv2.CAP_PROP_POS_FRAMES, min(frame_total, frame_number - 1))
|
if frame_total <= 0:
|
||||||
|
capture.release()
|
||||||
|
return None
|
||||||
|
|
||||||
|
target_index = 0 if frame_number <= 1 else min(frame_total - 1, frame_number - 1)
|
||||||
|
capture.set(cv2.CAP_PROP_POS_FRAMES, target_index)
|
||||||
has_frame, frame = capture.read()
|
has_frame, frame = capture.read()
|
||||||
|
|
||||||
if has_frame and modules.globals.color_correction:
|
if has_frame and modules.globals.color_correction:
|
||||||
|
|||||||
@@ -1,10 +1,21 @@
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
from sklearn.cluster import KMeans
|
from sklearn.cluster import KMeans
|
||||||
from sklearn.metrics import silhouette_score
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
def find_cluster_centroids(embeddings, max_k=10) -> Any:
|
def find_cluster_centroids(embeddings, max_k=10) -> Any:
|
||||||
|
n_samples = len(embeddings)
|
||||||
|
if n_samples == 0:
|
||||||
|
raise ValueError("embeddings must not be empty")
|
||||||
|
if max_k < 1:
|
||||||
|
raise ValueError("max_k must be at least 1")
|
||||||
|
|
||||||
|
max_k = min(max_k, n_samples)
|
||||||
|
if max_k == 1:
|
||||||
|
kmeans = KMeans(n_clusters=1, random_state=0)
|
||||||
|
kmeans.fit(embeddings)
|
||||||
|
return kmeans.cluster_centers_
|
||||||
|
|
||||||
inertia = []
|
inertia = []
|
||||||
cluster_centroids = []
|
cluster_centroids = []
|
||||||
K = range(1, max_k+1)
|
K = range(1, max_k+1)
|
||||||
|
|||||||
+13
-6
@@ -58,7 +58,7 @@ def parse_args() -> None:
|
|||||||
program.add_argument('--live-resizable', help='The live camera frame is resizable', dest='live_resizable', action='store_true', default=False)
|
program.add_argument('--live-resizable', help='The live camera frame is resizable', dest='live_resizable', action='store_true', default=False)
|
||||||
program.add_argument('--max-memory', help='maximum amount of RAM in GB', dest='max_memory', type=int, default=suggest_max_memory())
|
program.add_argument('--max-memory', help='maximum amount of RAM in GB', dest='max_memory', type=int, default=suggest_max_memory())
|
||||||
program.add_argument('--execution-provider', help='execution provider', dest='execution_provider', default=[suggest_default_execution_provider()], choices=suggest_execution_providers(), nargs='+')
|
program.add_argument('--execution-provider', help='execution provider', dest='execution_provider', default=[suggest_default_execution_provider()], choices=suggest_execution_providers(), nargs='+')
|
||||||
program.add_argument('--execution-threads', help='number of execution threads', dest='execution_threads', type=int, default=suggest_execution_threads())
|
program.add_argument('--execution-threads', help='number of execution threads', dest='execution_threads', type=int, default=None)
|
||||||
program.add_argument('-v', '--version', action='version', version=f'{modules.metadata.name} {modules.metadata.version}')
|
program.add_argument('-v', '--version', action='version', version=f'{modules.metadata.name} {modules.metadata.version}')
|
||||||
|
|
||||||
# register deprecated args
|
# register deprecated args
|
||||||
@@ -90,6 +90,12 @@ def parse_args() -> None:
|
|||||||
modules.globals.execution_threads = args.execution_threads
|
modules.globals.execution_threads = args.execution_threads
|
||||||
modules.globals.lang = args.lang
|
modules.globals.lang = args.lang
|
||||||
|
|
||||||
|
# The argparse default (None) avoids evaluating suggest_execution_threads()
|
||||||
|
# before providers are decoded, and deprecated-arg overrides above may
|
||||||
|
# have already set execution_threads.
|
||||||
|
if modules.globals.execution_threads is None:
|
||||||
|
modules.globals.execution_threads = suggest_execution_threads()
|
||||||
|
|
||||||
#for ENHANCER tumblers:
|
#for ENHANCER tumblers:
|
||||||
for enhancer_key in ('face_enhancer', 'face_enhancer_gpen256', 'face_enhancer_gpen512'):
|
for enhancer_key in ('face_enhancer', 'face_enhancer_gpen256', 'face_enhancer_gpen512'):
|
||||||
modules.globals.fp_ui[enhancer_key] = enhancer_key in args.frame_processor
|
modules.globals.fp_ui[enhancer_key] = enhancer_key in args.frame_processor
|
||||||
@@ -132,9 +138,9 @@ def suggest_max_memory() -> int:
|
|||||||
|
|
||||||
|
|
||||||
def suggest_default_execution_provider() -> str:
|
def suggest_default_execution_provider() -> str:
|
||||||
"""Pick the best available provider: cuda > rocm > coreml > dml > cpu."""
|
"""Pick the best available provider: cuda > rocm > coreml > openvino > dml > cpu."""
|
||||||
available = encode_execution_providers(onnxruntime.get_available_providers())
|
available = encode_execution_providers(onnxruntime.get_available_providers())
|
||||||
for pref in ('cuda', 'rocm', 'coreml', 'dml'):
|
for pref in ('cuda', 'rocm', 'coreml', 'openvino', 'dml'):
|
||||||
if pref in available:
|
if pref in available:
|
||||||
return pref
|
return pref
|
||||||
return 'cpu'
|
return 'cpu'
|
||||||
@@ -157,6 +163,8 @@ def suggest_execution_threads() -> int:
|
|||||||
return 1
|
return 1
|
||||||
if 'CUDAExecutionProvider' in modules.globals.execution_providers:
|
if 'CUDAExecutionProvider' in modules.globals.execution_providers:
|
||||||
return 2
|
return 2
|
||||||
|
if 'OpenVINOExecutionProvider' in modules.globals.execution_providers:
|
||||||
|
return 1
|
||||||
|
|
||||||
# For CPU execution, use most cores but leave some for system
|
# For CPU execution, use most cores but leave some for system
|
||||||
return max(4, min(cpu_count - 2, 16))
|
return max(4, min(cpu_count - 2, 16))
|
||||||
@@ -171,8 +179,6 @@ def limit_resources() -> None:
|
|||||||
# limit memory usage
|
# limit memory usage
|
||||||
if modules.globals.max_memory:
|
if modules.globals.max_memory:
|
||||||
memory = modules.globals.max_memory * 1024 ** 3
|
memory = modules.globals.max_memory * 1024 ** 3
|
||||||
if platform.system().lower() == 'darwin':
|
|
||||||
memory = modules.globals.max_memory * 1024 ** 6
|
|
||||||
if platform.system().lower() == 'windows':
|
if platform.system().lower() == 'windows':
|
||||||
import ctypes
|
import ctypes
|
||||||
kernel32 = ctypes.windll.kernel32
|
kernel32 = ctypes.windll.kernel32
|
||||||
@@ -324,7 +330,8 @@ def start() -> None:
|
|||||||
def destroy(to_quit=True) -> None:
|
def destroy(to_quit=True) -> None:
|
||||||
if modules.globals.target_path:
|
if modules.globals.target_path:
|
||||||
clean_temp(modules.globals.target_path)
|
clean_temp(modules.globals.target_path)
|
||||||
if to_quit: quit()
|
if to_quit:
|
||||||
|
quit()
|
||||||
|
|
||||||
|
|
||||||
def run() -> None:
|
def run() -> None:
|
||||||
|
|||||||
@@ -4,9 +4,8 @@ from typing import Any
|
|||||||
import insightface
|
import insightface
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
import cv2
|
|
||||||
import numpy as np
|
|
||||||
import modules.globals
|
import modules.globals
|
||||||
|
from modules import imread_unicode, imwrite_unicode
|
||||||
from tqdm import tqdm
|
from tqdm import tqdm
|
||||||
from modules.typing import Frame
|
from modules.typing import Frame
|
||||||
from modules.cluster_analysis import find_cluster_centroids, find_closest_centroid
|
from modules.cluster_analysis import find_cluster_centroids, find_closest_centroid
|
||||||
@@ -255,8 +254,10 @@ def add_blank_map() -> Any:
|
|||||||
def get_unique_faces_from_target_image() -> Any:
|
def get_unique_faces_from_target_image() -> Any:
|
||||||
try:
|
try:
|
||||||
modules.globals.source_target_map = []
|
modules.globals.source_target_map = []
|
||||||
target_frame = cv2.imread(modules.globals.target_path)
|
target_frame = imread_unicode(modules.globals.target_path)
|
||||||
many_faces = get_many_faces(target_frame)
|
many_faces = get_many_faces(target_frame)
|
||||||
|
if many_faces is None:
|
||||||
|
return None
|
||||||
i = 0
|
i = 0
|
||||||
|
|
||||||
for face in many_faces:
|
for face in many_faces:
|
||||||
@@ -289,8 +290,10 @@ def get_unique_faces_from_target_video() -> Any:
|
|||||||
|
|
||||||
i = 0
|
i = 0
|
||||||
for temp_frame_path in tqdm(temp_frame_paths, desc="Extracting face embeddings from frames"):
|
for temp_frame_path in tqdm(temp_frame_paths, desc="Extracting face embeddings from frames"):
|
||||||
temp_frame = cv2.imread(temp_frame_path)
|
temp_frame = imread_unicode(temp_frame_path)
|
||||||
many_faces = get_many_faces(temp_frame)
|
many_faces = get_many_faces(temp_frame)
|
||||||
|
if many_faces is None:
|
||||||
|
continue
|
||||||
|
|
||||||
for face in many_faces:
|
for face in many_faces:
|
||||||
face_embeddings.append(face.normed_embedding)
|
face_embeddings.append(face.normed_embedding)
|
||||||
@@ -332,6 +335,9 @@ def default_target_face():
|
|||||||
best_frame = frame
|
best_frame = frame
|
||||||
break
|
break
|
||||||
|
|
||||||
|
if best_face is None:
|
||||||
|
continue # No faces detected in this cluster — skip
|
||||||
|
|
||||||
for frame in map['target_faces_in_frame']:
|
for frame in map['target_faces_in_frame']:
|
||||||
for face in frame['faces']:
|
for face in frame['faces']:
|
||||||
if face['det_score'] > best_face['det_score']:
|
if face['det_score'] > best_face['det_score']:
|
||||||
@@ -340,7 +346,7 @@ def default_target_face():
|
|||||||
|
|
||||||
x_min, y_min, x_max, y_max = best_face['bbox']
|
x_min, y_min, x_max, y_max = best_face['bbox']
|
||||||
|
|
||||||
target_frame = cv2.imread(best_frame['location'])
|
target_frame = imread_unicode(best_frame['location'])
|
||||||
map['target'] = {
|
map['target'] = {
|
||||||
'cv2' : target_frame[int(y_min):int(y_max), int(x_min):int(x_max)],
|
'cv2' : target_frame[int(y_min):int(y_max), int(x_min):int(x_max)],
|
||||||
'face' : best_face
|
'face' : best_face
|
||||||
@@ -356,7 +362,7 @@ def dump_faces(centroids: Any, frame_face_embeddings: list):
|
|||||||
Path(temp_directory_path + f"/{i}").mkdir(parents=True, exist_ok=True)
|
Path(temp_directory_path + f"/{i}").mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
for frame in tqdm(frame_face_embeddings, desc=f"Copying faces to temp/./{i}"):
|
for frame in tqdm(frame_face_embeddings, desc=f"Copying faces to temp/./{i}"):
|
||||||
temp_frame = cv2.imread(frame['location'])
|
temp_frame = imread_unicode(frame['location'])
|
||||||
|
|
||||||
j = 0
|
j = 0
|
||||||
for face in frame['faces']:
|
for face in frame['faces']:
|
||||||
@@ -364,5 +370,5 @@ def dump_faces(centroids: Any, frame_face_embeddings: list):
|
|||||||
x_min, y_min, x_max, y_max = face['bbox']
|
x_min, y_min, x_max, y_max = face['bbox']
|
||||||
|
|
||||||
if temp_frame[int(y_min):int(y_max), int(x_min):int(x_max)].size > 0:
|
if temp_frame[int(y_min):int(y_max), int(x_min):int(x_max)].size > 0:
|
||||||
cv2.imwrite(temp_directory_path + f"/{i}/{frame['frame']}_{j}.png", temp_frame[int(y_min):int(y_max), int(x_min):int(x_max)])
|
imwrite_unicode(temp_directory_path + f"/{i}/{frame['frame']}_{j}.png", temp_frame[int(y_min):int(y_max), int(x_min):int(x_max)])
|
||||||
j += 1
|
j += 1
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ from __future__ import annotations
|
|||||||
import os
|
import os
|
||||||
import cv2
|
import cv2
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from typing import Tuple, Optional
|
from typing import Tuple
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# CUDA availability detection (evaluated once at import time)
|
# CUDA availability detection (evaluated once at import time)
|
||||||
|
|||||||
@@ -392,7 +392,7 @@ def _decompose_split(model) -> bool:
|
|||||||
|
|
||||||
# Collect all needed boundary constants
|
# Collect all needed boundary constants
|
||||||
for _, (a, b) in splits:
|
for _, (a, b) in splits:
|
||||||
ensure_const(f"_sp_s0", [0])
|
ensure_const("_sp_s0", [0])
|
||||||
ensure_const(f"_sp_s{a}", [a])
|
ensure_const(f"_sp_s{a}", [a])
|
||||||
ensure_const(f"_sp_s{a + b}", [a + b])
|
ensure_const(f"_sp_s{a + b}", [a + b])
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,15 @@ ONNX_PROVIDERS: List[str] = _detect_onnx_providers()
|
|||||||
HAS_CUDA_PROVIDER: bool = "CUDAExecutionProvider" in ONNX_PROVIDERS
|
HAS_CUDA_PROVIDER: bool = "CUDAExecutionProvider" in ONNX_PROVIDERS
|
||||||
HAS_COREML_PROVIDER: bool = "CoreMLExecutionProvider" in ONNX_PROVIDERS
|
HAS_COREML_PROVIDER: bool = "CoreMLExecutionProvider" in ONNX_PROVIDERS
|
||||||
HAS_DML_PROVIDER: bool = "DmlExecutionProvider" in ONNX_PROVIDERS
|
HAS_DML_PROVIDER: bool = "DmlExecutionProvider" in ONNX_PROVIDERS
|
||||||
|
HAS_OPENVINO_PROVIDER: bool = "OpenVINOExecutionProvider" in ONNX_PROVIDERS
|
||||||
|
|
||||||
|
# OpenVINO execution-provider config shared by every ONNX session builder.
|
||||||
|
# AUTO:GPU,NPU,CPU lets OpenVINO pick the best available device in priority
|
||||||
|
# order (Intel GPU → NPU → CPU).
|
||||||
|
OPENVINO_PROVIDER_CONFIG = (
|
||||||
|
"OpenVINOExecutionProvider",
|
||||||
|
{"device_type": "AUTO:GPU,NPU,CPU"},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def camera_backends() -> List[Tuple[int, int]]:
|
def camera_backends() -> List[Tuple[int, int]]:
|
||||||
@@ -65,6 +74,8 @@ def accelerator_label() -> str:
|
|||||||
return "CoreML (Apple Neural Engine)"
|
return "CoreML (Apple Neural Engine)"
|
||||||
if HAS_COREML_PROVIDER:
|
if HAS_COREML_PROVIDER:
|
||||||
return "CoreML"
|
return "CoreML"
|
||||||
|
if HAS_OPENVINO_PROVIDER:
|
||||||
|
return "OpenVINO (Intel)"
|
||||||
if HAS_DML_PROVIDER:
|
if HAS_DML_PROVIDER:
|
||||||
return "DirectML"
|
return "DirectML"
|
||||||
return "CPU"
|
return "CPU"
|
||||||
|
|||||||
@@ -1,4 +1,17 @@
|
|||||||
|
import importlib.util
|
||||||
|
import os
|
||||||
|
|
||||||
import numpy
|
import numpy
|
||||||
|
|
||||||
|
# Keras 3 defaults to the TensorFlow backend, which has no Python 3.14 wheels.
|
||||||
|
# opennsfw2 only runs inference, so any installed backend works; pick one that
|
||||||
|
# is actually present before opennsfw2 imports keras.
|
||||||
|
if "KERAS_BACKEND" not in os.environ:
|
||||||
|
for _backend in ("torch", "tensorflow", "jax"):
|
||||||
|
if importlib.util.find_spec(_backend) is not None:
|
||||||
|
os.environ["KERAS_BACKEND"] = _backend
|
||||||
|
break
|
||||||
|
|
||||||
import opennsfw2
|
import opennsfw2
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
import cv2 # Add OpenCV import
|
import cv2 # Add OpenCV import
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import numpy as np
|
|||||||
import onnxruntime
|
import onnxruntime
|
||||||
|
|
||||||
import modules.globals
|
import modules.globals
|
||||||
|
from modules.platform_info import OPENVINO_PROVIDER_CONFIG
|
||||||
|
|
||||||
IS_APPLE_SILICON = platform.system() == "Darwin" and platform.machine() == "arm64"
|
IS_APPLE_SILICON = platform.system() == "Darwin" and platform.machine() == "arm64"
|
||||||
|
|
||||||
@@ -50,6 +51,9 @@ def build_provider_config(providers=None):
|
|||||||
"AllowLowPrecisionAccumulationOnGPU": 1,
|
"AllowLowPrecisionAccumulationOnGPU": 1,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
|
elif p == "OpenVINOExecutionProvider":
|
||||||
|
# AUTO lets OpenVINO select the best device
|
||||||
|
config.append(OPENVINO_PROVIDER_CONFIG)
|
||||||
else:
|
else:
|
||||||
config.append(p)
|
config.append(p)
|
||||||
return config
|
return config
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ def load_frame_processor_module(frame_processor: str) -> Any:
|
|||||||
frame_processor_module = importlib.import_module(f'modules.processors.frame.{frame_processor}')
|
frame_processor_module = importlib.import_module(f'modules.processors.frame.{frame_processor}')
|
||||||
for method_name in FRAME_PROCESSORS_INTERFACE:
|
for method_name in FRAME_PROCESSORS_INTERFACE:
|
||||||
if not hasattr(frame_processor_module, method_name):
|
if not hasattr(frame_processor_module, method_name):
|
||||||
|
print(f"Frame processor {frame_processor} is missing required method {method_name}")
|
||||||
sys.exit()
|
sys.exit()
|
||||||
except ImportError:
|
except ImportError:
|
||||||
print(f"Frame processor {frame_processor} not found")
|
print(f"Frame processor {frame_processor} not found")
|
||||||
@@ -59,7 +60,7 @@ def set_frame_processors_modules_from_ui(frame_processors: List[str]) -> None:
|
|||||||
current_processor_names = [proc.__name__.split('.')[-1] for proc in FRAME_PROCESSORS_MODULES]
|
current_processor_names = [proc.__name__.split('.')[-1] for proc in FRAME_PROCESSORS_MODULES]
|
||||||
|
|
||||||
for frame_processor, state in modules.globals.fp_ui.items():
|
for frame_processor, state in modules.globals.fp_ui.items():
|
||||||
if state == True and frame_processor not in current_processor_names:
|
if state and frame_processor not in current_processor_names:
|
||||||
try:
|
try:
|
||||||
frame_processor_module = load_frame_processor_module(frame_processor)
|
frame_processor_module = load_frame_processor_module(frame_processor)
|
||||||
FRAME_PROCESSORS_MODULES.append(frame_processor_module)
|
FRAME_PROCESSORS_MODULES.append(frame_processor_module)
|
||||||
@@ -70,7 +71,7 @@ def set_frame_processors_modules_from_ui(frame_processors: List[str]) -> None:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Warning: Error loading frame processor {frame_processor} requested by UI state: {e}")
|
print(f"Warning: Error loading frame processor {frame_processor} requested by UI state: {e}")
|
||||||
|
|
||||||
elif state == False and frame_processor in current_processor_names:
|
elif not state and frame_processor in current_processor_names:
|
||||||
try:
|
try:
|
||||||
module_to_remove = next((mod for mod in FRAME_PROCESSORS_MODULES if mod.__name__.endswith(f'.{frame_processor}')), None)
|
module_to_remove = next((mod for mod in FRAME_PROCESSORS_MODULES if mod.__name__.endswith(f'.{frame_processor}')), None)
|
||||||
if module_to_remove:
|
if module_to_remove:
|
||||||
@@ -125,7 +126,7 @@ def process_video_in_memory(source_path: str, target_path: str, fps: float) -> b
|
|||||||
Returns True on success, False on failure (caller should fall back to the
|
Returns True on success, False on failure (caller should fall back to the
|
||||||
disk-based pipeline).
|
disk-based pipeline).
|
||||||
"""
|
"""
|
||||||
import cv2
|
from modules import imread_unicode
|
||||||
from modules.face_analyser import get_one_face
|
from modules.face_analyser import get_one_face
|
||||||
from modules.utilities import (
|
from modules.utilities import (
|
||||||
get_video_dimensions,
|
get_video_dimensions,
|
||||||
@@ -138,7 +139,7 @@ def process_video_in_memory(source_path: str, target_path: str, fps: float) -> b
|
|||||||
# --- Pre-load source face (needed by face_swapper in simple mode) ---
|
# --- Pre-load source face (needed by face_swapper in simple mode) ---
|
||||||
source_face = None
|
source_face = None
|
||||||
if source_path and os.path.exists(source_path):
|
if source_path and os.path.exists(source_path):
|
||||||
source_img = cv2.imread(source_path)
|
source_img = imread_unicode(source_path)
|
||||||
if source_img is not None:
|
if source_img is not None:
|
||||||
source_face = get_one_face(source_img)
|
source_face = get_one_face(source_img)
|
||||||
del source_img
|
del source_img
|
||||||
|
|||||||
@@ -10,8 +10,9 @@ import onnxruntime
|
|||||||
|
|
||||||
import modules.globals
|
import modules.globals
|
||||||
import modules.processors.frame.core
|
import modules.processors.frame.core
|
||||||
|
from modules import imread_unicode, imwrite_unicode
|
||||||
from modules.core import update_status
|
from modules.core import update_status
|
||||||
from modules.face_analyser import get_one_face, get_many_faces
|
from modules.face_analyser import get_many_faces
|
||||||
from modules.typing import Frame, Face
|
from modules.typing import Frame, Face
|
||||||
from modules.utilities import (
|
from modules.utilities import (
|
||||||
is_image,
|
is_image,
|
||||||
@@ -407,7 +408,7 @@ def process_frames(
|
|||||||
progress.update(1)
|
progress.update(1)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
temp_frame = cv2.imread(temp_frame_path)
|
temp_frame = imread_unicode(temp_frame_path)
|
||||||
if temp_frame is None:
|
if temp_frame is None:
|
||||||
print(
|
print(
|
||||||
f"{NAME}: Warning: Failed to read frame {temp_frame_path}, skipping."
|
f"{NAME}: Warning: Failed to read frame {temp_frame_path}, skipping."
|
||||||
@@ -417,7 +418,7 @@ def process_frames(
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
result_frame = process_frame(None, temp_frame)
|
result_frame = process_frame(None, temp_frame)
|
||||||
cv2.imwrite(temp_frame_path, result_frame)
|
imwrite_unicode(temp_frame_path, result_frame)
|
||||||
if progress:
|
if progress:
|
||||||
progress.update(1)
|
progress.update(1)
|
||||||
|
|
||||||
@@ -426,12 +427,12 @@ def process_image(
|
|||||||
source_path: str | None, target_path: str, output_path: str
|
source_path: str | None, target_path: str, output_path: str
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Processes a single image file."""
|
"""Processes a single image file."""
|
||||||
target_frame = cv2.imread(target_path)
|
target_frame = imread_unicode(target_path)
|
||||||
if target_frame is None:
|
if target_frame is None:
|
||||||
print(f"{NAME}: Error: Failed to read target image {target_path}")
|
print(f"{NAME}: Error: Failed to read target image {target_path}")
|
||||||
return
|
return
|
||||||
result_frame = process_frame(None, target_frame)
|
result_frame = process_frame(None, target_frame)
|
||||||
cv2.imwrite(output_path, result_frame)
|
imwrite_unicode(output_path, result_frame)
|
||||||
print(f"{NAME}: Enhanced image saved to {output_path}")
|
print(f"{NAME}: Enhanced image saved to {output_path}")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,11 +4,9 @@ from typing import Any, List
|
|||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
import cv2
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
import modules.globals
|
import modules.globals
|
||||||
import modules.processors.frame.core
|
import modules.processors.frame.core
|
||||||
|
from modules import imread_unicode, imwrite_unicode
|
||||||
from modules.core import update_status
|
from modules.core import update_status
|
||||||
from modules.face_analyser import get_one_face
|
from modules.face_analyser import get_one_face
|
||||||
from modules.typing import Frame, Face
|
from modules.typing import Frame, Face
|
||||||
@@ -103,24 +101,24 @@ def process_frames(
|
|||||||
source_path: str | None, temp_frame_paths: List[str], progress: Any = None
|
source_path: str | None, temp_frame_paths: List[str], progress: Any = None
|
||||||
) -> None:
|
) -> None:
|
||||||
for temp_frame_path in temp_frame_paths:
|
for temp_frame_path in temp_frame_paths:
|
||||||
temp_frame = cv2.imread(temp_frame_path)
|
temp_frame = imread_unicode(temp_frame_path)
|
||||||
if temp_frame is None:
|
if temp_frame is None:
|
||||||
if progress:
|
if progress:
|
||||||
progress.update(1)
|
progress.update(1)
|
||||||
continue
|
continue
|
||||||
result = process_frame(None, temp_frame)
|
result = process_frame(None, temp_frame)
|
||||||
cv2.imwrite(temp_frame_path, result)
|
imwrite_unicode(temp_frame_path, result)
|
||||||
if progress:
|
if progress:
|
||||||
progress.update(1)
|
progress.update(1)
|
||||||
|
|
||||||
|
|
||||||
def process_image(source_path: str | None, target_path: str, output_path: str) -> None:
|
def process_image(source_path: str | None, target_path: str, output_path: str) -> None:
|
||||||
target_frame = cv2.imread(target_path)
|
target_frame = imread_unicode(target_path)
|
||||||
if target_frame is None:
|
if target_frame is None:
|
||||||
print(f"{NAME}: Error: Failed to read target image {target_path}")
|
print(f"{NAME}: Error: Failed to read target image {target_path}")
|
||||||
return
|
return
|
||||||
result_frame = process_frame(None, target_frame)
|
result_frame = process_frame(None, target_frame)
|
||||||
cv2.imwrite(output_path, result_frame)
|
imwrite_unicode(output_path, result_frame)
|
||||||
print(f"{NAME}: Enhanced image saved to {output_path}")
|
print(f"{NAME}: Enhanced image saved to {output_path}")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,11 +4,9 @@ from typing import Any, List
|
|||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
import cv2
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
import modules.globals
|
import modules.globals
|
||||||
import modules.processors.frame.core
|
import modules.processors.frame.core
|
||||||
|
from modules import imread_unicode, imwrite_unicode
|
||||||
from modules.core import update_status
|
from modules.core import update_status
|
||||||
from modules.face_analyser import get_one_face
|
from modules.face_analyser import get_one_face
|
||||||
from modules.typing import Frame, Face
|
from modules.typing import Frame, Face
|
||||||
@@ -103,24 +101,24 @@ def process_frames(
|
|||||||
source_path: str | None, temp_frame_paths: List[str], progress: Any = None
|
source_path: str | None, temp_frame_paths: List[str], progress: Any = None
|
||||||
) -> None:
|
) -> None:
|
||||||
for temp_frame_path in temp_frame_paths:
|
for temp_frame_path in temp_frame_paths:
|
||||||
temp_frame = cv2.imread(temp_frame_path)
|
temp_frame = imread_unicode(temp_frame_path)
|
||||||
if temp_frame is None:
|
if temp_frame is None:
|
||||||
if progress:
|
if progress:
|
||||||
progress.update(1)
|
progress.update(1)
|
||||||
continue
|
continue
|
||||||
result = process_frame(None, temp_frame)
|
result = process_frame(None, temp_frame)
|
||||||
cv2.imwrite(temp_frame_path, result)
|
imwrite_unicode(temp_frame_path, result)
|
||||||
if progress:
|
if progress:
|
||||||
progress.update(1)
|
progress.update(1)
|
||||||
|
|
||||||
|
|
||||||
def process_image(source_path: str | None, target_path: str, output_path: str) -> None:
|
def process_image(source_path: str | None, target_path: str, output_path: str) -> None:
|
||||||
target_frame = cv2.imread(target_path)
|
target_frame = imread_unicode(target_path)
|
||||||
if target_frame is None:
|
if target_frame is None:
|
||||||
print(f"{NAME}: Error: Failed to read target image {target_path}")
|
print(f"{NAME}: Error: Failed to read target image {target_path}")
|
||||||
return
|
return
|
||||||
result_frame = process_frame(None, target_frame)
|
result_frame = process_frame(None, target_frame)
|
||||||
cv2.imwrite(output_path, result_frame)
|
imwrite_unicode(output_path, result_frame)
|
||||||
print(f"{NAME}: Enhanced image saved to {output_path}")
|
print(f"{NAME}: Enhanced image saved to {output_path}")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import cv2
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
from modules.typing import Face, Frame
|
from modules.typing import Face, Frame
|
||||||
import modules.globals
|
import modules.globals
|
||||||
from modules.gpu_processing import gpu_gaussian_blur, gpu_resize, gpu_cvt_color
|
from modules.gpu_processing import gpu_gaussian_blur, gpu_resize
|
||||||
|
|
||||||
def apply_color_transfer(source, target):
|
def apply_color_transfer(source, target):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import numpy as np
|
|||||||
import platform
|
import platform
|
||||||
import modules.globals
|
import modules.globals
|
||||||
import modules.processors.frame.core
|
import modules.processors.frame.core
|
||||||
|
from modules import imread_unicode, imwrite_unicode
|
||||||
from modules.core import update_status
|
from modules.core import update_status
|
||||||
from modules.face_analyser import get_one_face, get_many_faces, default_source_face
|
from modules.face_analyser import get_one_face, get_many_faces, default_source_face
|
||||||
from modules.typing import Face, Frame
|
from modules.typing import Face, Frame
|
||||||
@@ -16,7 +17,8 @@ from modules.utilities import (
|
|||||||
is_video,
|
is_video,
|
||||||
)
|
)
|
||||||
from modules.cluster_analysis import find_closest_centroid
|
from modules.cluster_analysis import find_closest_centroid
|
||||||
from modules.gpu_processing import gpu_gaussian_blur, gpu_sharpen, gpu_add_weighted, gpu_resize, gpu_cvt_color
|
from modules.gpu_processing import gpu_gaussian_blur, gpu_sharpen, gpu_add_weighted, gpu_resize
|
||||||
|
from modules.platform_info import OPENVINO_PROVIDER_CONFIG
|
||||||
import os
|
import os
|
||||||
from collections import deque
|
from collections import deque
|
||||||
import time
|
import time
|
||||||
@@ -269,6 +271,8 @@ def get_face_swapper() -> Any:
|
|||||||
# Use bare provider — ONNX Runtime defaults are
|
# Use bare provider — ONNX Runtime defaults are
|
||||||
# fastest on modern GPUs (Blackwell/sm_120).
|
# fastest on modern GPUs (Blackwell/sm_120).
|
||||||
providers_config.append(p)
|
providers_config.append(p)
|
||||||
|
elif p == "OpenVINOExecutionProvider":
|
||||||
|
providers_config.append(OPENVINO_PROVIDER_CONFIG)
|
||||||
else:
|
else:
|
||||||
providers_config.append(p)
|
providers_config.append(p)
|
||||||
FACE_SWAPPER = insightface.model_zoo.get_model(
|
FACE_SWAPPER = insightface.model_zoo.get_model(
|
||||||
@@ -680,7 +684,8 @@ def apply_post_processing(current_frame: Frame, swapped_face_bboxes: List[np.nda
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
face_region = processed_frame[y1:y2, x1:x2]
|
face_region = processed_frame[y1:y2, x1:x2]
|
||||||
if face_region.size == 0: continue
|
if face_region.size == 0:
|
||||||
|
continue
|
||||||
|
|
||||||
# Apply sharpening (GPU-accelerated when CUDA OpenCV is available)
|
# Apply sharpening (GPU-accelerated when CUDA OpenCV is available)
|
||||||
try:
|
try:
|
||||||
@@ -815,9 +820,11 @@ def process_frame_v2(temp_frame: Frame, temp_frame_path: str = "") -> Frame:
|
|||||||
else: # Single face or specific mapping
|
else: # Single face or specific mapping
|
||||||
for map_data in source_target_map:
|
for map_data in source_target_map:
|
||||||
source_info = map_data.get("source", {})
|
source_info = map_data.get("source", {})
|
||||||
if not source_info: continue # Skip if no source info
|
if not source_info:
|
||||||
|
continue # Skip if no source info
|
||||||
source_face = source_info.get("face")
|
source_face = source_info.get("face")
|
||||||
if not source_face: continue # Skip if no source defined for this map entry
|
if not source_face:
|
||||||
|
continue # Skip if no source defined for this map entry
|
||||||
|
|
||||||
if is_image(modules.globals.target_path):
|
if is_image(modules.globals.target_path):
|
||||||
target_info = map_data.get("target", {})
|
target_info = map_data.get("target", {})
|
||||||
@@ -854,7 +861,8 @@ def process_frame_v2(temp_frame: Frame, temp_frame_path: str = "") -> Frame:
|
|||||||
if len(detected_faces) <= len(target_embeddings):
|
if len(detected_faces) <= len(target_embeddings):
|
||||||
# More targets defined than detected - match each detected face
|
# More targets defined than detected - match each detected face
|
||||||
for detected_face in detected_faces:
|
for detected_face in detected_faces:
|
||||||
if detected_face.normed_embedding is None: continue
|
if detected_face.normed_embedding is None:
|
||||||
|
continue
|
||||||
closest_idx, _ = find_closest_centroid(target_embeddings, detected_face.normed_embedding)
|
closest_idx, _ = find_closest_centroid(target_embeddings, detected_face.normed_embedding)
|
||||||
if 0 <= closest_idx < len(source_faces):
|
if 0 <= closest_idx < len(source_faces):
|
||||||
source_target_pairs.append((source_faces[closest_idx], detected_face))
|
source_target_pairs.append((source_faces[closest_idx], detected_face))
|
||||||
@@ -862,7 +870,8 @@ def process_frame_v2(temp_frame: Frame, temp_frame_path: str = "") -> Frame:
|
|||||||
# More faces detected than targets defined - match each target embedding to closest detected face
|
# More faces detected than targets defined - match each target embedding to closest detected face
|
||||||
detected_embeddings = [f.normed_embedding for f in detected_faces if f.normed_embedding is not None]
|
detected_embeddings = [f.normed_embedding for f in detected_faces if f.normed_embedding is not None]
|
||||||
detected_faces_with_embedding = [f for f in detected_faces if f.normed_embedding is not None]
|
detected_faces_with_embedding = [f for f in detected_faces if f.normed_embedding is not None]
|
||||||
if not detected_embeddings: return processed_frame # No embeddings to match
|
if not detected_embeddings:
|
||||||
|
return processed_frame # No embeddings to match
|
||||||
|
|
||||||
for i, target_embedding in enumerate(target_embeddings):
|
for i, target_embedding in enumerate(target_embeddings):
|
||||||
if 0 <= i < len(source_faces): # Ensure source face exists for this embedding
|
if 0 <= i < len(source_faces): # Ensure source face exists for this embedding
|
||||||
@@ -912,7 +921,7 @@ def process_frames(
|
|||||||
# Log the error but allow proceeding; subsequent check will stop processing.
|
# Log the error but allow proceeding; subsequent check will stop processing.
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
source_img = cv2.imread(source_path)
|
source_img = imread_unicode(source_path)
|
||||||
if source_img is None:
|
if source_img is None:
|
||||||
# Specific error for file reading failure
|
# Specific error for file reading failure
|
||||||
update_status(f"Error reading source image file {source_path}. Please check the path and file integrity.", NAME)
|
update_status(f"Error reading source image file {source_path}. Please check the path and file integrity.", NAME)
|
||||||
@@ -936,7 +945,7 @@ def process_frames(
|
|||||||
|
|
||||||
# --- Stop processing entirely if in Simple Mode and source face is invalid ---
|
# --- Stop processing entirely if in Simple Mode and source face is invalid ---
|
||||||
if not use_v2 and source_face is None:
|
if not use_v2 and source_face is None:
|
||||||
update_status(f"Halting video processing: Invalid or no face detected in source image for simple mode.", NAME)
|
update_status("Halting video processing: Invalid or no face detected in source image for simple mode.", NAME)
|
||||||
if progress:
|
if progress:
|
||||||
# Ensure the progress bar completes if it was started
|
# Ensure the progress bar completes if it was started
|
||||||
remaining_updates = total_frames - progress.n if hasattr(progress, 'n') else total_frames
|
remaining_updates = total_frames - progress.n if hasattr(progress, 'n') else total_frames
|
||||||
@@ -952,14 +961,16 @@ def process_frames(
|
|||||||
# Read the target frame
|
# Read the target frame
|
||||||
temp_frame = None
|
temp_frame = None
|
||||||
try:
|
try:
|
||||||
temp_frame = cv2.imread(temp_frame_path)
|
temp_frame = imread_unicode(temp_frame_path)
|
||||||
if temp_frame is None:
|
if temp_frame is None:
|
||||||
print(f"{NAME}: Error: Could not read frame: {temp_frame_path}, skipping.")
|
print(f"{NAME}: Error: Could not read frame: {temp_frame_path}, skipping.")
|
||||||
if progress: progress.update(1)
|
if progress:
|
||||||
|
progress.update(1)
|
||||||
continue # Skip this frame if read fails
|
continue # Skip this frame if read fails
|
||||||
except Exception as read_e:
|
except Exception as read_e:
|
||||||
print(f"{NAME}: Error reading frame {temp_frame_path}: {read_e}, skipping.")
|
print(f"{NAME}: Error reading frame {temp_frame_path}: {read_e}, skipping.")
|
||||||
if progress: progress.update(1)
|
if progress:
|
||||||
|
progress.update(1)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Select processing function and execute
|
# Select processing function and execute
|
||||||
@@ -988,7 +999,7 @@ def process_frames(
|
|||||||
# Write the result back to the same frame path with optimized compression
|
# Write the result back to the same frame path with optimized compression
|
||||||
try:
|
try:
|
||||||
# Use PNG compression level 3 (faster) instead of default 9
|
# Use PNG compression level 3 (faster) instead of default 9
|
||||||
write_success = cv2.imwrite(temp_frame_path, result_frame, [cv2.IMWRITE_PNG_COMPRESSION, 3])
|
write_success = imwrite_unicode(temp_frame_path, result_frame, [cv2.IMWRITE_PNG_COMPRESSION, 3])
|
||||||
if not write_success:
|
if not write_success:
|
||||||
print(f"{NAME}: Error: Failed to write processed frame to {temp_frame_path}")
|
print(f"{NAME}: Error: Failed to write processed frame to {temp_frame_path}")
|
||||||
except Exception as write_e:
|
except Exception as write_e:
|
||||||
@@ -1018,7 +1029,7 @@ def process_image(source_path: str, target_path: str, output_path: str) -> None:
|
|||||||
|
|
||||||
# Read target first
|
# Read target first
|
||||||
try:
|
try:
|
||||||
target_frame = cv2.imread(target_path)
|
target_frame = imread_unicode(target_path)
|
||||||
if target_frame is None:
|
if target_frame is None:
|
||||||
update_status(f"Error: Could not read target image: {target_path}", NAME)
|
update_status(f"Error: Could not read target image: {target_path}", NAME)
|
||||||
return
|
return
|
||||||
@@ -1037,7 +1048,7 @@ def process_image(source_path: str, target_path: str, output_path: str) -> None:
|
|||||||
|
|
||||||
else: # Simple mode
|
else: # Simple mode
|
||||||
try:
|
try:
|
||||||
source_img = cv2.imread(source_path)
|
source_img = imread_unicode(source_path)
|
||||||
if source_img is None:
|
if source_img is None:
|
||||||
update_status(f"Error: Could not read source image: {source_path}", NAME)
|
update_status(f"Error: Could not read source image: {source_path}", NAME)
|
||||||
return
|
return
|
||||||
@@ -1053,7 +1064,7 @@ def process_image(source_path: str, target_path: str, output_path: str) -> None:
|
|||||||
|
|
||||||
# Write the result if processing was successful
|
# Write the result if processing was successful
|
||||||
if result is not None:
|
if result is not None:
|
||||||
write_success = cv2.imwrite(output_path, result)
|
write_success = imwrite_unicode(output_path, result)
|
||||||
if write_success:
|
if write_success:
|
||||||
update_status(f"Output image saved to: {output_path}", NAME)
|
update_status(f"Output image saved to: {output_path}", NAME)
|
||||||
else:
|
else:
|
||||||
@@ -1496,7 +1507,8 @@ def apply_color_transfer(source, target):
|
|||||||
if len(source.shape) == 2: # Grayscale
|
if len(source.shape) == 2: # Grayscale
|
||||||
source = cv2.cvtColor(source, cv2.COLOR_GRAY2BGR)
|
source = cv2.cvtColor(source, cv2.COLOR_GRAY2BGR)
|
||||||
source = np.clip(source, 0, 255).astype(np.uint8)
|
source = np.clip(source, 0, 255).astype(np.uint8)
|
||||||
if len(source.shape)!= 3 or source.shape[2]!= 3: raise ValueError("Conversion failed")
|
if len(source.shape) != 3 or source.shape[2] != 3:
|
||||||
|
raise ValueError("Conversion failed")
|
||||||
except Exception:
|
except Exception:
|
||||||
return source
|
return source
|
||||||
if len(target.shape) != 3 or target.shape[2] != 3 or target.dtype != np.uint8:
|
if len(target.shape) != 3 or target.shape[2] != 3 or target.dtype != np.uint8:
|
||||||
@@ -1505,7 +1517,8 @@ def apply_color_transfer(source, target):
|
|||||||
if len(target.shape) == 2: # Grayscale
|
if len(target.shape) == 2: # Grayscale
|
||||||
target = cv2.cvtColor(target, cv2.COLOR_GRAY2BGR)
|
target = cv2.cvtColor(target, cv2.COLOR_GRAY2BGR)
|
||||||
target = np.clip(target, 0, 255).astype(np.uint8)
|
target = np.clip(target, 0, 255).astype(np.uint8)
|
||||||
if len(target.shape)!= 3 or target.shape[2]!= 3: raise ValueError("Conversion failed")
|
if len(target.shape) != 3 or target.shape[2] != 3:
|
||||||
|
raise ValueError("Conversion failed")
|
||||||
except Exception:
|
except Exception:
|
||||||
return source # Return original source if target invalid
|
return source # Return original source if target invalid
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
# Import the tkinter fix to patch the ScreenChanged error
|
# Import the tkinter fix to patch the ScreenChanged error (module patches Tk on import)
|
||||||
import tkinter_fix
|
import tkinter_fix # noqa: F401
|
||||||
|
|
||||||
import core
|
import core
|
||||||
|
|
||||||
|
|||||||
+5
-4
@@ -73,6 +73,7 @@ from modules.utilities import (
|
|||||||
is_image,
|
is_image,
|
||||||
is_video,
|
is_video,
|
||||||
)
|
)
|
||||||
|
from modules import imread_unicode
|
||||||
from modules.video_capture import VideoCapturer
|
from modules.video_capture import VideoCapturer
|
||||||
|
|
||||||
if platform.system() == "Windows":
|
if platform.system() == "Windows":
|
||||||
@@ -988,7 +989,7 @@ class PreviewWindow(QWidget):
|
|||||||
from modules.processors.frame.core import get_frame_processors_modules as _gfpm
|
from modules.processors.frame.core import get_frame_processors_modules as _gfpm
|
||||||
for fp in _gfpm(modules.globals.frame_processors):
|
for fp in _gfpm(modules.globals.frame_processors):
|
||||||
temp_frame = fp.process_frame(
|
temp_frame = fp.process_frame(
|
||||||
get_one_face(cv2.imread(modules.globals.source_path)), temp_frame
|
get_one_face(imread_unicode(modules.globals.source_path)), temp_frame
|
||||||
)
|
)
|
||||||
# Fit to current widget size while preserving aspect ratio.
|
# Fit to current widget size while preserving aspect ratio.
|
||||||
h, w = temp_frame.shape[:2]
|
h, w = temp_frame.shape[:2]
|
||||||
@@ -1071,7 +1072,7 @@ class _ProcessingWorker(QThread):
|
|||||||
and modules.globals.source_path != last_source_path
|
and modules.globals.source_path != last_source_path
|
||||||
):
|
):
|
||||||
last_source_path = modules.globals.source_path
|
last_source_path = modules.globals.source_path
|
||||||
source_image = get_one_face(cv2.imread(modules.globals.source_path))
|
source_image = get_one_face(imread_unicode(modules.globals.source_path))
|
||||||
|
|
||||||
det_count += 1
|
det_count += 1
|
||||||
if det_count % det_interval == 0:
|
if det_count % det_interval == 0:
|
||||||
@@ -1337,7 +1338,7 @@ class MapperDialog(QDialog):
|
|||||||
)
|
)
|
||||||
if not path:
|
if not path:
|
||||||
return
|
return
|
||||||
cv2_img = cv2.imread(path)
|
cv2_img = imread_unicode(path)
|
||||||
face = get_one_face(cv2_img)
|
face = get_one_face(cv2_img)
|
||||||
if face is None:
|
if face is None:
|
||||||
self.set_status("Face could not be detected in last upload!")
|
self.set_status("Face could not be detected in last upload!")
|
||||||
@@ -1442,7 +1443,7 @@ class LiveMapperDialog(QDialog):
|
|||||||
)
|
)
|
||||||
if not path:
|
if not path:
|
||||||
return
|
return
|
||||||
cv2_img = cv2.imread(path)
|
cv2_img = imread_unicode(path)
|
||||||
face = get_one_face(cv2_img)
|
face = get_one_face(cv2_img)
|
||||||
if face is None:
|
if face is None:
|
||||||
self.set_status("Face could not be detected in last upload!")
|
self.set_status("Face could not be detected in last upload!")
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import cv2
|
import cv2
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import sys
|
|
||||||
import time
|
import time
|
||||||
from typing import Optional, Tuple, Callable
|
from typing import Optional, Tuple, Callable
|
||||||
import platform
|
import platform
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
[tool.ruff]
|
||||||
|
target-version = "py310"
|
||||||
|
|
||||||
|
[tool.ruff.lint]
|
||||||
|
# Deterministic, low-risk rules enforced in CI. Other rules (F841, E402, F821)
|
||||||
|
# surface real findings but require human judgement to fix safely, so they are
|
||||||
|
# left out of the gate for now. Intentional side-effect imports should be
|
||||||
|
# annotated with `# noqa: F401`.
|
||||||
|
select = ["E701", "E711", "E712", "F401", "F541"]
|
||||||
+15
-14
@@ -1,17 +1,18 @@
|
|||||||
numpy>=1.23.5,<2
|
numpy>=2.0,<3
|
||||||
typing-extensions>=4.8.0
|
typing-extensions>=4.15.0
|
||||||
opencv-python==4.10.0.84
|
opencv-python==4.14.0.94
|
||||||
cv2_enumerate_cameras==1.1.15
|
opencv-python-headless==4.14.0.94
|
||||||
onnx==1.18.0
|
cv2_enumerate_cameras==1.3.3
|
||||||
|
onnx==1.22.0
|
||||||
insightface==0.7.3
|
insightface==0.7.3
|
||||||
psutil==5.9.8
|
psutil==7.2.2
|
||||||
PySide6>=6.7,<7
|
PySide6>=6.7,<7
|
||||||
pillow==12.1.1
|
pillow==12.3.0
|
||||||
tqdm>=4.65.0
|
tqdm>=4.66.3
|
||||||
onnxruntime-silicon==1.16.3; sys_platform == 'darwin' and platform_machine == 'arm64'
|
onnxruntime==1.28.0; sys_platform == 'darwin' and platform_machine == 'arm64'
|
||||||
onnxruntime-gpu==1.23.2; sys_platform != 'darwin'
|
onnxruntime==1.23.0; sys_platform == 'darwin' and platform_machine != 'arm64'
|
||||||
tensorflow>=2.15.0; sys_platform != 'darwin'
|
onnxruntime-gpu==1.26.0; sys_platform != 'darwin'
|
||||||
tensorflow>=2.15.0; sys_platform == 'darwin' and python_version < '3.13'
|
opennsfw2==0.18.0
|
||||||
opennsfw2==0.10.2
|
keras>=3.0.0
|
||||||
protobuf==4.25.1
|
protobuf>=6.33.5,<8
|
||||||
pygrabber; sys_platform == 'win32'
|
pygrabber; sys_platform == 'win32'
|
||||||
|
|||||||
@@ -31,6 +31,30 @@ if sys.platform == "win32":
|
|||||||
except (OSError, AttributeError):
|
except (OSError, AttributeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# On Windows, register OpenVINO DLL directories so onnxruntime's
|
||||||
|
# OpenVINOExecutionProvider can find openvino.dll. This must happen
|
||||||
|
# before any ONNX InferenceSession is created. Failure is non-fatal:
|
||||||
|
# OpenVINO simply isn't installed, and onnxruntime will fall back to CPU.
|
||||||
|
try:
|
||||||
|
from onnxruntime.tools.add_openvino_win_libs import ( # type: ignore[import-untyped] # noqa: E501
|
||||||
|
add_openvino_libs_to_path,
|
||||||
|
)
|
||||||
|
add_openvino_libs_to_path()
|
||||||
|
except ImportError:
|
||||||
|
# onnxruntime build without the OpenVINO tooling module — no-op.
|
||||||
|
pass
|
||||||
|
except FileNotFoundError:
|
||||||
|
# OpenVINO site-packages dir absent — no-op.
|
||||||
|
pass
|
||||||
|
except SystemExit as exc:
|
||||||
|
# add_openvino_libs_to_path() calls sys.exit() when OpenVINO libs
|
||||||
|
# can't be located (e.g. OPENVINO_LIB_PATHS unset). Log the message
|
||||||
|
# it raised with so the failure is visible, but keep startup alive.
|
||||||
|
print(
|
||||||
|
f"[startup] OpenVINO DLL registration skipped: {exc}",
|
||||||
|
flush=True,
|
||||||
|
)
|
||||||
|
|
||||||
# On Linux, pre-load NVIDIA shared libraries (cuDNN, cuBLAS, nvrtc...) shipped
|
# On Linux, pre-load NVIDIA shared libraries (cuDNN, cuBLAS, nvrtc...) shipped
|
||||||
# inside the venv via pip wheels (nvidia-cudnn-cu12, etc.). LD_LIBRARY_PATH
|
# inside the venv via pip wheels (nvidia-cudnn-cu12, etc.). LD_LIBRARY_PATH
|
||||||
# cannot be set after Python starts, so we use ctypes.CDLL with RTLD_GLOBAL
|
# cannot be set after Python starts, so we use ctypes.CDLL with RTLD_GLOBAL
|
||||||
|
|||||||
Reference in New Issue
Block a user