Compare commits
217 Commits
Deep-Live-Cam
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| d9a5500bdf | |||
| 86134b6e1d | |||
| 9e6f30c0a4 | |||
| 97321a740d | |||
| f5f7ac7764 | |||
| 77d3492eef | |||
| 8e3d6e7c65 | |||
| ee9699ee70 | |||
| 3c8b259a3f | |||
| 30b27c2b71 | |||
| 0d8f3b1f82 | |||
| 6e9e7addf2 | |||
| 0c7e871bfc | |||
| e340b0da8a | |||
| d0f81ed755 | |||
| de01b28802 | |||
| b645d5e60b | |||
| 31b3a97003 | |||
| e3b46e83b7 | |||
| e93fb95903 | |||
| aabf41050a | |||
| e57116de68 | |||
| d5338a3eae | |||
| 7ec3a4be29 | |||
| ca6cba9311 | |||
| d89385457e | |||
| b015f0099f | |||
| e56a79222e | |||
| 5b0bf735b5 | |||
| c02bd519d8 | |||
| 36bb1a29b0 | |||
| 2bbc150bfb | |||
| a1722c7b2e | |||
| 07b4d66965 | |||
| ff7cc3ac2f | |||
| f0ec0744f7 | |||
| 36b6ea0019 | |||
| 523ee53c34 | |||
| e544889805 | |||
| c6524facfb | |||
| 91baa6c0a5 | |||
| a4c617af3e | |||
| 9a33f5e184 | |||
| 2b36300b8c | |||
| 21c029f51e | |||
| 06bc8f2152 | |||
| 63b90c428e | |||
| df8e8b427e | |||
| dfd145b996 | |||
| b3c4ed9250 | |||
| 2411f1e9b1 | |||
| 96224efe07 | |||
| 8e05142cda | |||
| a007db2ffa | |||
| 475740b22b | |||
| 600ce34c8d | |||
| 865ab3ca02 | |||
| 178578b034 | |||
| b53132f3a4 | |||
| 00da11b491 | |||
| b82fdc3f31 | |||
| 3ffa9f38b0 | |||
| 3f98d4c826 | |||
| 9b6ca286b9 | |||
| 28c60b69d1 | |||
| fcf547d7d2 | |||
| ae2d21456d | |||
| 0999c0447e | |||
| f9270c5d1c | |||
| fdbc29c1a9 | |||
| 87d982e6f8 | |||
| cf47dabf0e | |||
| d0d90ecc03 | |||
| 2b70131e6a | |||
| fc86365a90 | |||
| 1dd0e8e509 | |||
| 4e0ff540f0 | |||
| f0fae811d8 | |||
| 42687f5bd9 | |||
| 9086072b8e | |||
| 12fda0a3ed | |||
| d963430854 | |||
| 5855d15c09 | |||
| fcc73d0add | |||
| 8d4a386a27 | |||
| b98c5234d8 | |||
| 8bdc14a789 | |||
| f121083bc8 | |||
| 745d449ca6 | |||
| ec6d7d2995 | |||
| e791f2f18a | |||
| 3795e41fd7 | |||
| ab8a1c82c1 | |||
| e1842ae0ba | |||
| 989106e914 | |||
| de27fb8a81 | |||
| 28109e93bb | |||
| fc312516e3 | |||
| 72049f3e91 | |||
| 6cb5de01f8 | |||
| 0bcf340217 | |||
| 994a63c546 | |||
| d5a3fb0c47 | |||
| 9690070399 | |||
| f3e83b985c | |||
| e3e3638b79 | |||
| 4a7874a968 | |||
| 75122da389 | |||
| 7063bba4b3 | |||
| bdbd7dcfbc | |||
| a64940def7 | |||
| fe4a87e8f2 | |||
| 9ecd2dab83 | |||
| c9f36eb350 | |||
| b1f610d432 | |||
| d86c36dc47 | |||
| 647c5f250f | |||
| ae88412aae | |||
| b7e011f5e7 | |||
| 532e7c05ee | |||
| 267a273cb2 | |||
| 938aa9eaf1 | |||
| 37bac27302 | |||
| 84836932e6 | |||
| e879d2ca64 | |||
| 181144ce33 | |||
| 890beb0eae | |||
| 75b5b096d6 | |||
| 40e47a469c | |||
| 874abb4e59 | |||
| 18b259da70 | |||
| 01900dcfb5 | |||
| 07e30fe781 | |||
| 3dda4f2179 | |||
| 71735e4f60 | |||
| 90d5c28542 | |||
| 104d8cf4d6 | |||
| ac3696b69d | |||
| 76fb209e6c | |||
| 2dcd552c4b | |||
| 66248a37b4 | |||
| aa9b7ed3b6 | |||
| 51a4246050 | |||
| 3f1c072fac | |||
| f91f9203e7 | |||
| 80477676b4 | |||
| c728994e6b | |||
| 65da3be2a4 | |||
| 390b88216b | |||
| dabaa64695 | |||
| 1fad1cd43a | |||
| 2f67e2f159 | |||
| a3af249ea6 | |||
| 5bc3ada632 | |||
| 650e89eb21 | |||
| 4d2aea37b7 | |||
| 28c4b34db1 | |||
| 49e8f78513 | |||
| d753f5d4b0 | |||
| 4fb69476d8 | |||
| f3adfd194d | |||
| e5f04cf917 | |||
| 67394a3157 | |||
| 186d155e1b | |||
| 87081e78d0 | |||
| f79373d4db | |||
| 513e413956 | |||
| f82cebf86e | |||
| d45dedc9a6 | |||
| 2d489b57ec | |||
| ccc04983cf | |||
| 2506c5a261 | |||
| e862ff1456 | |||
| db594c0e7c | |||
| 6a5b75ec45 | |||
| 79e1ce5093 | |||
| fda4878bfd | |||
| 5ff922e2a4 | |||
| 9ed5a72289 | |||
| 0c8e2d5794 | |||
| a0aafbc97c | |||
| f95b07423b | |||
| 3947053c89 | |||
| 0e6a6f84f5 | |||
| bb331a6db0 | |||
| ec48b0048f | |||
| acc4812551 | |||
| 87ee05d7b3 | |||
| ce03dbf200 | |||
| 704aeb73b1 | |||
| f5c8290e1c | |||
| f164d9234b | |||
| 74009c1d5d | |||
| e6a1c8dd95 | |||
| 0e3f2c8dc0 | |||
| 464dc2a0aa | |||
| a05754fb28 | |||
| 9727f34923 | |||
| a86544a4b4 | |||
| 979da7aa1d | |||
| 4a37bb2a97 | |||
| 21d3c8766a | |||
| ee19c5158a | |||
| 693c9bb268 | |||
| 5132f86cdc | |||
| cab2efa200 | |||
| 6e29e4061b | |||
| 2a7ae010a8 | |||
| a834811974 | |||
| d2aaf46e69 | |||
| d07d4a6a26 | |||
| 09f0343639 | |||
| 75913c513e | |||
| 7f38539508 | |||
| b38831dfdf | |||
| b518f4337d | |||
| aed933c1db |
@@ -25,3 +25,5 @@ models/DMDNet.pth
|
||||
faceswap/
|
||||
.vscode/
|
||||
switch_states.json
|
||||
/models
|
||||
install.bat
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<h1 align="center">Deep-Live-Cam</h1>
|
||||
<h1 align="center">Deep-Live-Cam 2.1</h1>
|
||||
|
||||
<p align="center">
|
||||
Real-time face swap and video deepfake with a single click and only a single image.
|
||||
@@ -9,37 +9,96 @@
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="media/demo.gif" alt="Demo GIF">
|
||||
<img src="media/avgpcperformancedemo.gif" alt="Performance Demo GIF">
|
||||
<img src="media/demo.gif" alt="Demo GIF" width="800">
|
||||
</p>
|
||||
|
||||
|
||||
## Disclaimer
|
||||
|
||||
This software is intended as a productive contribution to the AI-generated media industry. It aims to assist artists with tasks like animating custom characters or using them as models for clothing, etc.
|
||||
This deepfake software is designed to be a productive tool for the AI-generated media industry. It can assist artists in animating custom characters, creating engaging content, and even using models for clothing design.
|
||||
|
||||
We are aware of the potential for unethical applications and are committed to preventative measures. A built-in check prevents the program from processing inappropriate media (nudity, graphic content, sensitive material like war footage, etc.). We will continue to develop this project responsibly, adhering to law and ethics. We may shut down the project or add watermarks if legally required.
|
||||
We are aware of the potential for unethical applications and are committed to preventative measures. A built-in check prevents the program from processing inappropriate media (nudity, graphic content, sensitive material like war footage, etc.). We will continue to develop this project responsibly, adhering to the law and ethics. We may shut down the project or add watermarks if legally required.
|
||||
|
||||
- Ethical Use: 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.
|
||||
|
||||
- Content Restrictions: The software includes built-in checks to prevent processing inappropriate media, such as nudity, graphic content, or sensitive material.
|
||||
|
||||
- Legal Compliance: We adhere to all relevant laws and ethical guidelines. If legally required, we may shut down the project or add watermarks to the output.
|
||||
|
||||
- User Responsibility: We are not responsible for end-user actions. Users must ensure their use of the software aligns with ethical standards and legal requirements.
|
||||
|
||||
By using this software, you agree to these terms and commit to using it in a manner that respects the rights and dignity of others.
|
||||
|
||||
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)
|
||||
|
||||
## Quick Start - Download Prebuilt
|
||||
<div style="margin: 28px 0;">
|
||||
<div style="margin-bottom: 20px;">
|
||||
<a href="https://hacksider.gumroad.com/l/vccdmm" target="_blank">
|
||||
<img src="https://github.com/user-attachments/assets/c702bb7d-d9c0-466a-9ad2-02849294e540" alt="Download Button 1" style="width: 280px; display: block;">
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<a href="https://krshh.gumroad.com/l/Deep-Live-Cam-Mac" target="_blank">
|
||||
<img src="https://github.com/user-attachments/assets/9a302750-2d54-457d-bdc8-6ed7c6af0e1a" alt="Download Button 2" style="width: 280px; display: block;">
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<a href="https://deeplivecam.net/index.php/quickstart"> <img src="media/Download.png" width="285" height="77" />
|
||||
|
||||
##### 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.
|
||||
|
||||
###### 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.
|
||||
|
||||
## TLDR; Live Deepfake in just 3 Clicks
|
||||

