Compare commits
329 Commits
experimental
...
privacy
| Author | SHA1 | Date | |
|---|---|---|---|
| 48c83151a4 | |||
| bb3502d9bd | |||
| a101a1f3f1 | |||
| 01ef955372 | |||
| ab3b73631b | |||
| d8fc1ffa04 | |||
| 5dfd1c0ced | |||
| db594c0e7c | |||
| 6a5b75ec45 | |||
| 79e1ce5093 | |||
| 59cd3be0f9 | |||
| ccb676ac17 | |||
| f0c66732e7 | |||
| 8055d79daf | |||
| 3c7dd1a574 | |||
| 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 | |||
| 7def969831 | |||
| 6bf503e669 | |||
| 28513d6c1f | |||
| f6abe502b6 | |||
| b38ef62447 | |||
| a3469b7bd4 | |||
| c03f697729 | |||
| 742bcab130 | |||
| 22940d1b99 | |||
| d8a5cdbc19 | |||
| 6219da4b1b | |||
| 22e1110ec4 | |||
| 82d5d34912 | |||
| 60e82ea200 | |||
| 8be7368949 | |||
| 5003c04386 | |||
| aed933c1db | |||
| a50ea98bc2 | |||
| 6a9bf2acfb | |||
| 395cecf11d | |||
| ebf4e95c3a | |||
| 5974ba2a68 | |||
| 75c53ac7aa | |||
| 8aeb406ea2 | |||
| 8b3bd734cf | |||
| b0aac8bd04 | |||
| 9dc3c3e9c2 | |||
| 21989d4a49 | |||
| b97185d2bf | |||
| 81da9a23ca | |||
| 007867a6f6 | |||
| 7ec9d61608 | |||
| eeff1a87fa | |||
| bc1149cd80 | |||
| 11c10b354f | |||
| 71aae3fe07 | |||
| b995eca033 | |||
| b17e52dea2 | |||
| 3a858847e3 | |||
| 77c19d1073 | |||
| 7472dfb694 | |||
| 41c6916273 | |||
| ed7a21687c | |||
| 5ce991651d | |||
| 432984b3b6 | |||
| 47c8f7acc0 | |||
| 606137c58f | |||
| 76b94ac034 | |||
| 84ca1dc2f2 | |||
| 681c20dbbd | |||
| c240f6e31c | |||
| ba9d58e04e | |||
| 4bb979faf0 | |||
| eae69c4b47 | |||
| f7823906d1 | |||
| a1d9b73742 | |||
| 5f5fe8890a | |||
| a9e8f27360 | |||
| de4f765878 | |||
| c72582506d | |||
| 7fb6b54c0b | |||
| d6236a0eed | |||
| 6171141505 | |||
| 08adb53b8f | |||
| 9e5446582e | |||
| b9c7c0db6f | |||
| cab8b9afcb | |||
| 4d8ba6396a | |||
| e4761e4d66 | |||
| a840986159 | |||
| 4874282642 | |||
| 71c33437fc | |||
| a39b2e8d81 | |||
| a7e775f918 | |||
| 5919995fa1 | |||
| 8746c9bd36 | |||
| 6a9ac5b70a | |||
| 916c2f82d8 | |||
| 80f6ea9e65 | |||
| 9e24281a94 | |||
| 82b527487a | |||
| abde84ea57 | |||
| c599bb3e34 | |||
| 39db53abd6 | |||
| 29c9c119d3 | |||
| fad626e84c | |||
| 5ef255c3c3 | |||
| 6f6f93a4ad | |||
| c75f941716 | |||
| e4af521592 | |||
| 6d40560c92 | |||
| 570648efd0 | |||
| 2dc429440e | |||
| 240995bbe4 | |||
| fe8e54ddc1 | |||
| 1462ee9aeb | |||
| 3da987340b | |||
| a4216bf9ec | |||
| ab26413ce8 | |||
| 94b0b63b3b | |||
| 53d473164b | |||
| 673439d47c | |||
| bbad5e08bb | |||
| 88164c6303 | |||
| a49d3fc6e5 | |||
| e531f6f26e | |||
| c39f6ac33b | |||
| 5812ef3cc9 | |||
| b9aac85635 | |||
| 75decc5838 | |||
| f38ebb485a | |||
| 95742c8fd5 | |||
| 60e27f4755 | |||
| 3d741bd269 | |||
| d4e5b8078d | |||
| 61b51fc5d4 | |||
| f19e425143 | |||
| 7d6bdad086 | |||
| 12c0a7ac86 | |||
| c08bec22e3 | |||
| bdd7c593e1 | |||
| 6e618baf34 | |||
| 0edcaae713 | |||
| dff6cec2f9 | |||
| 4d1d2c86af | |||
| e00c398825 | |||
| 0e481609ea | |||
| 683481804c | |||
| 5845b9c480 | |||
| 71cf39fd98 | |||
| 92db20eba4 | |||
| f1e365799e | |||
| 6d1238212a | |||
| 92a0994f01 | |||
| cad40b25dc | |||
| 1b4c0ce43e | |||
| fd4e3f546d | |||
| 5bcd6dabde | |||
| 3e1f333e5e | |||
| 1f71d274b5 | |||
| bbfdf83267 | |||
| 88254c3952 | |||
| 069e9b46e6 | |||
| 80de3dc32e | |||
| 911148cc6b | |||
| b229545454 | |||
| 375d4ae620 | |||
| bcfb9f24ea | |||
| a905d161e5 | |||
| d78df54721 | |||
| 4067d24c26 | |||
| 9c22e63d7b | |||
| 0350f23519 | |||
| d1ec0a17b2 | |||
| bd8ed6e7eb | |||
| ea7bbd49fe | |||
| 2f29d323d9 | |||
| c6e00796c8 | |||
| 2641f9e344 | |||
| 5dd621b2b0 | |||
| 05413cc989 | |||
| c49d0e0e3c | |||
| 88e3274d96 | |||
| 9bf2080ac8 | |||
| 5ab00388b7 | |||
| 4768488653 | |||
| 569c9ca25a | |||
| c9f8537a15 | |||
| 2d99e392ff | |||
| 1725ba95e9 | |||
| abe1e67c0e | |||
| 2b9d10f182 | |||
| 674f584895 | |||
| 325187b513 | |||
| 8c6d0134a8 | |||
| d2f57fa4dd | |||
| 2f2380b98d | |||
| e5c29749bb | |||
| b505ae7b90 | |||
| 373134cfa1 | |||
| 523d80550d | |||
| 621c3f035e | |||
| 83529c8ca8 | |||
| d38a816b55 | |||
| 9fccb069df | |||
| 1829d5650b | |||
| be36016a69 | |||
| 26e764c842 | |||
| 08b7d56b47 | |||
| 969c8796d5 | |||
| 0d8fe7f930 | |||
| 7be92ac3e5 | |||
| 24414e8d75 | |||
| c6309136ad | |||
| cec588f1c1 | |||
| e899707542 | |||
| 336ce2d0d6 | |||
| 3f58bdc714 | |||
| a2d2f20b5a | |||
| 1415493327 | |||
| c8851038fa | |||
| e74b6ebe42 | |||
| b2fa95e2fc | |||
| f133d48f60 | |||
| e1a01cfba2 | |||
| 06e5e76797 | |||
| 16c1b44927 | |||
| 229375465d | |||
| 49d3f9a3cc | |||
| 39238ee80f | |||
| d7c6226eb7 | |||
| eb140e59c2 | |||
| f122006024 | |||
| 0a144ec57f | |||
| 9acf77b6ed | |||
| fd07185043 | |||
| da3498c36f | |||
| 53fc65ca7c | |||
| 397c84fa8b | |||
| 6381f63722 | |||
| 83ca917c66 | |||
| 2d34201cfc | |||
| f762b61a12 | |||
| 14625dbfde | |||
| dc8563372d | |||
| 5dcd30e587 | |||
| e84369862e | |||
| a9f869e491 | |||
| 03fb6bf619 | |||
| c91ab8bbd2 | |||
| 79c6615a68 | |||
| 3c708b0fcb | |||
| 3107f74165 | |||
| 99704f3a18 | |||
| 40598daea9 | |||
| 528c30e3ba | |||
| 446487a70c | |||
| 7f95b69bc5 | |||
| 540dad346e | |||
| aa94f2ae7e | |||
| 3755198ecd | |||
| 4f62119c2e | |||
| 42b54ef330 | |||
| 6d28a52869 | |||
| 7313a332c8 | |||
| e4b494174d | |||
| 69d863b44a | |||
| d10314c8d6 | |||
| 9d20e04336 | |||
| df99f6ca17 | |||
| 22abb8c25f | |||
| eab5ba7027 | |||
| c288d82713 | |||
| c8d526157a | |||
| 4324b41b9e | |||
| a6e00211f0 | |||
| 99214c7ab1 | |||
| 080d6f5110 | |||
| 155546b937 | |||
| 79fbb7998c | |||
| 5ce2fd298b | |||
| 740410dd73 | |||
| cbc7c22f1c | |||
| a31e81fa66 | |||
| e8a8acca9f | |||
| a9d4564726 | |||
| fc47cffb18 | |||
| fff3009c80 | |||
| 84c10400b9 | |||
| 9f58dfeee1 | |||
| 04e72a85c3 | |||
| 6a17297e2f | |||
| ddd19474da | |||
| d49a77b3a3 |
@@ -0,0 +1,26 @@
|
||||
***[Remove this]The issue would be closed without notice and be considered spam if the template is not followed.***
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Error Message**
|
||||
|
||||
`<The error message in terminal>`
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. Windows]
|
||||
- Version [e.g. 22]
|
||||
- GPU
|
||||
- CPU
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
|
||||
**Confirmation (Mandatory)**
|
||||
- [ ] I have followed the template
|
||||
- [ ] This is not a query about how to increase performance
|
||||
- [ ] I have checked the issues page, and this is not a duplicate
|
||||
|
||||
@@ -6,17 +6,22 @@ __pycache__/
|
||||
.todo
|
||||
*.log
|
||||
*.backup
|
||||
|
||||
tf_env/
|
||||
*.png
|
||||
*.mp4
|
||||
*.mkv
|
||||
|
||||
.tmp/
|
||||
temp/
|
||||
.venv/
|
||||
venv/
|
||||
env/
|
||||
workflow/
|
||||
gfpgan/
|
||||
models/inswapper_128.onnx
|
||||
models/GFPGANv1.4.pth
|
||||
*.onnx
|
||||
models/DMDNet.pth
|
||||
faceswap/
|
||||
.vscode/
|
||||
switch_states.json
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
3.10.0
|
||||
@@ -0,0 +1,38 @@
|
||||
# Collaboration Guidelines and Codebase Quality Standards
|
||||
|
||||
To ensure smooth collaboration and maintain the high quality of our codebase, please adhere to the following guidelines:
|
||||
|
||||
## Branching Strategy
|
||||
|
||||
* **`premain`**:
|
||||
* Always push your changes to the `premain` branch initially.
|
||||
* This safeguards the `main` branch from unintentional disruptions.
|
||||
* All tests will be performed on the `premain` branch.
|
||||
* Changes will only be merged into `main` after several hours or days of rigorous testing.
|
||||
* **`experimental`**:
|
||||
* For large or potentially disruptive changes, use the `experimental` branch.
|
||||
* This allows for thorough discussion and review before considering a merge into `main`.
|
||||
|
||||
## Pre-Pull Request Checklist
|
||||
|
||||
Before creating a Pull Request (PR), ensure you have completed the following tests:
|
||||
|
||||
### Functionality
|
||||
|
||||
* **Realtime Faceswap**:
|
||||
* Test with face enhancer **enabled** and **disabled**.
|
||||
* **Map Faces**:
|
||||
* Test with both options (**enabled** and **disabled**).
|
||||
* **Camera Listing**:
|
||||
* Verify that all cameras are listed accurately.
|
||||
|
||||
### Stability
|
||||
|
||||
* **Realtime FPS**:
|
||||
* Confirm that there is no drop in real-time frames per second (FPS).
|
||||
* **Boot Time**:
|
||||
* Changes should not negatively impact the boot time of either the application or the real-time faceswap feature.
|
||||
* **GPU Overloading**:
|
||||
* Test for a minimum of 15 minutes to guarantee no GPU overloading, which could lead to crashes.
|
||||
* **App Performance**:
|
||||
* The application should remain responsive and not exhibit any lag.
|
||||
@@ -1,163 +1,264 @@
|
||||