|
||||
1. Select a face
|
||||
2. Select which camera to use
|
||||
3. Press live!
|
||||
|
||||
## Features & Uses - Everything is in real-time
|
||||
|
||||
### Mouth Mask
|
||||
|
||||
**Retain your original mouth for accurate movement using Mouth Mask**
|
||||
|
||||
<p align="center">
|
||||
<img src="media/ludwig.gif" alt="resizable-gif">
|
||||
</p>
|
||||
|
||||
### Face Mapping
|
||||
|
||||
**Use different faces on multiple subjects simultaneously**
|
||||
|
||||
<p align="center">
|
||||
<img src="media/streamers.gif" alt="face_mapping_source">
|
||||
</p>
|
||||
|
||||
### Your Movie, Your Face
|
||||
|
||||
**Watch movies with any face in real-time**
|
||||
|
||||
<p align="center">
|
||||
<img src="media/movie.gif" alt="movie">
|
||||
</p>
|
||||
|
||||
### Live Show
|
||||
|
||||
**Run Live shows and performances**
|
||||
|
||||
<p align="center">
|
||||
<img src="media/live_show.gif" alt="show">
|
||||
</p>
|
||||
|
||||
### Memes
|
||||
|
||||
**Create Your Most Viral Meme Yet**
|
||||
|
||||
<p align="center">
|
||||
<img src="media/meme.gif" alt="show" width="450">
|
||||
<br>
|
||||
<sub>Created using Many Faces feature in Deep-Live-Cam</sub>
|
||||
</p>
|
||||
|
||||
### Omegle
|
||||
|
||||
**Surprise people on Omegle**
|
||||
|
||||
<p align="center">
|
||||
<video src="https://github.com/user-attachments/assets/2e9b9b82-fa04-4b70-9f56-b1f68e7672d0" width="450" controls></video>
|
||||
</p>
|
||||
|
||||
## Installation (Manual)
|
||||
**Please be aware that the installation needs technical skills and is not for beginners, consider downloading the prebuilt.**
|
||||
|
||||
**Please be aware that the installation requires technical skills and is not for beginners. Consider downloading the quickstart version.**
|
||||
|
||||
<details>
|
||||
<summary>Click to see the process</summary>
|
||||
@@ -50,22 +109,23 @@ This is more likely to work on your computer but will be slower as it utilizes t
|
||||
|
||||
**1. Set up Your Platform**
|
||||
|
||||
- Python (3.10 recommended)
|
||||
- Python (3.11 recommended)
|
||||
- pip
|
||||
- git
|
||||
- [ffmpeg](https://www.youtube.com/watch?v=OlNWCpFdVMA)
|
||||
- [ffmpeg](https://www.youtube.com/watch?v=OlNWCpFdVMA) - ```iex (irm ffmpeg.tc.ht)```
|
||||
- [Visual Studio 2022 Runtimes (Windows)](https://visualstudio.microsoft.com/visual-cpp-build-tools/)
|
||||
|
||||
**2. Clone Repository**
|
||||
**2. Clone the Repository**
|
||||
|
||||
```bash
|
||||
https://github.com/hacksider/Deep-Live-Cam.git
|
||||
git clone https://github.com/hacksider/Deep-Live-Cam.git
|
||||
cd Deep-Live-Cam
|
||||
```
|
||||
|
||||
**3. Download Models**
|
||||
**3. Download the Models**
|
||||
|
||||
1. [GFPGANv1.4](https://huggingface.co/hacksider/deep-live-cam/resolve/main/GFPGANv1.4.pth)
|
||||
2. [inswapper_128_fp16.onnx](https://huggingface.co/hacksider/deep-live-cam/resolve/main/inswapper_128.onnx) (Note: Use this [replacement version](https://github.com/facefusion/facefusion-assets/releases/download/models/inswapper_128.onnx) if you encounter issues)
|
||||
1. [GFPGANv1.4](https://huggingface.co/hacksider/deep-live-cam/resolve/main/GFPGANv1.4.onnx)
|
||||
2. [inswapper\_128\_fp16.onnx](https://huggingface.co/hacksider/deep-live-cam/resolve/main/inswapper_128_fp16.onnx)
|
||||
|
||||
Place these files in the "**models**" folder.
|
||||
|
||||
@@ -73,54 +133,129 @@ Place these files in the "**models**" folder.
|
||||
|
||||
We highly recommend using a `venv` to avoid issues.
|
||||
|
||||
|
||||
For Windows:
|
||||
```bash
|
||||
python -m venv venv
|
||||
venv\Scripts\activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
For Linux:
|
||||
```bash
|
||||
# Ensure you use the installed Python 3.10
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
**For macOS:** Install or upgrade the `python-tk` package:
|
||||
**For macOS:**
|
||||
|
||||
Apple Silicon (M1/M2/M3) requires specific setup:
|
||||
|
||||
```bash
|
||||
# Install Python 3.11 (specific version is important)
|
||||
brew install python@3.11
|
||||
|
||||
# Install tkinter package (required for the GUI)
|
||||
brew install python-tk@3.10
|
||||
|
||||
# Create and activate virtual environment with Python 3.11
|
||||
python3.11 -m venv venv
|
||||
source venv/bin/activate
|
||||
|
||||
# Install dependencies
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
** In case something goes wrong and you need to reinstall the virtual environment **
|
||||
|
||||
```bash
|
||||
# Deactivate the virtual environment
|
||||
rm -rf venv
|
||||
|
||||
# Reinstall the virtual environment
|
||||
python -m venv venv
|
||||
source venv/bin/activate
|
||||
|
||||
# install the dependencies again
|
||||
pip install -r requirements.txt
|
||||
|
||||
# gfpgan and basicsrs issue fix
|
||||
pip install git+https://github.com/xinntao/BasicSR.git@master
|
||||
pip uninstall gfpgan -y
|
||||
pip install git+https://github.com/TencentARC/GFPGAN.git@master
|
||||
```
|
||||
|
||||
**Run:** If you don't have a GPU, you can run Deep-Live-Cam using `python run.py`. Note that initial execution will download models (~300MB).
|
||||
|
||||
|
||||
### GPU Acceleration
|
||||
|
||||
**CUDA Execution Provider (Nvidia)**
|
||||
|
||||
1. Install [CUDA Toolkit 11.8](https://developer.nvidia.com/cuda-11-8-0-download-archive) or [CUDA Toolkit 12.1.1](https://developer.nvidia.com/cuda-12-1-1-download-archive)
|
||||
2. Install dependencies:
|
||||
1. Install [CUDA Toolkit 12.8.0](https://developer.nvidia.com/cuda-12-8-0-download-archive)
|
||||
2. Install [cuDNN v8.9.7 for CUDA 12.x](https://developer.nvidia.com/rdp/cudnn-archive) (required for onnxruntime-gpu):
|
||||
- Download cuDNN v8.9.7 for CUDA 12.x
|
||||
- Make sure the cuDNN bin directory is in your system PATH
|
||||
3. Install dependencies:
|
||||
|
||||
```bash
|
||||
pip install -U torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu128
|
||||
pip uninstall onnxruntime onnxruntime-gpu
|
||||
pip install onnxruntime-gpu==1.16.3
|
||||
pip install onnxruntime-gpu==1.21.0
|
||||
```
|
||||
|
||||
3. Usage:
|
||||
|
||||
```bash
|
||||
python run.py --execution-provider cuda
|
||||
```
|
||||
|
||||
**CoreML Execution Provider (Apple Silicon)**
|
||||
|
||||
1. Install dependencies:
|
||||
Apple Silicon (M1/M2/M3) specific installation:
|
||||
|
||||
1. Make sure you've completed the macOS setup above using Python 3.10.
|
||||
2. Install dependencies:
|
||||
|
||||
```bash
|
||||
pip uninstall onnxruntime onnxruntime-silicon
|
||||
pip install onnxruntime-silicon==1.13.1
|
||||
```
|
||||
2. Usage:
|
||||
|
||||
3. Usage (important: specify Python 3.10):
|
||||
|
||||
```bash
|
||||
python run.py --execution-provider coreml
|
||||
python3.10 run.py --execution-provider coreml
|
||||
```
|
||||
|
||||
**Important Notes for macOS:**
|
||||
- You **must** use Python 3.10, not newer versions like 3.11 or 3.13
|
||||
- Always run with `python3.10` 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.10`
|
||||
- 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:
|
||||
```bash
|
||||
# List all installed Python versions
|
||||
brew list | grep python
|
||||
|
||||
# Uninstall conflicting versions if needed
|
||||
brew uninstall --ignore-dependencies python@3.11 python@3.13
|
||||
|
||||
# Keep only Python 3.11
|
||||
brew cleanup
|
||||
```
|
||||
|
||||
**CoreML Execution Provider (Apple Legacy)**
|
||||
|
||||
1. Install dependencies:
|
||||
|
||||
```bash
|
||||
pip uninstall onnxruntime onnxruntime-coreml
|
||||
pip install onnxruntime-coreml==1.13.1
|
||||
pip install onnxruntime-coreml==1.21.0
|
||||
```
|
||||
|
||||
2. Usage:
|
||||
|
||||
```bash
|
||||
python run.py --execution-provider coreml
|
||||
```
|
||||
@@ -128,11 +263,14 @@ python run.py --execution-provider coreml
|
||||
**DirectML Execution Provider (Windows)**
|
||||
|
||||
1. Install dependencies:
|
||||
|
||||
```bash
|
||||
pip uninstall onnxruntime onnxruntime-directml
|
||||
pip install onnxruntime-directml==1.15.1
|
||||
pip install onnxruntime-directml==1.21.0
|
||||
```
|
||||
|
||||
2. Usage:
|
||||
|
||||
```bash
|
||||
python run.py --execution-provider directml
|
||||
```
|
||||
@@ -140,18 +278,19 @@ python run.py --execution-provider directml
|
||||
**OpenVINO™ Execution Provider (Intel)**
|
||||
|
||||
1. Install dependencies:
|
||||
|
||||
```bash
|
||||
pip uninstall onnxruntime onnxruntime-openvino
|
||||
pip install onnxruntime-openvino==1.15.0
|
||||
pip install onnxruntime-openvino==1.21.0
|
||||
```
|
||||
|
||||
2. Usage:
|
||||
|
||||
```bash
|
||||
python run.py --execution-provider openvino
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
**1. Image/Video Mode**
|
||||
@@ -170,32 +309,8 @@ python run.py --execution-provider openvino
|
||||
- Use a screen capture tool like OBS to stream.
|
||||
- To change the face, select a new source image.
|
||||
|
||||
## Features - Everything is realtime
|
||||
|
||||
### Mouth Mask
|
||||
|
||||
**Retain your original mouth using Mouth Mask**
|
||||
|
||||

|
||||
|
||||
### Face Mapping
|
||||
|
||||
**Use different faces on multiple subjects**
|
||||
|
||||

|
||||
|
||||
### Your Movie, Your Face
|
||||
|
||||
**Watch movies with any face in realtime**
|
||||
|
||||

|
||||
|
||||
|
||||
## Benchmarks
|
||||
|
||||
**Nearly 0% detection!**
|
||||
|
||||

|
||||
## Download all models in this huggingface link
|
||||
- [**Download models here**](https://huggingface.co/hacksider/deep-live-cam/tree/main)
|
||||
|
||||
## Command Line Arguments (Unmaintained)
|
||||
|
||||
@@ -212,7 +327,6 @@ options:
|
||||
--many-faces process every face
|
||||
--map-faces map source target faces
|
||||
--mouth-mask mask the mouth region
|
||||
--nsfw-filter filter the NSFW image or video
|
||||
--video-encoder {libx264,libx265,libvpx-vp9} adjust output video encoder
|
||||
--video-quality [0-51] adjust output video quality
|
||||
--live-mirror the live camera display as you see it in the front-facing camera frame
|
||||
@@ -225,42 +339,42 @@ options:
|
||||
|
||||
Looking for a CLI mode? Using the -s/--source argument will make the run program in cli mode.
|
||||
|
||||
|
||||
## Press
|
||||
**We are always open to criticism and ready to improve, that's why we didn't cherrypick anything.**
|
||||
|
||||
- [*"Deep-Live-Cam goes viral, allowing anyone to become a digital doppelganger"*](https://arstechnica.com/information-technology/2024/08/new-ai-tool-enables-real-time-face-swapping-on-webcams-raising-fraud-concerns/) - Ars Technica
|
||||
- [*"Thanks Deep Live Cam, shapeshifters are among us now"*](https://dataconomy.com/2024/08/15/what-is-deep-live-cam-github-deepfake/) - Dataconomy
|
||||
- [*"This free AI tool lets you become anyone during video-calls"*](https://www.newsbytesapp.com/news/science/deep-live-cam-ai-impersonation-tool-goes-viral/story) - NewsBytes
|
||||
- [*"OK, this viral AI live stream software is truly terrifying"*](https://www.creativebloq.com/ai/ok-this-viral-ai-live-stream-software-is-truly-terrifying) - Creative Bloq
|
||||
- [*"Deepfake AI Tool Lets You Become Anyone in a Video Call With Single Photo"*](https://petapixel.com/2024/08/14/deep-live-cam-deepfake-ai-tool-lets-you-become-anyone-in-a-video-call-with-single-photo-mark-zuckerberg-jd-vance-elon-musk/) - PetaPixel
|
||||
- [*"Deep-Live-Cam Uses AI to Transform Your Face in Real-Time, Celebrities Included"*](https://www.techeblog.com/deep-live-cam-ai-transform-face/) - TechEBlog
|
||||
- [*"An AI tool that "makes you look like anyone" during a video call is going viral online"*](https://telegrafi.com/en/a-tool-that-makes-you-look-like-anyone-during-a-video-call-is-going-viral-on-the-Internet/) - Telegrafi
|
||||
- [*"This Deepfake Tool Turning Images Into Livestreams is Topping the GitHub Charts"*](https://decrypt.co/244565/this-deepfake-tool-turning-images-into-livestreams-is-topping-the-github-charts) - Emerge
|
||||
- [*"New Real-Time Face-Swapping AI Allows Anyone to Mimic Famous Faces"*](https://www.digitalmusicnews.com/2024/08/15/face-swapping-ai-real-time-mimic/) - Digital Music News
|
||||
- [*"This real-time webcam deepfake tool raises alarms about the future of identity theft"*](https://www.diyphotography.net/this-real-time-webcam-deepfake-tool-raises-alarms-about-the-future-of-identity-theft/) - DIYPhotography
|
||||
- [*"That's Crazy, Oh God. That's Fucking Freaky Dude... That's So Wild Dude"*](https://www.youtube.com/watch?time_continue=1074&v=py4Tc-Y8BcY) - SomeOrdinaryGamers
|
||||
- [*"Alright look look look, now look chat, we can do any face we want to look like chat"*](https://www.youtube.com/live/mFsCe7AIxq8?feature=shared&t=2686) - IShowSpeed
|
||||
- [**Ars Technica**](https://arstechnica.com/information-technology/2024/08/new-ai-tool-enables-real-time-face-swapping-on-webcams-raising-fraud-concerns/) - *"Deep-Live-Cam goes viral, allowing anyone to become a digital doppelganger"*
|
||||
- [**Yahoo!**](https://www.yahoo.com/tech/ok-viral-ai-live-stream-080041056.html) - *"OK, this viral AI live stream software is truly terrifying"*
|
||||
- [**CNN Brasil**](https://www.cnnbrasil.com.br/tecnologia/ia-consegue-clonar-rostos-na-webcam-entenda-funcionamento/) - *"AI can clone faces on webcam; understand how it works"*
|
||||
- [**Bloomberg Technoz**](https://www.bloombergtechnoz.com/detail-news/71032/kenalan-dengan-teknologi-deep-live-cam-bisa-jadi-alat-menipu) - *"Get to know Deep Live Cam technology, it can be used as a tool for deception."*
|
||||
- [**TrendMicro**](https://www.trendmicro.com/vinfo/gb/security/news/cyber-attacks/ai-vs-ai-deepfakes-and-ekyc) - *"AI vs AI: DeepFakes and eKYC"*
|
||||
- [**PetaPixel**](https://petapixel.com/2024/08/14/deep-live-cam-deepfake-ai-tool-lets-you-become-anyone-in-a-video-call-with-single-photo-mark-zuckerberg-jd-vance-elon-musk/) - *"Deepfake AI Tool Lets You Become Anyone in a Video Call With Single Photo"*
|
||||
- [**SomeOrdinaryGamers**](https://www.youtube.com/watch?time_continue=1074&v=py4Tc-Y8BcY) - *"That's Crazy, Oh God. That's Fucking Freaky Dude... That's So Wild Dude"*
|
||||
- [**IShowSpeed**](https://www.youtube.com/live/mFsCe7AIxq8?feature=shared&t=2686) - *"Alright look look look, now look chat, we can do any face we want to look like chat"*
|
||||
- [**TechLinked (Linus Tech Tips)**](https://www.youtube.com/watch?v=wnCghLjqv3s&t=551s) - *"They do a pretty good job matching poses, expression and even the lighting"*
|
||||
- [**IShowSpeed**](https://youtu.be/JbUPRmXRUtE?t=3964) - *"What the F***! Why do I look like Vinny Jr? I look exactly like Vinny Jr!? No, this shit is crazy! Bro This is F*** Crazy!"*
|
||||
|
||||
|
||||
## Credits
|
||||
|
||||
- [ffmpeg](https://ffmpeg.org/): for making video related operations easy
|
||||
- [ffmpeg](https://ffmpeg.org/): for making video-related operations easy
|
||||
- [Henry](https://github.com/henryruhs): One of the major contributor in this repo
|
||||
- [deepinsight](https://github.com/deepinsight): for their [insightface](https://github.com/deepinsight/insightface) project which provided a well-made library and models. Please be reminded that the [use of the model is for non-commercial research purposes only](https://github.com/deepinsight/insightface?tab=readme-ov-file#license).
|
||||
- [havok2-htwo](https://github.com/havok2-htwo): for sharing the code for webcam
|
||||
- [GosuDRM](https://github.com/GosuDRM) : for open version of roop
|
||||
- [GosuDRM](https://github.com/GosuDRM): for the open version of roop
|
||||
- [pereiraroland26](https://github.com/pereiraroland26): Multiple faces support
|
||||
- [vic4key](https://github.com/vic4key) : For supporting/contributing on this project
|
||||
- [KRSHH](https://github.com/KRSHH) : For his contributions
|
||||
- [vic4key](https://github.com/vic4key): For supporting/contributing to this project
|
||||
- [kier007](https://github.com/kier007): for improving the user experience
|
||||
- [qitianai](https://github.com/qitianai): for multi-lingual support
|
||||
- [laurigates](https://github.com/laurigates): Decoupling stuffs to make everything faster!
|
||||
- and [all developers](https://github.com/hacksider/Deep-Live-Cam/graphs/contributors) behind libraries used in this project.
|
||||
- Foot Note: Please be informed that the base author of the code is [s0md3v](https://github.com/s0md3v/roop)
|
||||
- All the wonderful users who helped making this project go viral by starring the repo ❤️
|
||||
- Footnote: Please be informed that the base author of the code is [s0md3v](https://github.com/s0md3v/roop)
|
||||
- All the wonderful users who helped make this project go viral by starring the repo ❤️
|
||||
|
||||
[](https://github.com/hacksider/Deep-Live-Cam/stargazers)
|
||||
|
||||
## Contributions
|
||||
|
||||

|
||||
|
||||
## Stars to the Moon 🚀
|
||||
|
||||
<a href="https://star-history.com/#hacksider/deep-live-cam&Date">
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"Source x Target Mapper": "Quelle x Ziel Zuordnung",
|
||||
"select a source image": "Wähle ein Quellbild",
|
||||
"Preview": "Vorschau",
|
||||
"select a target image or video": "Wähle ein Zielbild oder Video",
|
||||
"save image output file": "Bildausgabedatei speichern",
|
||||
"save video output file": "Videoausgabedatei speichern",
|
||||
"select a target image": "Wähle ein Zielbild",
|
||||
"source": "Quelle",
|
||||
"Select a target": "Wähle ein Ziel",
|
||||
"Select a face": "Wähle ein Gesicht",
|
||||
"Keep audio": "Audio beibehalten",
|
||||
"Face Enhancer": "Gesichtsverbesserung",
|
||||
"Many faces": "Mehrere Gesichter",
|
||||
"Show FPS": "FPS anzeigen",
|
||||
"Keep fps": "FPS beibehalten",
|
||||
"Keep frames": "Frames beibehalten",
|
||||
"Fix Blueish Cam": "Bläuliche Kamera korrigieren",
|
||||
"Mouth Mask": "Mundmaske",
|
||||
"Show Mouth Mask Box": "Mundmaskenrahmen anzeigen",
|
||||
"Start": "Starten",
|
||||
"Live": "Live",
|
||||
"Destroy": "Beenden",
|
||||
"Map faces": "Gesichter zuordnen",
|
||||
"Processing...": "Verarbeitung läuft...",
|
||||
"Processing succeed!": "Verarbeitung erfolgreich!",
|
||||
"Processing ignored!": "Verarbeitung ignoriert!",
|
||||
"Failed to start camera": "Kamera konnte nicht gestartet werden",
|
||||
"Please complete pop-up or close it.": "Bitte das Pop-up komplettieren oder schließen.",
|
||||
"Getting unique faces": "Einzigartige Gesichter erfassen",
|
||||
"Please select a source image first": "Bitte zuerst ein Quellbild auswählen",
|
||||
"No faces found in target": "Keine Gesichter im Zielbild gefunden",
|
||||
"Add": "Hinzufügen",
|
||||
"Clear": "Löschen",
|
||||
"Submit": "Absenden",
|
||||
"Select source image": "Quellbild auswählen",
|
||||
"Select target image": "Zielbild auswählen",
|
||||
"Please provide mapping!": "Bitte eine Zuordnung angeben!",
|
||||
"At least 1 source with target is required!": "Mindestens eine Quelle mit einem Ziel ist erforderlich!",
|
||||
"At least 1 source with target is required!": "Mindestens eine Quelle mit einem Ziel ist erforderlich!",
|
||||
"Face could not be detected in last upload!": "Im letzten Upload konnte kein Gesicht erkannt werden!",
|
||||
"Select Camera:": "Kamera auswählen:",
|
||||
"All mappings cleared!": "Alle Zuordnungen gelöscht!",
|
||||
"Mappings successfully submitted!": "Zuordnungen erfolgreich übermittelt!",
|
||||
"Source x Target Mapper is already open.": "Quell-zu-Ziel-Zuordnung ist bereits geöffnet."
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"Source x Target Mapper": "Mapeador de fuente x destino",
|
||||
"select a source image": "Seleccionar imagen fuente",
|
||||
"Preview": "Vista previa",
|
||||
"select a target image or video": "elegir un video o una imagen fuente",
|
||||
"save image output file": "guardar imagen final",
|
||||
"save video output file": "guardar video final",
|
||||
"select a target image": "elegir una imagen objetiva",
|
||||
"source": "fuente",
|
||||
"Select a target": "Elegir un destino",
|
||||
"Select a face": "Elegir una cara",
|
||||
"Keep audio": "Mantener audio original",
|
||||
"Face Enhancer": "Potenciador de caras",
|
||||
"Many faces": "Varias caras",
|
||||
"Show FPS": "Mostrar fps",
|
||||
"Keep fps": "Mantener fps",
|
||||
"Keep frames": "Mantener frames",
|
||||
"Fix Blueish Cam": "Corregir tono azul de video",
|
||||
"Mouth Mask": "Máscara de boca",
|
||||
"Show Mouth Mask Box": "Mostrar área de la máscara de boca",
|
||||
"Start": "Iniciar",
|
||||
"Live": "En vivo",
|
||||
"Destroy": "Borrar",
|
||||
"Map faces": "Mapear caras",
|
||||
"Processing...": "Procesando...",
|
||||
"Processing succeed!": "¡Proceso terminado con éxito!",
|
||||
"Processing ignored!": "¡Procesamiento omitido!",
|
||||
"Failed to start camera": "No se pudo iniciar la cámara",
|
||||
"Please complete pop-up or close it.": "Complete o cierre el pop-up",
|
||||
"Getting unique faces": "Buscando caras únicas",
|
||||
"Please select a source image first": "Primero, seleccione una imagen fuente",
|
||||
"No faces found in target": "No se encontró una cara en el destino",
|
||||
"Add": "Agregar",
|
||||
"Clear": "Limpiar",
|
||||
"Submit": "Enviar",
|
||||
"Select source image": "Seleccionar imagen fuente",
|
||||
"Select target image": "Seleccionar imagen destino",
|
||||
"Please provide mapping!": "Por favor, proporcione un mapeo",
|
||||
"At least 1 source with target is required!": "Se requiere al menos una fuente con un destino.",
|
||||
"At least 1 source with target is required!": "Se requiere al menos una fuente con un destino.",
|
||||
"Face could not be detected in last upload!": "¡No se pudo encontrar una cara en el último video o imagen!",
|
||||
"Select Camera:": "Elegir cámara:",
|
||||
"All mappings cleared!": "¡Todos los mapeos fueron borrados!",
|
||||
"Mappings successfully submitted!": "Mapeos enviados con éxito!",
|
||||
"Source x Target Mapper is already open.": "El mapeador de fuente x destino ya está abierto."
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"Source x Target Mapper": "Source x Target Kartoitin",
|
||||
"select an source image": "Valitse lähde kuva",
|
||||
"Preview": "Esikatsele",
|
||||
"select an target image or video": "Valitse kohde kuva tai video",
|
||||
"save image output file": "tallenna kuva",
|
||||
"save video output file": "tallenna video",
|
||||
"select an target image": "Valitse kohde kuva",
|
||||
"source": "lähde",
|
||||
"Select a target": "Valitse kohde",
|
||||
"Select a face": "Valitse kasvot",
|
||||
"Keep audio": "Säilytä ääni",
|
||||
"Face Enhancer": "Kasvojen Parantaja",
|
||||
"Many faces": "Useampia kasvoja",
|
||||
"Show FPS": "Näytä FPS",
|
||||
"Keep fps": "Säilytä FPS",
|
||||
"Keep frames": "Säilytä ruudut",
|
||||
"Fix Blueish Cam": "Korjaa Sinertävä Kamera",
|
||||
"Mouth Mask": "Suu Maski",
|
||||
"Show Mouth Mask Box": "Näytä Suu Maski Laatiko",
|
||||
"Start": "Aloita",
|
||||
"Live": "Live",
|
||||
"Destroy": "Tuhoa",
|
||||
"Map faces": "Kartoita kasvot",
|
||||
"Processing...": "Prosessoi...",
|
||||
"Processing succeed!": "Prosessointi onnistui!",
|
||||
"Processing ignored!": "Prosessointi lopetettu!",
|
||||
"Failed to start camera": "Kameran käynnistäminen epäonnistui",
|
||||
"Please complete pop-up or close it.": "Viimeistele tai sulje ponnahdusikkuna",
|
||||
"Getting unique faces": "Hankitaan uniikkeja kasvoja",
|
||||
"Please select a source image first": "Valitse ensin lähde kuva",
|
||||
"No faces found in target": "Kasvoja ei löydetty kohteessa",
|
||||
"Add": "Lisää",
|
||||
"Clear": "Tyhjennä",
|
||||
"Submit": "Lähetä",
|
||||
"Select source image": "Valitse lähde kuva",
|
||||
"Select target image": "Valitse kohde kuva",
|
||||
"Please provide mapping!": "Tarjoa kartoitus!",
|
||||
"Atleast 1 source with target is required!": "Vähintään 1 lähde kohteen kanssa on vaadittu!",
|
||||
"At least 1 source with target is required!": "Vähintään 1 lähde kohteen kanssa on vaadittu!",
|
||||
"Face could not be detected in last upload!": "Kasvoja ei voitu tunnistaa edellisessä latauksessa!",
|
||||
"Select Camera:": "Valitse Kamera:",
|
||||
"All mappings cleared!": "Kaikki kartoitukset tyhjennetty!",
|
||||
"Mappings successfully submitted!": "Kartoitukset lähetety onnistuneesti!",
|
||||
"Source x Target Mapper is already open.": "Lähde x Kohde Kartoittaja on jo auki."
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"Source x Target Mapper": "Pemetaan Sumber x Target",
|
||||
"select a source image": "Pilih gambar sumber",
|
||||
"Preview": "Pratinjau",
|
||||
"select a target image or video": "Pilih gambar atau video target",
|
||||
"save image output file": "Simpan file keluaran gambar",
|
||||
"save video output file": "Simpan file keluaran video",
|
||||
"select a target image": "Pilih gambar target",
|
||||
"source": "Sumber",
|
||||
"Select a target": "Pilih target",
|
||||
"Select a face": "Pilih wajah",
|
||||
"Keep audio": "Pertahankan audio",
|
||||
"Face Enhancer": "Peningkat wajah",
|
||||
"Many faces": "Banyak wajah",
|
||||
"Show FPS": "Tampilkan FPS",
|
||||
"Keep fps": "Pertahankan FPS",
|
||||
"Keep frames": "Pertahankan frame",
|
||||
"Fix Blueish Cam": "Perbaiki kamera kebiruan",
|
||||
"Mouth Mask": "Masker mulut",
|
||||
"Show Mouth Mask Box": "Tampilkan kotak masker mulut",
|
||||
"Start": "Mulai",
|
||||
"Live": "Langsung",
|
||||
"Destroy": "Hentikan",
|
||||
"Map faces": "Petakan wajah",
|
||||
"Processing...": "Sedang memproses...",
|
||||
"Processing succeed!": "Pemrosesan berhasil!",
|
||||
"Processing ignored!": "Pemrosesan diabaikan!",
|
||||
"Failed to start camera": "Gagal memulai kamera",
|
||||
"Please complete pop-up or close it.": "Harap selesaikan atau tutup pop-up.",
|
||||
"Getting unique faces": "Mengambil wajah unik",
|
||||
"Please select a source image first": "Silakan pilih gambar sumber terlebih dahulu",
|
||||
"No faces found in target": "Tidak ada wajah ditemukan pada target",
|
||||
"Add": "Tambah",
|
||||
"Clear": "Bersihkan",
|
||||
"Submit": "Kirim",
|
||||
"Select source image": "Pilih gambar sumber",
|
||||
"Select target image": "Pilih gambar target",
|
||||
"Please provide mapping!": "Harap tentukan pemetaan!",
|
||||
"At least 1 source with target is required!": "Minimal 1 sumber dengan target diperlukan!",
|
||||
"Face could not be detected in last upload!": "Wajah tidak dapat terdeteksi pada unggahan terakhir!",
|
||||
"Select Camera:": "Pilih Kamera:",
|
||||
"All mappings cleared!": "Semua pemetaan telah dibersihkan!",
|
||||
"Mappings successfully submitted!": "Pemetaan berhasil dikirim!",
|
||||
"Source x Target Mapper is already open.": "Pemetaan Sumber x Target sudah terbuka."
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"Source x Target Mapper": "ប្រភប x បន្ថែម Mapper",
|
||||
"select a source image": "ជ្រើសរើសប្រភពរូបភាព",
|
||||
"Preview": "បង្ហាញ",
|
||||
"select a target image or video": "ជ្រើសរើសគោលដៅរូបភាពឬវីដេអូ",
|
||||
"save image output file": "រក្សាទុកលទ្ធផលឯកសាររូបភាព",
|
||||
"save video output file": "រក្សាទុកលទ្ធផលឯកសារវីដេអូ",
|
||||
"select a target image": "ជ្រើសរើសគោលដៅរូបភាព",
|
||||
"source": "ប្រភព",
|
||||
"Select a target": "ជ្រើសរើសគោលដៅ",
|
||||
"Select a face": "ជ្រើសរើសមុខ",
|
||||
"Keep audio": "រម្លងសម្លេង",
|
||||
"Face Enhancer": "ឧបករណ៍ពង្រឹងមុខ",
|
||||
"Many faces": "ទម្រង់មុខច្រើន",
|
||||
"Show FPS": "បង្ហាញ FPS",
|
||||
"Keep fps": "រម្លង fps",
|
||||
"Keep frames": "រម្លងទម្រង់",
|
||||
"Fix Blueish Cam": "ជួសជុល Cam Blueish",
|
||||
"Mouth Mask": "របាំងមាត់",
|
||||
"Show Mouth Mask Box": "បង្ហាញប្រអប់របាំងមាត់",
|
||||
"Start": "ចាប់ផ្ដើម",
|
||||
"Live": "ផ្សាយផ្ទាល់",
|
||||
"Destroy": "លុប",
|
||||
"Map faces": "ផែនទីមុខ",
|
||||
"Processing...": "កំពុងដំណើរការ...",
|
||||
"Processing succeed!": "ការដំណើរការទទួលបានជោគជ័យ!",
|
||||
"Processing ignored!": "ការដំណើរការមិនទទួលបានជោគជ័យ!",
|
||||
"Failed to start camera": "បរាជ័យដើម្បីចាប់ផ្ដើមបើកកាមេរ៉ា",
|
||||
"Please complete pop-up or close it.": "សូមបញ្ចប់ផ្ទាំងផុស ឬបិទវា.",
|
||||
"Getting unique faces": "ការចាប់ផ្ដើមទម្រង់មុខប្លែក",
|
||||
"Please select a source image first": "សូមជ្រើសរើសប្រភពរូបភាពដំបូង",
|
||||
"No faces found in target": "រកអត់ឃើញមុខនៅក្នុងគោលដៅ",
|
||||
"Add": "បន្ថែម",
|
||||
"Clear": "សម្អាត",
|
||||
"Submit": "បញ្ចូន",
|
||||
"Select source image": "ជ្រើសរើសប្រភពរូបភាព",
|
||||
"Select target image": "ជ្រើសរើសគោលដៅរូបភាព",
|
||||
"Please provide mapping!": "សូមផ្ដល់នៅផែនទី",
|
||||
"At least 1 source with target is required!": "ត្រូវការប្រភពយ៉ាងហោចណាស់ ១ ដែលមានគោលដៅ!",
|
||||
"Face could not be detected in last upload!": "មុខមិនអាចភ្ជាប់នៅក្នុងការបង្ហេាះចុងក្រោយ!",
|
||||
"Select Camera:": "ជ្រើសរើសកាមេរ៉ា",
|
||||
"All mappings cleared!": "ផែនទីទាំងអស់ត្រូវបានសម្អាត!",
|
||||
"Mappings successfully submitted!": "ផែនទីត្រូវបានបញ្ជូនជោគជ័យ!",
|
||||
"Source x Target Mapper is already open.": "ប្រភព x Target Mapper បានបើករួចហើយ។"
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"Source x Target Mapper": "소스 x 타겟 매퍼",
|
||||
"select a source image": "소스 이미지 선택",
|
||||
"Preview": "미리보기",
|
||||
"select a target image or video": "타겟 이미지 또는 영상 선택",
|
||||
"save image output file": "이미지 출력 파일 저장",
|
||||
"save video output file": "영상 출력 파일 저장",
|
||||
"select a target image": "타겟 이미지 선택",
|
||||
"source": "소스",
|
||||
"Select a target": "타겟 선택",
|
||||
"Select a face": "얼굴 선택",
|
||||
"Keep audio": "오디오 유지",
|
||||
"Face Enhancer": "얼굴 향상",
|
||||
"Many faces": "여러 얼굴",
|
||||
"Show FPS": "FPS 표시",
|
||||
"Keep fps": "FPS 유지",
|
||||
"Keep frames": "프레임 유지",
|
||||
"Fix Blueish Cam": "푸른빛 카메라 보정",
|
||||
"Mouth Mask": "입 마스크",
|
||||
"Show Mouth Mask Box": "입 마스크 박스 표시",
|
||||
"Start": "시작",
|
||||
"Live": "라이브",
|
||||
"Destroy": "종료",
|
||||
"Map faces": "얼굴 매핑",
|
||||
"Processing...": "처리 중...",
|
||||
"Processing succeed!": "처리 성공!",
|
||||
"Processing ignored!": "처리 무시됨!",
|
||||
"Failed to start camera": "카메라 시작 실패",
|
||||
"Please complete pop-up or close it.": "팝업을 완료하거나 닫아주세요.",
|
||||
"Getting unique faces": "고유 얼굴 가져오는 중",
|
||||
"Please select a source image first": "먼저 소스 이미지를 선택해주세요",
|
||||
"No faces found in target": "타겟에서 얼굴을 찾을 수 없음",
|
||||
"Add": "추가",
|
||||
"Clear": "지우기",
|
||||
"Submit": "제출",
|
||||
"Select source image": "소스 이미지 선택",
|
||||
"Select target image": "타겟 이미지 선택",
|
||||
"Please provide mapping!": "매핑을 입력해주세요!",
|
||||
"At least 1 source with target is required!": "최소 하나의 소스와 타겟이 필요합니다!",
|
||||
"Face could not be detected in last upload!": "최근 업로드에서 얼굴을 감지할 수 없습니다!",
|
||||
"Select Camera:": "카메라 선택:",
|
||||
"All mappings cleared!": "모든 매핑이 삭제되었습니다!",
|
||||
"Mappings successfully submitted!": "매핑이 성공적으로 제출되었습니다!",
|
||||
"Source x Target Mapper is already open.": "소스 x 타겟 매퍼가 이미 열려 있습니다."
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"Source x Target Mapper": "Mapeador de Origem x Destino",
|
||||
"select an source image": "Escolha uma imagem de origem",
|
||||
"Preview": "Prévia",
|
||||
"select an target image or video": "Escolha uma imagem ou vídeo de destino",
|
||||
"save image output file": "Salvar imagem final",
|
||||
"save video output file": "Salvar vídeo final",
|
||||
"select an target image": "Escolha uma imagem de destino",
|
||||
"source": "Origem",
|
||||
"Select a target": "Escolha o destino",
|
||||
"Select a face": "Escolha um rosto",
|
||||
"Keep audio": "Manter o áudio original",
|
||||
"Face Enhancer": "Melhorar rosto",
|
||||
"Many faces": "Vários rostos",
|
||||
"Show FPS": "Mostrar FPS",
|
||||
"Keep fps": "Manter FPS",
|
||||
"Keep frames": "Manter frames",
|
||||
"Fix Blueish Cam": "Corrigir tom azulado da câmera",
|
||||
"Mouth Mask": "Máscara da boca",
|
||||
"Show Mouth Mask Box": "Mostrar área da máscara da boca",
|
||||
"Start": "Começar",
|
||||
"Live": "Ao vivo",
|
||||
"Destroy": "Destruir",
|
||||
"Map faces": "Mapear rostos",
|
||||
"Processing...": "Processando...",
|
||||
"Processing succeed!": "Tudo certo!",
|
||||
"Processing ignored!": "Processamento ignorado!",
|
||||
"Failed to start camera": "Não foi possível iniciar a câmera",
|
||||
"Please complete pop-up or close it.": "Finalize ou feche o pop-up",
|
||||
"Getting unique faces": "Buscando rostos diferentes",
|
||||
"Please select a source image first": "Selecione primeiro uma imagem de origem",
|
||||
"No faces found in target": "Nenhum rosto encontrado na imagem de destino",
|
||||
"Add": "Adicionar",
|
||||
"Clear": "Limpar",
|
||||
"Submit": "Enviar",
|
||||
"Select source image": "Escolha a imagem de origem",
|
||||
"Select target image": "Escolha a imagem de destino",
|
||||
"Please provide mapping!": "Você precisa realizar o mapeamento!",
|
||||
"Atleast 1 source with target is required!": "É necessária pelo menos uma origem com um destino!",
|
||||
"At least 1 source with target is required!": "É necessária pelo menos uma origem com um destino!",
|
||||
"Face could not be detected in last upload!": "Não conseguimos detectar o rosto na última imagem!",
|
||||
"Select Camera:": "Escolher câmera:",
|
||||
"All mappings cleared!": "Todos os mapeamentos foram removidos!",
|
||||
"Mappings successfully submitted!": "Mapeamentos enviados com sucesso!",
|
||||
"Source x Target Mapper is already open.": "O Mapeador de Origem x Destino já está aberto."
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"Source x Target Mapper": "Сопоставитель Источник x Цель",
|
||||
"select a source image": "выберите исходное изображение",
|
||||
"Preview": "Предпросмотр",
|
||||
"select a target image or video": "выберите целевое изображение или видео",
|
||||
"save image output file": "сохранить выходной файл изображения",
|
||||
"save video output file": "сохранить выходной файл видео",
|
||||
"select a target image": "выберите целевое изображение",
|
||||
"source": "источник",
|
||||
"Select a target": "Выберите целевое изображение",
|
||||
"Select a face": "Выберите лицо",
|
||||
"Keep audio": "Сохранить аудио",
|
||||
"Face Enhancer": "Улучшение лица",
|
||||
"Many faces": "Несколько лиц",
|
||||
"Show FPS": "Показать FPS",
|
||||
"Keep fps": "Сохранить FPS",
|
||||
"Keep frames": "Сохранить кадры",
|
||||
"Fix Blueish Cam": "Исправить синеву камеры",
|
||||
"Mouth Mask": "Маска рта",
|
||||
"Show Mouth Mask Box": "Показать рамку маски рта",
|
||||
"Start": "Старт",
|
||||
"Live": "В реальном времени",
|
||||
"Destroy": "Остановить",
|
||||
"Map faces": "Сопоставить лица",
|
||||
"Processing...": "Обработка...",
|
||||
"Processing succeed!": "Обработка успешна!",
|
||||
"Processing ignored!": "Обработка проигнорирована!",
|
||||
"Failed to start camera": "Не удалось запустить камеру",
|
||||
"Please complete pop-up or close it.": "Пожалуйста, заполните всплывающее окно или закройте его.",
|
||||
"Getting unique faces": "Получение уникальных лиц",
|
||||
"Please select a source image first": "Сначала выберите исходное изображение, пожалуйста",
|
||||
"No faces found in target": "В целевом изображении не найдено лиц",
|
||||
"Add": "Добавить",
|
||||
"Clear": "Очистить",
|
||||
"Submit": "Отправить",
|
||||
"Select source image": "Выбрать исходное изображение",
|
||||
"Select target image": "Выбрать целевое изображение",
|
||||
"Please provide mapping!": "Пожалуйста, укажите сопоставление!",
|
||||
"At least 1 source with target is required!": "Требуется хотя бы 1 источник с целью!",
|
||||
"Face could not be detected in last upload!": "Лицо не обнаружено в последнем загруженном изображении!",
|
||||
"Select Camera:": "Выберите камеру:",
|
||||
"All mappings cleared!": "Все сопоставления очищены!",
|
||||
"Mappings successfully submitted!": "Сопоставления успешно отправлены!",
|
||||
"Source x Target Mapper is already open.": "Сопоставитель Источник-Цель уже открыт."
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"Source x Target Mapper": "ตัวจับคู่ต้นทาง x ปลายทาง",
|
||||
"select a source image": "เลือกรูปภาพต้นฉบับ",
|
||||
"Preview": "ตัวอย่าง",
|
||||
"select a target image or video": "เลือกรูปภาพหรือวิดีโอเป้าหมาย",
|
||||
"save image output file": "บันทึกไฟล์รูปภาพ",
|
||||
"save video output file": "บันทึกไฟล์วิดีโอ",
|
||||
"select a target image": "เลือกรูปภาพเป้าหมาย",
|
||||
"source": "ต้นฉบับ",
|
||||
"Select a target": "เลือกเป้าหมาย",
|
||||
"Select a face": "เลือกใบหน้า",
|
||||
"Keep audio": "เก็บเสียง",
|
||||
"Face Enhancer": "ปรับปรุงใบหน้า",
|
||||
"Many faces": "หลายใบหน้า",
|
||||
"Show FPS": "แสดง FPS",
|
||||
"Keep fps": "คงค่า FPS",
|
||||
"Keep frames": "คงค่าเฟรม",
|
||||
"Fix Blueish Cam": "แก้ไขภาพอมฟ้าจากกล้อง",
|
||||
"Mouth Mask": "มาสก์ปาก",
|
||||
"Show Mouth Mask Box": "แสดงกรอบมาสก์ปาก",
|
||||
"Start": "เริ่ม",
|
||||
"Live": "สด",
|
||||
"Destroy": "หยุด",
|
||||
"Map faces": "จับคู่ใบหน้า",
|
||||
"Processing...": "กำลังประมวลผล...",
|
||||
"Processing succeed!": "ประมวลผลสำเร็จแล้ว!",
|
||||
"Processing ignored!": "การประมวลผลถูกละเว้น",
|
||||
"Failed to start camera": "ไม่สามารถเริ่มกล้องได้",
|
||||
"Please complete pop-up or close it.": "โปรดดำเนินการในป๊อปอัปให้เสร็จสิ้น หรือปิด",
|
||||
"Getting unique faces": "กำลังค้นหาใบหน้าที่ไม่ซ้ำกัน",
|
||||
"Please select a source image first": "โปรดเลือกภาพต้นฉบับก่อน",
|
||||
"No faces found in target": "ไม่พบใบหน้าในภาพเป้าหมาย",
|
||||
"Add": "เพิ่ม",
|
||||
"Clear": "ล้าง",
|
||||
"Submit": "ส่ง",
|
||||
"Select source image": "เลือกภาพต้นฉบับ",
|
||||
"Select target image": "เลือกภาพเป้าหมาย",
|
||||
"Please provide mapping!": "โปรดระบุการจับคู่!",
|
||||
"At least 1 source with target is required!": "ต้องมีการจับคู่ต้นฉบับกับเป้าหมายอย่างน้อย 1 คู่!",
|
||||
"Face could not be detected in last upload!": "ไม่สามารถตรวจพบใบหน้าในไฟล์อัปโหลดล่าสุด!",
|
||||
"Select Camera:": "เลือกกล้อง:",
|
||||
"All mappings cleared!": "ล้างการจับคู่ทั้งหมดแล้ว!",
|
||||
"Mappings successfully submitted!": "ส่งการจับคู่สำเร็จแล้ว!",
|
||||
"Source x Target Mapper is already open.": "ตัวจับคู่ต้นทาง x ปลายทาง เปิดอยู่แล้ว"
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"Source x Target Mapper": "Source x Target Mapper",
|
||||
"select a source image": "选择一个源图像",
|
||||
"Preview": "预览",
|
||||
"select a target image or video": "选择一个目标图像或视频",
|
||||
"save image output file": "保存图像输出文件",
|
||||
"save video output file": "保存视频输出文件",
|
||||
"select a target image": "选择一个目标图像",
|
||||
"source": "源",
|
||||
"Select a target": "选择一个目标",
|
||||
"Select a face": "选择一张脸",
|
||||
"Keep audio": "保留音频",
|
||||
"Face Enhancer": "面纹增强器",
|
||||
"Many faces": "多脸",
|
||||
"Show FPS": "显示帧率",
|
||||
"Keep fps": "保持帧率",
|
||||
"Keep frames": "保持帧数",
|
||||
"Fix Blueish Cam": "修复偏蓝的摄像头",
|
||||
"Mouth Mask": "口罩",
|
||||
"Show Mouth Mask Box": "显示口罩盒",
|
||||
"Start": "开始",
|
||||
"Live": "直播",
|
||||
"Destroy": "结束",
|
||||
"Map faces": "识别人脸",
|
||||
"Processing...": "处理中...",
|
||||
"Processing succeed!": "处理成功!",
|
||||
"Processing ignored!": "处理被忽略!",
|
||||
"Failed to start camera": "启动相机失败",
|
||||
"Please complete pop-up or close it.": "请先完成弹出窗口或者关闭它",
|
||||
"Getting unique faces": "获取独特面部",
|
||||
"Please select a source image first": "请先选择一个源图像",
|
||||
"No faces found in target": "目标图像中没有人脸",
|
||||
"Add": "添加",
|
||||
"Clear": "清除",
|
||||
"Submit": "确认",
|
||||
"Select source image": "请选取源图像",
|
||||
"Select target image": "请选取目标图像",
|
||||
"Please provide mapping!": "请提供映射",
|
||||
"At least 1 source with target is required!": "至少需要一个来源图像与目标图像相关!",
|
||||
"At least 1 source with target is required!": "至少需要一个来源图像与目标图像相关!",
|
||||
"Face could not be detected in last upload!": "最近上传的图像中没有检测到人脸!",
|
||||
"Select Camera:": "选择摄像头",
|
||||
"All mappings cleared!": "所有映射均已清除!",
|
||||
"Mappings successfully submitted!": "成功提交映射!",
|
||||
"Source x Target Mapper is already open.": "源 x 目标映射器已打开。"
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 9.6 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 9.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 8.2 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.0 MiB |
@@ -0,0 +1,18 @@
|
||||
import os
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
# Utility function to support unicode characters in file paths for reading
|
||||
def imread_unicode(path, flags=cv2.IMREAD_COLOR):
|
||||
return cv2.imdecode(np.fromfile(path, dtype=np.uint8), flags)
|
||||
|
||||
# Utility function to support unicode characters in file paths for writing
|
||||
def imwrite_unicode(path, img, params=None):
|
||||
root, ext = os.path.splitext(path)
|
||||
if not ext:
|
||||
ext = ".png"
|
||||
result, encoded_img = cv2.imencode(ext, img, params if params else [])
|
||||
result, encoded_img = cv2.imencode(f".{ext}", img, params if params is not None else [])
|
||||
encoded_img.tofile(path)
|
||||
return True
|
||||
return False
|
||||
+2
-1
@@ -1,6 +1,7 @@
|
||||
from typing import Any
|
||||
import cv2
|
||||
import modules.globals # Import the globals to check the color correction toggle
|
||||
from modules.gpu_processing import gpu_cvt_color
|
||||
|
||||
|
||||
def get_video_frame(video_path: str, frame_number: int = 0) -> Any:
|
||||
@@ -19,7 +20,7 @@ def get_video_frame(video_path: str, frame_number: int = 0) -> Any:
|
||||
|
||||
if has_frame and modules.globals.color_correction:
|
||||
# Convert the frame color if necessary
|
||||
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
||||
frame = gpu_cvt_color(frame, cv2.COLOR_BGR2RGB)
|
||||
|
||||
capture.release()
|
||||
return frame if has_frame else None
|
||||
|
||||
+54
-12
@@ -11,7 +11,11 @@ import platform
|
||||
import signal
|
||||
import shutil
|
||||
import argparse
|
||||
try:
|
||||
import torch
|
||||
HAS_TORCH = True
|
||||
except ImportError:
|
||||
HAS_TORCH = False
|
||||
import onnxruntime
|
||||
import tensorflow
|
||||
|
||||
@@ -21,10 +25,11 @@ import modules.ui as ui
|
||||
from modules.processors.frame.core import get_frame_processors_modules
|
||||
from modules.utilities import has_image_extension, is_image, is_video, detect_fps, create_video, extract_frames, get_temp_frame_paths, restore_audio, create_temp, move_temp, clean_temp, normalize_output_path
|
||||
|
||||
if 'ROCMExecutionProvider' in modules.globals.execution_providers:
|
||||
if HAS_TORCH and 'ROCMExecutionProvider' in modules.globals.execution_providers:
|
||||
del torch
|
||||
|
||||
warnings.filterwarnings('ignore', category=FutureWarning, module='insightface')
|
||||
if HAS_TORCH:
|
||||
warnings.filterwarnings('ignore', category=UserWarning, module='torchvision')
|
||||
|
||||
|
||||
@@ -34,7 +39,7 @@ def parse_args() -> None:
|
||||
program.add_argument('-s', '--source', help='select an source image', dest='source_path')
|
||||
program.add_argument('-t', '--target', help='select an target image or video', dest='target_path')
|
||||
program.add_argument('-o', '--output', help='select output file or directory', dest='output_path')
|
||||
program.add_argument('--frame-processor', help='pipeline of frame processors', dest='frame_processor', default=['face_swapper'], choices=['face_swapper', 'face_enhancer'], nargs='+')
|
||||
program.add_argument('--frame-processor', help='pipeline of frame processors', dest='frame_processor', default=['face_swapper'], choices=['face_swapper', 'face_enhancer', 'face_enhancer_gpen256', 'face_enhancer_gpen512'], nargs='+')
|
||||
program.add_argument('--keep-fps', help='keep original fps', dest='keep_fps', action='store_true', default=False)
|
||||
program.add_argument('--keep-audio', help='keep original audio', dest='keep_audio', action='store_true', default=True)
|
||||
program.add_argument('--keep-frames', help='keep temporary frames', dest='keep_frames', action='store_true', default=False)
|
||||
@@ -44,6 +49,7 @@ def parse_args() -> None:
|
||||
program.add_argument('--mouth-mask', help='mask the mouth region', dest='mouth_mask', action='store_true', default=False)
|
||||
program.add_argument('--video-encoder', help='adjust output video encoder', dest='video_encoder', default='libx264', choices=['libx264', 'libx265', 'libvpx-vp9'])
|
||||
program.add_argument('--video-quality', help='adjust output video quality', dest='video_quality', type=int, default=18, choices=range(52), metavar='[0-51]')
|
||||
program.add_argument('-l', '--lang', help='Ui language', default="en")
|
||||
program.add_argument('--live-mirror', help='The live camera display as you see it in the front-facing camera frame', dest='live_mirror', 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())
|
||||
@@ -78,12 +84,11 @@ def parse_args() -> None:
|
||||
modules.globals.max_memory = args.max_memory
|
||||
modules.globals.execution_providers = decode_execution_providers(args.execution_provider)
|
||||
modules.globals.execution_threads = args.execution_threads
|
||||
modules.globals.lang = args.lang
|
||||
|
||||
#for ENHANCER tumbler:
|
||||
if 'face_enhancer' in args.frame_processor:
|
||||
modules.globals.fp_ui['face_enhancer'] = True
|
||||
else:
|
||||
modules.globals.fp_ui['face_enhancer'] = False
|
||||
#for ENHANCER tumblers:
|
||||
for enhancer_key in ('face_enhancer', 'face_enhancer_gpen256', 'face_enhancer_gpen512'):
|
||||
modules.globals.fp_ui[enhancer_key] = enhancer_key in args.frame_processor
|
||||
|
||||
# translate deprecated args
|
||||
if args.source_path_deprecated:
|
||||
@@ -127,11 +132,22 @@ def suggest_execution_providers() -> List[str]:
|
||||
|
||||
|
||||
def suggest_execution_threads() -> int:
|
||||
"""Suggest optimal thread count based on hardware and execution provider."""
|
||||
import os
|
||||
|
||||
# Get CPU count
|
||||
cpu_count = os.cpu_count() or 4
|
||||
|
||||
if 'DmlExecutionProvider' in modules.globals.execution_providers:
|
||||
return 1
|
||||
if 'ROCMExecutionProvider' in modules.globals.execution_providers:
|
||||
return 1
|
||||
return 8
|
||||
if 'CUDAExecutionProvider' in modules.globals.execution_providers:
|
||||
# For CUDA, use more threads for parallel frame processing
|
||||
return min(cpu_count, 16)
|
||||
|
||||
# For CPU execution, use most cores but leave some for system
|
||||
return max(4, min(cpu_count - 2, 16))
|
||||
|
||||
|
||||
def limit_resources() -> None:
|
||||
@@ -154,7 +170,7 @@ def limit_resources() -> None:
|
||||
|
||||
|
||||
def release_resources() -> None:
|
||||
if 'CUDAExecutionProvider' in modules.globals.execution_providers:
|
||||
if 'CUDAExecutionProvider' in modules.globals.execution_providers and HAS_TORCH:
|
||||
torch.cuda.empty_cache()
|
||||
|
||||
|
||||
@@ -174,10 +190,16 @@ def update_status(message: str, scope: str = 'DLC.CORE') -> None:
|
||||
ui.update_status(message)
|
||||
|
||||
def start() -> None:
|
||||
"""Start processing with performance monitoring."""
|
||||
import time
|
||||
|
||||
start_time = time.time()
|
||||
|
||||
for frame_processor in get_frame_processors_modules(modules.globals.frame_processors):
|
||||
if not frame_processor.pre_start():
|
||||
return
|
||||
update_status('Processing...')
|
||||
|
||||
# process image to image
|
||||
if has_image_extension(modules.globals.target_path):
|
||||
if modules.globals.nsfw_filter and ui.check_and_ignore_nsfw(modules.globals.target_path, destroy):
|
||||
@@ -191,26 +213,40 @@ def start() -> None:
|
||||
frame_processor.process_image(modules.globals.source_path, modules.globals.output_path, modules.globals.output_path)
|
||||
release_resources()
|
||||
if is_image(modules.globals.target_path):
|
||||
update_status('Processing to image succeed!')
|
||||
elapsed = time.time() - start_time
|
||||
update_status(f'Processing to image succeed! (Time: {elapsed:.2f}s)')
|
||||
else:
|
||||
update_status('Processing to image failed!')
|
||||
return
|
||||
|
||||
# process image to videos
|
||||
if modules.globals.nsfw_filter and ui.check_and_ignore_nsfw(modules.globals.target_path, destroy):
|
||||
return
|
||||
|
||||
extraction_start = time.time()
|
||||
if not modules.globals.map_faces:
|
||||
update_status('Creating temp resources...')
|
||||
create_temp(modules.globals.target_path)
|
||||
update_status('Extracting frames...')
|
||||
extract_frames(modules.globals.target_path)
|
||||
extraction_time = time.time() - extraction_start
|
||||
update_status(f'Frame extraction completed in {extraction_time:.2f}s')
|
||||
|
||||
temp_frame_paths = get_temp_frame_paths(modules.globals.target_path)
|
||||
total_frames = len(temp_frame_paths)
|
||||
update_status(f'Processing {total_frames} frames with {modules.globals.execution_threads} threads...')
|
||||
|
||||
processing_start = time.time()
|
||||
for frame_processor in get_frame_processors_modules(modules.globals.frame_processors):
|
||||
update_status('Progressing...', frame_processor.NAME)
|
||||
frame_processor.process_video(modules.globals.source_path, temp_frame_paths)
|
||||
release_resources()
|
||||
processing_time = time.time() - processing_start
|
||||
fps_processing = total_frames / processing_time if processing_time > 0 else 0
|
||||
update_status(f'Frame processing completed in {processing_time:.2f}s ({fps_processing:.2f} fps)')
|
||||
|
||||
# handles fps
|
||||
encoding_start = time.time()
|
||||
if modules.globals.keep_fps:
|
||||
update_status('Detecting fps...')
|
||||
fps = detect_fps(modules.globals.target_path)
|
||||
@@ -219,6 +255,9 @@ def start() -> None:
|
||||
else:
|
||||
update_status('Creating video with 30.0 fps...')
|
||||
create_video(modules.globals.target_path)
|
||||
encoding_time = time.time() - encoding_start
|
||||
update_status(f'Video encoding completed in {encoding_time:.2f}s')
|
||||
|
||||
# handle audio
|
||||
if modules.globals.keep_audio:
|
||||
if modules.globals.keep_fps:
|
||||
@@ -228,10 +267,13 @@ def start() -> None:
|
||||
restore_audio(modules.globals.target_path, modules.globals.output_path)
|
||||
else:
|
||||
move_temp(modules.globals.target_path, modules.globals.output_path)
|
||||
|
||||
# clean and validate
|
||||
clean_temp(modules.globals.target_path)
|
||||
|
||||
total_time = time.time() - start_time
|
||||
if is_video(modules.globals.target_path):
|
||||
update_status('Processing to video succeed!')
|
||||
update_status(f'Processing to video succeed! Total time: {total_time:.2f}s')
|
||||
else:
|
||||
update_status('Processing to video failed!')
|
||||
|
||||
@@ -253,5 +295,5 @@ def run() -> None:
|
||||
if modules.globals.headless:
|
||||
start()
|
||||
else:
|
||||
window = ui.init(start, destroy)
|
||||
window = ui.init(start, destroy, modules.globals.lang)
|
||||
window.mainloop()
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
from typing import Any
|
||||
|
||||
from insightface.app.common import Face
|
||||
import numpy
|
||||
|
||||
Face = Face
|
||||
Frame = numpy.ndarray[Any, Any]
|
||||
+23
-13
@@ -2,6 +2,7 @@ import os
|
||||
import shutil
|
||||
from typing import Any
|
||||
import insightface
|
||||
import threading
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
@@ -13,13 +14,22 @@ from modules.utilities import get_temp_directory_path, create_temp, extract_fram
|
||||
from pathlib import Path
|
||||
|
||||
FACE_ANALYSER = None
|
||||
FACE_ANALYSER_LOCK = threading.Lock()
|
||||
|
||||
|
||||
def get_face_analyser() -> Any:
|
||||
"""Get face analyser with thread-safe initialization."""
|
||||
global FACE_ANALYSER
|
||||
|
||||
if FACE_ANALYSER is None:
|
||||
FACE_ANALYSER = insightface.app.FaceAnalysis(name='buffalo_l', providers=modules.globals.execution_providers)
|
||||
with FACE_ANALYSER_LOCK:
|
||||
# Double-check after acquiring lock
|
||||
if FACE_ANALYSER is None:
|
||||
FACE_ANALYSER = insightface.app.FaceAnalysis(
|
||||
name='buffalo_l',
|
||||
providers=modules.globals.execution_providers,
|
||||
allowed_modules=['detection', 'recognition', 'landmark_2d_106']
|
||||
)
|
||||
FACE_ANALYSER.prepare(ctx_id=0, det_size=(640, 640))
|
||||
return FACE_ANALYSER
|
||||
|
||||
@@ -39,13 +49,13 @@ def get_many_faces(frame: Frame) -> Any:
|
||||
return None
|
||||
|
||||
def has_valid_map() -> bool:
|
||||
for map in modules.globals.souce_target_map:
|
||||
for map in modules.globals.source_target_map:
|
||||
if "source" in map and "target" in map:
|
||||
return True
|
||||
return False
|
||||
|
||||
def default_source_face() -> Any:
|
||||
for map in modules.globals.souce_target_map:
|
||||
for map in modules.globals.source_target_map:
|
||||
if "source" in map:
|
||||
return map['source']['face']
|
||||
return None
|
||||
@@ -53,7 +63,7 @@ def default_source_face() -> Any:
|
||||
def simplify_maps() -> Any:
|
||||
centroids = []
|
||||
faces = []
|
||||
for map in modules.globals.souce_target_map:
|
||||
for map in modules.globals.source_target_map:
|
||||
if "source" in map and "target" in map:
|
||||
centroids.append(map['target']['face'].normed_embedding)
|
||||
faces.append(map['source']['face'])
|
||||
@@ -64,10 +74,10 @@ def simplify_maps() -> Any:
|
||||
def add_blank_map() -> Any:
|
||||
try:
|
||||
max_id = -1
|
||||
if len(modules.globals.souce_target_map) > 0:
|
||||
max_id = max(modules.globals.souce_target_map, key=lambda x: x['id'])['id']
|
||||
if len(modules.globals.source_target_map) > 0:
|
||||
max_id = max(modules.globals.source_target_map, key=lambda x: x['id'])['id']
|
||||
|
||||
modules.globals.souce_target_map.append({
|
||||
modules.globals.source_target_map.append({
|
||||
'id' : max_id + 1
|
||||
})
|
||||
except ValueError:
|
||||
@@ -75,14 +85,14 @@ def add_blank_map() -> Any:
|
||||
|
||||
def get_unique_faces_from_target_image() -> Any:
|
||||
try:
|
||||
modules.globals.souce_target_map = []
|
||||
modules.globals.source_target_map = []
|
||||
target_frame = cv2.imread(modules.globals.target_path)
|
||||
many_faces = get_many_faces(target_frame)
|
||||
i = 0
|
||||
|
||||
for face in many_faces:
|
||||
x_min, y_min, x_max, y_max = face['bbox']
|
||||
modules.globals.souce_target_map.append({
|
||||
modules.globals.source_target_map.append({
|
||||
'id' : i,
|
||||
'target' : {
|
||||
'cv2' : target_frame[int(y_min):int(y_max), int(x_min):int(x_max)],
|
||||
@@ -96,7 +106,7 @@ def get_unique_faces_from_target_image() -> Any:
|
||||
|
||||
def get_unique_faces_from_target_video() -> Any:
|
||||
try:
|
||||
modules.globals.souce_target_map = []
|
||||
modules.globals.source_target_map = []
|
||||
frame_face_embeddings = []
|
||||
face_embeddings = []
|
||||
|
||||
@@ -127,7 +137,7 @@ def get_unique_faces_from_target_video() -> Any:
|
||||
face['target_centroid'] = closest_centroid_index
|
||||
|
||||
for i in range(len(centroids)):
|
||||
modules.globals.souce_target_map.append({
|
||||
modules.globals.source_target_map.append({
|
||||
'id' : i
|
||||
})
|
||||
|
||||
@@ -135,7 +145,7 @@ def get_unique_faces_from_target_video() -> Any:
|
||||
for frame in tqdm(frame_face_embeddings, desc=f"Mapping frame embeddings to centroids-{i}"):
|
||||
temp.append({'frame': frame['frame'], 'faces': [face for face in frame['faces'] if face['target_centroid'] == i], 'location': frame['location']})
|
||||
|
||||
modules.globals.souce_target_map[i]['target_faces_in_frame'] = temp
|
||||
modules.globals.source_target_map[i]['target_faces_in_frame'] = temp
|
||||
|
||||
# dump_faces(centroids, frame_face_embeddings)
|
||||
default_target_face()
|
||||
@@ -144,7 +154,7 @@ def get_unique_faces_from_target_video() -> Any:
|
||||
|
||||
|
||||
def default_target_face():
|
||||
for map in modules.globals.souce_target_map:
|
||||
for map in modules.globals.source_target_map:
|
||||
best_face = None
|
||||
best_frame = None
|
||||
for frame in map['target_faces_in_frame']:
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
class LanguageManager:
|
||||
def __init__(self, default_language="en"):
|
||||
self.current_language = default_language
|
||||
self.translations = {}
|
||||
self.load_language(default_language)
|
||||
|
||||
def load_language(self, language_code) -> bool:
|
||||
"""load language file"""
|
||||
if language_code == "en":
|
||||
return True
|
||||
try:
|
||||
file_path = Path(__file__).parent.parent / f"locales/{language_code}.json"
|
||||
with open(file_path, "r", encoding="utf-8") as file:
|
||||
self.translations = json.load(file)
|
||||
self.current_language = language_code
|
||||
return True
|
||||
except FileNotFoundError:
|
||||
print(f"Language file not found: {language_code}")
|
||||
return False
|
||||
|
||||
def _(self, key, default=None) -> str:
|
||||
"""get translate text"""
|
||||
return self.translations.get(key, default if default else key)
|
||||
+60
-30
@@ -1,3 +1,5 @@
|
||||
# --- START OF FILE globals.py ---
|
||||
|
||||
import os
|
||||
from typing import List, Dict, Any
|
||||
|
||||
@@ -9,35 +11,63 @@ file_types = [
|
||||
("Video", ("*.mp4", "*.mkv")),
|
||||
]
|
||||
|
||||
souce_target_map = []
|
||||
simple_map = {}
|
||||
# Face Mapping Data
|
||||
source_target_map: List[Dict[str, Any]] = [] # Stores detailed map for image/video processing
|
||||
simple_map: Dict[str, Any] = {} # Stores simplified map (embeddings/faces) for live/simple mode
|
||||
|
||||
source_path = None
|
||||
target_path = None
|
||||
output_path = None
|
||||
# Paths
|
||||
source_path: str | None = None
|
||||
target_path: str | None = None
|
||||
output_path: str | None = None
|
||||
|
||||
# Processing Options
|
||||
frame_processors: List[str] = []
|
||||
keep_fps = True
|
||||
keep_audio = True
|
||||
keep_frames = False
|
||||
many_faces = False
|
||||
map_faces = False
|
||||
color_correction = False # New global variable for color correction toggle
|
||||
nsfw_filter = False
|
||||
video_encoder = None
|
||||
video_quality = None
|
||||
live_mirror = False
|
||||
live_resizable = True
|
||||
max_memory = None
|
||||
execution_providers: List[str] = []
|
||||
execution_threads = None
|
||||
headless = None
|
||||
log_level = "error"
|
||||
fp_ui: Dict[str, bool] = {"face_enhancer": False}
|
||||
camera_input_combobox = None
|
||||
webcam_preview_running = False
|
||||
show_fps = False
|
||||
mouth_mask = False
|
||||
show_mouth_mask_box = False
|
||||
mask_feather_ratio = 8
|
||||
mask_down_size = 0.50
|
||||
mask_size = 1
|
||||
keep_fps: bool = True
|
||||
keep_audio: bool = True
|
||||
keep_frames: bool = False
|
||||
many_faces: bool = False # Process all detected faces with default source
|
||||
map_faces: bool = False # Use source_target_map or simple_map for specific swaps
|
||||
poisson_blend: bool = False # Enable Poisson Blending for smoother face swaps
|
||||
color_correction: bool = False # Enable color correction (implementation specific)
|
||||
nsfw_filter: bool = False
|
||||
|
||||
# Video Output Options
|
||||
video_encoder: str | None = None
|
||||
video_quality: int | None = None # Typically a CRF value or bitrate
|
||||
|
||||
# Live Mode Options
|
||||
live_mirror: bool = False
|
||||
live_resizable: bool = True
|
||||
camera_input_combobox: Any | None = None # Placeholder for UI element if needed
|
||||
webcam_preview_running: bool = False
|
||||
show_fps: bool = False
|
||||
|
||||
# System Configuration
|
||||
max_memory: int | None = None # Memory limit in GB? (Needs clarification)
|
||||
execution_providers: List[str] = [] # e.g., ['CUDAExecutionProvider', 'CPUExecutionProvider']
|
||||
execution_threads: int | None = None # Number of threads for CPU execution
|
||||
headless: bool | None = None # Run without UI?
|
||||
log_level: str = "error" # Logging level (e.g., 'debug', 'info', 'warning', 'error')
|
||||
|
||||
# Face Processor UI Toggles (Example)
|
||||
fp_ui: Dict[str, bool] = {"face_enhancer": False, "face_enhancer_gpen256": False, "face_enhancer_gpen512": False}
|
||||
|
||||
# Face Swapper Specific Options
|
||||
face_swapper_enabled: bool = True # General toggle for the swapper processor
|
||||
opacity: float = 1.0 # Blend factor for the swapped face (0.0-1.0)
|
||||
sharpness: float = 0.0 # Sharpness enhancement for swapped face (0.0-1.0+)
|
||||
|
||||
# Mouth Mask Options
|
||||
mouth_mask: bool = False # Enable mouth area masking/pasting
|
||||
show_mouth_mask_box: bool = False # Visualize the mouth mask area (for debugging)
|
||||
mask_feather_ratio: int = 12 # Denominator for feathering calculation (higher = smaller feather)
|
||||
mask_down_size: float = 0.1 # Expansion factor for lower lip mask (relative)
|
||||
mask_size: float = 1.0 # Expansion factor for upper lip mask (relative)
|
||||
mouth_mask_size: float = 0.0 # Mouth mask size (0-100; 0=off, 100=mouth to chin)
|
||||
|
||||
# --- START: Added for Frame Interpolation ---
|
||||
enable_interpolation: bool = True # Toggle temporal smoothing
|
||||
interpolation_weight: float = 0 # Blend weight for current frame (0.0-1.0). Lower=smoother.
|
||||
# --- END: Added for Frame Interpolation ---
|
||||
|
||||
# --- END OF FILE globals.py ---
|
||||
|
||||
@@ -0,0 +1,286 @@
|
||||
# --- START OF FILE gpu_processing.py ---
|
||||
"""
|
||||
GPU-accelerated image processing using OpenCV CUDA (cv2.cuda.GpuMat).
|
||||
|
||||
Provides drop-in replacements for common cv2 functions. When OpenCV is built
|
||||
with CUDA support the functions transparently upload → process → download via
|
||||
GpuMat; otherwise they fall back to the regular CPU path so the rest of the
|
||||
codebase never has to care whether CUDA is available.
|
||||
|
||||
Usage
|
||||
-----
|
||||
from modules.gpu_processing import (
|
||||
gpu_gaussian_blur, gpu_sharpen, gpu_add_weighted,
|
||||
gpu_resize, gpu_cvt_color, gpu_flip,
|
||||
is_gpu_accelerated,
|
||||
)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
from typing import Tuple, Optional
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# CUDA availability detection (evaluated once at import time)
|
||||
# ---------------------------------------------------------------------------
|
||||
CUDA_AVAILABLE: bool = False
|
||||
|
||||
try:
|
||||
# cv2.cuda.GpuMat is only present when OpenCV is compiled with CUDA
|
||||
_test_mat = cv2.cuda.GpuMat()
|
||||
# Verify we have the required filter / image-processing functions
|
||||
_has_gauss = hasattr(cv2.cuda, "createGaussianFilter")
|
||||
_has_resize = hasattr(cv2.cuda, "resize")
|
||||
_has_cvt = hasattr(cv2.cuda, "cvtColor")
|
||||
if _has_gauss and _has_resize and _has_cvt:
|
||||
CUDA_AVAILABLE = True
|
||||
print("[gpu_processing] OpenCV CUDA support detected – GPU-accelerated processing enabled.")
|
||||
else:
|
||||
missing = []
|
||||
if not _has_gauss:
|
||||
missing.append("createGaussianFilter")
|
||||
if not _has_resize:
|
||||
missing.append("resize")
|
||||
if not _has_cvt:
|
||||
missing.append("cvtColor")
|
||||
print(f"[gpu_processing] cv2.cuda.GpuMat exists but missing: {', '.join(missing)} – falling back to CPU.")
|
||||
except Exception:
|
||||
print("[gpu_processing] OpenCV CUDA not available – using CPU fallback for all operations.")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Internal helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _ensure_uint8(img: np.ndarray) -> np.ndarray:
|
||||
"""Clip and convert to uint8 if necessary."""
|
||||
if img.dtype != np.uint8:
|
||||
return np.clip(img, 0, 255).astype(np.uint8)
|
||||
return img
|
||||
|
||||
|
||||
def _ksize_odd(ksize: Tuple[int, int]) -> Tuple[int, int]:
|
||||
"""Ensure kernel dimensions are positive and odd (required by GaussianBlur)."""
|
||||
kw = max(1, ksize[0] // 2 * 2 + 1) if ksize[0] > 0 else 0
|
||||
kh = max(1, ksize[1] // 2 * 2 + 1) if ksize[1] > 0 else 0
|
||||
return (kw, kh)
|
||||
|
||||
|
||||
def _cv_type_for(img: np.ndarray) -> int:
|
||||
"""Return the OpenCV type constant matching *img* (uint8 only)."""
|
||||
channels = 1 if img.ndim == 2 else img.shape[2]
|
||||
if channels == 1:
|
||||
return cv2.CV_8UC1
|
||||
elif channels == 3:
|
||||
return cv2.CV_8UC3
|
||||
elif channels == 4:
|
||||
return cv2.CV_8UC4
|
||||
return cv2.CV_8UC3 # fallback
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Public API – Gaussian Blur
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def gpu_gaussian_blur(
|
||||
src: np.ndarray,
|
||||
ksize: Tuple[int, int],
|
||||
sigma_x: float,
|
||||
sigma_y: float = 0,
|
||||
) -> np.ndarray:
|
||||
"""Drop-in replacement for ``cv2.GaussianBlur`` with CUDA acceleration.
|
||||
|
||||
Parameters match ``cv2.GaussianBlur(src, ksize, sigmaX, sigmaY)``.
|
||||
When *ksize* is ``(0, 0)`` OpenCV computes the kernel size from *sigma_x*.
|
||||
"""
|
||||
if CUDA_AVAILABLE:
|
||||
try:
|
||||
src_u8 = _ensure_uint8(src)
|
||||
cv_type = _cv_type_for(src_u8)
|
||||
ks = _ksize_odd(ksize) if ksize != (0, 0) else ksize
|
||||
|
||||
gauss = cv2.cuda.createGaussianFilter(cv_type, cv_type, ks, sigma_x, sigma_y)
|
||||
gpu_src = cv2.cuda.GpuMat()
|
||||
gpu_src.upload(src_u8)
|
||||
gpu_dst = gauss.apply(gpu_src)
|
||||
return gpu_dst.download()
|
||||
except cv2.error:
|
||||
pass
|
||||
|
||||
return cv2.GaussianBlur(src, ksize, sigma_x, sigmaY=sigma_y)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Public API – addWeighted
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def gpu_add_weighted(
|
||||
src1: np.ndarray,
|
||||
alpha: float,
|
||||
src2: np.ndarray,
|
||||
beta: float,
|
||||
gamma: float,
|
||||
) -> np.ndarray:
|
||||
"""Drop-in replacement for ``cv2.addWeighted`` with CUDA acceleration."""
|
||||
if CUDA_AVAILABLE:
|
||||
try:
|
||||
s1 = _ensure_uint8(src1)
|
||||
s2 = _ensure_uint8(src2)
|
||||
g1 = cv2.cuda.GpuMat()
|
||||
g2 = cv2.cuda.GpuMat()
|
||||
g1.upload(s1)
|
||||
g2.upload(s2)
|
||||
gpu_dst = cv2.cuda.addWeighted(g1, alpha, g2, beta, gamma)
|
||||
return gpu_dst.download()
|
||||
except cv2.error:
|
||||
pass
|
||||
|
||||
return cv2.addWeighted(src1, alpha, src2, beta, gamma)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Public API – Unsharp-mask sharpening
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def gpu_sharpen(
|
||||
src: np.ndarray,
|
||||
strength: float,
|
||||
sigma: float = 3,
|
||||
) -> np.ndarray:
|
||||
"""Unsharp-mask sharpening, optionally GPU-accelerated.
|
||||
|
||||
Equivalent to::
|
||||
|
||||
blurred = GaussianBlur(src, (0,0), sigma)
|
||||
result = addWeighted(src, 1+strength, blurred, -strength, 0)
|
||||
"""
|
||||
if strength <= 0:
|
||||
return src
|
||||
|
||||
if CUDA_AVAILABLE:
|
||||
try:
|
||||
src_u8 = _ensure_uint8(src)
|
||||
cv_type = _cv_type_for(src_u8)
|
||||
|
||||
gauss = cv2.cuda.createGaussianFilter(cv_type, cv_type, (0, 0), sigma)
|
||||
gpu_src = cv2.cuda.GpuMat()
|
||||
gpu_src.upload(src_u8)
|
||||
gpu_blurred = gauss.apply(gpu_src)
|
||||
gpu_sharp = cv2.cuda.addWeighted(gpu_src, 1.0 + strength, gpu_blurred, -strength, 0)
|
||||
result = gpu_sharp.download()
|
||||
return np.clip(result, 0, 255).astype(np.uint8)
|
||||
except cv2.error:
|
||||
pass
|
||||
|
||||
blurred = cv2.GaussianBlur(src, (0, 0), sigma)
|
||||
sharpened = cv2.addWeighted(src, 1.0 + strength, blurred, -strength, 0)
|
||||
return np.clip(sharpened, 0, 255).astype(np.uint8)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Public API – Resize
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Map common cv2 interpolation flags to their CUDA equivalents
|
||||
_INTERP_MAP = {
|
||||
cv2.INTER_NEAREST: cv2.INTER_NEAREST,
|
||||
cv2.INTER_LINEAR: cv2.INTER_LINEAR,
|
||||
cv2.INTER_CUBIC: cv2.INTER_CUBIC,
|
||||
cv2.INTER_AREA: cv2.INTER_AREA,
|
||||
cv2.INTER_LANCZOS4: cv2.INTER_LANCZOS4,
|
||||
}
|
||||
|
||||
|
||||
def gpu_resize(
|
||||
src: np.ndarray,
|
||||
dsize: Tuple[int, int],
|
||||
fx: float = 0,
|
||||
fy: float = 0,
|
||||
interpolation: int = cv2.INTER_LINEAR,
|
||||
) -> np.ndarray:
|
||||
"""Drop-in replacement for ``cv2.resize`` with CUDA acceleration.
|
||||
|
||||
Parameters match ``cv2.resize(src, dsize, fx=fx, fy=fy, interpolation=...)``.
|
||||
"""
|
||||
if CUDA_AVAILABLE:
|
||||
try:
|
||||
src_u8 = _ensure_uint8(src)
|
||||
gpu_src = cv2.cuda.GpuMat()
|
||||
gpu_src.upload(src_u8)
|
||||
|
||||
interp = _INTERP_MAP.get(interpolation, cv2.INTER_LINEAR)
|
||||
|
||||
if dsize and dsize[0] > 0 and dsize[1] > 0:
|
||||
gpu_dst = cv2.cuda.resize(gpu_src, dsize, interpolation=interp)
|
||||
else:
|
||||
gpu_dst = cv2.cuda.resize(gpu_src, (0, 0), fx=fx, fy=fy, interpolation=interp)
|
||||
|
||||
return gpu_dst.download()
|
||||
except cv2.error:
|
||||
pass
|
||||
|
||||
return cv2.resize(src, dsize, fx=fx, fy=fy, interpolation=interpolation)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Public API – Color conversion
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def gpu_cvt_color(
|
||||
src: np.ndarray,
|
||||
code: int,
|
||||
) -> np.ndarray:
|
||||
"""Drop-in replacement for ``cv2.cvtColor`` with CUDA acceleration.
|
||||
|
||||
Parameters match ``cv2.cvtColor(src, code)``.
|
||||
"""
|
||||
if CUDA_AVAILABLE:
|
||||
try:
|
||||
src_u8 = _ensure_uint8(src)
|
||||
gpu_src = cv2.cuda.GpuMat()
|
||||
gpu_src.upload(src_u8)
|
||||
gpu_dst = cv2.cuda.cvtColor(gpu_src, code)
|
||||
return gpu_dst.download()
|
||||
except cv2.error:
|
||||
pass
|
||||
|
||||
return cv2.cvtColor(src, code)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Public API – Flip
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def gpu_flip(
|
||||
src: np.ndarray,
|
||||
flip_code: int,
|
||||
) -> np.ndarray:
|
||||
"""Drop-in replacement for ``cv2.flip`` with CUDA acceleration.
|
||||
|
||||
Parameters match ``cv2.flip(src, flipCode)``.
|
||||
*flip_code*: 0 = vertical, 1 = horizontal, -1 = both.
|
||||
"""
|
||||
if CUDA_AVAILABLE:
|
||||
try:
|
||||
src_u8 = _ensure_uint8(src)
|
||||
gpu_src = cv2.cuda.GpuMat()
|
||||
gpu_src.upload(src_u8)
|
||||
gpu_dst = cv2.cuda.flip(gpu_src, flip_code)
|
||||
return gpu_dst.download()
|
||||
except cv2.error:
|
||||
pass
|
||||
|
||||
return cv2.flip(src, flip_code)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Convenience: check at runtime whether GPU path is active
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def is_gpu_accelerated() -> bool:
|
||||
"""Return ``True`` when the CUDA path will be used."""
|
||||
return CUDA_AVAILABLE
|
||||
|
||||
# --- END OF FILE gpu_processing.py ---
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
name = 'Deep-Live-Cam'
|
||||
version = '1.8'
|
||||
version = '2.1'
|
||||
edition = 'GitHub Edition'
|
||||
@@ -0,0 +1,6 @@
|
||||
"""Shared path constants for the Deep-Live-Cam project."""
|
||||
|
||||
import os
|
||||
|
||||
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
MODELS_DIR = os.path.join(ROOT_DIR, "models")
|
||||
@@ -3,6 +3,7 @@ import opennsfw2
|
||||
from PIL import Image
|
||||
import cv2 # Add OpenCV import
|
||||
import modules.globals # Import globals to access the color correction toggle
|
||||
from modules.gpu_processing import gpu_cvt_color
|
||||
|
||||
from modules.typing import Frame
|
||||
|
||||
@@ -14,7 +15,7 @@ model = None
|
||||
def predict_frame(target_frame: Frame) -> bool:
|
||||
# Convert the frame to RGB before processing if color correction is enabled
|
||||
if modules.globals.color_correction:
|
||||
target_frame = cv2.cvtColor(target_frame, cv2.COLOR_BGR2RGB)
|
||||
target_frame = gpu_cvt_color(target_frame, cv2.COLOR_BGR2RGB)
|
||||
|
||||
image = Image.fromarray(target_frame)
|
||||
image = opennsfw2.preprocess_image(image, opennsfw2.Preprocessing.YAHOO)
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
"""Shared ONNX-based face enhancement utilities for GPEN-BFR models.
|
||||
|
||||
Provides session creation, pre/post processing, and the core
|
||||
enhance-face-via-ONNX pipeline.
|
||||
"""
|
||||
|
||||
import os
|
||||
import platform
|
||||
import threading
|
||||
from typing import Any
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
import onnxruntime
|
||||
|
||||
import modules.globals
|
||||
|
||||
IS_APPLE_SILICON = platform.system() == "Darwin" and platform.machine() == "arm64"
|
||||
|
||||
# Limit concurrent ONNX calls to avoid VRAM exhaustion on multi-face frames
|
||||
THREAD_SEMAPHORE = threading.Semaphore(min(max(1, (os.cpu_count() or 1)), 8))
|
||||
|
||||
|
||||
def create_onnx_session(model_path: str) -> onnxruntime.InferenceSession:
|
||||
"""Create an ONNX Runtime session using the configured execution providers."""
|
||||
providers = modules.globals.execution_providers
|
||||
session = onnxruntime.InferenceSession(model_path, providers=providers)
|
||||
return session
|
||||
|
||||
|
||||
def warmup_session(session: onnxruntime.InferenceSession) -> None:
|
||||
"""Run a dummy inference pass to trigger JIT / compile caching."""
|
||||
try:
|
||||
input_feed = {
|
||||
inp.name: np.zeros(
|
||||
[d if isinstance(d, int) and d > 0 else 1 for d in inp.shape],
|
||||
dtype=np.float32,
|
||||
)
|
||||
for inp in session.get_inputs()
|
||||
}
|
||||
session.run(None, input_feed)
|
||||
except Exception as e:
|
||||
print(f"ONNX enhancer warmup skipped (non-fatal): {e}")
|
||||
|
||||
|
||||
def preprocess_face(face_img: np.ndarray, input_size: int) -> np.ndarray:
|
||||
"""Resize, normalize, and convert a BGR face crop to ONNX input blob.
|
||||
|
||||
GPEN-BFR expects [1, 3, H, W] float32 in RGB, normalized to [-1, 1].
|
||||
"""
|
||||
resized = cv2.resize(face_img, (input_size, input_size), interpolation=cv2.INTER_LINEAR)
|
||||
rgb = cv2.cvtColor(resized, cv2.COLOR_BGR2RGB)
|
||||
blob = rgb.astype(np.float32) / 255.0 * 2.0 - 1.0
|
||||
blob = np.transpose(blob, (2, 0, 1))[np.newaxis, ...]
|
||||
return blob
|
||||
|
||||
|
||||
def postprocess_face(output: np.ndarray) -> np.ndarray:
|
||||
"""Convert ONNX output [1, 3, H, W] float32 back to BGR uint8 image."""
|
||||
img = output[0].transpose(1, 2, 0)
|
||||
img = ((img + 1.0) / 2.0 * 255.0)
|
||||
img = np.clip(img, 0, 255).astype(np.uint8)
|
||||
img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
|
||||
return img
|
||||
|
||||
|
||||
def _get_face_affine(face: Any, input_size: int):
|
||||
"""Compute affine transform to align a face to GPEN input space.
|
||||
|
||||
Returns (M, inv_M) — forward and inverse affine matrices.
|
||||
"""
|
||||
template = np.array([
|
||||
[0.31556875, 0.4615741],
|
||||
[0.68262291, 0.4615741],
|
||||
[0.50009375, 0.6405054],
|
||||
[0.34947187, 0.8246919],
|
||||
[0.65343645, 0.8246919],
|
||||
], dtype=np.float32) * input_size
|
||||
|
||||
landmarks = None
|
||||
if hasattr(face, "kps") and face.kps is not None:
|
||||
landmarks = face.kps.astype(np.float32)
|
||||
elif hasattr(face, "landmark_2d_106") and face.landmark_2d_106 is not None:
|
||||
lm106 = face.landmark_2d_106
|
||||
landmarks = np.array([
|
||||
lm106[38], # left eye
|
||||
lm106[88], # right eye
|
||||
lm106[86], # nose tip
|
||||
lm106[52], # left mouth
|
||||
lm106[61], # right mouth
|
||||
], dtype=np.float32)
|
||||
|
||||
if landmarks is None or len(landmarks) < 5:
|
||||
return None, None
|
||||
|
||||
M = cv2.estimateAffinePartial2D(landmarks, template, method=cv2.LMEDS)[0]
|
||||
if M is None:
|
||||
return None, None
|
||||
inv_M = cv2.invertAffineTransform(M)
|
||||
return M, inv_M
|
||||
|
||||
|
||||
def enhance_face_onnx(
|
||||
frame: np.ndarray,
|
||||
face: Any,
|
||||
session: onnxruntime.InferenceSession,
|
||||
input_size: int,
|
||||
) -> np.ndarray:
|
||||
"""Enhance a single face in the frame using an ONNX face restoration model."""
|
||||
M, inv_M = _get_face_affine(face, input_size)
|
||||
if M is None:
|
||||
return frame
|
||||
|
||||
face_crop = cv2.warpAffine(
|
||||
frame, M, (input_size, input_size),
|
||||
flags=cv2.INTER_LINEAR, borderMode=cv2.BORDER_REPLICATE,
|
||||
)
|
||||
|
||||
blob = preprocess_face(face_crop, input_size)
|
||||
with THREAD_SEMAPHORE:
|
||||
output = session.run(None, {session.get_inputs()[0].name: blob})[0]
|
||||
enhanced = postprocess_face(output)
|
||||
|
||||
# Create mask for blending (feathered edges)
|
||||
mask = np.ones((input_size, input_size), dtype=np.float32)
|
||||
border = max(1, input_size // 16)
|
||||
mask[:border, :] = np.linspace(0, 1, border)[:, np.newaxis]
|
||||
mask[-border:, :] = np.linspace(1, 0, border)[:, np.newaxis]
|
||||
mask[:, :border] = np.minimum(mask[:, :border], np.linspace(0, 1, border)[np.newaxis, :])
|
||||
mask[:, -border:] = np.minimum(mask[:, -border:], np.linspace(1, 0, border)[np.newaxis, :])
|
||||
|
||||
h, w = frame.shape[:2]
|
||||
warped_enhanced = cv2.warpAffine(
|
||||
enhanced, inv_M, (w, h),
|
||||
flags=cv2.INTER_LINEAR, borderValue=(0, 0, 0),
|
||||
)
|
||||
warped_mask = cv2.warpAffine(
|
||||
mask, inv_M, (w, h),
|
||||
flags=cv2.INTER_LINEAR, borderValue=0,
|
||||
)
|
||||
|
||||
mask_3ch = warped_mask[:, :, np.newaxis]
|
||||
result = (warped_enhanced.astype(np.float32) * mask_3ch +
|
||||
frame.astype(np.float32) * (1.0 - mask_3ch))
|
||||
return np.clip(result, 0, 255).astype(np.uint8)
|
||||
@@ -17,8 +17,17 @@ FRAME_PROCESSORS_INTERFACE = [
|
||||
'process_video'
|
||||
]
|
||||
|
||||
ALLOWED_PROCESSORS = {
|
||||
'face_swapper',
|
||||
'face_enhancer',
|
||||
'face_enhancer_gpen256',
|
||||
'face_enhancer_gpen512'
|
||||
}
|
||||
|
||||
def load_frame_processor_module(frame_processor: str) -> Any:
|
||||
if frame_processor not in ALLOWED_PROCESSORS:
|
||||
print(f"Frame processor {frame_processor} is not allowed")
|
||||
sys.exit()
|
||||
try:
|
||||
frame_processor_module = importlib.import_module(f'modules.processors.frame.{frame_processor}')
|
||||
for method_name in FRAME_PROCESSORS_INTERFACE:
|
||||
@@ -42,27 +51,54 @@ def get_frame_processors_modules(frame_processors: List[str]) -> List[ModuleType
|
||||
|
||||
def set_frame_processors_modules_from_ui(frame_processors: List[str]) -> None:
|
||||
global 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():
|
||||
if state == True and frame_processor not in frame_processors:
|
||||
frame_processor_module = load_frame_processor_module(frame_processor)
|
||||
FRAME_PROCESSORS_MODULES.append(frame_processor_module)
|
||||
modules.globals.frame_processors.append(frame_processor)
|
||||
if state == False:
|
||||
if state == True and frame_processor not in current_processor_names:
|
||||
try:
|
||||
frame_processor_module = load_frame_processor_module(frame_processor)
|
||||
FRAME_PROCESSORS_MODULES.remove(frame_processor_module)
|
||||
FRAME_PROCESSORS_MODULES.append(frame_processor_module)
|
||||
if frame_processor not in modules.globals.frame_processors:
|
||||
modules.globals.frame_processors.append(frame_processor)
|
||||
except SystemExit:
|
||||
print(f"Warning: Failed to load frame processor {frame_processor} requested by UI state.")
|
||||
except Exception as 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:
|
||||
try:
|
||||
module_to_remove = next((mod for mod in FRAME_PROCESSORS_MODULES if mod.__name__.endswith(f'.{frame_processor}')), None)
|
||||
if module_to_remove:
|
||||
FRAME_PROCESSORS_MODULES.remove(module_to_remove)
|
||||
if frame_processor in modules.globals.frame_processors:
|
||||
modules.globals.frame_processors.remove(frame_processor)
|
||||
except:
|
||||
pass
|
||||
except Exception as e:
|
||||
print(f"Warning: Error removing frame processor {frame_processor}: {e}")
|
||||
|
||||
def multi_process_frame(source_path: str, temp_frame_paths: List[str], process_frames: Callable[[str, List[str], Any], None], progress: Any = None) -> None:
|
||||
with ThreadPoolExecutor(max_workers=modules.globals.execution_threads) as executor:
|
||||
"""Process frames in parallel with optimized batching and memory management."""
|
||||
max_workers = modules.globals.execution_threads
|
||||
|
||||
# Determine optimal batch size based on available memory and thread count
|
||||
# Process frames in batches to avoid memory overflow
|
||||
batch_size = max(1, min(32, len(temp_frame_paths) // max(1, max_workers)))
|
||||
|
||||
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
||||
# Process in batches to manage memory better
|
||||
for i in range(0, len(temp_frame_paths), batch_size):
|
||||
batch = temp_frame_paths[i:i + batch_size]
|
||||
futures = []
|
||||
for path in temp_frame_paths:
|
||||
|
||||
for path in batch:
|
||||
future = executor.submit(process_frames, source_path, [path], progress)
|
||||
futures.append(future)
|
||||
|
||||
# Wait for batch to complete before starting next batch
|
||||
for future in futures:
|
||||
try:
|
||||
future.result()
|
||||
except Exception as e:
|
||||
print(f"Error processing frame: {e}")
|
||||
|
||||
|
||||
def process_video(source_path: str, frame_paths: list[str], process_frames: Callable[[str, List[str], Any], None]) -> None:
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
# --- START OF FILE face_enhancer.py ---
|
||||
# Uses ONNX Runtime for GFPGAN face enhancement (no torch/gfpgan dependency)
|
||||
|
||||
from typing import Any, List
|
||||
import cv2
|
||||
import threading
|
||||
import gfpgan
|
||||
import numpy as np
|
||||
import os
|
||||
|
||||
import onnxruntime
|
||||
|
||||
import modules.globals
|
||||
import modules.processors.frame.core
|
||||
from modules.core import update_status
|
||||
from modules.face_analyser import get_one_face
|
||||
from modules.face_analyser import get_one_face, get_many_faces
|
||||
from modules.typing import Frame, Face
|
||||
import platform
|
||||
import torch
|
||||
from modules.utilities import (
|
||||
conditional_download,
|
||||
is_image,
|
||||
is_video,
|
||||
)
|
||||
@@ -27,15 +29,29 @@ models_dir = os.path.join(
|
||||
os.path.dirname(os.path.dirname(os.path.dirname(abs_dir))), "models"
|
||||
)
|
||||
|
||||
# Standard FFHQ 5-point face template for 512x512 resolution
|
||||
# Points: left_eye, right_eye, nose, left_mouth, right_mouth
|
||||
FFHQ_TEMPLATE_512 = np.array(
|
||||
[
|
||||
[192.98138, 239.94708],
|
||||
[318.90277, 240.19366],
|
||||
[256.63416, 314.01935],
|
||||
[201.26117, 371.41043],
|
||||
[313.08905, 371.15118],
|
||||
],
|
||||
dtype=np.float32,
|
||||
)
|
||||
|
||||
|
||||
def pre_check() -> bool:
|
||||
download_directory_path = models_dir
|
||||
conditional_download(
|
||||
download_directory_path,
|
||||
[
|
||||
"https://github.com/TencentARC/GFPGAN/releases/download/v1.3.4/GFPGANv1.4.pth"
|
||||
],
|
||||
model_path = os.path.join(models_dir, "gfpgan-1024.onnx")
|
||||
if not os.path.exists(model_path):
|
||||
update_status(
|
||||
f"GFPGAN ONNX model not found at {model_path}. "
|
||||
"Please place gfpgan-1024.onnx in the models folder.",
|
||||
NAME,
|
||||
)
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
@@ -48,62 +64,309 @@ def pre_start() -> bool:
|
||||
return True
|
||||
|
||||
|
||||
def get_face_enhancer() -> Any:
|
||||
def get_face_enhancer() -> onnxruntime.InferenceSession:
|
||||
"""
|
||||
Initializes and returns the GFPGAN ONNX Runtime inference session,
|
||||
using the execution providers configured in modules.globals.
|
||||
"""
|
||||
global FACE_ENHANCER
|
||||
|
||||
with THREAD_LOCK:
|
||||
if FACE_ENHANCER is None:
|
||||
model_path = os.path.join(models_dir, "GFPGANv1.4.pth")
|
||||
model_path = os.path.join(models_dir, "gfpgan-1024.onnx")
|
||||
|
||||
match platform.system():
|
||||
case "Darwin": # Mac OS
|
||||
if torch.backends.mps.is_available():
|
||||
mps_device = torch.device("mps")
|
||||
FACE_ENHANCER = gfpgan.GFPGANer(model_path=model_path, upscale=1, device=mps_device) # type: ignore[attr-defined]
|
||||
else:
|
||||
FACE_ENHANCER = gfpgan.GFPGANer(model_path=model_path, upscale=1) # type: ignore[attr-defined]
|
||||
case _: # Other OS
|
||||
FACE_ENHANCER = gfpgan.GFPGANer(model_path=model_path, upscale=1) # type: ignore[attr-defined]
|
||||
if not os.path.exists(model_path):
|
||||
raise FileNotFoundError(
|
||||
f"{NAME}: Model not found at {model_path}"
|
||||
)
|
||||
|
||||
try:
|
||||
providers = modules.globals.execution_providers
|
||||
|
||||
session_options = onnxruntime.SessionOptions()
|
||||
session_options.graph_optimization_level = (
|
||||
onnxruntime.GraphOptimizationLevel.ORT_ENABLE_ALL
|
||||
)
|
||||
|
||||
FACE_ENHANCER = onnxruntime.InferenceSession(
|
||||
model_path,
|
||||
sess_options=session_options,
|
||||
providers=providers,
|
||||
)
|
||||
|
||||
input_info = FACE_ENHANCER.get_inputs()[0]
|
||||
output_info = FACE_ENHANCER.get_outputs()[0]
|
||||
active_providers = FACE_ENHANCER.get_providers()
|
||||
print(
|
||||
f"{NAME}: GFPGAN ONNX model loaded successfully."
|
||||
)
|
||||
print(
|
||||
f"{NAME}: Input: {input_info.name}, "
|
||||
f"shape: {input_info.shape}, type: {input_info.type}"
|
||||
)
|
||||
print(
|
||||
f"{NAME}: Output: {output_info.name}, "
|
||||
f"shape: {output_info.shape}, type: {output_info.type}"
|
||||
)
|
||||
print(f"{NAME}: Active providers: {active_providers}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"{NAME}: Error loading GFPGAN ONNX model: {e}")
|
||||
FACE_ENHANCER = None
|
||||
raise RuntimeError(
|
||||
f"{NAME}: Failed to load GFPGAN ONNX model: {e}"
|
||||
)
|
||||
|
||||
if FACE_ENHANCER is None:
|
||||
raise RuntimeError(
|
||||
f"{NAME}: Failed to initialize GFPGAN ONNX session. Check logs."
|
||||
)
|
||||
|
||||
return FACE_ENHANCER
|
||||
|
||||
|
||||
def _align_face(
|
||||
frame: Frame, landmarks_5: np.ndarray, output_size: int
|
||||
) -> tuple:
|
||||
"""
|
||||
Align and crop a face from the frame using 5-point landmarks and the
|
||||
standard FFHQ template.
|
||||
|
||||
Returns:
|
||||
(aligned_face, affine_matrix) or (None, None) on failure.
|
||||
"""
|
||||
# Scale the 512-base template to the desired output size
|
||||
scale = output_size / 512.0
|
||||
template = FFHQ_TEMPLATE_512 * scale
|
||||
|
||||
# Estimate a similarity transform (4 DOF: rotation, scale, tx, ty)
|
||||
affine_matrix, _ = cv2.estimateAffinePartial2D(
|
||||
landmarks_5, template, method=cv2.LMEDS
|
||||
)
|
||||
if affine_matrix is None:
|
||||
return None, None
|
||||
|
||||
# Warp the face to the aligned position
|
||||
aligned_face = cv2.warpAffine(
|
||||
frame,
|
||||
affine_matrix,
|
||||
(output_size, output_size),
|
||||
borderMode=cv2.BORDER_CONSTANT,
|
||||
borderValue=(135, 133, 132),
|
||||
)
|
||||
|
||||
return aligned_face, affine_matrix
|
||||
|
||||
|
||||
def _paste_back(
|
||||
frame: Frame,
|
||||
enhanced_face: np.ndarray,
|
||||
affine_matrix: np.ndarray,
|
||||
output_size: int,
|
||||
) -> Frame:
|
||||
"""
|
||||
Paste an enhanced (aligned) face back onto the original frame using the
|
||||
inverse affine transform with feathered-edge blending.
|
||||
"""
|
||||
h, w = frame.shape[:2]
|
||||
|
||||
# Inverse the affine warp
|
||||
inv_matrix = cv2.invertAffineTransform(affine_matrix)
|
||||
inv_restored = cv2.warpAffine(
|
||||
enhanced_face,
|
||||
inv_matrix,
|
||||
(w, h),
|
||||
borderMode=cv2.BORDER_CONSTANT,
|
||||
borderValue=(0, 0, 0),
|
||||
)
|
||||
|
||||
# Build a soft feathered mask in aligned space for edge blending
|
||||
face_mask = np.ones((output_size, output_size), dtype=np.float32)
|
||||
|
||||
# Feather the border (5 % of the size on each edge)
|
||||
border = max(1, int(output_size * 0.05))
|
||||
ramp_up = np.linspace(0.0, 1.0, border, dtype=np.float32)
|
||||
ramp_down = np.linspace(1.0, 0.0, border, dtype=np.float32)
|
||||
|
||||
# Top / bottom rows
|
||||
face_mask[:border, :] *= ramp_up[:, None]
|
||||
face_mask[-border:, :] *= ramp_down[:, None]
|
||||
# Left / right columns
|
||||
face_mask[:, :border] *= ramp_up[None, :]
|
||||
face_mask[:, -border:] *= ramp_down[None, :]
|
||||
|
||||
# Expand to 3-channel
|
||||
face_mask_3c = np.stack([face_mask] * 3, axis=-1)
|
||||
|
||||
# Warp mask back to original frame space
|
||||
inv_mask = cv2.warpAffine(
|
||||
face_mask_3c,
|
||||
inv_matrix,
|
||||
(w, h),
|
||||
borderMode=cv2.BORDER_CONSTANT,
|
||||
borderValue=(0, 0, 0),
|
||||
)
|
||||
inv_mask = np.clip(inv_mask, 0.0, 1.0)
|
||||
|
||||
# Alpha-blend
|
||||
result = (
|
||||
frame.astype(np.float32) * (1.0 - inv_mask)
|
||||
+ inv_restored.astype(np.float32) * inv_mask
|
||||
)
|
||||
return np.clip(result, 0, 255).astype(np.uint8)
|
||||
|
||||
|
||||
def _preprocess_face(aligned_face: np.ndarray) -> np.ndarray:
|
||||
"""
|
||||
Convert an aligned BGR uint8 face image to the ONNX model input tensor.
|
||||
Format: NCHW float32, normalised to [-1, 1].
|
||||
"""
|
||||
# BGR -> RGB
|
||||
rgb = cv2.cvtColor(aligned_face, cv2.COLOR_BGR2RGB).astype(np.float32)
|
||||
# [0, 255] -> [0, 1] -> [-1, 1]
|
||||
rgb = rgb / 255.0
|
||||
rgb = (rgb - 0.5) / 0.5
|
||||
# HWC -> CHW, add batch dim
|
||||
chw = np.transpose(rgb, (2, 0, 1))
|
||||
return np.expand_dims(chw, axis=0) # shape: (1, 3, H, W)
|
||||
|
||||
|
||||
def _postprocess_face(output: np.ndarray) -> np.ndarray:
|
||||
"""
|
||||
Convert the ONNX model output tensor back to a BGR uint8 image.
|
||||
Expects input in NCHW format with values in [-1, 1].
|
||||
"""
|
||||
face = np.squeeze(output) # remove batch dim -> (3, H, W)
|
||||
face = np.transpose(face, (1, 2, 0)) # CHW -> HWC
|
||||
# [-1, 1] -> [0, 1] -> [0, 255]
|
||||
face = (face + 1.0) / 2.0
|
||||
face = np.clip(face * 255.0, 0, 255).astype(np.uint8)
|
||||
# RGB -> BGR
|
||||
return cv2.cvtColor(face, cv2.COLOR_RGB2BGR)
|
||||
|
||||
|
||||
def enhance_face(temp_frame: Frame) -> Frame:
|
||||
with THREAD_SEMAPHORE:
|
||||
_, _, temp_frame = get_face_enhancer().enhance(temp_frame, paste_back=True)
|
||||
"""Enhances all faces in a frame using the GFPGAN ONNX model."""
|
||||
session = get_face_enhancer()
|
||||
|
||||
# Determine model input resolution from the session metadata
|
||||
input_info = session.get_inputs()[0]
|
||||
input_name = input_info.name
|
||||
input_shape = input_info.shape # e.g. [1, 3, 512, 512]
|
||||
# Safely extract input size (handle dynamic / symbolic dimensions)
|
||||
try:
|
||||
align_size = int(input_shape[2])
|
||||
if align_size <= 0:
|
||||
align_size = 512
|
||||
except (ValueError, TypeError, IndexError):
|
||||
align_size = 512
|
||||
|
||||
# Detect faces using InsightFace (already a project dependency)
|
||||
faces = get_many_faces(temp_frame)
|
||||
if not faces:
|
||||
return temp_frame
|
||||
|
||||
result_frame = temp_frame.copy()
|
||||
|
||||
def process_frame(source_face: Face, temp_frame: Frame) -> Frame:
|
||||
target_face = get_one_face(temp_frame)
|
||||
if target_face:
|
||||
for face in faces:
|
||||
# Need the 5-point key-points for alignment
|
||||
if not hasattr(face, "kps") or face.kps is None:
|
||||
continue
|
||||
|
||||
landmarks_5 = face.kps.astype(np.float32)
|
||||
if landmarks_5.shape[0] < 5:
|
||||
continue
|
||||
|
||||
# Align / crop the face at the model's INPUT resolution
|
||||
aligned_face, affine_matrix = _align_face(
|
||||
temp_frame, landmarks_5, output_size=align_size
|
||||
)
|
||||
if aligned_face is None or affine_matrix is None:
|
||||
continue
|
||||
|
||||
try:
|
||||
with THREAD_SEMAPHORE:
|
||||
input_tensor = _preprocess_face(aligned_face)
|
||||
output_tensor = session.run(None, {input_name: input_tensor})[0]
|
||||
enhanced_bgr = _postprocess_face(output_tensor)
|
||||
|
||||
# The model may output at a different resolution than its input
|
||||
# (e.g. input 512x512 → output 1024x1024). Resize the enhanced
|
||||
# face back to the alignment size so the inverse affine maps
|
||||
# correctly.
|
||||
eh, ew = enhanced_bgr.shape[:2]
|
||||
if eh != align_size or ew != align_size:
|
||||
enhanced_bgr = cv2.resize(
|
||||
enhanced_bgr,
|
||||
(align_size, align_size),
|
||||
interpolation=cv2.INTER_LANCZOS4,
|
||||
)
|
||||
|
||||
# Paste enhanced face back onto the frame
|
||||
result_frame = _paste_back(
|
||||
result_frame, enhanced_bgr, affine_matrix, output_size=align_size
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"{NAME}: Error enhancing a face: {e}")
|
||||
continue
|
||||
|
||||
return result_frame
|
||||
|
||||
|
||||
def process_frame(source_face: Face | None, temp_frame: Frame) -> Frame:
|
||||
"""Processes a frame: enhances face if detected."""
|
||||
temp_frame = enhance_face(temp_frame)
|
||||
return temp_frame
|
||||
|
||||
|
||||
def process_frames(
|
||||
source_path: str, temp_frame_paths: List[str], progress: Any = None
|
||||
source_path: str | None, temp_frame_paths: List[str], progress: Any = None
|
||||
) -> None:
|
||||
"""Processes multiple frames from file paths."""
|
||||
for temp_frame_path in temp_frame_paths:
|
||||
if not os.path.exists(temp_frame_path):
|
||||
print(
|
||||
f"{NAME}: Warning: Frame path not found {temp_frame_path}, skipping."
|
||||
)
|
||||
if progress:
|
||||
progress.update(1)
|
||||
continue
|
||||
|
||||
temp_frame = cv2.imread(temp_frame_path)
|
||||
result = process_frame(None, temp_frame)
|
||||
cv2.imwrite(temp_frame_path, result)
|
||||
if temp_frame is None:
|
||||
print(
|
||||
f"{NAME}: Warning: Failed to read frame {temp_frame_path}, skipping."
|
||||
)
|
||||
if progress:
|
||||
progress.update(1)
|
||||
continue
|
||||
|
||||
result_frame = process_frame(None, temp_frame)
|
||||
cv2.imwrite(temp_frame_path, result_frame)
|
||||
if progress:
|
||||
progress.update(1)
|
||||
|
||||
|
||||
def process_image(source_path: str, target_path: str, output_path: str) -> None:
|
||||
def process_image(
|
||||
source_path: str | None, target_path: str, output_path: str
|
||||
) -> None:
|
||||
"""Processes a single image file."""
|
||||
target_frame = cv2.imread(target_path)
|
||||
result = process_frame(None, target_frame)
|
||||
cv2.imwrite(output_path, result)
|
||||
if target_frame is None:
|
||||
print(f"{NAME}: Error: Failed to read target image {target_path}")
|
||||
return
|
||||
result_frame = process_frame(None, target_frame)
|
||||
cv2.imwrite(output_path, result_frame)
|
||||
print(f"{NAME}: Enhanced image saved to {output_path}")
|
||||
|
||||
|
||||
def process_video(source_path: str, temp_frame_paths: List[str]) -> None:
|
||||
modules.processors.frame.core.process_video(None, temp_frame_paths, process_frames)
|
||||
def process_video(
|
||||
source_path: str | None, temp_frame_paths: List[str]
|
||||
) -> None:
|
||||
"""Processes video frames using the frame processor core."""
|
||||
modules.processors.frame.core.process_video(
|
||||
source_path, temp_frame_paths, process_frames
|
||||
)
|
||||
|
||||
|
||||
def process_frame_v2(temp_frame: Frame) -> Frame:
|
||||
target_face = get_one_face(temp_frame)
|
||||
if target_face:
|
||||
temp_frame = enhance_face(temp_frame)
|
||||
return temp_frame
|
||||
# --- END OF FILE face_enhancer.py ---
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
"""GPEN-BFR-256 face enhancer — ONNX-based face restoration at 256x256."""
|
||||
|
||||
from typing import Any, List
|
||||
import os
|
||||
import threading
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
import modules.globals
|
||||
import modules.processors.frame.core
|
||||
from modules.core import update_status
|
||||
from modules.face_analyser import get_one_face
|
||||
from modules.typing import Frame, Face
|
||||
from modules.utilities import (
|
||||
is_image,
|
||||
is_video,
|
||||
)
|
||||
from modules.processors.frame._onnx_enhancer import (
|
||||
create_onnx_session,
|
||||
warmup_session,
|
||||
enhance_face_onnx,
|
||||
)
|
||||
|
||||
NAME = "DLC.FACE-ENHANCER-GPEN256"
|
||||
INPUT_SIZE = 256
|
||||
MODEL_URL = "https://github.com/harisreedhar/Face-Upscalers-ONNX/releases/download/GPEN-BFR/GPEN-BFR-256.onnx"
|
||||
MODEL_FILE = "GPEN-BFR-256.onnx"
|
||||
|
||||
ENHANCER = None
|
||||
THREAD_LOCK = threading.Lock()
|
||||
|
||||
abs_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
models_dir = os.path.join(
|
||||
os.path.dirname(os.path.dirname(os.path.dirname(abs_dir))), "models"
|
||||
)
|
||||
|
||||
|
||||
def pre_check() -> bool:
|
||||
model_path = os.path.join(models_dir, MODEL_FILE)
|
||||
if not os.path.exists(model_path):
|
||||
update_status(f"Downloading {MODEL_FILE}...", NAME)
|
||||
from modules.utilities import conditional_download
|
||||
conditional_download(models_dir, [MODEL_URL])
|
||||
return True
|
||||
|
||||
|
||||
def pre_start() -> bool:
|
||||
if not is_image(modules.globals.target_path) and not is_video(modules.globals.target_path):
|
||||
update_status("Select an image or video for target path.", NAME)
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def get_enhancer() -> Any:
|
||||
global ENHANCER
|
||||
with THREAD_LOCK:
|
||||
if ENHANCER is None:
|
||||
model_path = os.path.join(models_dir, MODEL_FILE)
|
||||
if not os.path.exists(model_path):
|
||||
from modules.utilities import conditional_download
|
||||
conditional_download(models_dir, [MODEL_URL])
|
||||
if not os.path.exists(model_path):
|
||||
raise FileNotFoundError(f"Model file not found: {model_path}")
|
||||
print(f"{NAME}: Loading ONNX model from {model_path}")
|
||||
ENHANCER = create_onnx_session(model_path)
|
||||
warmup_session(ENHANCER)
|
||||
print(f"{NAME}: Model loaded successfully.")
|
||||
return ENHANCER
|
||||
|
||||
|
||||
def enhance_face(temp_frame: Frame, face: Face) -> Frame:
|
||||
try:
|
||||
session = get_enhancer()
|
||||
except Exception as e:
|
||||
print(f"{NAME}: {e}")
|
||||
return temp_frame
|
||||
try:
|
||||
return enhance_face_onnx(temp_frame, face, session, INPUT_SIZE)
|
||||
except Exception as e:
|
||||
print(f"{NAME}: Error during face enhancement: {e}")
|
||||
return temp_frame
|
||||
|
||||
|
||||
def process_frame(source_face: Face | None, temp_frame: Frame) -> Frame:
|
||||
target_face = get_one_face(temp_frame)
|
||||
if target_face is None:
|
||||
return temp_frame
|
||||
return enhance_face(temp_frame, target_face)
|
||||
|
||||
|
||||
def process_frame_v2(temp_frame: Frame) -> Frame:
|
||||
target_face = get_one_face(temp_frame)
|
||||
if target_face:
|
||||
temp_frame = enhance_face(temp_frame, target_face)
|
||||
return temp_frame
|
||||
|
||||
|
||||
def process_frames(
|
||||
source_path: str | None, temp_frame_paths: List[str], progress: Any = None
|
||||
) -> None:
|
||||
for temp_frame_path in temp_frame_paths:
|
||||
temp_frame = cv2.imread(temp_frame_path)
|
||||
if temp_frame is None:
|
||||
if progress:
|
||||
progress.update(1)
|
||||
continue
|
||||
result = process_frame(None, temp_frame)
|
||||
cv2.imwrite(temp_frame_path, result)
|
||||
if progress:
|
||||
progress.update(1)
|
||||
|
||||
|
||||
def process_image(source_path: str | None, target_path: str, output_path: str) -> None:
|
||||
target_frame = cv2.imread(target_path)
|
||||
if target_frame is None:
|
||||
print(f"{NAME}: Error: Failed to read target image {target_path}")
|
||||
return
|
||||
result_frame = process_frame(None, target_frame)
|
||||
cv2.imwrite(output_path, result_frame)
|
||||
print(f"{NAME}: Enhanced image saved to {output_path}")
|
||||
|
||||
|
||||
def process_video(source_path: str | None, temp_frame_paths: List[str]) -> None:
|
||||
modules.processors.frame.core.process_video(source_path, temp_frame_paths, process_frames)
|
||||
@@ -0,0 +1,125 @@
|
||||
"""GPEN-BFR-512 face enhancer — ONNX-based face restoration at 512x512."""
|
||||
|
||||
from typing import Any, List
|
||||
import os
|
||||
import threading
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
import modules.globals
|
||||
import modules.processors.frame.core
|
||||
from modules.core import update_status
|
||||
from modules.face_analyser import get_one_face
|
||||
from modules.typing import Frame, Face
|
||||
from modules.utilities import (
|
||||
is_image,
|
||||
is_video,
|
||||
)
|
||||
from modules.processors.frame._onnx_enhancer import (
|
||||
create_onnx_session,
|
||||
warmup_session,
|
||||
enhance_face_onnx,
|
||||
)
|
||||
|
||||
NAME = "DLC.FACE-ENHANCER-GPEN512"
|
||||
INPUT_SIZE = 512
|
||||
MODEL_URL = "https://github.com/harisreedhar/Face-Upscalers-ONNX/releases/download/GPEN-BFR/GPEN-BFR-512.onnx"
|
||||
MODEL_FILE = "GPEN-BFR-512.onnx"
|
||||
|
||||
ENHANCER = None
|
||||
THREAD_LOCK = threading.Lock()
|
||||
|
||||
abs_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
models_dir = os.path.join(
|
||||
os.path.dirname(os.path.dirname(os.path.dirname(abs_dir))), "models"
|
||||
)
|
||||
|
||||
|
||||
def pre_check() -> bool:
|
||||
model_path = os.path.join(models_dir, MODEL_FILE)
|
||||
if not os.path.exists(model_path):
|
||||
update_status(f"Downloading {MODEL_FILE}...", NAME)
|
||||
from modules.utilities import conditional_download
|
||||
conditional_download(models_dir, [MODEL_URL])
|
||||
return True
|
||||
|
||||
|
||||
def pre_start() -> bool:
|
||||
if not is_image(modules.globals.target_path) and not is_video(modules.globals.target_path):
|
||||
update_status("Select an image or video for target path.", NAME)
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def get_enhancer() -> Any:
|
||||
global ENHANCER
|
||||
with THREAD_LOCK:
|
||||
if ENHANCER is None:
|
||||
model_path = os.path.join(models_dir, MODEL_FILE)
|
||||
if not os.path.exists(model_path):
|
||||
from modules.utilities import conditional_download
|
||||
conditional_download(models_dir, [MODEL_URL])
|
||||
if not os.path.exists(model_path):
|
||||
raise FileNotFoundError(f"Model file not found: {model_path}")
|
||||
print(f"{NAME}: Loading ONNX model from {model_path}")
|
||||
ENHANCER = create_onnx_session(model_path)
|
||||
warmup_session(ENHANCER)
|
||||
print(f"{NAME}: Model loaded successfully.")
|
||||
return ENHANCER
|
||||
|
||||
|
||||
def enhance_face(temp_frame: Frame, face: Face) -> Frame:
|
||||
try:
|
||||
session = get_enhancer()
|
||||
except Exception as e:
|
||||
print(f"{NAME}: {e}")
|
||||
return temp_frame
|
||||
try:
|
||||
return enhance_face_onnx(temp_frame, face, session, INPUT_SIZE)
|
||||
except Exception as e:
|
||||
print(f"{NAME}: Error during face enhancement: {e}")
|
||||
return temp_frame
|
||||
|
||||
|
||||
def process_frame(source_face: Face | None, temp_frame: Frame) -> Frame:
|
||||
target_face = get_one_face(temp_frame)
|
||||
if target_face is None:
|
||||
return temp_frame
|
||||
return enhance_face(temp_frame, target_face)
|
||||
|
||||
|
||||
def process_frame_v2(temp_frame: Frame) -> Frame:
|
||||
target_face = get_one_face(temp_frame)
|
||||
if target_face:
|
||||
temp_frame = enhance_face(temp_frame, target_face)
|
||||
return temp_frame
|
||||
|
||||
|
||||
def process_frames(
|
||||
source_path: str | None, temp_frame_paths: List[str], progress: Any = None
|
||||
) -> None:
|
||||
for temp_frame_path in temp_frame_paths:
|
||||
temp_frame = cv2.imread(temp_frame_path)
|
||||
if temp_frame is None:
|
||||
if progress:
|
||||
progress.update(1)
|
||||
continue
|
||||
result = process_frame(None, temp_frame)
|
||||
cv2.imwrite(temp_frame_path, result)
|
||||
if progress:
|
||||
progress.update(1)
|
||||
|
||||
|
||||
def process_image(source_path: str | None, target_path: str, output_path: str) -> None:
|
||||
target_frame = cv2.imread(target_path)
|
||||
if target_frame is None:
|
||||
print(f"{NAME}: Error: Failed to read target image {target_path}")
|
||||
return
|
||||
result_frame = process_frame(None, target_frame)
|
||||
cv2.imwrite(output_path, result_frame)
|
||||
print(f"{NAME}: Enhanced image saved to {output_path}")
|
||||
|
||||
|
||||
def process_video(source_path: str | None, temp_frame_paths: List[str]) -> None:
|
||||
modules.processors.frame.core.process_video(source_path, temp_frame_paths, process_frames)
|
||||
@@ -0,0 +1,577 @@
|
||||
import cv2
|
||||
import numpy as np
|
||||
from modules.typing import Face, Frame
|
||||
import modules.globals
|
||||
from modules.gpu_processing import gpu_gaussian_blur, gpu_resize, gpu_cvt_color
|
||||
|
||||
def apply_color_transfer(source, target):
|
||||
"""
|
||||
Apply color transfer from target to source image using LAB color space.
|
||||
Uses float32 throughout for performance (sufficient precision for 8-bit images).
|
||||
"""
|
||||
# Convert to float32 [0,1] range for proper LAB conversion
|
||||
source_f32 = source.astype(np.float32) / 255.0
|
||||
target_f32 = target.astype(np.float32) / 255.0
|
||||
|
||||
source_lab = cv2.cvtColor(source_f32, cv2.COLOR_BGR2LAB)
|
||||
target_lab = cv2.cvtColor(target_f32, cv2.COLOR_BGR2LAB)
|
||||
|
||||
source_mean, source_std = cv2.meanStdDev(source_lab)
|
||||
target_mean, target_std = cv2.meanStdDev(target_lab)
|
||||
|
||||
# Reshape mean and std to be broadcastable (already float64 from meanStdDev, cast to f32)
|
||||
source_mean = source_mean.reshape(1, 1, 3).astype(np.float32)
|
||||
source_std = np.maximum(source_std.reshape(1, 1, 3), 1e-6).astype(np.float32)
|
||||
target_mean = target_mean.reshape(1, 1, 3).astype(np.float32)
|
||||
target_std = target_std.reshape(1, 1, 3).astype(np.float32)
|
||||
|
||||
# Perform the color transfer in LAB space
|
||||
result_lab = (source_lab - source_mean) * (target_std / source_std) + target_mean
|
||||
|
||||
# Convert back to BGR and uint8
|
||||
result_bgr = cv2.cvtColor(result_lab, cv2.COLOR_LAB2BGR)
|
||||
return np.clip(result_bgr * 255.0, 0, 255).astype(np.uint8)
|
||||
|
||||
def create_face_mask(face: Face, frame: Frame) -> np.ndarray:
|
||||
mask = np.zeros(frame.shape[:2], dtype=np.uint8)
|
||||
landmarks = face.landmark_2d_106
|
||||
if landmarks is not None:
|
||||
# Convert landmarks to int32
|
||||
landmarks = landmarks.astype(np.int32)
|
||||
|
||||
# Extract facial features
|
||||
right_side_face = landmarks[0:16]
|
||||
left_side_face = landmarks[17:32]
|
||||
right_eye = landmarks[33:42]
|
||||
right_eye_brow = landmarks[43:51]
|
||||
left_eye = landmarks[87:96]
|
||||
left_eye_brow = landmarks[97:105]
|
||||
|
||||
# Calculate padding
|
||||
padding = int(
|
||||
np.linalg.norm(right_side_face[0] - left_side_face[-1]) * 0.05
|
||||
) # 5% of face width
|
||||
|
||||
# Create a slightly larger convex hull for padding
|
||||
face_outline = landmarks[0:33]
|
||||
hull = cv2.convexHull(face_outline)
|
||||
# Vectorized hull padding — expand each point outward from center
|
||||
center = np.mean(face_outline, axis=0, dtype=np.float32)
|
||||
hull_pts = hull.reshape(-1, 2).astype(np.float32)
|
||||
directions = hull_pts - center
|
||||
norms = np.linalg.norm(directions, axis=1, keepdims=True)
|
||||
norms = np.maximum(norms, 1e-6) # avoid division by zero
|
||||
directions /= norms
|
||||
hull_padded = (hull_pts + directions * padding).astype(np.int32)
|
||||
|
||||
# Fill the padded convex hull
|
||||
cv2.fillConvexPoly(mask, hull_padded, 255)
|
||||
|
||||
# Smooth the mask edges (GPU-accelerated when available)
|
||||
mask = gpu_gaussian_blur(mask, (5, 5), 3)
|
||||
|
||||
return mask
|
||||
|
||||
def create_lower_mouth_mask(
|
||||
face: Face, frame: Frame
|
||||
) -> (np.ndarray, np.ndarray, tuple, np.ndarray):
|
||||
mask = np.zeros(frame.shape[:2], dtype=np.uint8)
|
||||
mouth_cutout = None
|
||||
lower_lip_polygon = None
|
||||
mouth_box = (0,0,0,0)
|
||||
|
||||
landmarks = face.landmark_2d_106
|
||||
if landmarks is not None:
|
||||
# Use outer mouth landmarks (52-71) to capture the full mouth area
|
||||
lower_lip_order = list(range(52, 72))
|
||||
|
||||
if max(lower_lip_order) >= landmarks.shape[0]:
|
||||
return mask, mouth_cutout, mouth_box, lower_lip_polygon
|
||||
|
||||
lower_lip_landmarks = landmarks[lower_lip_order].astype(np.float32)
|
||||
|
||||
# Calculate the center of the landmarks
|
||||
center = np.mean(lower_lip_landmarks, axis=0)
|
||||
|
||||
# Expand the landmarks outward using the mouth_mask_size
|
||||
mouth_mask_size = getattr(modules.globals, "mouth_mask_size", 0.0) # 0-100 slider
|
||||
expansion_factor = 1 + (mouth_mask_size / 100.0) * 2.5
|
||||
|
||||
# Expand with extra downward bias toward chin
|
||||
offsets = lower_lip_landmarks - center
|
||||
chin_bias = 1 + (mouth_mask_size / 100.0) * 1.5
|
||||
scale_y = np.where(offsets[:, 1] > 0, expansion_factor * chin_bias, expansion_factor)
|
||||
expanded_landmarks = lower_lip_landmarks.copy()
|
||||
expanded_landmarks[:, 0] = center[0] + offsets[:, 0] * expansion_factor
|
||||
expanded_landmarks[:, 1] = center[1] + offsets[:, 1] * scale_y
|
||||
|
||||
# Convert back to integer coordinates
|
||||
expanded_landmarks = expanded_landmarks.astype(np.int32)
|
||||
|
||||
# Calculate bounding box for the expanded lower mouth
|
||||
min_x, min_y = np.min(expanded_landmarks, axis=0)
|
||||
max_x, max_y = np.max(expanded_landmarks, axis=0)
|
||||
|
||||
# Add some padding to the bounding box
|
||||
padding = int((max_x - min_x) * 0.1) # 10% padding
|
||||
min_x = max(0, min_x - padding)
|
||||
min_y = max(0, min_y - padding)
|
||||
max_x = min(frame.shape[1], max_x + padding)
|
||||
max_y = min(frame.shape[0], max_y + padding)
|
||||
|
||||
# Ensure the bounding box dimensions are valid
|
||||
if max_x <= min_x or max_y <= min_y:
|
||||
if (max_x - min_x) <= 1:
|
||||
max_x = min_x + 1
|
||||
if (max_y - min_y) <= 1:
|
||||
max_y = min_y + 1
|
||||
|
||||
# Create the mask
|
||||
mask_roi = np.zeros((max_y - min_y, max_x - min_x), dtype=np.uint8)
|
||||
# Shift polygon coordinates relative to the ROI's top-left corner
|
||||
polygon_relative_to_roi = expanded_landmarks - [min_x, min_y]
|
||||
cv2.fillPoly(mask_roi, [polygon_relative_to_roi], 255)
|
||||
|
||||
# Apply Gaussian blur to soften the mask edges (GPU-accelerated when available)
|
||||
mask_roi = gpu_gaussian_blur(mask_roi, (15, 15), 5)
|
||||
|
||||
# Place the mask ROI in the full-sized mask
|
||||
mask[min_y:max_y, min_x:max_x] = mask_roi
|
||||
|
||||
# Extract the masked area from the frame
|
||||
mouth_cutout = frame[min_y:max_y, min_x:max_x].copy()
|
||||
|
||||
# Return the expanded lower lip polygon in original frame coordinates
|
||||
lower_lip_polygon = expanded_landmarks
|
||||
mouth_box = (min_x, min_y, max_x, max_y)
|
||||
|
||||
return mask, mouth_cutout, mouth_box, lower_lip_polygon
|
||||
|
||||
def create_eyes_mask(face: Face, frame: Frame) -> (np.ndarray, np.ndarray, tuple, np.ndarray):
|
||||
mask = np.zeros(frame.shape[:2], dtype=np.uint8)
|
||||
eyes_cutout = None
|
||||
landmarks = face.landmark_2d_106
|
||||
if landmarks is not None:
|
||||
# Left eye landmarks (87-96) and right eye landmarks (33-42)
|
||||
left_eye = landmarks[87:96]
|
||||
right_eye = landmarks[33:42]
|
||||
|
||||
# Calculate centers and dimensions for each eye
|
||||
left_eye_center = np.mean(left_eye, axis=0).astype(np.int32)
|
||||
right_eye_center = np.mean(right_eye, axis=0).astype(np.int32)
|
||||
|
||||
# Calculate eye dimensions with size adjustment
|
||||
def get_eye_dimensions(eye_points):
|
||||
x_coords = eye_points[:, 0]
|
||||
y_coords = eye_points[:, 1]
|
||||
width = int((np.max(x_coords) - np.min(x_coords)) * (1 + modules.globals.mask_down_size * modules.globals.eyes_mask_size))
|
||||
height = int((np.max(y_coords) - np.min(y_coords)) * (1 + modules.globals.mask_down_size * modules.globals.eyes_mask_size))
|
||||
return width, height
|
||||
|
||||
left_width, left_height = get_eye_dimensions(left_eye)
|
||||
right_width, right_height = get_eye_dimensions(right_eye)
|
||||
|
||||
# Add extra padding
|
||||
padding = int(max(left_width, right_width) * 0.2)
|
||||
|
||||
# Calculate bounding box for both eyes
|
||||
min_x = min(left_eye_center[0] - left_width//2, right_eye_center[0] - right_width//2) - padding
|
||||
max_x = max(left_eye_center[0] + left_width//2, right_eye_center[0] + right_width//2) + padding
|
||||
min_y = min(left_eye_center[1] - left_height//2, right_eye_center[1] - right_height//2) - padding
|
||||
max_y = max(left_eye_center[1] + left_height//2, right_eye_center[1] + right_height//2) + padding
|
||||
|
||||
# Ensure coordinates are within frame bounds
|
||||
min_x = max(0, min_x)
|
||||
min_y = max(0, min_y)
|
||||
max_x = min(frame.shape[1], max_x)
|
||||
max_y = min(frame.shape[0], max_y)
|
||||
|
||||
# Create mask for the eyes region
|
||||
mask_roi = np.zeros((max_y - min_y, max_x - min_x), dtype=np.uint8)
|
||||
|
||||
# Draw ellipses for both eyes
|
||||
left_center = (left_eye_center[0] - min_x, left_eye_center[1] - min_y)
|
||||
right_center = (right_eye_center[0] - min_x, right_eye_center[1] - min_y)
|
||||
|
||||
# Calculate axes lengths (half of width and height)
|
||||
left_axes = (left_width//2, left_height//2)
|
||||
right_axes = (right_width//2, right_height//2)
|
||||
|
||||
# Draw filled ellipses
|
||||
cv2.ellipse(mask_roi, left_center, left_axes, 0, 0, 360, 255, -1)
|
||||
cv2.ellipse(mask_roi, right_center, right_axes, 0, 0, 360, 255, -1)
|
||||
|
||||
# Apply Gaussian blur to soften mask edges (GPU-accelerated when available)
|
||||
mask_roi = gpu_gaussian_blur(mask_roi, (15, 15), 5)
|
||||
|
||||
# Place the mask ROI in the full-sized mask
|
||||
mask[min_y:max_y, min_x:max_x] = mask_roi
|
||||
|
||||
# Extract the masked area from the frame
|
||||
eyes_cutout = frame[min_y:max_y, min_x:max_x].copy()
|
||||
|
||||
# Create polygon points for visualization
|
||||
def create_ellipse_points(center, axes):
|
||||
t = np.linspace(0, 2*np.pi, 32)
|
||||
x = center[0] + axes[0] * np.cos(t)
|
||||
y = center[1] + axes[1] * np.sin(t)
|
||||
return np.column_stack((x, y)).astype(np.int32)
|
||||
|
||||
# Generate points for both ellipses
|
||||
left_points = create_ellipse_points((left_eye_center[0], left_eye_center[1]), (left_width//2, left_height//2))
|
||||
right_points = create_ellipse_points((right_eye_center[0], right_eye_center[1]), (right_width//2, right_height//2))
|
||||
|
||||
# Combine points for both eyes
|
||||
eyes_polygon = np.vstack([left_points, right_points])
|
||||
|
||||
return mask, eyes_cutout, (min_x, min_y, max_x, max_y), eyes_polygon
|
||||
|
||||
def create_curved_eyebrow(points):
|
||||
if len(points) >= 5:
|
||||
# Sort points by x-coordinate
|
||||
sorted_idx = np.argsort(points[:, 0])
|
||||
sorted_points = points[sorted_idx]
|
||||
|
||||
# Calculate dimensions
|
||||
x_min, y_min = np.min(sorted_points, axis=0)
|
||||
x_max, y_max = np.max(sorted_points, axis=0)
|
||||
width = x_max - x_min
|
||||
height = y_max - y_min
|
||||
|
||||
# Create more points for smoother curve
|
||||
num_points = 50
|
||||
x = np.linspace(x_min, x_max, num_points)
|
||||
|
||||
# Fit quadratic curve through points for more natural arch
|
||||
coeffs = np.polyfit(sorted_points[:, 0], sorted_points[:, 1], 2)
|
||||
y = np.polyval(coeffs, x)
|
||||
|
||||
# Increased offsets to create more separation
|
||||
top_offset = height * 0.5 # Increased from 0.3 to shift up more
|
||||
bottom_offset = height * 0.2 # Increased from 0.1 to shift down more
|
||||
|
||||
# Create smooth curves
|
||||
top_curve = y - top_offset
|
||||
bottom_curve = y + bottom_offset
|
||||
|
||||
# Create curved endpoints with more pronounced taper
|
||||
end_points = 5
|
||||
start_x = np.linspace(x[0] - width * 0.15, x[0], end_points) # Increased taper
|
||||
end_x = np.linspace(x[-1], x[-1] + width * 0.15, end_points) # Increased taper
|
||||
|
||||
# Create tapered ends
|
||||
start_curve = np.column_stack((
|
||||
start_x,
|
||||
np.linspace(bottom_curve[0], top_curve[0], end_points)
|
||||
))
|
||||
end_curve = np.column_stack((
|
||||
end_x,
|
||||
np.linspace(bottom_curve[-1], top_curve[-1], end_points)
|
||||
))
|
||||
|
||||
# Combine all points to form a smooth contour
|
||||
contour_points = np.vstack([
|
||||
start_curve,
|
||||
np.column_stack((x, top_curve)),
|
||||
end_curve,
|
||||
np.column_stack((x[::-1], bottom_curve[::-1]))
|
||||
])
|
||||
|
||||
# Add slight padding for better coverage
|
||||
center = np.mean(contour_points, axis=0)
|
||||
vectors = contour_points - center
|
||||
padded_points = center + vectors * 1.2 # Increased padding slightly
|
||||
|
||||
return padded_points
|
||||
return points
|
||||
|
||||
def create_eyebrows_mask(face: Face, frame: Frame) -> (np.ndarray, np.ndarray, tuple, np.ndarray):
|
||||
mask = np.zeros(frame.shape[:2], dtype=np.uint8)
|
||||
eyebrows_cutout = None
|
||||
landmarks = face.landmark_2d_106
|
||||
if landmarks is not None:
|
||||
# Left eyebrow landmarks (97-105) and right eyebrow landmarks (43-51)
|
||||
left_eyebrow = landmarks[97:105].astype(np.float32)
|
||||
right_eyebrow = landmarks[43:51].astype(np.float32)
|
||||
|
||||
# Calculate centers and dimensions for each eyebrow
|
||||
left_center = np.mean(left_eyebrow, axis=0)
|
||||
right_center = np.mean(right_eyebrow, axis=0)
|
||||
|
||||
# Calculate bounding box with padding adjusted by size
|
||||
all_points = np.vstack([left_eyebrow, right_eyebrow])
|
||||
padding_factor = modules.globals.eyebrows_mask_size
|
||||
min_x = np.min(all_points[:, 0]) - 25 * padding_factor
|
||||
max_x = np.max(all_points[:, 0]) + 25 * padding_factor
|
||||
min_y = np.min(all_points[:, 1]) - 20 * padding_factor
|
||||
max_y = np.max(all_points[:, 1]) + 15 * padding_factor
|
||||
|
||||
# Ensure coordinates are within frame bounds
|
||||
min_x = max(0, int(min_x))
|
||||
min_y = max(0, int(min_y))
|
||||
max_x = min(frame.shape[1], int(max_x))
|
||||
max_y = min(frame.shape[0], int(max_y))
|
||||
|
||||
# Create mask for the eyebrows region
|
||||
mask_roi = np.zeros((max_y - min_y, max_x - min_x), dtype=np.uint8)
|
||||
|
||||
try:
|
||||
# Convert points to local coordinates
|
||||
left_local = left_eyebrow - [min_x, min_y]
|
||||
right_local = right_eyebrow - [min_x, min_y]
|
||||
|
||||
def create_curved_eyebrow(points):
|
||||
if len(points) >= 5:
|
||||
# Sort points by x-coordinate
|
||||
sorted_idx = np.argsort(points[:, 0])
|
||||
sorted_points = points[sorted_idx]
|
||||
|
||||
# Calculate dimensions
|
||||
x_min, y_min = np.min(sorted_points, axis=0)
|
||||
x_max, y_max = np.max(sorted_points, axis=0)
|
||||
width = x_max - x_min
|
||||
height = y_max - y_min
|
||||
|
||||
# Create more points for smoother curve
|
||||
num_points = 50
|
||||
x = np.linspace(x_min, x_max, num_points)
|
||||
|
||||
# Fit quadratic curve through points for more natural arch
|
||||
coeffs = np.polyfit(sorted_points[:, 0], sorted_points[:, 1], 2)
|
||||
y = np.polyval(coeffs, x)
|
||||
|
||||
# Increased offsets to create more separation
|
||||
top_offset = height * 0.5 # Increased from 0.3 to shift up more
|
||||
bottom_offset = height * 0.2 # Increased from 0.1 to shift down more
|
||||
|
||||
# Create smooth curves
|
||||
top_curve = y - top_offset
|
||||
bottom_curve = y + bottom_offset
|
||||
|
||||
# Create curved endpoints with more pronounced taper
|
||||
end_points = 5
|
||||
start_x = np.linspace(x[0] - width * 0.15, x[0], end_points) # Increased taper
|
||||
end_x = np.linspace(x[-1], x[-1] + width * 0.15, end_points) # Increased taper
|
||||
|
||||
# Create tapered ends
|
||||
start_curve = np.column_stack((
|
||||
start_x,
|
||||
np.linspace(bottom_curve[0], top_curve[0], end_points)
|
||||
))
|
||||
end_curve = np.column_stack((
|
||||
end_x,
|
||||
np.linspace(bottom_curve[-1], top_curve[-1], end_points)
|
||||
))
|
||||
|
||||
# Combine all points to form a smooth contour
|
||||
contour_points = np.vstack([
|
||||
start_curve,
|
||||
np.column_stack((x, top_curve)),
|
||||
end_curve,
|
||||
np.column_stack((x[::-1], bottom_curve[::-1]))
|
||||
])
|
||||
|
||||
# Add slight padding for better coverage
|
||||
center = np.mean(contour_points, axis=0)
|
||||
vectors = contour_points - center
|
||||
padded_points = center + vectors * 1.2 # Increased padding slightly
|
||||
|
||||
return padded_points
|
||||
return points
|
||||
|
||||
# Generate and draw eyebrow shapes
|
||||
left_shape = create_curved_eyebrow(left_local)
|
||||
right_shape = create_curved_eyebrow(right_local)
|
||||
|
||||
# Apply multi-stage blurring for natural feathering (GPU-accelerated when available)
|
||||
# First, strong Gaussian blur for initial softening
|
||||
mask_roi = gpu_gaussian_blur(mask_roi, (21, 21), 7)
|
||||
|
||||
# Second, medium blur for transition areas
|
||||
mask_roi = gpu_gaussian_blur(mask_roi, (11, 11), 3)
|
||||
|
||||
# Finally, light blur for fine details
|
||||
mask_roi = gpu_gaussian_blur(mask_roi, (5, 5), 1)
|
||||
|
||||
# Normalize mask values
|
||||
mask_roi = cv2.normalize(mask_roi, None, 0, 255, cv2.NORM_MINMAX)
|
||||
|
||||
# Place the mask ROI in the full-sized mask
|
||||
mask[min_y:max_y, min_x:max_x] = mask_roi
|
||||
|
||||
# Extract the masked area from the frame
|
||||
eyebrows_cutout = frame[min_y:max_y, min_x:max_x].copy()
|
||||
|
||||
# Combine points for visualization
|
||||
eyebrows_polygon = np.vstack([
|
||||
left_shape + [min_x, min_y],
|
||||
right_shape + [min_x, min_y]
|
||||
]).astype(np.int32)
|
||||
|
||||
except Exception as e:
|
||||
# Fallback to simple polygons if curve fitting fails
|
||||
left_local = left_eyebrow - [min_x, min_y]
|
||||
right_local = right_eyebrow - [min_x, min_y]
|
||||
cv2.fillPoly(mask_roi, [left_local.astype(np.int32)], 255)
|
||||
cv2.fillPoly(mask_roi, [right_local.astype(np.int32)], 255)
|
||||
mask_roi = gpu_gaussian_blur(mask_roi, (21, 21), 7)
|
||||
mask[min_y:max_y, min_x:max_x] = mask_roi
|
||||
eyebrows_cutout = frame[min_y:max_y, min_x:max_x].copy()
|
||||
eyebrows_polygon = np.vstack([left_eyebrow, right_eyebrow]).astype(np.int32)
|
||||
|
||||
return mask, eyebrows_cutout, (min_x, min_y, max_x, max_y), eyebrows_polygon
|
||||
|
||||
def apply_mask_area(
|
||||
frame: np.ndarray,
|
||||
cutout: np.ndarray,
|
||||
box: tuple,
|
||||
face_mask: np.ndarray,
|
||||
polygon: np.ndarray,
|
||||
) -> np.ndarray:
|
||||
min_x, min_y, max_x, max_y = box
|
||||
box_width = max_x - min_x
|
||||
box_height = max_y - min_y
|
||||
|
||||
if (
|
||||
cutout is None
|
||||
or box_width is None
|
||||
or box_height is None
|
||||
or face_mask is None
|
||||
or polygon is None
|
||||
):
|
||||
return frame
|
||||
|
||||
try:
|
||||
resized_cutout = gpu_resize(cutout, (box_width, box_height))
|
||||
roi = frame[min_y:max_y, min_x:max_x]
|
||||
|
||||
if roi.shape != resized_cutout.shape:
|
||||
resized_cutout = gpu_resize(
|
||||
resized_cutout, (roi.shape[1], roi.shape[0])
|
||||
)
|
||||
|
||||
color_corrected_area = apply_color_transfer(resized_cutout, roi)
|
||||
|
||||
# Create mask for the area
|
||||
polygon_mask = np.zeros(roi.shape[:2], dtype=np.uint8)
|
||||
|
||||
# Split points for left and right parts if needed
|
||||
if len(polygon) > 50: # Arbitrary threshold to detect if we have multiple parts
|
||||
mid_point = len(polygon) // 2
|
||||
left_points = polygon[:mid_point] - [min_x, min_y]
|
||||
right_points = polygon[mid_point:] - [min_x, min_y]
|
||||
cv2.fillPoly(polygon_mask, [left_points], 255)
|
||||
cv2.fillPoly(polygon_mask, [right_points], 255)
|
||||
else:
|
||||
adjusted_polygon = polygon - [min_x, min_y]
|
||||
cv2.fillPoly(polygon_mask, [adjusted_polygon], 255)
|
||||
|
||||
# Apply strong initial feathering (GPU-accelerated when available)
|
||||
polygon_mask = gpu_gaussian_blur(polygon_mask, (21, 21), 7)
|
||||
|
||||
# Apply additional feathering
|
||||
feather_amount = min(
|
||||
30,
|
||||
box_width // modules.globals.mask_feather_ratio,
|
||||
box_height // modules.globals.mask_feather_ratio,
|
||||
)
|
||||
feathered_mask = cv2.GaussianBlur(
|
||||
polygon_mask.astype(np.float32), (0, 0), feather_amount
|
||||
)
|
||||
max_val = feathered_mask.max()
|
||||
if max_val > 1e-6:
|
||||
feathered_mask *= np.float32(1.0 / max_val)
|
||||
|
||||
# Apply additional smoothing to the mask edges
|
||||
feathered_mask = cv2.GaussianBlur(feathered_mask, (5, 5), 1)
|
||||
|
||||
face_mask_roi = face_mask[min_y:max_y, min_x:max_x]
|
||||
combined_mask = feathered_mask * (face_mask_roi.astype(np.float32) * np.float32(1.0 / 255.0))
|
||||
|
||||
combined_mask_3ch = combined_mask[:, :, np.newaxis]
|
||||
inv_mask = np.float32(1.0) - combined_mask_3ch
|
||||
blended = (
|
||||
color_corrected_area * combined_mask_3ch + roi * inv_mask
|
||||
).astype(np.uint8)
|
||||
|
||||
# Apply face mask to blended result
|
||||
face_mask_f32 = face_mask_roi[:, :, np.newaxis].astype(np.float32) * np.float32(1.0 / 255.0)
|
||||
face_mask_3channel = np.broadcast_to(face_mask_f32, blended.shape)
|
||||
final_blend = blended * face_mask_3channel + roi * (np.float32(1.0) - face_mask_3channel)
|
||||
|
||||
frame[min_y:max_y, min_x:max_x] = final_blend.astype(np.uint8)
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
return frame
|
||||
|
||||
def draw_mask_visualization(
|
||||
frame: Frame,
|
||||
mask_data: tuple,
|
||||
label: str,
|
||||
draw_method: str = "polygon"
|
||||
) -> Frame:
|
||||
mask, cutout, (min_x, min_y, max_x, max_y), polygon = mask_data
|
||||
|
||||
vis_frame = frame.copy()
|
||||
|
||||
# Ensure coordinates are within frame bounds
|
||||
height, width = vis_frame.shape[:2]
|
||||
min_x, min_y = max(0, min_x), max(0, min_y)
|
||||
max_x, max_y = min(width, max_x), min(height, max_y)
|
||||
|
||||
if draw_method == "ellipse" and len(polygon) > 50: # For eyes
|
||||
# Split points for left and right parts
|
||||
mid_point = len(polygon) // 2
|
||||
left_points = polygon[:mid_point]
|
||||
right_points = polygon[mid_point:]
|
||||
|
||||
try:
|
||||
# Fit ellipses to points - need at least 5 points
|
||||
if len(left_points) >= 5 and len(right_points) >= 5:
|
||||
# Convert points to the correct format for ellipse fitting
|
||||
left_points = left_points.astype(np.float32)
|
||||
right_points = right_points.astype(np.float32)
|
||||
|
||||
# Fit ellipses
|
||||
left_ellipse = cv2.fitEllipse(left_points)
|
||||
right_ellipse = cv2.fitEllipse(right_points)
|
||||
|
||||
# Draw the ellipses
|
||||
cv2.ellipse(vis_frame, left_ellipse, (0, 255, 0), 2)
|
||||
cv2.ellipse(vis_frame, right_ellipse, (0, 255, 0), 2)
|
||||
except Exception as e:
|
||||
# If ellipse fitting fails, draw simple rectangles as fallback
|
||||
left_rect = cv2.boundingRect(left_points)
|
||||
right_rect = cv2.boundingRect(right_points)
|
||||
cv2.rectangle(vis_frame,
|
||||
(left_rect[0], left_rect[1]),
|
||||
(left_rect[0] + left_rect[2], left_rect[1] + left_rect[3]),
|
||||
(0, 255, 0), 2)
|
||||
cv2.rectangle(vis_frame,
|
||||
(right_rect[0], right_rect[1]),
|
||||
(right_rect[0] + right_rect[2], right_rect[1] + right_rect[3]),
|
||||
(0, 255, 0), 2)
|
||||
else: # For mouth and eyebrows
|
||||
# Draw the polygon
|
||||
if len(polygon) > 50: # If we have multiple parts
|
||||
mid_point = len(polygon) // 2
|
||||
left_points = polygon[:mid_point]
|
||||
right_points = polygon[mid_point:]
|
||||
cv2.polylines(vis_frame, [left_points], True, (0, 255, 0), 2, cv2.LINE_AA)
|
||||
cv2.polylines(vis_frame, [right_points], True, (0, 255, 0), 2, cv2.LINE_AA)
|
||||
else:
|
||||
cv2.polylines(vis_frame, [polygon], True, (0, 255, 0), 2, cv2.LINE_AA)
|
||||
|
||||
# Add label
|
||||
cv2.putText(
|
||||
vis_frame,
|
||||
label,
|
||||
(min_x, min_y - 10),
|
||||
cv2.FONT_HERSHEY_SIMPLEX,
|
||||
0.5,
|
||||
(255, 255, 255),
|
||||
1,
|
||||
)
|
||||
|
||||
return vis_frame
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Import the tkinter fix to patch the ScreenChanged error
|
||||
import tkinter_fix
|
||||
|
||||
import core
|
||||
|
||||
if __name__ == '__main__':
|
||||
core.run()
|
||||
@@ -0,0 +1,26 @@
|
||||
import tkinter
|
||||
|
||||
# Only needs to be imported once at the beginning of the application
|
||||
def apply_patch():
|
||||
# Create a monkey patch for the internal _tkinter module
|
||||
original_init = tkinter.Tk.__init__
|
||||
|
||||
def patched_init(self, *args, **kwargs):
|
||||
# Call the original init
|
||||
original_init(self, *args, **kwargs)
|
||||
|
||||
# Define the missing ::tk::ScreenChanged procedure
|
||||
self.tk.eval("""
|
||||
if {[info commands ::tk::ScreenChanged] == ""} {
|
||||
proc ::tk::ScreenChanged {args} {
|
||||
# Do nothing
|
||||
return
|
||||
}
|
||||
}
|
||||
""")
|
||||
|
||||
# Apply the monkey patch
|
||||
tkinter.Tk.__init__ = patched_init
|
||||
|
||||
# Apply the patch automatically when this module is imported
|
||||
apply_patch()
|
||||
+669
-207
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,74 @@
|
||||
"""Lightweight hover tooltip for CustomTkinter widgets."""
|
||||
|
||||
import customtkinter as ctk
|
||||
|
||||
|
||||
class ToolTip:
|
||||
"""Show a floating tooltip popup when the user hovers over a widget.
|
||||
|
||||
Usage:
|
||||
ToolTip(my_button, "Helpful description text")
|
||||
"""
|
||||
|
||||
def __init__(self, widget: ctk.CTkBaseClass, text: str, delay: int = 500):
|
||||
self._widget = widget
|
||||
self._text = text
|
||||
self._delay = delay
|
||||
self._tooltip_window = None
|
||||
self._after_id = None
|
||||
|
||||
widget.bind("<Enter>", self._schedule_show, add="+")
|
||||
widget.bind("<Leave>", self._hide, add="+")
|
||||
|
||||
def _schedule_show(self, event=None):
|
||||
self._cancel()
|
||||
self._after_id = self._widget.after(self._delay, self._show)
|
||||
|
||||
def _show(self):
|
||||
if self._tooltip_window is not None:
|
||||
return
|
||||
|
||||
x = self._widget.winfo_rootx() + 20
|
||||
y = self._widget.winfo_rooty() + self._widget.winfo_height() + 5
|
||||
|
||||
self._tooltip_window = tw = ctk.CTkToplevel(self._widget)
|
||||
tw.withdraw()
|
||||
tw.overrideredirect(True)
|
||||
|
||||
label = ctk.CTkLabel(
|
||||
tw,
|
||||
text=self._text,
|
||||
fg_color="#333333",
|
||||
text_color="#EEEEEE",
|
||||
corner_radius=6,
|
||||
padx=8,
|
||||
pady=4,
|
||||
)
|
||||
label.pack()
|
||||
|
||||
tw.update_idletasks()
|
||||
|
||||
# Clamp to screen bounds
|
||||
screen_w = tw.winfo_screenwidth()
|
||||
screen_h = tw.winfo_screenheight()
|
||||
tip_w = tw.winfo_reqwidth()
|
||||
tip_h = tw.winfo_reqheight()
|
||||
|
||||
if x + tip_w > screen_w:
|
||||
x = screen_w - tip_w - 5
|
||||
if y + tip_h > screen_h:
|
||||
y = self._widget.winfo_rooty() - tip_h - 5
|
||||
|
||||
tw.geometry(f"+{x}+{y}")
|
||||
tw.deiconify()
|
||||
|
||||
def _hide(self, event=None):
|
||||
self._cancel()
|
||||
if self._tooltip_window is not None:
|
||||
self._tooltip_window.destroy()
|
||||
self._tooltip_window = None
|
||||
|
||||
def _cancel(self):
|
||||
if self._after_id is not None:
|
||||
self._widget.after_cancel(self._after_id)
|
||||
self._after_id = None
|
||||
+132
-30
@@ -15,19 +15,16 @@ import modules.globals
|
||||
TEMP_FILE = "temp.mp4"
|
||||
TEMP_DIRECTORY = "temp"
|
||||
|
||||
# monkey patch ssl for mac
|
||||
if platform.system().lower() == "darwin":
|
||||
ssl._create_default_https_context = ssl._create_unverified_context
|
||||
|
||||
|
||||
def run_ffmpeg(args: List[str]) -> bool:
|
||||
"""Run ffmpeg with hardware acceleration and optimized settings."""
|
||||
commands = [
|
||||
"ffmpeg",
|
||||
"-hide_banner",
|
||||
"-hwaccel",
|
||||
"auto",
|
||||
"-loglevel",
|
||||
modules.globals.log_level,
|
||||
"-hwaccel", "auto", # Auto-detect hardware acceleration
|
||||
"-hwaccel_output_format", "auto", # Use hardware format when possible
|
||||
"-threads", str(modules.globals.execution_threads or 0), # 0 = auto-detect optimal thread count
|
||||
"-loglevel", modules.globals.log_level,
|
||||
]
|
||||
commands.extend(args)
|
||||
try:
|
||||
@@ -61,39 +58,131 @@ def detect_fps(target_path: str) -> float:
|
||||
|
||||
|
||||
def extract_frames(target_path: str) -> None:
|
||||
"""Extract frames with hardware acceleration and optimized settings."""
|
||||
temp_directory_path = get_temp_directory_path(target_path)
|
||||
|
||||
# Use hardware-accelerated decoding and optimized pixel format
|
||||
run_ffmpeg(
|
||||
[
|
||||
"-i",
|
||||
target_path,
|
||||
"-pix_fmt",
|
||||
"rgb24",
|
||||
"-i", target_path,
|
||||
"-vf", "format=rgb24", # Use video filter for format conversion (faster)
|
||||
"-vsync", "0", # Prevent frame duplication
|
||||
"-frame_pts", "1", # Preserve frame timing
|
||||
os.path.join(temp_directory_path, "%04d.png"),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def create_video(target_path: str, fps: float = 30.0) -> None:
|
||||
"""Create video with hardware-accelerated encoding and optimized settings."""
|
||||
temp_output_path = get_temp_output_path(target_path)
|
||||
temp_directory_path = get_temp_directory_path(target_path)
|
||||
run_ffmpeg(
|
||||
[
|
||||
"-r",
|
||||
str(fps),
|
||||
"-i",
|
||||
os.path.join(temp_directory_path, "%04d.png"),
|
||||
"-c:v",
|
||||
modules.globals.video_encoder,
|
||||
"-crf",
|
||||
str(modules.globals.video_quality),
|
||||
"-pix_fmt",
|
||||
"yuv420p",
|
||||
"-vf",
|
||||
"colorspace=bt709:iall=bt601-6-625:fast=1",
|
||||
|
||||
# Determine optimal encoder based on available hardware
|
||||
encoder = modules.globals.video_encoder
|
||||
encoder_options = []
|
||||
|
||||
# GPU-accelerated encoding options
|
||||
if 'CUDAExecutionProvider' in modules.globals.execution_providers:
|
||||
# NVIDIA GPU encoding
|
||||
if encoder == 'libx264':
|
||||
encoder = 'h264_nvenc'
|
||||
encoder_options = [
|
||||
"-preset", "p7", # Highest quality preset for NVENC
|
||||
"-tune", "hq", # High quality tuning
|
||||
"-rc", "vbr", # Variable bitrate
|
||||
"-cq", str(modules.globals.video_quality), # Quality level
|
||||
"-b:v", "0", # Let CQ control bitrate
|
||||
"-multipass", "fullres", # Two-pass encoding for better quality
|
||||
]
|
||||
elif encoder == 'libx265':
|
||||
encoder = 'hevc_nvenc'
|
||||
encoder_options = [
|
||||
"-preset", "p7",
|
||||
"-tune", "hq",
|
||||
"-rc", "vbr",
|
||||
"-cq", str(modules.globals.video_quality),
|
||||
"-b:v", "0",
|
||||
]
|
||||
elif 'DmlExecutionProvider' in modules.globals.execution_providers:
|
||||
# AMD/Intel GPU encoding (DirectML on Windows)
|
||||
if encoder == 'libx264':
|
||||
# Try AMD AMF encoder
|
||||
encoder = 'h264_amf'
|
||||
encoder_options = [
|
||||
"-quality", "quality", # Quality mode
|
||||
"-rc", "vbr_latency",
|
||||
"-qp_i", str(modules.globals.video_quality),
|
||||
"-qp_p", str(modules.globals.video_quality),
|
||||
]
|
||||
elif encoder == 'libx265':
|
||||
encoder = 'hevc_amf'
|
||||
encoder_options = [
|
||||
"-quality", "quality",
|
||||
"-rc", "vbr_latency",
|
||||
"-qp_i", str(modules.globals.video_quality),
|
||||
"-qp_p", str(modules.globals.video_quality),
|
||||
]
|
||||
else:
|
||||
# CPU encoding with optimized settings
|
||||
if encoder == 'libx264':
|
||||
encoder_options = [
|
||||
"-preset", "medium", # Balance speed/quality
|
||||
"-crf", str(modules.globals.video_quality),
|
||||
"-tune", "film", # Optimize for film content
|
||||
]
|
||||
elif encoder == 'libx265':
|
||||
encoder_options = [
|
||||
"-preset", "medium",
|
||||
"-crf", str(modules.globals.video_quality),
|
||||
"-x265-params", "log-level=error",
|
||||
]
|
||||
elif encoder == 'libvpx-vp9':
|
||||
encoder_options = [
|
||||
"-crf", str(modules.globals.video_quality),
|
||||
"-b:v", "0", # Constant quality mode
|
||||
"-cpu-used", "2", # Speed vs quality (0-5, lower=slower/better)
|
||||
]
|
||||
|
||||
# Build ffmpeg command
|
||||
ffmpeg_args = [
|
||||
"-r", str(fps),
|
||||
"-i", os.path.join(temp_directory_path, "%04d.png"),
|
||||
"-c:v", encoder,
|
||||
]
|
||||
|
||||
# Add encoder-specific options
|
||||
ffmpeg_args.extend(encoder_options)
|
||||
|
||||
# Add common options
|
||||
ffmpeg_args.extend([
|
||||
"-pix_fmt", "yuv420p",
|
||||
"-movflags", "+faststart", # Enable fast start for web playback
|
||||
"-vf", "colorspace=bt709:iall=bt601-6-625:fast=1",
|
||||
"-y",
|
||||
temp_output_path,
|
||||
])
|
||||
|
||||
# Try with hardware encoder first, fallback to software if it fails
|
||||
success = run_ffmpeg(ffmpeg_args)
|
||||
|
||||
if not success and encoder in ['h264_nvenc', 'hevc_nvenc', 'h264_amf', 'hevc_amf']:
|
||||
# Fallback to software encoding
|
||||
print(f"Hardware encoding with {encoder} failed, falling back to software encoding...")
|
||||
fallback_encoder = 'libx264' if 'h264' in encoder else 'libx265'
|
||||
ffmpeg_args_fallback = [
|
||||
"-r", str(fps),
|
||||
"-i", os.path.join(temp_directory_path, "%04d.png"),
|
||||
"-c:v", fallback_encoder,
|
||||
"-preset", "medium",
|
||||
"-crf", str(modules.globals.video_quality),
|
||||
"-pix_fmt", "yuv420p",
|
||||
"-movflags", "+faststart",
|
||||
"-vf", "colorspace=bt709:iall=bt601-6-625:fast=1",
|
||||
"-y",
|
||||
temp_output_path,
|
||||
]
|
||||
)
|
||||
run_ffmpeg(ffmpeg_args_fallback)
|
||||
|
||||
|
||||
def restore_audio(target_path: str, output_path: str) -> None:
|
||||
@@ -193,8 +282,15 @@ def conditional_download(download_directory_path: str, urls: List[str]) -> None:
|
||||
download_directory_path, os.path.basename(url)
|
||||
)
|
||||
if not os.path.exists(download_file_path):
|
||||
request = urllib.request.urlopen(url) # type: ignore[attr-defined]
|
||||
total = int(request.headers.get("Content-Length", 0))
|
||||
request = urllib.request.Request(url)
|
||||
|
||||
# Create a specific SSL context for macOS to avoid globally disabling verification
|
||||
ctx = None
|
||||
if platform.system().lower() == "darwin":
|
||||
ctx = ssl._create_unverified_context()
|
||||
|
||||
response = urllib.request.urlopen(request, context=ctx)
|
||||
total = int(response.headers.get("Content-Length", 0))
|
||||
with tqdm(
|
||||
total=total,
|
||||
desc="Downloading",
|
||||
@@ -202,7 +298,13 @@ def conditional_download(download_directory_path: str, urls: List[str]) -> None:
|
||||
unit_scale=True,
|
||||
unit_divisor=1024,
|
||||
) as progress:
|
||||
urllib.request.urlretrieve(url, download_file_path, reporthook=lambda count, block_size, total_size: progress.update(block_size)) # type: ignore[attr-defined]
|
||||
with open(download_file_path, "wb") as f:
|
||||
while True:
|
||||
buffer = response.read(8192)
|
||||
if not buffer:
|
||||
break
|
||||
f.write(buffer)
|
||||
progress.update(len(buffer))
|
||||
|
||||
|
||||
def resolve_relative_path(path: str) -> str:
|
||||
|
||||
+7
-15
@@ -1,24 +1,16 @@
|
||||
--extra-index-url https://download.pytorch.org/whl/cu118
|
||||
|
||||
numpy>=1.23.5,<2
|
||||
typing-extensions>=4.8.0
|
||||
opencv-python==4.10.0.84
|
||||
cv2_enumerate_cameras==1.1.15
|
||||
onnx==1.16.0
|
||||
onnx==1.18.0
|
||||
insightface==0.7.3
|
||||
psutil==5.9.8
|
||||
tk==0.1.0
|
||||
customtkinter==5.2.2
|
||||
pillow==9.5.0
|
||||
torch==2.0.1+cu118; sys_platform != 'darwin'
|
||||
torch==2.0.1; sys_platform == 'darwin'
|
||||
torchvision==0.15.2+cu118; sys_platform != 'darwin'
|
||||
torchvision==0.15.2; sys_platform == 'darwin'
|
||||
pillow==12.1.1
|
||||
onnxruntime-silicon==1.16.3; sys_platform == 'darwin' and platform_machine == 'arm64'
|
||||
onnxruntime-gpu==1.16.3; sys_platform != 'darwin'
|
||||
tensorflow==2.12.1; sys_platform != 'darwin'
|
||||
onnxruntime-gpu==1.23.2; sys_platform != 'darwin'
|
||||
tensorflow; sys_platform != 'darwin'
|
||||
opennsfw2==0.10.2
|
||||
protobuf==4.23.2
|
||||
tqdm==4.66.4
|
||||
gfpgan==1.3.8
|
||||
tkinterdnd2==0.4.2
|
||||
pygrabber==0.2
|
||||
protobuf==4.25.1
|
||||
pygrabber
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Import the tkinter fix to patch the ScreenChanged error
|
||||
import tkinter_fix
|
||||
|
||||
from modules import core
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import os
|
||||
os.environ.setdefault('TK_SILENCE_DEPRECATION', '1')
|
||||
|
||||
import tkinter
|
||||
|
||||
# Only needs to be imported once at the beginning of the application
|
||||
def apply_patch():
|
||||
# Create a monkey patch for the internal _tkinter module
|
||||
original_init = tkinter.Tk.__init__
|
||||
|
||||
def patched_init(self, *args, **kwargs):
|
||||
# Call the original init
|
||||
original_init(self, *args, **kwargs)
|
||||
|
||||
# Define the missing ::tk::ScreenChanged procedure
|
||||
self.tk.eval("""
|
||||
if {[info commands ::tk::ScreenChanged] == ""} {
|
||||
proc ::tk::ScreenChanged {args} {
|
||||
# Do nothing
|
||||
return
|
||||
}
|
||||
}
|
||||
""")
|
||||
|
||||
# Apply the monkey patch
|
||||
tkinter.Tk.__init__ = patched_init
|
||||
|
||||
# Apply the patch automatically when this module is imported
|
||||
apply_patch()
|
||||
Reference in New Issue
Block a user