|
||||
<h1 align="center">Deep-Live-Cam</h1>
|
||||
|
||||
<p align="center">
|
||||
Real-time face swap and video deepfake with a single click and only a single image.
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://trendshift.io/repositories/11395" target="_blank"><img src="https://trendshift.io/api/badge/repositories/11395" alt="hacksider%2FDeep-Live-Cam | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="media/demo.gif" alt="Demo GIF" width="800">
|
||||
</p>
|
||||
|
||||
## Disclaimer
|
||||
|
||||
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 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.
|
||||
|
||||
|
||||
## Disclaimer
|
||||
This software is meant to be a productive contribution to the rapidly growing AI-generated media industry. It will help artists with tasks such as animating a custom character or using the character as a model for clothing etc.
|
||||
## Quick Start - Pre-built (Windows / Nvidia)
|
||||
|
||||
The developers of this software are aware of its possible unethical applications and are committed to take preventative measures against them. It has a built-in check which prevents the program from working on inappropriate media including but not limited to nudity, graphic content, sensitive material such as war footage etc. We will continue to develop this project in the positive direction while adhering to law and ethics. This project may be shut down or include watermarks on the output if requested by law.
|
||||
<a href="https://hacksider.gumroad.com/l/vccdmm"> <img src="https://github.com/user-attachments/assets/7d993b32-e3e8-4cd3-bbfb-a549152ebdd5" width="285" height="77" />
|
||||
|
||||
Users of this software are expected to use this software responsibly while abiding the local law. If face of a real person is being used, users are suggested to get consent from the concerned person and clearly mention that it is a deepfake when posting content online. Developers of this software will not be responsible for actions of end-users.
|
||||
##### This is the fastest build you can get if you have a discrete NVIDIA GPU.
|
||||
|
||||
## How do I install it?
|
||||
## Quick Start - Pre-built (Mac / Silicon)
|
||||
|
||||
<a href="https://krshh.gumroad.com/l/Deep-Live-Cam-Mac"> <img src="https://github.com/user-attachments/assets/d5d913b5-a7de-4609-96b9-979a5749a703" width="285" height="77" />
|
||||
|
||||
###### 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 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>
|
||||
|
||||
|
||||
### Basic: It is more likely to work on your computer but it will also be very slow. You can follow instructions for the basic install (This usually runs via **CPU**)
|
||||
#### 1.Setup your platform
|
||||
- python (3.10 recommended)
|
||||
## Installation (Manual)
|
||||
|
||||
**Please be aware that the installation requires technical skills and is not for beginners. Consider downloading the prebuilt version.**
|
||||
|
||||
<details>
|
||||
<summary>Click to see the process</summary>
|
||||
|
||||
### Installation
|
||||
|
||||
This is more likely to work on your computer but will be slower as it utilizes the CPU.
|
||||
|
||||
**1. Set up Your Platform**
|
||||
|
||||
- Python (3.10 recommended)
|
||||
- pip
|
||||
- git
|
||||
- [ffmpeg](https://www.youtube.com/watch?v=OlNWCpFdVMA)
|
||||
- [visual studio 2022 runtimes (windows)](https://visualstudio.microsoft.com/visual-cpp-build-tools/)
|
||||
#### 2. Clone Repository
|
||||
https://github.com/hacksider/Deep-Live-Cam.git
|
||||
- [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/)
|
||||
|
||||
#### 3. Download Models
|
||||
**2. Clone the Repository**
|
||||
|
||||
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_fp16.onnx)
|
||||
|
||||
Then put those 2 files on the "**models**" folder
|
||||
|
||||
#### 4. Install dependency
|
||||
We highly recommend to work with a `venv` to avoid issues.
|
||||
```bash
|
||||
https://github.com/hacksider/Deep-Live-Cam.git
|
||||
```
|
||||
|
||||
**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_fp16.onnx)
|
||||
|
||||
Place these files in the "**models**" folder.
|
||||
|
||||
**4. Install Dependencies**
|
||||
|
||||
We highly recommend using a `venv` to avoid issues.
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
##### DONE!!! If you dont have any GPU, You should be able to run roop using `python run.py` command. Keep in mind that while running the program for first time, it will download some models which can take time depending on your network connection.
|
||||
|
||||
### *Proceed if you want to use GPU Acceleration
|
||||
### CUDA Execution Provider (Nvidia)*
|
||||
|
||||
1. Install [CUDA Toolkit 11.8](https://developer.nvidia.com/cuda-11-8-0-download-archive)
|
||||
|
||||
2. Install dependencies:
|
||||
|
||||
**For macOS:** Install or upgrade the `python-tk` package:
|
||||
|
||||
```bash
|
||||
brew install python-tk@3.10
|
||||
```
|
||||
|
||||
**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:
|
||||
|
||||
```bash
|
||||
pip uninstall onnxruntime onnxruntime-gpu
|
||||
pip install onnxruntime-gpu==1.16.3
|
||||
|
||||
```
|
||||
|
||||
3. Usage in case the provider is available:
|
||||
3. Usage:
|
||||
|
||||
```
|
||||
```bash
|
||||
python run.py --execution-provider cuda
|
||||
|
||||
```
|
||||
|
||||
### [](https://github.com/s0md3v/roop/wiki/2.-Acceleration#coreml-execution-provider-apple-silicon)CoreML Execution Provider (Apple Silicon)
|
||||
**CoreML Execution Provider (Apple Silicon)**
|
||||
|
||||
1. Install dependencies:
|
||||
1. Install dependencies:
|
||||
|
||||
```
|
||||
```bash
|
||||
pip uninstall onnxruntime onnxruntime-silicon
|
||||
pip install onnxruntime-silicon==1.13.1
|
||||
|
||||
```
|
||||
|
||||
2. Usage in case the provider is available:
|
||||
2. Usage:
|
||||
|
||||
```
|
||||
```bash
|
||||
python run.py --execution-provider coreml
|
||||
|
||||
```
|
||||
|
||||
### [](https://github.com/s0md3v/roop/wiki/2.-Acceleration#coreml-execution-provider-apple-legacy)CoreML Execution Provider (Apple Legacy)
|
||||
**CoreML Execution Provider (Apple Legacy)**
|
||||
|
||||
1. Install dependencies:
|
||||
1. Install dependencies:
|
||||
|
||||
```
|
||||
```bash
|
||||
pip uninstall onnxruntime onnxruntime-coreml
|
||||
pip install onnxruntime-coreml==1.13.1
|
||||
|
||||
```
|
||||
|
||||
2. Usage in case the provider is available:
|
||||
2. Usage:
|
||||
|
||||
```
|
||||
```bash
|
||||
python run.py --execution-provider coreml
|
||||
|
||||
```
|
||||
|
||||
### [](https://github.com/s0md3v/roop/wiki/2.-Acceleration#directml-execution-provider-windows)DirectML Execution Provider (Windows)
|
||||
**DirectML Execution Provider (Windows)**
|
||||
|
||||
1. Install dependencies:
|
||||
1. Install dependencies:
|
||||
|
||||
```
|
||||
```bash
|
||||
pip uninstall onnxruntime onnxruntime-directml
|
||||
pip install onnxruntime-directml==1.15.1
|
||||
|
||||
```
|
||||
|
||||
2. Usage in case the provider is available:
|
||||
2. Usage:
|
||||
|
||||
```
|
||||
```bash
|
||||
python run.py --execution-provider directml
|
||||
|
||||
```
|
||||
|
||||
### [](https://github.com/s0md3v/roop/wiki/2.-Acceleration#openvino-execution-provider-intel)OpenVINO™ Execution Provider (Intel)
|
||||
**OpenVINO™ Execution Provider (Intel)**
|
||||
|
||||
1. Install dependencies:
|
||||
1. Install dependencies:
|
||||
|
||||
```
|
||||
```bash
|
||||
pip uninstall onnxruntime onnxruntime-openvino
|
||||
pip install onnxruntime-openvino==1.15.0
|
||||
|
||||
```
|
||||
|
||||
2. Usage in case the provider is available:
|
||||
2. Usage:
|
||||
|
||||
```
|
||||
```bash
|
||||
python run.py --execution-provider openvino
|
||||
```
|
||||
|
||||
## How do I use it?
|
||||
> Note: When you run this program for the first time, it will download some models ~300MB in size.
|
||||
</details>
|
||||
|
||||
Executing `python run.py` command will launch this window:
|
||||

|
||||
## Usage
|
||||
|
||||
Choose a face (image with desired face) and the target image/video (image/video in which you want to replace the face) and click on `Start`. Open file explorer and navigate to the directory you select your output to be in. You will find a directory named `<video_title>` where you can see the frames being swapped in realtime. Once the processing is done, it will create the output file. That's it.
|
||||
**1. Image/Video Mode**
|
||||
|
||||
## For the webcam mode
|
||||
Just follow the clicks on the screenshot
|
||||
1. Select a face
|
||||
2. Click live
|
||||
3. Wait for a few second (it takes a longer time, usually 10 to 30 seconds before the preview shows up)
|
||||
- Execute `python run.py`.
|
||||
- Choose a source face image and a target image/video.
|
||||
- Click "Start".
|
||||
- The output will be saved in a directory named after the target video.
|
||||
|
||||

|
||||
**2. Webcam Mode**
|
||||
|
||||
Just use your favorite screencapture to stream like OBS
|
||||
> Note: In case you want to change your face, just select another picture, the preview mode will then restart (so just wait a bit).
|
||||
- Execute `python run.py`.
|
||||
- Select a source face image.
|
||||
- Click "Live".
|
||||
- Wait for the preview to appear (10-30 seconds).
|
||||
- Use a screen capture tool like OBS to stream.
|
||||
- To change the face, select a new source image.
|
||||
|
||||
|
||||
Additional command line arguments are given below. To learn out what they do, check [this guide](https://github.com/s0md3v/roop/wiki/Advanced-Options).
|
||||
## Command Line Arguments (Unmaintained)
|
||||
|
||||
```
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
-s SOURCE_PATH, --source SOURCE_PATH select an source image
|
||||
-t TARGET_PATH, --target TARGET_PATH select an target image or video
|
||||
-s SOURCE_PATH, --source SOURCE_PATH select a source image
|
||||
-t TARGET_PATH, --target TARGET_PATH select a target image or video
|
||||
-o OUTPUT_PATH, --output OUTPUT_PATH select output file or directory
|
||||
--frame-processor FRAME_PROCESSOR [FRAME_PROCESSOR ...] frame processors (choices: face_swapper, face_enhancer, ...)
|
||||
--keep-fps keep original fps
|
||||
--keep-audio keep original audio
|
||||
--keep-frames keep temporary frames
|
||||
--many-faces process every face
|
||||
--map-faces map source target faces
|
||||
--mouth-mask mask the mouth region
|
||||
--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
|
||||
--live-resizable the live camera frame is resizable
|
||||
--max-memory MAX_MEMORY maximum amount of RAM in GB
|
||||
--execution-provider {cpu} [{cpu} ...] available execution provider (choices: cpu, ...)
|
||||
--execution-threads EXECUTION_THREADS number of execution threads
|
||||
@@ -166,10 +267,51 @@ 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 are ready to improve, that's why we didn't cherry-pick 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
|
||||
|
||||
## Credits
|
||||
- [henryruhs](https://github.com/henryruhs): for being an irreplaceable contributor to the project
|
||||
- [ffmpeg](https://ffmpeg.org/): for making video related operations easy
|
||||
- [deepinsight](https://github.com/deepinsight): for their [insightface](https://github.com/deepinsight/insightface) project which provided a well-made library and models.
|
||||
- [havok2-htwo](https://github.com/havok2-htwo) : for sharing the code for webcam
|
||||
- [GosuDRM](https://github.com/GosuDRM/nsfw-roop) : for uncensoring roop
|
||||
- and all developers behind libraries used in this project.
|
||||
|
||||
- [ffmpeg](https://ffmpeg.org/): for making video-related operations easy
|
||||
- [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 the open version of roop
|
||||
- [pereiraroland26](https://github.com/pereiraroland26): Multiple faces support
|
||||
- [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
|
||||
- and [all developers](https://github.com/hacksider/Deep-Live-Cam/graphs/contributors) behind libraries used in this project.
|
||||
- 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">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=hacksider/deep-live-cam&type=Date&theme=dark" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=hacksider/deep-live-cam&type=Date" />
|
||||
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=hacksider/deep-live-cam&type=Date" />
|
||||
</picture>
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 6.2 MiB |
|
Before Width: | Height: | Size: 80 KiB |
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"Source x Target Mapper": "Source x Target Mapper",
|
||||
"select an source image": "选择一个源图像",
|
||||
"Preview": "预览",
|
||||
"select an target image or video": "选择一个目标图像或视频",
|
||||
"save image output file": "保存图像输出文件",
|
||||
"save video output file": "保存视频输出文件",
|
||||
"select an 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!": "请提供映射",
|
||||
"Atleast 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 目标映射器已打开。"
|
||||
}
|
||||
|
After Width: | Height: | Size: 5.2 MiB |
|
After Width: | Height: | Size: 2.8 MiB |
|
Before Width: | Height: | Size: 11 MiB After Width: | Height: | Size: 11 MiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 8.2 MiB |
|
After Width: | Height: | Size: 5.3 MiB |
|
After Width: | Height: | Size: 5.0 MiB |
|
After Width: | Height: | Size: 14 MiB |
|
After Width: | Height: | Size: 13 MiB |
@@ -1 +1,4 @@
|
||||
just put the models in this folder
|
||||
just put the models in this folder -
|
||||
|
||||
https://huggingface.co/hacksider/deep-live-cam/resolve/main/inswapper_128_fp16.onnx?download=true
|
||||
https://github.com/TencentARC/GFPGAN/releases/download/v1.3.4/GFPGANv1.4.pth
|
||||
|
||||
@@ -1,16 +1,28 @@
|
||||
from typing import Any
|
||||
import cv2
|
||||
import modules.globals # Import the globals to check the color correction toggle
|
||||
|
||||
|
||||
def get_video_frame(video_path: str, frame_number: int = 0) -> Any:
|
||||
capture = cv2.VideoCapture(video_path)
|
||||
|
||||
# Set MJPEG format to ensure correct color space handling
|
||||
capture.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(*'MJPG'))
|
||||
|
||||
# Only force RGB conversion if color correction is enabled
|
||||
if modules.globals.color_correction:
|
||||
capture.set(cv2.CAP_PROP_CONVERT_RGB, 1)
|
||||
|
||||
frame_total = capture.get(cv2.CAP_PROP_FRAME_COUNT)
|
||||
capture.set(cv2.CAP_PROP_POS_FRAMES, min(frame_total, frame_number - 1))
|
||||
has_frame, frame = capture.read()
|
||||
|
||||
if has_frame and modules.globals.color_correction:
|
||||
# Convert the frame color if necessary
|
||||
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
||||
|
||||
capture.release()
|
||||
if has_frame:
|
||||
return frame
|
||||
return None
|
||||
return frame if has_frame else None
|
||||
|
||||
|
||||
def get_video_frame_total(video_path: str) -> int:
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import numpy as np
|
||||
from sklearn.cluster import KMeans
|
||||
from sklearn.metrics import silhouette_score
|
||||
from typing import Any
|
||||
|
||||
|
||||
def find_cluster_centroids(embeddings, max_k=10) -> Any:
|
||||
inertia = []
|
||||
cluster_centroids = []
|
||||
K = range(1, max_k+1)
|
||||
|
||||
for k in K:
|
||||
kmeans = KMeans(n_clusters=k, random_state=0)
|
||||
kmeans.fit(embeddings)
|
||||
inertia.append(kmeans.inertia_)
|
||||
cluster_centroids.append({"k": k, "centroids": kmeans.cluster_centers_})
|
||||
|
||||
diffs = [inertia[i] - inertia[i+1] for i in range(len(inertia)-1)]
|
||||
optimal_centroids = cluster_centroids[diffs.index(max(diffs)) + 1]['centroids']
|
||||
|
||||
return optimal_centroids
|
||||
|
||||
def find_closest_centroid(centroids: list, normed_face_embedding) -> list:
|
||||
try:
|
||||
centroids = np.array(centroids)
|
||||
normed_face_embedding = np.array(normed_face_embedding)
|
||||
similarities = np.dot(centroids, normed_face_embedding)
|
||||
closest_centroid_index = np.argmax(similarities)
|
||||
|
||||
return closest_centroid_index, centroids[closest_centroid_index]
|
||||
except ValueError:
|
||||
return None
|
||||
@@ -20,6 +20,7 @@ import modules.metadata
|
||||
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
|
||||
from modules.fake_face_handler import cleanup_fake_face
|
||||
|
||||
if 'ROCMExecutionProvider' in modules.globals.execution_providers:
|
||||
del torch
|
||||
@@ -35,12 +36,16 @@ def parse_args() -> None:
|
||||
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('--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)
|
||||
program.add_argument('--many-faces', help='process every face', dest='many_faces', action='store_true', default=False)
|
||||
program.add_argument('--nsfw-filter', help='filter the NSFW image or video', dest='nsfw_filter', action='store_true', default=False)
|
||||
program.add_argument('--map-faces', help='map source target faces', dest='map_faces', action='store_true', default=False)
|
||||
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())
|
||||
program.add_argument('--execution-provider', help='execution provider', dest='execution_provider', default=['cpu'], choices=suggest_execution_providers(), nargs='+')
|
||||
program.add_argument('--execution-threads', help='number of execution threads', dest='execution_threads', type=int, default=suggest_execution_threads())
|
||||
@@ -59,23 +64,27 @@ def parse_args() -> None:
|
||||
modules.globals.output_path = normalize_output_path(modules.globals.source_path, modules.globals.target_path, args.output_path)
|
||||
modules.globals.frame_processors = args.frame_processor
|
||||
modules.globals.headless = args.source_path or args.target_path or args.output_path
|
||||
modules.globals.keep_fps = args.keep_fps
|
||||
modules.globals.keep_fps = True
|
||||
modules.globals.keep_frames = True
|
||||
modules.globals.keep_audio = args.keep_audio
|
||||
modules.globals.keep_frames = args.keep_frames
|
||||
modules.globals.many_faces = args.many_faces
|
||||
modules.globals.mouth_mask = args.mouth_mask
|
||||
modules.globals.nsfw_filter = args.nsfw_filter
|
||||
modules.globals.map_faces = args.map_faces
|
||||
modules.globals.video_encoder = args.video_encoder
|
||||
modules.globals.video_quality = args.video_quality
|
||||
modules.globals.live_mirror = args.live_mirror
|
||||
modules.globals.live_resizable = args.live_resizable
|
||||
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
|
||||
|
||||
modules.globals.nsfw = False
|
||||
|
||||
# translate deprecated args
|
||||
if args.source_path_deprecated:
|
||||
@@ -165,18 +174,19 @@ def update_status(message: str, scope: str = 'DLC.CORE') -> None:
|
||||
if not modules.globals.headless:
|
||||
ui.update_status(message)
|
||||
|
||||
|
||||
def start() -> None:
|
||||
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 == False:
|
||||
from modules.predicter import predict_image
|
||||
if predict_image(modules.globals.target_path):
|
||||
destroy()
|
||||
shutil.copy2(modules.globals.target_path, modules.globals.output_path)
|
||||
if modules.globals.nsfw_filter and ui.check_and_ignore_nsfw(modules.globals.target_path, destroy):
|
||||
return
|
||||
try:
|
||||
shutil.copy2(modules.globals.target_path, modules.globals.output_path)
|
||||
except Exception as e:
|
||||
print("Error copying file:", str(e))
|
||||
for frame_processor in get_frame_processors_modules(modules.globals.frame_processors):
|
||||
update_status('Progressing...', frame_processor.NAME)
|
||||
frame_processor.process_image(modules.globals.source_path, modules.globals.output_path, modules.globals.output_path)
|
||||
@@ -187,14 +197,15 @@ def start() -> None:
|
||||
update_status('Processing to image failed!')
|
||||
return
|
||||
# process image to videos
|
||||
if modules.globals.nsfw == False:
|
||||
from modules.predicter import predict_video
|
||||
if predict_video(modules.globals.target_path):
|
||||
destroy()
|
||||
update_status('Creating temp resources...')
|
||||
create_temp(modules.globals.target_path)
|
||||
update_status('Extracting frames...')
|
||||
extract_frames(modules.globals.target_path)
|
||||
if modules.globals.nsfw_filter and ui.check_and_ignore_nsfw(modules.globals.target_path, destroy):
|
||||
return
|
||||
|
||||
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)
|
||||
|
||||
temp_frame_paths = get_temp_frame_paths(modules.globals.target_path)
|
||||
for frame_processor in get_frame_processors_modules(modules.globals.frame_processors):
|
||||
update_status('Progressing...', frame_processor.NAME)
|
||||
@@ -226,10 +237,11 @@ def start() -> None:
|
||||
update_status('Processing to video failed!')
|
||||
|
||||
|
||||
def destroy() -> None:
|
||||
def destroy(to_quit=True) -> None:
|
||||
if modules.globals.target_path:
|
||||
clean_temp(modules.globals.target_path)
|
||||
quit()
|
||||
cleanup_fake_face()
|
||||
if to_quit: quit()
|
||||
|
||||
|
||||
def run() -> None:
|
||||
@@ -243,5 +255,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()
|
||||
|
||||
|
After Width: | Height: | Size: 264 KiB |
@@ -1,8 +1,16 @@
|
||||
import os
|
||||
import shutil
|
||||
from typing import Any
|
||||
import insightface
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
import modules.globals
|
||||
from tqdm import tqdm
|
||||
from modules.typing import Frame
|
||||
from modules.cluster_analysis import find_cluster_centroids, find_closest_centroid
|
||||
from modules.utilities import get_temp_directory_path, create_temp, extract_frames, clean_temp, get_temp_frame_paths
|
||||
from pathlib import Path
|
||||
|
||||
FACE_ANALYSER = None
|
||||
|
||||
@@ -29,3 +37,153 @@ def get_many_faces(frame: Frame) -> Any:
|
||||
return get_face_analyser().get(frame)
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
def has_valid_map() -> bool:
|
||||
for map in modules.globals.souce_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:
|
||||
if "source" in map:
|
||||
return map['source']['face']
|
||||
return None
|
||||
|
||||
def simplify_maps() -> Any:
|
||||
centroids = []
|
||||
faces = []
|
||||
for map in modules.globals.souce_target_map:
|
||||
if "source" in map and "target" in map:
|
||||
centroids.append(map['target']['face'].normed_embedding)
|
||||
faces.append(map['source']['face'])
|
||||
|
||||
modules.globals.simple_map = {'source_faces': faces, 'target_embeddings': centroids}
|
||||
return None
|
||||
|
||||
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']
|
||||
|
||||
modules.globals.souce_target_map.append({
|
||||
'id' : max_id + 1
|
||||
})
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
def get_unique_faces_from_target_image() -> Any:
|
||||
try:
|
||||
modules.globals.souce_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({
|
||||
'id' : i,
|
||||
'target' : {
|
||||
'cv2' : target_frame[int(y_min):int(y_max), int(x_min):int(x_max)],
|
||||
'face' : face
|
||||
}
|
||||
})
|
||||
i = i + 1
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
def get_unique_faces_from_target_video() -> Any:
|
||||
try:
|
||||
modules.globals.souce_target_map = []
|
||||
frame_face_embeddings = []
|
||||
face_embeddings = []
|
||||
|
||||
print('Creating temp resources...')
|
||||
clean_temp(modules.globals.target_path)
|
||||
create_temp(modules.globals.target_path)
|
||||
print('Extracting frames...')
|
||||
extract_frames(modules.globals.target_path)
|
||||
|
||||
temp_frame_paths = get_temp_frame_paths(modules.globals.target_path)
|
||||
|
||||
i = 0
|
||||
for temp_frame_path in tqdm(temp_frame_paths, desc="Extracting face embeddings from frames"):
|
||||
temp_frame = cv2.imread(temp_frame_path)
|
||||
many_faces = get_many_faces(temp_frame)
|
||||
|
||||
for face in many_faces:
|
||||
face_embeddings.append(face.normed_embedding)
|
||||
|
||||
frame_face_embeddings.append({'frame': i, 'faces': many_faces, 'location': temp_frame_path})
|
||||
i += 1
|
||||
|
||||
centroids = find_cluster_centroids(face_embeddings)
|
||||
|
||||
for frame in frame_face_embeddings:
|
||||
for face in frame['faces']:
|
||||
closest_centroid_index, _ = find_closest_centroid(centroids, face.normed_embedding)
|
||||
face['target_centroid'] = closest_centroid_index
|
||||
|
||||
for i in range(len(centroids)):
|
||||
modules.globals.souce_target_map.append({
|
||||
'id' : i
|
||||
})
|
||||
|
||||
temp = []
|
||||
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
|
||||
|
||||
# dump_faces(centroids, frame_face_embeddings)
|
||||
default_target_face()
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
def default_target_face():
|
||||
for map in modules.globals.souce_target_map:
|
||||
best_face = None
|
||||
best_frame = None
|
||||
for frame in map['target_faces_in_frame']:
|
||||
if len(frame['faces']) > 0:
|
||||
best_face = frame['faces'][0]
|
||||
best_frame = frame
|
||||
break
|
||||
|
||||
for frame in map['target_faces_in_frame']:
|
||||
for face in frame['faces']:
|
||||
if face['det_score'] > best_face['det_score']:
|
||||
best_face = face
|
||||
best_frame = frame
|
||||
|
||||
x_min, y_min, x_max, y_max = best_face['bbox']
|
||||
|
||||
target_frame = cv2.imread(best_frame['location'])
|
||||
map['target'] = {
|
||||
'cv2' : target_frame[int(y_min):int(y_max), int(x_min):int(x_max)],
|
||||
'face' : best_face
|
||||
}
|
||||
|
||||
|
||||
def dump_faces(centroids: Any, frame_face_embeddings: list):
|
||||
temp_directory_path = get_temp_directory_path(modules.globals.target_path)
|
||||
|
||||
for i in range(len(centroids)):
|
||||
if os.path.exists(temp_directory_path + f"/{i}") and os.path.isdir(temp_directory_path + f"/{i}"):
|
||||
shutil.rmtree(temp_directory_path + f"/{i}")
|
||||
Path(temp_directory_path + f"/{i}").mkdir(parents=True, exist_ok=True)
|
||||
|
||||
for frame in tqdm(frame_face_embeddings, desc=f"Copying faces to temp/./{i}"):
|
||||
temp_frame = cv2.imread(frame['location'])
|
||||
|
||||
j = 0
|
||||
for face in frame['faces']:
|
||||
if face['target_centroid'] == i:
|
||||
x_min, y_min, x_max, y_max = face['bbox']
|
||||
|
||||
if temp_frame[int(y_min):int(y_max), int(x_min):int(x_max)].size > 0:
|
||||
cv2.imwrite(temp_directory_path + f"/{i}/{frame['frame']}_{j}.png", temp_frame[int(y_min):int(y_max), int(x_min):int(x_max)])
|
||||
j += 1
|
||||
@@ -0,0 +1,120 @@
|
||||
import os
|
||||
import requests
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
import cv2
|
||||
import numpy as np
|
||||
import modules.globals
|
||||
|
||||
def add_padding_to_face(image, padding_ratio=0.3):
|
||||
"""Add padding around the face image
|
||||
|
||||
Args:
|
||||
image: The input face image
|
||||
padding_ratio: Amount of padding to add as a ratio of image dimensions
|
||||
|
||||
Returns:
|
||||
Padded image with background padding added
|
||||
"""
|
||||
if image is None:
|
||||
return None
|
||||
|
||||
height, width = image.shape[:2]
|
||||
pad_x = int(width * padding_ratio)
|
||||
pad_y = int(height * padding_ratio)
|
||||
|
||||
# Create larger image with padding
|
||||
padded_height = height + 2 * pad_y
|
||||
padded_width = width + 2 * pad_x
|
||||
padded_image = np.zeros((padded_height, padded_width, 3), dtype=np.uint8)
|
||||
|
||||
# Fill padded area with blurred and darkened edge pixels
|
||||
edge_color = cv2.blur(image, (15, 15))
|
||||
edge_color = (edge_color * 0.6).astype(np.uint8) # Darken the padding
|
||||
|
||||
# Fill the padded image with original face
|
||||
padded_image[pad_y:pad_y+height, pad_x:pad_x+width] = image
|
||||
|
||||
# Fill padding areas with edge color
|
||||
# Top padding - repeat first row
|
||||
top_edge = edge_color[0, :, :]
|
||||
for i in range(pad_y):
|
||||
padded_image[i, pad_x:pad_x+width] = top_edge
|
||||
|
||||
# Bottom padding - repeat last row
|
||||
bottom_edge = edge_color[-1, :, :]
|
||||
for i in range(pad_y):
|
||||
padded_image[pad_y+height+i, pad_x:pad_x+width] = bottom_edge
|
||||
|
||||
# Left padding - repeat first column
|
||||
left_edge = edge_color[:, 0, :]
|
||||
for i in range(pad_x):
|
||||
padded_image[pad_y:pad_y+height, i] = left_edge
|
||||
|
||||
# Right padding - repeat last column
|
||||
right_edge = edge_color[:, -1, :]
|
||||
for i in range(pad_x):
|
||||
padded_image[pad_y:pad_y+height, pad_x+width+i] = right_edge
|
||||
|
||||
# Fill corners with nearest edge colors
|
||||
# Top-left corner
|
||||
padded_image[:pad_y, :pad_x] = edge_color[0, 0, :]
|
||||
# Top-right corner
|
||||
padded_image[:pad_y, pad_x+width:] = edge_color[0, -1, :]
|
||||
# Bottom-left corner
|
||||
padded_image[pad_y+height:, :pad_x] = edge_color[-1, 0, :]
|
||||
# Bottom-right corner
|
||||
padded_image[pad_y+height:, pad_x+width:] = edge_color[-1, -1, :]
|
||||
|
||||
return padded_image
|
||||
|
||||
def get_fake_face() -> str:
|
||||
"""Fetch a face from thispersondoesnotexist.com and save it temporarily"""
|
||||
try:
|
||||
# Create temp directory if it doesn't exist
|
||||
temp_dir = Path(tempfile.gettempdir()) / "deep-live-cam"
|
||||
temp_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Generate temp file path
|
||||
temp_file = temp_dir / "fake_face.jpg"
|
||||
|
||||
# Basic headers to mimic a browser request
|
||||
headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
|
||||
}
|
||||
|
||||
# Fetch the image
|
||||
response = requests.get('https://thispersondoesnotexist.com', headers=headers)
|
||||
|
||||
if response.status_code == 200:
|
||||
# Read image from response
|
||||
image_array = np.asarray(bytearray(response.content), dtype=np.uint8)
|
||||
image = cv2.imdecode(image_array, cv2.IMREAD_COLOR)
|
||||
|
||||
# Add padding around the face
|
||||
padded_image = add_padding_to_face(image)
|
||||
|
||||
# Save the padded image
|
||||
cv2.imwrite(str(temp_file), padded_image)
|
||||
return str(temp_file)
|
||||
else:
|
||||
print(f"Failed to fetch fake face: {response.status_code}")
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"Error fetching fake face: {str(e)}")
|
||||
return None
|
||||
|
||||
def cleanup_fake_face():
|
||||
"""Clean up the temporary fake face image"""
|
||||
try:
|
||||
if modules.globals.fake_face_path and os.path.exists(modules.globals.fake_face_path):
|
||||
os.remove(modules.globals.fake_face_path)
|
||||
modules.globals.fake_face_path = None
|
||||
except Exception as e:
|
||||
print(f"Error cleaning up fake face: {str(e)}")
|
||||
|
||||
def refresh_fake_face():
|
||||
"""Refresh the fake face image"""
|
||||
cleanup_fake_face()
|
||||
modules.globals.fake_face_path = get_fake_face()
|
||||
return modules.globals.fake_face_path is not None
|
||||
@@ -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)
|
||||
@@ -1,30 +1,52 @@
|
||||
import os
|
||||
from typing import List, Dict
|
||||
from typing import List, Dict, Any
|
||||
|
||||
ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
WORKFLOW_DIR = os.path.join(ROOT_DIR, 'workflow')
|
||||
WORKFLOW_DIR = os.path.join(ROOT_DIR, "workflow")
|
||||
|
||||
file_types = [
|
||||
('Image', ('*.png','*.jpg','*.jpeg','*.gif','*.bmp')),
|
||||
('Video', ('*.mp4','*.mkv'))
|
||||
("Image", ("*.png", "*.jpg", "*.jpeg", "*.gif", "*.bmp")),
|
||||
("Video", ("*.mp4", "*.mkv")),
|
||||
]
|
||||
|
||||
souce_target_map = []
|
||||
simple_map = {}
|
||||
|
||||
source_path = None
|
||||
target_path = None
|
||||
output_path = None
|
||||
frame_processors: List[str] = []
|
||||
keep_fps = None
|
||||
keep_audio = None
|
||||
keep_frames = None
|
||||
many_faces = None
|
||||
keep_fps = True
|
||||
keep_audio = True
|
||||
keep_frames = False
|
||||
many_faces = False
|
||||
map_faces = False
|
||||
color_correction = False
|
||||
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] = {}
|
||||
nsfw = None
|
||||
log_level = "error"
|
||||
fp_ui: Dict[str, bool] = {"face_enhancer": False}
|
||||
camera_input_combobox = None
|
||||
webcam_preview_running = False
|
||||
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
|
||||
mouth_mask_size = 1.0
|
||||
eyes_mask = False
|
||||
show_eyes_mask_box = False
|
||||
eyebrows_mask = False
|
||||
show_eyebrows_mask_box = False
|
||||
eyes_mask_size = 1.0
|
||||
eyebrows_mask_size = 1.0
|
||||
use_fake_face = False
|
||||
fake_face_path = None
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
name = 'Deep Live Cam'
|
||||
version = '1.3.0'
|
||||
edition = 'Portable'
|
||||
name = 'Deep-Live-Cam'
|
||||
version = '1.8'
|
||||
edition = 'GitHub Edition'
|
||||
|
||||
@@ -1,16 +1,27 @@
|
||||
import numpy
|
||||
import opennsfw2
|
||||
from PIL import Image
|
||||
import cv2 # Add OpenCV import
|
||||
import modules.globals # Import globals to access the color correction toggle
|
||||
|
||||
from modules.typing import Frame
|
||||
|
||||
MAX_PROBABILITY = 0.85
|
||||
|
||||
# Preload the model once for efficiency
|
||||
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)
|
||||
|
||||
image = Image.fromarray(target_frame)
|
||||
image = opennsfw2.preprocess_image(image, opennsfw2.Preprocessing.YAHOO)
|
||||
model = opennsfw2.make_open_nsfw_model()
|
||||
global model
|
||||
if model is None:
|
||||
model = opennsfw2.make_open_nsfw_model()
|
||||
|
||||
views = numpy.expand_dims(image, axis=0)
|
||||
_, probability = model.predict(views)[0]
|
||||
return probability > MAX_PROBABILITY
|
||||
|
||||
@@ -9,23 +9,41 @@ 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 conditional_download, resolve_relative_path, is_image, is_video
|
||||
import platform
|
||||
import torch
|
||||
from modules.utilities import (
|
||||
conditional_download,
|
||||
is_image,
|
||||
is_video,
|
||||
)
|
||||
|
||||
FACE_ENHANCER = None
|
||||
THREAD_SEMAPHORE = threading.Semaphore()
|
||||
THREAD_LOCK = threading.Lock()
|
||||
NAME = 'DLC.FACE-ENHANCER'
|
||||
NAME = "DLC.FACE-ENHANCER"
|
||||
|
||||
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:
|
||||
download_directory_path = resolve_relative_path('..\models')
|
||||
conditional_download(download_directory_path, ['https://github.com/TencentARC/GFPGAN/releases/download/v1.3.4/GFPGANv1.4.pth'])
|
||||
download_directory_path = models_dir
|
||||
conditional_download(
|
||||
download_directory_path,
|
||||
[
|
||||
"https://github.com/TencentARC/GFPGAN/releases/download/v1.3.4/GFPGANv1.4.pth"
|
||||
],
|
||||
)
|
||||
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)
|
||||
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
|
||||
|
||||
@@ -35,21 +53,24 @@ def get_face_enhancer() -> Any:
|
||||
|
||||
with THREAD_LOCK:
|
||||
if FACE_ENHANCER is None:
|
||||
if os.name == 'nt':
|
||||
model_path = resolve_relative_path('..\models\GFPGANv1.4.pth')
|
||||
# todo: set models path https://github.com/TencentARC/GFPGAN/issues/399
|
||||
else:
|
||||
model_path = resolve_relative_path('../models/GFPGANv1.4.pth')
|
||||
FACE_ENHANCER = gfpgan.GFPGANer(model_path=model_path, upscale=1) # type: ignore[attr-defined]
|
||||
model_path = os.path.join(models_dir, "GFPGANv1.4.pth")
|
||||
|
||||
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]
|
||||
|
||||
return FACE_ENHANCER
|
||||
|
||||
|
||||
def enhance_face(temp_frame: Frame) -> Frame:
|
||||
with THREAD_SEMAPHORE:
|
||||
_, _, temp_frame = get_face_enhancer().enhance(
|
||||
temp_frame,
|
||||
paste_back=True
|
||||
)
|
||||
_, _, temp_frame = get_face_enhancer().enhance(temp_frame, paste_back=True)
|
||||
return temp_frame
|
||||
|
||||
|
||||
@@ -60,7 +81,9 @@ def process_frame(source_face: Face, temp_frame: Frame) -> Frame:
|
||||
return temp_frame
|
||||
|
||||
|
||||
def process_frames(source_path: str, temp_frame_paths: List[str], progress: Any = None) -> None:
|
||||
def process_frames(
|
||||
source_path: str, temp_frame_paths: List[str], progress: Any = None
|
||||
) -> None:
|
||||
for temp_frame_path in temp_frame_paths:
|
||||
temp_frame = cv2.imread(temp_frame_path)
|
||||
result = process_frame(None, temp_frame)
|
||||
@@ -77,3 +100,10 @@ def process_image(source_path: str, target_path: str, output_path: str) -> None:
|
||||
|
||||
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_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
|
||||
|
||||
@@ -0,0 +1,634 @@
|
||||
import cv2
|
||||
import numpy as np
|
||||
from modules.typing import Face, Frame
|
||||
import modules.globals
|
||||
|
||||
def apply_color_transfer(source, target):
|
||||
"""
|
||||
Apply color transfer from target to source image
|
||||
"""
|
||||
source = cv2.cvtColor(source, cv2.COLOR_BGR2LAB).astype("float32")
|
||||
target = cv2.cvtColor(target, cv2.COLOR_BGR2LAB).astype("float32")
|
||||
|
||||
source_mean, source_std = cv2.meanStdDev(source)
|
||||
target_mean, target_std = cv2.meanStdDev(target)
|
||||
|
||||
# Reshape mean and std to be broadcastable
|
||||
source_mean = source_mean.reshape(1, 1, 3)
|
||||
source_std = source_std.reshape(1, 1, 3)
|
||||
target_mean = target_mean.reshape(1, 1, 3)
|
||||
target_std = target_std.reshape(1, 1, 3)
|
||||
|
||||
# Perform the color transfer
|
||||
source = (source - source_mean) * (target_std / source_std) + target_mean
|
||||
|
||||
return cv2.cvtColor(np.clip(source, 0, 255).astype("uint8"), cv2.COLOR_LAB2BGR)
|
||||
|
||||
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 forehead extension
|
||||
right_eyebrow_top = np.min(right_eye_brow[:, 1])
|
||||
left_eyebrow_top = np.min(left_eye_brow[:, 1])
|
||||
eyebrow_top = min(right_eyebrow_top, left_eyebrow_top)
|
||||
|
||||
face_top = np.min([right_side_face[0, 1], left_side_face[-1, 1]])
|
||||
forehead_height = face_top - eyebrow_top
|
||||
extended_forehead_height = int(forehead_height * 5.0) # Extend by 50%
|
||||
|
||||
# Create forehead points
|
||||
forehead_left = right_side_face[0].copy()
|
||||
forehead_right = left_side_face[-1].copy()
|
||||
forehead_left[1] -= extended_forehead_height
|
||||
forehead_right[1] -= extended_forehead_height
|
||||
|
||||
# Combine all points to create the face outline
|
||||
face_outline = np.vstack(
|
||||
[
|
||||
[forehead_left],
|
||||
right_side_face,
|
||||
left_side_face[::-1], # Reverse left side to create a continuous outline
|
||||
[forehead_right],
|
||||
]
|
||||
)
|
||||
|
||||
# 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
|
||||
hull = cv2.convexHull(face_outline)
|
||||
hull_padded = []
|
||||
for point in hull:
|
||||
x, y = point[0]
|
||||
center = np.mean(face_outline, axis=0)
|
||||
direction = np.array([x, y]) - center
|
||||
direction = direction / np.linalg.norm(direction)
|
||||
padded_point = np.array([x, y]) + direction * padding
|
||||
hull_padded.append(padded_point)
|
||||
|
||||
hull_padded = np.array(hull_padded, dtype=np.int32)
|
||||
|
||||
# Fill the padded convex hull
|
||||
cv2.fillConvexPoly(mask, hull_padded, 255)
|
||||
|
||||
# Smooth the mask edges
|
||||
mask = cv2.GaussianBlur(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
|
||||
landmarks = face.landmark_2d_106
|
||||
if landmarks is not None:
|
||||
# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
||||
lower_lip_order = [
|
||||
65,
|
||||
66,
|
||||
62,
|
||||
70,
|
||||
69,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
0,
|
||||
8,
|
||||
7,
|
||||
6,
|
||||
5,
|
||||
4,
|
||||
3,
|
||||
2,
|
||||
65,
|
||||
]
|
||||
lower_lip_landmarks = landmarks[lower_lip_order].astype(
|
||||
np.float32
|
||||
) # Use float for precise calculations
|
||||
|
||||
# Calculate the center of the landmarks
|
||||
center = np.mean(lower_lip_landmarks, axis=0)
|
||||
|
||||
# Expand the landmarks outward using the mouth_mask_size
|
||||
expansion_factor = (
|
||||
1 + modules.globals.mask_down_size * modules.globals.mouth_mask_size
|
||||
) # Adjust expansion based on slider
|
||||
expanded_landmarks = (lower_lip_landmarks - center) * expansion_factor + center
|
||||
|
||||
# Extend the top lip part
|
||||
toplip_indices = [
|
||||
20,
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
] # Indices for landmarks 2, 65, 66, 62, 70, 69, 18
|
||||
toplip_extension = (
|
||||
modules.globals.mask_size * modules.globals.mouth_mask_size * 0.5
|
||||
) # Adjust extension based on slider
|
||||
for idx in toplip_indices:
|
||||
direction = expanded_landmarks[idx] - center
|
||||
direction = direction / np.linalg.norm(direction)
|
||||
expanded_landmarks[idx] += direction * toplip_extension
|
||||
|
||||
# Extend the bottom part (chin area)
|
||||
chin_indices = [
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
15,
|
||||
16,
|
||||
] # Indices for landmarks 21, 22, 23, 24, 0, 8
|
||||
chin_extension = 2 * 0.2 # Adjust this factor to control the extension
|
||||
for idx in chin_indices:
|
||||
expanded_landmarks[idx][1] += (
|
||||
expanded_landmarks[idx][1] - center[1]
|
||||
) * chin_extension
|
||||
|
||||
# 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)
|
||||
cv2.fillPoly(mask_roi, [expanded_landmarks - [min_x, min_y]], 255)
|
||||
|
||||
# Apply Gaussian blur to soften the mask edges
|
||||
mask_roi = cv2.GaussianBlur(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
|
||||
|
||||
return mask, mouth_cutout, (min_x, min_y, max_x, max_y), 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
|
||||
mask_roi = cv2.GaussianBlur(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
|
||||
# First, strong Gaussian blur for initial softening
|
||||
mask_roi = cv2.GaussianBlur(mask_roi, (21, 21), 7)
|
||||
|
||||
# Second, medium blur for transition areas
|
||||
mask_roi = cv2.GaussianBlur(mask_roi, (11, 11), 3)
|
||||
|
||||
# Finally, light blur for fine details
|
||||
mask_roi = cv2.GaussianBlur(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 = cv2.GaussianBlur(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 = cv2.resize(cutout, (box_width, box_height))
|
||||
roi = frame[min_y:max_y, min_x:max_x]
|
||||
|
||||
if roi.shape != resized_cutout.shape:
|
||||
resized_cutout = cv2.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
|
||||
polygon_mask = cv2.GaussianBlur(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(float), (0, 0), feather_amount
|
||||
)
|
||||
feathered_mask = feathered_mask / feathered_mask.max()
|
||||
|
||||
# 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 / 255.0)
|
||||
|
||||
combined_mask = combined_mask[:, :, np.newaxis]
|
||||
blended = (
|
||||
color_corrected_area * combined_mask + roi * (1 - combined_mask)
|
||||
).astype(np.uint8)
|
||||
|
||||
# Apply face mask to blended result
|
||||
face_mask_3channel = (
|
||||
np.repeat(face_mask_roi[:, :, np.newaxis], 3, axis=2) / 255.0
|
||||
)
|
||||
final_blend = blended * face_mask_3channel + roi * (1 - 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
|
||||
@@ -2,34 +2,62 @@ from typing import Any, List
|
||||
import cv2
|
||||
import insightface
|
||||
import threading
|
||||
|
||||
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, get_many_faces
|
||||
from modules.face_analyser import get_one_face, get_many_faces, default_source_face
|
||||
from modules.typing import Face, Frame
|
||||
from modules.utilities import conditional_download, resolve_relative_path, is_image, is_video
|
||||
from modules.utilities import (
|
||||
conditional_download,
|
||||
is_image,
|
||||
is_video,
|
||||
)
|
||||
from modules.cluster_analysis import find_closest_centroid
|
||||
from modules.processors.frame.face_masking import (
|
||||
create_face_mask,
|
||||
create_lower_mouth_mask,
|
||||
create_eyes_mask,
|
||||
create_eyebrows_mask,
|
||||
apply_mask_area,
|
||||
draw_mask_visualization
|
||||
)
|
||||
import os
|
||||
|
||||
FACE_SWAPPER = None
|
||||
THREAD_LOCK = threading.Lock()
|
||||
NAME = 'DLC.FACE-SWAPPER'
|
||||
NAME = "DLC.FACE-SWAPPER"
|
||||
|
||||
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:
|
||||
download_directory_path = resolve_relative_path('../models')
|
||||
conditional_download(download_directory_path, ['https://huggingface.co/hacksider/deep-live-cam/blob/main/inswapper_128_fp16.onnx'])
|
||||
download_directory_path = abs_dir
|
||||
conditional_download(
|
||||
download_directory_path,
|
||||
[
|
||||
"https://huggingface.co/hacksider/deep-live-cam/blob/main/inswapper_128_fp16.onnx"
|
||||
],
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
def pre_start() -> bool:
|
||||
if not is_image(modules.globals.source_path):
|
||||
update_status('Select an image for source path.', NAME)
|
||||
if not modules.globals.map_faces and not is_image(modules.globals.source_path):
|
||||
update_status("Select an image for source path.", NAME)
|
||||
return False
|
||||
elif not get_one_face(cv2.imread(modules.globals.source_path)):
|
||||
update_status('No face in source path detected.', NAME)
|
||||
elif not modules.globals.map_faces and not get_one_face(
|
||||
cv2.imread(modules.globals.source_path)
|
||||
):
|
||||
update_status("No face in source path detected.", NAME)
|
||||
return False
|
||||
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)
|
||||
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
|
||||
|
||||
@@ -39,16 +67,86 @@ def get_face_swapper() -> Any:
|
||||
|
||||
with THREAD_LOCK:
|
||||
if FACE_SWAPPER is None:
|
||||
model_path = resolve_relative_path('../models/inswapper_128_fp16.onnx')
|
||||
FACE_SWAPPER = insightface.model_zoo.get_model(model_path, providers=modules.globals.execution_providers)
|
||||
model_path = os.path.join(models_dir, "inswapper_128_fp16.onnx")
|
||||
FACE_SWAPPER = insightface.model_zoo.get_model(
|
||||
model_path, providers=modules.globals.execution_providers
|
||||
)
|
||||
return FACE_SWAPPER
|
||||
|
||||
|
||||
def swap_face(source_face: Face, target_face: Face, temp_frame: Frame) -> Frame:
|
||||
return get_face_swapper().get(temp_frame, target_face, source_face, paste_back=True)
|
||||
face_swapper = get_face_swapper()
|
||||
|
||||
# Apply the face swap
|
||||
swapped_frame = face_swapper.get(
|
||||
temp_frame, target_face, source_face, paste_back=True
|
||||
)
|
||||
|
||||
# Create face mask for both mouth and eyes masking
|
||||
face_mask = create_face_mask(target_face, temp_frame)
|
||||
|
||||
if modules.globals.mouth_mask:
|
||||
# Create and apply mouth mask
|
||||
mouth_mask_data = create_lower_mouth_mask(target_face, temp_frame)
|
||||
swapped_frame = apply_mask_area(
|
||||
swapped_frame,
|
||||
mouth_mask_data[1], # mouth_cutout
|
||||
mouth_mask_data[2], # mouth_box
|
||||
face_mask,
|
||||
mouth_mask_data[3] # mouth_polygon
|
||||
)
|
||||
|
||||
if modules.globals.show_mouth_mask_box:
|
||||
swapped_frame = draw_mask_visualization(
|
||||
swapped_frame,
|
||||
mouth_mask_data,
|
||||
"Lower Mouth Mask"
|
||||
)
|
||||
|
||||
if modules.globals.eyes_mask:
|
||||
# Create and apply eyes mask
|
||||
eyes_mask_data = create_eyes_mask(target_face, temp_frame)
|
||||
swapped_frame = apply_mask_area(
|
||||
swapped_frame,
|
||||
eyes_mask_data[1], # eyes_cutout
|
||||
eyes_mask_data[2], # eyes_box
|
||||
face_mask,
|
||||
eyes_mask_data[3] # eyes_polygon
|
||||
)
|
||||
|
||||
if modules.globals.show_eyes_mask_box:
|
||||
swapped_frame = draw_mask_visualization(
|
||||
swapped_frame,
|
||||
eyes_mask_data,
|
||||
"Eyes Mask",
|
||||
draw_method="ellipse"
|
||||
)
|
||||
|
||||
if modules.globals.eyebrows_mask:
|
||||
# Create and apply eyebrows mask
|
||||
eyebrows_mask_data = create_eyebrows_mask(target_face, temp_frame)
|
||||
swapped_frame = apply_mask_area(
|
||||
swapped_frame,
|
||||
eyebrows_mask_data[1], # eyebrows_cutout
|
||||
eyebrows_mask_data[2], # eyebrows_box
|
||||
face_mask,
|
||||
eyebrows_mask_data[3] # eyebrows_polygon
|
||||
)
|
||||
|
||||
if modules.globals.show_eyebrows_mask_box:
|
||||
swapped_frame = draw_mask_visualization(
|
||||
swapped_frame,
|
||||
eyebrows_mask_data,
|
||||
"Eyebrows Mask"
|
||||
)
|
||||
|
||||
return swapped_frame
|
||||
|
||||
|
||||
def process_frame(source_face: Face, temp_frame: Frame) -> Frame:
|
||||
if modules.globals.color_correction:
|
||||
temp_frame = cv2.cvtColor(temp_frame, cv2.COLOR_BGR2RGB)
|
||||
|
||||
if modules.globals.many_faces:
|
||||
many_faces = get_many_faces(temp_frame)
|
||||
if many_faces:
|
||||
@@ -61,26 +159,145 @@ def process_frame(source_face: Face, temp_frame: Frame) -> Frame:
|
||||
return temp_frame
|
||||
|
||||
|
||||
def process_frames(source_path: str, temp_frame_paths: List[str], progress: Any = None) -> None:
|
||||
source_face = get_one_face(cv2.imread(source_path))
|
||||
for temp_frame_path in temp_frame_paths:
|
||||
temp_frame = cv2.imread(temp_frame_path)
|
||||
try:
|
||||
result = process_frame(source_face, temp_frame)
|
||||
cv2.imwrite(temp_frame_path, result)
|
||||
except Exception as exception:
|
||||
print(exception)
|
||||
pass
|
||||
if progress:
|
||||
progress.update(1)
|
||||
def process_frame_v2(temp_frame: Frame, temp_frame_path: str = "") -> Frame:
|
||||
if is_image(modules.globals.target_path):
|
||||
if modules.globals.many_faces:
|
||||
source_face = default_source_face()
|
||||
for map in modules.globals.souce_target_map:
|
||||
target_face = map["target"]["face"]
|
||||
temp_frame = swap_face(source_face, target_face, temp_frame)
|
||||
|
||||
elif not modules.globals.many_faces:
|
||||
for map in modules.globals.souce_target_map:
|
||||
if "source" in map:
|
||||
source_face = map["source"]["face"]
|
||||
target_face = map["target"]["face"]
|
||||
temp_frame = swap_face(source_face, target_face, temp_frame)
|
||||
|
||||
elif is_video(modules.globals.target_path):
|
||||
if modules.globals.many_faces:
|
||||
source_face = default_source_face()
|
||||
for map in modules.globals.souce_target_map:
|
||||
target_frame = [
|
||||
f
|
||||
for f in map["target_faces_in_frame"]
|
||||
if f["location"] == temp_frame_path
|
||||
]
|
||||
|
||||
for frame in target_frame:
|
||||
for target_face in frame["faces"]:
|
||||
temp_frame = swap_face(source_face, target_face, temp_frame)
|
||||
|
||||
elif not modules.globals.many_faces:
|
||||
for map in modules.globals.souce_target_map:
|
||||
if "source" in map:
|
||||
target_frame = [
|
||||
f
|
||||
for f in map["target_faces_in_frame"]
|
||||
if f["location"] == temp_frame_path
|
||||
]
|
||||
source_face = map["source"]["face"]
|
||||
|
||||
for frame in target_frame:
|
||||
for target_face in frame["faces"]:
|
||||
temp_frame = swap_face(source_face, target_face, temp_frame)
|
||||
|
||||
else:
|
||||
detected_faces = get_many_faces(temp_frame)
|
||||
if modules.globals.many_faces:
|
||||
if detected_faces:
|
||||
source_face = default_source_face()
|
||||
for target_face in detected_faces:
|
||||
temp_frame = swap_face(source_face, target_face, temp_frame)
|
||||
|
||||
elif not modules.globals.many_faces:
|
||||
if detected_faces:
|
||||
if len(detected_faces) <= len(
|
||||
modules.globals.simple_map["target_embeddings"]
|
||||
):
|
||||
for detected_face in detected_faces:
|
||||
closest_centroid_index, _ = find_closest_centroid(
|
||||
modules.globals.simple_map["target_embeddings"],
|
||||
detected_face.normed_embedding,
|
||||
)
|
||||
|
||||
temp_frame = swap_face(
|
||||
modules.globals.simple_map["source_faces"][
|
||||
closest_centroid_index
|
||||
],
|
||||
detected_face,
|
||||
temp_frame,
|
||||
)
|
||||
else:
|
||||
detected_faces_centroids = []
|
||||
for face in detected_faces:
|
||||
detected_faces_centroids.append(face.normed_embedding)
|
||||
i = 0
|
||||
for target_embedding in modules.globals.simple_map[
|
||||
"target_embeddings"
|
||||
]:
|
||||
closest_centroid_index, _ = find_closest_centroid(
|
||||
detected_faces_centroids, target_embedding
|
||||
)
|
||||
|
||||
temp_frame = swap_face(
|
||||
modules.globals.simple_map["source_faces"][i],
|
||||
detected_faces[closest_centroid_index],
|
||||
temp_frame,
|
||||
)
|
||||
i += 1
|
||||
return temp_frame
|
||||
|
||||
|
||||
def process_frames(
|
||||
source_path: str, temp_frame_paths: List[str], progress: Any = None
|
||||
) -> None:
|
||||
if not modules.globals.map_faces:
|
||||
source_face = get_one_face(cv2.imread(source_path))
|
||||
for temp_frame_path in temp_frame_paths:
|
||||
temp_frame = cv2.imread(temp_frame_path)
|
||||
try:
|
||||
result = process_frame(source_face, temp_frame)
|
||||
cv2.imwrite(temp_frame_path, result)
|
||||
except Exception as exception:
|
||||
print(exception)
|
||||
pass
|
||||
if progress:
|
||||
progress.update(1)
|
||||
else:
|
||||
for temp_frame_path in temp_frame_paths:
|
||||
temp_frame = cv2.imread(temp_frame_path)
|
||||
try:
|
||||
result = process_frame_v2(temp_frame, temp_frame_path)
|
||||
cv2.imwrite(temp_frame_path, result)
|
||||
except Exception as exception:
|
||||
print(exception)
|
||||
pass
|
||||
if progress:
|
||||
progress.update(1)
|
||||
|
||||
|
||||
def process_image(source_path: str, target_path: str, output_path: str) -> None:
|
||||
source_face = get_one_face(cv2.imread(source_path))
|
||||
target_frame = cv2.imread(target_path)
|
||||
result = process_frame(source_face, target_frame)
|
||||
cv2.imwrite(output_path, result)
|
||||
if not modules.globals.map_faces:
|
||||
source_face = get_one_face(cv2.imread(source_path))
|
||||
target_frame = cv2.imread(target_path)
|
||||
result = process_frame(source_face, target_frame)
|
||||
cv2.imwrite(output_path, result)
|
||||
else:
|
||||
if modules.globals.many_faces:
|
||||
update_status(
|
||||
"Many faces enabled. Using first source image. Progressing...", NAME
|
||||
)
|
||||
target_frame = cv2.imread(output_path)
|
||||
result = process_frame_v2(target_frame)
|
||||
cv2.imwrite(output_path, result)
|
||||
|
||||
|
||||
def process_video(source_path: str, temp_frame_paths: List[str]) -> None:
|
||||
modules.processors.frame.core.process_video(source_path, temp_frame_paths, process_frames)
|
||||
if modules.globals.map_faces and modules.globals.many_faces:
|
||||
update_status(
|
||||
"Many faces enabled. Using first source image. Progressing...", NAME
|
||||
)
|
||||
modules.processors.frame.core.process_video(
|
||||
source_path, temp_frame_paths, process_frames
|
||||
)
|
||||
|
||||
@@ -12,16 +12,23 @@ from tqdm import tqdm
|
||||
|
||||
import modules.globals
|
||||
|
||||
TEMP_FILE = 'temp.mp4'
|
||||
TEMP_DIRECTORY = 'temp'
|
||||
TEMP_FILE = "temp.mp4"
|
||||
TEMP_DIRECTORY = "temp"
|
||||
|
||||
# monkey patch ssl for mac
|
||||
if platform.system().lower() == 'darwin':
|
||||
if platform.system().lower() == "darwin":
|
||||
ssl._create_default_https_context = ssl._create_unverified_context
|
||||
|
||||
|
||||
def run_ffmpeg(args: List[str]) -> bool:
|
||||
commands = ['ffmpeg', '-hide_banner', '-hwaccel', 'auto', '-loglevel', modules.globals.log_level]
|
||||
commands = [
|
||||
"ffmpeg",
|
||||
"-hide_banner",
|
||||
"-hwaccel",
|
||||
"auto",
|
||||
"-loglevel",
|
||||
modules.globals.log_level,
|
||||
]
|
||||
commands.extend(args)
|
||||
try:
|
||||
subprocess.check_output(commands, stderr=subprocess.STDOUT)
|
||||
@@ -32,8 +39,19 @@ def run_ffmpeg(args: List[str]) -> bool:
|
||||
|
||||
|
||||
def detect_fps(target_path: str) -> float:
|
||||
command = ['ffprobe', '-v', 'error', '-select_streams', 'v:0', '-show_entries', 'stream=r_frame_rate', '-of', 'default=noprint_wrappers=1:nokey=1', target_path]
|
||||
output = subprocess.check_output(command).decode().strip().split('/')
|
||||
command = [
|
||||
"ffprobe",
|
||||
"-v",
|
||||
"error",
|
||||
"-select_streams",
|
||||
"v:0",
|
||||
"-show_entries",
|
||||
"stream=r_frame_rate",
|
||||
"-of",
|
||||
"default=noprint_wrappers=1:nokey=1",
|
||||
target_path,
|
||||
]
|
||||
output = subprocess.check_output(command).decode().strip().split("/")
|
||||
try:
|
||||
numerator, denominator = map(int, output)
|
||||
return numerator / denominator
|
||||
@@ -44,25 +62,65 @@ def detect_fps(target_path: str) -> float:
|
||||
|
||||
def extract_frames(target_path: str) -> None:
|
||||
temp_directory_path = get_temp_directory_path(target_path)
|
||||
run_ffmpeg(['-i', target_path, '-pix_fmt', 'rgb24', os.path.join(temp_directory_path, '%04d.png')])
|
||||
run_ffmpeg(
|
||||
[
|
||||
"-i",
|
||||
target_path,
|
||||
"-pix_fmt",
|
||||
"rgb24",
|
||||
os.path.join(temp_directory_path, "%04d.png"),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def create_video(target_path: str, fps: float = 30.0) -> None:
|
||||
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', '-y', temp_output_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",
|
||||
"-y",
|
||||
temp_output_path,
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def restore_audio(target_path: str, output_path: str) -> None:
|
||||
temp_output_path = get_temp_output_path(target_path)
|
||||
done = run_ffmpeg(['-i', temp_output_path, '-i', target_path, '-c:v', 'copy', '-map', '0:v:0', '-map', '1:a:0', '-y', output_path])
|
||||
done = run_ffmpeg(
|
||||
[
|
||||
"-i",
|
||||
temp_output_path,
|
||||
"-i",
|
||||
target_path,
|
||||
"-c:v",
|
||||
"copy",
|
||||
"-map",
|
||||
"0:v:0",
|
||||
"-map",
|
||||
"1:a:0",
|
||||
"-y",
|
||||
output_path,
|
||||
]
|
||||
)
|
||||
if not done:
|
||||
move_temp(target_path, output_path)
|
||||
|
||||
|
||||
def get_temp_frame_paths(target_path: str) -> List[str]:
|
||||
temp_directory_path = get_temp_directory_path(target_path)
|
||||
return glob.glob((os.path.join(glob.escape(temp_directory_path), '*.png')))
|
||||
return glob.glob((os.path.join(glob.escape(temp_directory_path), "*.png")))
|
||||
|
||||
|
||||
def get_temp_directory_path(target_path: str) -> str:
|
||||
@@ -81,7 +139,9 @@ def normalize_output_path(source_path: str, target_path: str, output_path: str)
|
||||
source_name, _ = os.path.splitext(os.path.basename(source_path))
|
||||
target_name, target_extension = os.path.splitext(os.path.basename(target_path))
|
||||
if os.path.isdir(output_path):
|
||||
return os.path.join(output_path, source_name + '-' + target_name + target_extension)
|
||||
return os.path.join(
|
||||
output_path, source_name + "-" + target_name + target_extension
|
||||
)
|
||||
return output_path
|
||||
|
||||
|
||||
@@ -108,20 +168,20 @@ def clean_temp(target_path: str) -> None:
|
||||
|
||||
|
||||
def has_image_extension(image_path: str) -> bool:
|
||||
return image_path.lower().endswith(('png', 'jpg', 'jpeg'))
|
||||
return image_path.lower().endswith(("png", "jpg", "jpeg"))
|
||||
|
||||
|
||||
def is_image(image_path: str) -> bool:
|
||||
if image_path and os.path.isfile(image_path):
|
||||
mimetype, _ = mimetypes.guess_type(image_path)
|
||||
return bool(mimetype and mimetype.startswith('image/'))
|
||||
return bool(mimetype and mimetype.startswith("image/"))
|
||||
return False
|
||||
|
||||
|
||||
def is_video(video_path: str) -> bool:
|
||||
if video_path and os.path.isfile(video_path):
|
||||
mimetype, _ = mimetypes.guess_type(video_path)
|
||||
return bool(mimetype and mimetype.startswith('video/'))
|
||||
return bool(mimetype and mimetype.startswith("video/"))
|
||||
return False
|
||||
|
||||
|
||||
@@ -129,12 +189,20 @@ def conditional_download(download_directory_path: str, urls: List[str]) -> None:
|
||||
if not os.path.exists(download_directory_path):
|
||||
os.makedirs(download_directory_path)
|
||||
for url in urls:
|
||||
download_file_path = os.path.join(download_directory_path, os.path.basename(url))
|
||||
download_file_path = os.path.join(
|
||||
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))
|
||||
with tqdm(total=total, desc='Downloading', unit='B', 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]
|
||||
request = urllib.request.urlopen(url) # type: ignore[attr-defined]
|
||||
total = int(request.headers.get("Content-Length", 0))
|
||||
with tqdm(
|
||||
total=total,
|
||||
desc="Downloading",
|
||||
unit="B",
|
||||
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]
|
||||
|
||||
|
||||
def resolve_relative_path(path: str) -> str:
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
import cv2
|
||||
import numpy as np
|
||||
from typing import Optional, Tuple, Callable
|
||||
import platform
|
||||
import threading
|
||||
|
||||
# Only import Windows-specific library if on Windows
|
||||
if platform.system() == "Windows":
|
||||
from pygrabber.dshow_graph import FilterGraph
|
||||
|
||||
|
||||
class VideoCapturer:
|
||||
def __init__(self, device_index: int):
|
||||
self.device_index = device_index
|
||||
self.frame_callback = None
|
||||
self._current_frame = None
|
||||
self._frame_ready = threading.Event()
|
||||
self.is_running = False
|
||||
self.cap = None
|
||||
|
||||
# Initialize Windows-specific components if on Windows
|
||||
if platform.system() == "Windows":
|
||||
self.graph = FilterGraph()
|
||||
# Verify device exists
|
||||
devices = self.graph.get_input_devices()
|
||||
if self.device_index >= len(devices):
|
||||
raise ValueError(
|
||||
f"Invalid device index {device_index}. Available devices: {len(devices)}"
|
||||
)
|
||||
|
||||
def start(self, width: int = 960, height: int = 540, fps: int = 60) -> bool:
|
||||
"""Initialize and start video capture"""
|
||||
try:
|
||||
if platform.system() == "Windows":
|
||||
# Windows-specific capture methods
|
||||
capture_methods = [
|
||||
(self.device_index, cv2.CAP_DSHOW), # Try DirectShow first
|
||||
(self.device_index, cv2.CAP_ANY), # Then try default backend
|
||||
(-1, cv2.CAP_ANY), # Try -1 as fallback
|
||||
(0, cv2.CAP_ANY), # Finally try 0 without specific backend
|
||||
]
|
||||
|
||||
for dev_id, backend in capture_methods:
|
||||
try:
|
||||
self.cap = cv2.VideoCapture(dev_id, backend)
|
||||
if self.cap.isOpened():
|
||||
break
|
||||
self.cap.release()
|
||||
except Exception:
|
||||
continue
|
||||
else:
|
||||
# Unix-like systems (Linux/Mac) capture method
|
||||
self.cap = cv2.VideoCapture(self.device_index)
|
||||
|
||||
if not self.cap or not self.cap.isOpened():
|
||||
raise RuntimeError("Failed to open camera")
|
||||
|
||||
# Configure format
|
||||
self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, width)
|
||||
self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, height)
|
||||
self.cap.set(cv2.CAP_PROP_FPS, fps)
|
||||
|
||||
self.is_running = True
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"Failed to start capture: {str(e)}")
|
||||
if self.cap:
|
||||
self.cap.release()
|
||||
return False
|
||||
|
||||
def read(self) -> Tuple[bool, Optional[np.ndarray]]:
|
||||
"""Read a frame from the camera"""
|
||||
if not self.is_running or self.cap is None:
|
||||
return False, None
|
||||
|
||||
ret, frame = self.cap.read()
|
||||
if ret:
|
||||
self._current_frame = frame
|
||||
if self.frame_callback:
|
||||
self.frame_callback(frame)
|
||||
return True, frame
|
||||
return False, None
|
||||
|
||||
def release(self) -> None:
|
||||
"""Stop capture and release resources"""
|
||||
if self.is_running and self.cap is not None:
|
||||
self.cap.release()
|
||||
self.is_running = False
|
||||
self.cap = None
|
||||
|
||||
def set_frame_callback(self, callback: Callable[[np.ndarray], None]) -> None:
|
||||
"""Set callback for frame processing"""
|
||||
self.frame_callback = callback
|
||||
@@ -1,23 +1,24 @@
|
||||
--extra-index-url https://download.pytorch.org/whl/cu118
|
||||
|
||||
numpy==1.23.5
|
||||
opencv-python==4.8.1.78
|
||||
numpy>=1.23.5,<2
|
||||
opencv-python==4.10.0.84
|
||||
cv2_enumerate_cameras==1.1.15
|
||||
onnx==1.16.0
|
||||
insightface==0.7.3
|
||||
psutil==5.9.8
|
||||
tk==0.1.0
|
||||
customtkinter==5.2.2
|
||||
pillow==9.5.0
|
||||
pillow==11.1.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'
|
||||
onnxruntime==1.18.0; sys_platform == 'darwin' and platform_machine != 'arm64'
|
||||
onnxruntime-silicon==1.16.3; sys_platform == 'darwin' and platform_machine == 'arm64'
|
||||
onnxruntime-gpu==1.18.0; sys_platform != 'darwin'
|
||||
tensorflow==2.13.0rc1; sys_platform == 'darwin'
|
||||
onnxruntime-gpu==1.16.3; sys_platform != 'darwin'
|
||||
tensorflow==2.12.1; 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
|
||||
|
||||
@@ -1 +1 @@
|
||||
python run.py --execution-provider cuda --execution-threads 60 --max-memory 60
|
||||
python run.py --execution-provider cuda
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
python run.py --execution-provider dml
|
||||
@@ -1 +0,0 @@
|
||||
python run.py --execution-provider dml
|
||||
@@ -1,13 +0,0 @@
|
||||
@echo off
|
||||
:: Installing Microsoft Visual C++ Runtime - all versions 1.0.1 if it's not already installed
|
||||
choco install vcredist-all
|
||||
:: Installing CUDA if it's not already installed
|
||||
choco install cuda
|
||||
:: Inatalling ffmpeg if it's not already installed
|
||||
choco install ffmpeg
|
||||
:: Installing Python if it's not already installed
|
||||
choco install python -y
|
||||
:: Assuming successful installation, we ensure pip is upgraded
|
||||
python -m ensurepip --upgrade
|
||||
:: Use pip to install the packages listed in 'requirements.txt'
|
||||
pip install -r requirements.txt
|
||||
@@ -1,122 +0,0 @@
|
||||
@echo off
|
||||
setlocal EnableDelayedExpansion
|
||||
|
||||
:: 1. Setup your platform
|
||||
echo Setting up your platform...
|
||||
|
||||
:: Python
|
||||
where python >nul 2>&1
|
||||
if %ERRORLEVEL% neq 0 (
|
||||
echo Python is not installed. Please install Python 3.10 or later.
|
||||
pause
|
||||
exit /b
|
||||
)
|
||||
|
||||
:: Pip
|
||||
where pip >nul 2>&1
|
||||
if %ERRORLEVEL% neq 0 (
|
||||
echo Pip is not installed. Please install Pip.
|
||||
pause
|
||||
exit /b
|
||||
)
|
||||
|
||||
:: Git
|
||||
where git >nul 2>&1
|
||||
if %ERRORLEVEL% neq 0 (
|
||||
echo Git is not installed. Installing Git...
|
||||
winget install --id Git.Git -e --source winget
|
||||
)
|
||||
|
||||
:: FFMPEG
|
||||
where ffmpeg >nul 2>&1
|
||||
if %ERRORLEVEL% neq 0 (
|
||||
echo FFMPEG is not installed. Installing FFMPEG...
|
||||
winget install --id Gyan.FFmpeg -e --source winget
|
||||
)
|
||||
|
||||
:: Visual Studio 2022 Runtimes
|
||||
echo Installing Visual Studio 2022 Runtimes...
|
||||
winget install --id Microsoft.VC++2015-2022Redist-x64 -e --source winget
|
||||
|
||||
:: 2. Clone Repository
|
||||
if exist Deep-Live-Cam (
|
||||
echo Deep-Live-Cam directory already exists.
|
||||
set /p overwrite="Do you want to overwrite? (Y/N): "
|
||||
if /i "%overwrite%"=="Y" (
|
||||
rmdir /s /q Deep-Live-Cam
|
||||
git clone https://github.com/hacksider/Deep-Live-Cam.git
|
||||
) else (
|
||||
echo Skipping clone, using existing directory.
|
||||
)
|
||||
) else (
|
||||
git clone https://github.com/hacksider/Deep-Live-Cam.git
|
||||
)
|
||||
cd Deep-Live-Cam
|
||||
|
||||
:: 3. Download Models
|
||||
echo Downloading models...
|
||||
mkdir models
|
||||
curl -L -o models/GFPGANv1.4.pth https://path.to.model/GFPGANv1.4.pth
|
||||
curl -L -o models/inswapper_128_fp16.onnx https://path.to.model/inswapper_128_fp16.onnx
|
||||
|
||||
:: 4. Install dependencies
|
||||
echo Creating a virtual environment...
|
||||
python -m venv venv
|
||||
call venv\Scripts\activate
|
||||
|
||||
echo Installing required Python packages...
|
||||
pip install --upgrade pip
|
||||
pip install -r requirements.txt
|
||||
|
||||
echo Setup complete. You can now run the application.
|
||||
|
||||
:: GPU Acceleration Options
|
||||
echo.
|
||||
echo Choose the GPU Acceleration Option if applicable:
|
||||
echo 1. CUDA (Nvidia)
|
||||
echo 2. CoreML (Apple Silicon)
|
||||
echo 3. CoreML (Apple Legacy)
|
||||
echo 4. DirectML (Windows)
|
||||
echo 5. OpenVINO (Intel)
|
||||
echo 6. None
|
||||
set /p choice="Enter your choice (1-6): "
|
||||
|
||||
if "%choice%"=="1" (
|
||||
echo Installing CUDA dependencies...
|
||||
pip uninstall -y onnxruntime onnxruntime-gpu
|
||||
pip install onnxruntime-gpu==1.16.3
|
||||
set exec_provider="cuda"
|
||||
) else if "%choice%"=="2" (
|
||||
echo Installing CoreML (Apple Silicon) dependencies...
|
||||
pip uninstall -y onnxruntime onnxruntime-silicon
|
||||
pip install onnxruntime-silicon==1.13.1
|
||||
set exec_provider="coreml"
|
||||
) else if "%choice%"=="3" (
|
||||
echo Installing CoreML (Apple Legacy) dependencies...
|
||||
pip uninstall -y onnxruntime onnxruntime-coreml
|
||||
pip install onnxruntime-coreml==1.13.1
|
||||
set exec_provider="coreml"
|
||||
) else if "%choice%"=="4" (
|
||||
echo Installing DirectML dependencies...
|
||||
pip uninstall -y onnxruntime onnxruntime-directml
|
||||
pip install onnxruntime-directml==1.15.1
|
||||
set exec_provider="directml"
|
||||
) else if "%choice%"=="5" (
|
||||
echo Installing OpenVINO dependencies...
|
||||
pip uninstall -y onnxruntime onnxruntime-openvino
|
||||
pip install onnxruntime-openvino==1.15.0
|
||||
set exec_provider="openvino"
|
||||
) else (
|
||||
echo Skipping GPU acceleration setup.
|
||||
)
|
||||
|
||||
:: Run the application
|
||||
if defined exec_provider (
|
||||
echo Running the application with %exec_provider% execution provider...
|
||||
python run.py --execution-provider %exec_provider%
|
||||
) else (
|
||||
echo Running the application...
|
||||
python run.py
|
||||
)
|
||||
|
||||
pause
|
||||