Compare commits

..

46 Commits

Author SHA1 Message Date
KRSHH 51b38fe253 Revert "Slight Performance Improvement"
This reverts commit 9a472e2435.
2024-11-07 14:58:03 +05:30
KRSHH 97a76c5e5b Revert "Update ui.py"
This reverts commit df72a5431b.
2024-11-07 14:57:47 +05:30
KRSHH df72a5431b Update ui.py 2024-10-29 14:53:19 +05:30
KRSHH 9a472e2435 Slight Performance Improvement
avg 9.5 to avg 10
2024-10-29 14:07:47 +05:30
KRSHH a581b81bd9 Change Switch/Slider positions 2024-10-28 16:53:55 +05:30
KRSHH 69b7970b87 Improved Camera Selection Menu 2024-10-28 13:50:44 +05:30
KRSHH 7fce1fd8b4 Update instructions.txt
Links to Models in Instructions
2024-10-28 13:15:13 +05:30
KRSHH c2918a52df Minimum Windows Size 2024-10-28 13:12:02 +05:30
KRSHH 64c0f085b1 Change Switch positions 2024-10-28 12:20:54 +05:30
KRSHH 3c30ab8a5d Opacity 2024-10-28 11:45:50 +05:30
KRSHH acc8e778b6 Mouth Mask And UI 2024-10-28 10:56:01 +05:30
KRSHH 4b6f60f59b Delete files from main 2024-10-21 19:15:21 +05:30
KRSHH 1178232268 Upload images to folder 2024-10-21 19:12:46 +05:30
KRSHH ad918c5523 Shift Media to a folder 2024-10-21 19:10:21 +05:30
KRSHH 7b8d6171b7 Frame by Frame Navigation 2024-10-19 14:04:05 +05:30
KRSHH 6b86b0a72d Preview Window Slider 2024-10-19 13:58:55 +05:30
KRSHH 936e78f93b Fix Mapper for Live 2024-10-17 13:06:00 +05:30
KRSHH aca0bcb7ce Formatting Fix 2024-10-14 21:48:57 +05:30
KRSHH 966cb5a7df Removed Package Repetition 2024-10-14 21:46:22 +05:30
KRSHH 1ab4bf06b1 Tips for DLC 2024-10-14 21:26:34 +05:30
KRSHH 6649e2a5df remember/save switch states 2024-10-14 20:32:17 +05:30
KRSHH 72d4c9e9c4 Update metadata.py - 1.6 2024-10-14 19:50:09 +05:30
KRSHH 76d65247b7 improved performance enhancement 2024-10-14 19:49:51 +05:30
KRSHH 37f224cb47 FPS Counter 2024-10-14 19:46:48 +05:30
KRSHH b58ffffd37 Fix the button position and bugs 2024-10-14 19:11:44 +05:30
KRSHH 75decc5838 Hotswap Source image - switch faces without closing live 2024-10-04 18:17:22 +05:30
Kenneth Estanislao f38ebb485a Update ui.py
removed opacity, will work on it later to optimize
2024-10-04 15:39:08 +08:00
Kenneth Estanislao 95742c8fd5 Merge pull request #686 from GhoulBoii/main
BOUNTY - Webcam Merged (tested)
2024-10-04 14:46:06 +08:00
Kenneth Estanislao 60e27f4755 Revert "Merge pull request #685 from KRSHH/main"
This reverts commit d4e5b8078d, reversing
changes made to c08bec22e3.
2024-10-03 14:51:38 +08:00
KRSHH 3d741bd269 Update README.md 2024-10-02 18:38:37 +05:30
KRSHH d4e5b8078d Merge pull request #685 from KRSHH/main
Live faceswap opacity slider
2024-10-02 15:24:53 +05:30
KRSHH 61b51fc5d4 Move the slider from live to root 2024-10-02 14:37:19 +05:30
KRSHH f19e425143 Update ui.py 2024-10-02 14:20:56 +05:30
KRSHH 7d6bdad086 Default opacity global 2024-10-02 13:24:23 +05:30
KRSHH 12c0a7ac86 Faceswap live opacity slider 2024-10-02 13:23:39 +05:30
Kenneth Estanislao c08bec22e3 Update issue templates 2024-10-01 14:26:03 +08:00
KRSHH bdd7c593e1 Update README.md 2024-09-28 20:53:29 +05:30
KRSHH 6e618baf34 Update README.md 2024-09-28 17:22:21 +05:30
KRSHH 0edcaae713 Merge pull request #650 from KRSHH/main
Preview video Frame by Frame using left and right arrow keys
2024-09-28 12:37:40 +05:30
KRSHH dff6cec2f9 Comment Indention fix 2024-09-27 20:59:48 +05:30
KRSHH 4d1d2c86af Preview video Frame by Frame using left and right arrow keys 2024-09-27 20:40:32 +05:30
KRSHH e00c398825 Merge branch 'hacksider:main' into main 2024-09-27 20:20:22 +05:30
KRSHH 0e481609ea Update README.md 2024-09-27 19:59:13 +05:30
KRSHH 683481804c Delete outdated docs directory 2024-09-26 11:03:46 +05:30
KRSHH 5845b9c480 Update README.md 2024-09-26 00:57:11 +05:30
Kenneth Estanislao 71cf39fd98 Update metadata.py
changes of version from 1.4 to 1.5 (UI modified)
2024-09-26 00:57:45 +08:00
22 changed files with 1102 additions and 207 deletions
+38
View File
@@ -0,0 +1,38 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.
+65 -41
View File
@@ -1,10 +1,13 @@
<h1 align="center">Deep-Live-Cam</h1>
![demo-gif](demo.gif)
![demo-gif](avgpcperformancedemo.gif)
<p align="center">
Real-time face swap and video deepfake with a single click and only a single image.
</p>
## Deep Live Cam
Real-time face swap and video deepfake with a single click and only a single image.
<p align="center">
<img src="media/demo.gif" alt="Demo GIF">
<img src="media/avgpcperformancedemo.gif" alt="Performance Demo GIF">
</p>
## Disclaimer
@@ -15,40 +18,14 @@ We are aware of the potential for unethical applications and are committed to pr
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.
## Features
### Resizable Preview Window
Dynamically improve performance using the `--live-resizable` parameter.
![resizable-gif](resizable.gif)
### Face Mapping
Track and change faces on the fly.
![face_mapping_source](face_mapping_source.gif)
**Source Video:**
![face-mapping](face_mapping.png)
**Enable Face Mapping:**
![face-mapping2](face_mapping2.png)
**Map the Faces:**
![face_mapping_result](face_mapping_result.gif)
**See the Magic!**
## Quick Start (Windows / Nvidia)
[Download pre-built version with CUDA support](https://hacksider.gumroad.com/l/vccdmm)
[![Download](media/download.png)](https://hacksider.gumroad.com/l/vccdmm)
[Download latest pre-built version with CUDA support](https://hacksider.gumroad.com/l/vccdmm) - No Manual Installation/Downloading required.
## Installation (Manual)
**Please be aware that the installation needs technical skills and is NOT for beginners, consider downloading the prebuilt. Please do NOT open platform and installation related issues on GitHub before discussing it on the discord server.**
### Basic Installation (CPU)
This is more likely to work on your computer but will be slower as it utilizes the CPU.
@@ -178,7 +155,43 @@ python run.py --execution-provider openvino
- Use a screen capture tool like OBS to stream.
- To change the face, select a new source image.
![demo-gif](demo.gif)
![demo-gif](media/demo.gif)
## Features
### Resizable Preview Window
Dynamically improve performance using the `--live-resizable` parameter.
![resizable-gif](media/resizable.gif)
### Face Mapping
Track and change faces on the fly.
![face_mapping_source](media/face_mapping_source.gif)
**Source Video:**
![face-mapping](media/face_mapping.png)
**Enable Face Mapping:**
![face-mapping2](media/face_mapping2.png)
**Map the Faces:**
![face_mapping_result](media/face_mapping_result.gif)
**See the Magic!**
![movie](media/movie.gif)
**Watch movies in realtime:**
It's as simple as opening a movie on the screen, and selecting OBS as your camera!
![image](media/movie_img.png)
## Command Line Arguments
@@ -361,14 +374,18 @@ For the latest experimental builds and features, see the [experimental branch](h
**TODO:**
- [x] Support multiple faces
- [ ] Develop a version for web app/service
- [ ] UI/UX enhancements for desktop app
- [ ] Speed up model loading
- [ ] Speed up real-time face swapping
- [x] Support multiple faces
- [x] UI/UX enhancements for desktop app
This is an open-source project developed in our free time. Updates may be delayed.
**Tips and Links:**
- [How to make the most of Deep-Live-Cam](https://hacksider.gumroad.com/p/how-to-make-the-most-on-deep-live-cam)
- Face enhancer is good, but still very slow for any live streaming purpose.
## Credits
@@ -382,7 +399,14 @@ This is an open-source project developed in our free time. Updates may be delaye
- and [all developers](https://github.com/hacksider/Deep-Live-Cam/graphs/contributors) behind libraries used in this project.
- Foot Note: [This is originally roop-cam, see the full history of the code here.](https://github.com/hacksider/roop-cam) Please be informed that the base author of the code is [s0md3v](https://github.com/s0md3v/roop)
## Thanks to all the contributors
<a href="https://github.com/hacksider/Deep-Live-Cam/graphs/contributors" target="_blank">
<img src="https://contrib.rocks/image?repo=hacksider/Deep-Live-Cam" />
## Contributions
![Alt](https://repobeats.axiom.co/api/embed/fec8e29c45dfdb9c5916f3a7830e1249308d20e1.svg "Repobeats analytics image")
## Star History
<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>
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 MiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

Before

Width:  |  Height:  |  Size: 5.2 MiB

After

Width:  |  Height:  |  Size: 5.2 MiB

View File

Before

Width:  |  Height:  |  Size: 11 MiB

After

Width:  |  Height:  |  Size: 11 MiB

View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 104 KiB

Before

Width:  |  Height:  |  Size: 4.0 MiB

After

Width:  |  Height:  |  Size: 4.0 MiB

Before

Width:  |  Height:  |  Size: 8.6 MiB

After

Width:  |  Height:  |  Size: 8.6 MiB

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 73 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 794 KiB

Before

Width:  |  Height:  |  Size: 4.3 MiB

After

Width:  |  Height:  |  Size: 4.3 MiB

+4 -1
View File
@@ -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
+24 -15
View File
@@ -2,11 +2,11 @@ import os
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 = []
@@ -16,22 +16,31 @@ 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
map_faces = None
color_correction = None # New global variable for color correction toggle
nsfw_filter = None
keep_fps = True # Initialize with default value
keep_audio = True # Initialize with default value
keep_frames = False # Initialize with default value
many_faces = False # Initialize with default value
map_faces = False # Initialize with default value
color_correction = False # Initialize with default value
nsfw_filter = False # Initialize with default value
video_encoder = None
video_quality = None
live_mirror = None
live_resizable = None
live_mirror = False # Initialize with default value
live_resizable = False # Initialize with default value
max_memory = None
execution_providers: List[str] = []
execution_threads = None
headless = None
log_level = 'error'
fp_ui: Dict[str, bool] = {}
log_level = "error"
fp_ui: Dict[str, bool] = {"face_enhancer": False} # Initialize with default value
camera_input_combobox = None
webcam_preview_running = False
webcam_preview_running = False
show_fps = False # Initialize with default value
mouth_mask = False
show_mouth_mask_box = False
mask_down_size = 0.5
mask_size = 1.0
mask_feather_ratio = 8
opacity_switch = False
face_opacity = 100
selected_camera = None
+1 -1
View File
@@ -1,3 +1,3 @@
name = 'Deep Live Cam'
version = '1.4.0'
version = '1.6.0'
edition = 'Portable'
+352 -33
View File
@@ -2,35 +2,49 @@ 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, 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,
resolve_relative_path,
is_image,
is_video,
)
from modules.cluster_analysis import find_closest_centroid
FACE_SWAPPER = None
THREAD_LOCK = threading.Lock()
NAME = 'DLC.FACE-SWAPPER'
NAME = "DLC.FACE-SWAPPER"
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.onnx'])
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"
],
)
return True
def pre_start() -> bool:
if not modules.globals.map_faces and not is_image(modules.globals.source_path):
update_status('Select an image for source path.', NAME)
update_status("Select an image for source path.", NAME)
return False
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)
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
@@ -40,20 +54,49 @@ def get_face_swapper() -> Any:
with THREAD_LOCK:
if FACE_SWAPPER is None:
model_path = resolve_relative_path('../models/inswapper_128.onnx')
FACE_SWAPPER = insightface.model_zoo.get_model(model_path, providers=modules.globals.execution_providers)
model_path = resolve_relative_path("../models/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)
swapped_frame = get_face_swapper().get(
temp_frame, target_face, source_face, paste_back=True
)
# Apply opacity if enabled
if modules.globals.opacity_switch:
opacity = modules.globals.face_opacity / 100
swapped_frame = cv2.addWeighted(
swapped_frame, opacity, temp_frame, 1 - opacity, 0
)
# Apply mouth mask if enabled
if modules.globals.mouth_mask:
face_mask = create_face_mask(target_face, temp_frame)
mouth_mask_data = create_lower_mouth_mask(target_face, temp_frame)
mouth_mask, mouth_cutout, mouth_box, lower_lip_polygon = mouth_mask_data
if mouth_box is not None:
swapped_frame = apply_mouth_area(
swapped_frame, mouth_cutout, mouth_box, face_mask, lower_lip_polygon
)
if modules.globals.show_mouth_mask_box:
swapped_frame = draw_mouth_mask_visualization(
swapped_frame, target_face, mouth_mask_data
)
return swapped_frame
def process_frame(source_face: Face, temp_frame: Frame) -> Frame:
# Ensure the frame is in RGB format if color correction is enabled
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:
@@ -71,34 +114,42 @@ def process_frame_v2(temp_frame: Frame, temp_frame_path: str = "") -> Frame:
if modules.globals.many_faces:
source_face = default_source_face()
for map in modules.globals.souce_target_map:
target_face = map['target']['face']
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']
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]
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']:
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']
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']:
for target_face in frame["faces"]:
temp_frame = swap_face(source_face, target_face, temp_frame)
else:
detected_faces = get_many_faces(temp_frame)
@@ -110,25 +161,46 @@ def process_frame_v2(temp_frame: Frame, temp_frame_path: str = "") -> Frame:
elif not modules.globals.many_faces:
if detected_faces:
if len(detected_faces) <= len(modules.globals.simple_map['target_embeddings']):
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)
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)
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)
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)
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)
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:
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:
@@ -162,7 +234,9 @@ def process_image(source_path: str, target_path: str, output_path: str) -> None:
cv2.imwrite(output_path, result)
else:
if modules.globals.many_faces:
update_status('Many faces enabled. Using first source image. Progressing...', NAME)
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)
@@ -170,5 +244,250 @@ 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:
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)
update_status(
"Many faces enabled. Using first source image. Progressing...", NAME
)
modules.processors.frame.core.process_video(
source_path, temp_frame_paths, process_frames
)
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:
landmarks = landmarks.astype(np.int32)
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]
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)
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
face_outline = np.vstack(
[
[forehead_left],
right_side_face,
left_side_face[::-1],
[forehead_right],
]
)
padding = int(np.linalg.norm(right_side_face[0] - left_side_face[-1]) * 0.05)
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)
cv2.fillConvexPoly(mask, hull_padded, 255)
mask = cv2.GaussianBlur(mask, (5, 5), 3)
return mask
def create_lower_mouth_mask(face: Face, frame: Frame) -> tuple:
mask = np.zeros(frame.shape[:2], dtype=np.uint8)
mouth_cutout = None
landmarks = face.landmark_2d_106
if landmarks is not None:
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)
center = np.mean(lower_lip_landmarks, axis=0)
expansion_factor = 1 + modules.globals.mask_down_size
expanded_landmarks = (lower_lip_landmarks - center) * expansion_factor + center
toplip_indices = [20, 0, 1, 2, 3, 4, 5]
toplip_extension = modules.globals.mask_size * 0.5
for idx in toplip_indices:
direction = expanded_landmarks[idx] - center
direction = direction / np.linalg.norm(direction)
expanded_landmarks[idx] += direction * toplip_extension
chin_indices = [11, 12, 13, 14, 15, 16]
chin_extension = 2 * 0.2
for idx in chin_indices:
expanded_landmarks[idx][1] += (
expanded_landmarks[idx][1] - center[1]
) * chin_extension
expanded_landmarks = expanded_landmarks.astype(np.int32)
min_x, min_y = np.min(expanded_landmarks, axis=0)
max_x, max_y = np.max(expanded_landmarks, axis=0)
padding = int((max_x - min_x) * 0.1)
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)
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
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)
mask_roi = cv2.GaussianBlur(mask_roi, (15, 15), 5)
mask[min_y:max_y, min_x:max_x] = mask_roi
mouth_cutout = frame[min_y:max_y, min_x:max_x].copy()
return mask, mouth_cutout, (min_x, min_y, max_x, max_y), expanded_landmarks
return mask, mouth_cutout, None, None
def apply_mouth_area(
frame: Frame,
mouth_cutout: np.ndarray,
mouth_box: tuple,
face_mask: np.ndarray,
mouth_polygon: np.ndarray,
) -> Frame:
min_x, min_y, max_x, max_y = mouth_box
box_width = max_x - min_x
box_height = max_y - min_y
if (
mouth_cutout is None
or box_width is None
or box_height is None
or face_mask is None
or mouth_polygon is None
):
return frame
try:
resized_mouth_cutout = cv2.resize(mouth_cutout, (box_width, box_height))
roi = frame[min_y:max_y, min_x:max_x]
if roi.shape != resized_mouth_cutout.shape:
resized_mouth_cutout = cv2.resize(
resized_mouth_cutout, (roi.shape[1], roi.shape[0])
)
color_corrected_mouth = apply_color_transfer(resized_mouth_cutout, roi)
polygon_mask = np.zeros(roi.shape[:2], dtype=np.uint8)
adjusted_polygon = mouth_polygon - [min_x, min_y]
cv2.fillPoly(polygon_mask, [adjusted_polygon], 255)
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()
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_mouth * combined_mask + roi * (1 - combined_mask)
).astype(np.uint8)
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 apply_color_transfer(source: np.ndarray, target: np.ndarray) -> np.ndarray:
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)
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)
source = (source - source_mean) * (target_std / source_std) + target_mean
return cv2.cvtColor(np.clip(source, 0, 255).astype("uint8"), cv2.COLOR_LAB2BGR)
def draw_mouth_mask_visualization(
frame: Frame, face: Face, mouth_mask_data: tuple
) -> Frame:
landmarks = face.landmark_2d_106
if landmarks is not None and mouth_mask_data is not None:
mask, mouth_cutout, (min_x, min_y, max_x, max_y), lower_lip_polygon = (
mouth_mask_data
)
vis_frame = frame.copy()
# Draw the lower lip polygon
cv2.polylines(vis_frame, [lower_lip_polygon], True, (0, 255, 0), 2)
# Add labels
cv2.putText(
vis_frame,
"Mouth Mask",
(min_x, min_y - 10),
cv2.FONT_HERSHEY_SIMPLEX,
0.5,
(255, 255, 255),
1,
)
return vis_frame
return frame
+617 -115
View File
@@ -3,8 +3,11 @@ import webbrowser
import customtkinter as ctk
from typing import Callable, Tuple
import cv2
from cv2_enumerate_cameras import enumerate_cameras
from PIL import Image, ImageOps
import tkinterdnd2 as tkdnd
import time
import json
import modules.globals
import modules.metadata
@@ -199,8 +202,193 @@ class TargetLabel(DragDropLabel):
target_label.configure(text="")
class ModernOptionMenu(ctk.CTkFrame):
def __init__(self, master, values, command=None, **kwargs):
super().__init__(master, fg_color="transparent")
self.values = values
self.command = command
# Set initial value based on saved camera or first available
self.current_value = (
modules.globals.selected_camera
if modules.globals.selected_camera in values
else (values[0] if values else "No cameras found")
)
# Main button
self.main_button = ctk.CTkButton(
self,
text=self.current_value,
command=self.show_dropdown,
width=300,
height=40,
corner_radius=8,
fg_color="#1f538d",
hover_color="#1a4572",
text_color="white",
font=("Roboto", 13, "bold"),
border_width=2,
border_color="#3d7ab8",
)
self.main_button.pack(expand=True, fill="both")
# Dropdown frame (initially hidden)
self.dropdown_frame = None
self.is_dropdown_visible = False
self.click_binding = None
def show_dropdown(self):
if self.is_dropdown_visible:
self.hide_dropdown()
return
# Calculate position and size
button_width = self.main_button.winfo_width()
dropdown_height = min(len(self.values) * 35, 200) # Limit max height
# Create and show dropdown with fixed size
self.dropdown_frame = ctk.CTkFrame(
self.winfo_toplevel(),
width=button_width,
height=dropdown_height,
fg_color="#1f538d",
corner_radius=8,
border_width=2,
border_color="#3d7ab8",
)
# Get the absolute coordinates of the button relative to the screen
button_x = self.winfo_rootx()
button_y = self.winfo_rooty()
# Position the dropdown above the button, using relative coordinates
relative_x = button_x - self.winfo_toplevel().winfo_rootx()
relative_y = button_y - self.winfo_toplevel().winfo_rooty() - dropdown_height
self.dropdown_frame.place(in_=self.winfo_toplevel(), x=relative_x, y=relative_y)
# Prevent frame from resizing
self.dropdown_frame.pack_propagate(False)
# Create scrollable frame if needed
if len(self.values) * 35 > 200:
scrollable_frame = ctk.CTkScrollableFrame(
self.dropdown_frame,
width=button_width - 20,
height=dropdown_height - 10,
fg_color="#1f538d",
scrollbar_button_color="#3d7ab8",
scrollbar_button_hover_color="#2b5d8b",
)
scrollable_frame.pack(expand=True, fill="both", padx=5, pady=5)
container = scrollable_frame
else:
container = self.dropdown_frame
# Add options
for value in self.values:
option_button = ctk.CTkButton(
container,
text=value,
fg_color="transparent",
hover_color="#233d54",
text_color="white",
height=35,
corner_radius=4,
font=("Roboto", 13),
command=lambda v=value: self.select_value(v),
)
option_button.pack(padx=2, pady=1, fill="x")
self.is_dropdown_visible = True
self.click_binding = self.winfo_toplevel().bind(
"<Button-1>", self.on_click_outside, add="+"
)
def on_click_outside(self, event):
if self.is_dropdown_visible:
widget_under_cursor = event.widget.winfo_containing(
event.x_root, event.y_root
)
if widget_under_cursor not in [self.main_button] + (
self.dropdown_frame.winfo_children() if self.dropdown_frame else []
):
self.hide_dropdown()
def hide_dropdown(self):
if self.dropdown_frame:
if self.click_binding:
self.winfo_toplevel().unbind("<Button-1>", self.click_binding)
self.click_binding = None
self.dropdown_frame.destroy()
self.dropdown_frame = None
self.is_dropdown_visible = False
def select_value(self, value):
self.current_value = value
self.main_button.configure(text=value)
self.hide_dropdown()
if self.command:
self.command(value)
def get(self):
return self.current_value
def save_switch_states():
switch_states = {
"keep_fps": modules.globals.keep_fps,
"keep_audio": modules.globals.keep_audio,
"keep_frames": modules.globals.keep_frames,
"many_faces": modules.globals.many_faces,
"map_faces": modules.globals.map_faces,
"color_correction": modules.globals.color_correction,
"nsfw_filter": modules.globals.nsfw_filter,
"live_mirror": modules.globals.live_mirror,
"live_resizable": modules.globals.live_resizable,
"fp_ui": modules.globals.fp_ui,
"show_fps": modules.globals.show_fps,
"mouth_mask": modules.globals.mouth_mask,
"show_mouth_mask_box": modules.globals.show_mouth_mask_box,
"mask_down_size": modules.globals.mask_down_size,
"mask_feather_ratio": modules.globals.mask_feather_ratio,
"selected_camera": modules.globals.selected_camera,
}
with open("switch_states.json", "w") as f:
json.dump(switch_states, f)
def load_switch_states():
try:
with open("switch_states.json", "r") as f:
switch_states = json.load(f)
modules.globals.keep_fps = switch_states.get("keep_fps", True)
modules.globals.keep_audio = switch_states.get("keep_audio", True)
modules.globals.keep_frames = switch_states.get("keep_frames", False)
modules.globals.many_faces = switch_states.get("many_faces", False)
modules.globals.map_faces = switch_states.get("map_faces", False)
modules.globals.color_correction = switch_states.get("color_correction", False)
modules.globals.nsfw_filter = switch_states.get("nsfw_filter", False)
modules.globals.live_mirror = switch_states.get("live_mirror", False)
modules.globals.live_resizable = switch_states.get("live_resizable", False)
modules.globals.fp_ui = switch_states.get("fp_ui", {"face_enhancer": False})
modules.globals.show_fps = switch_states.get("show_fps", False)
modules.globals.mouth_mask = switch_states.get("mouth_mask", False)
modules.globals.show_mouth_mask_box = switch_states.get(
"show_mouth_mask_box", False
)
modules.globals.mask_down_size = switch_states.get("mask_down_size", 0.5)
modules.globals.mask_feather_ratio = switch_states.get("mask_feather_ratio", 8)
modules.globals.selected_camera = switch_states.get("selected_camera", None)
except FileNotFoundError:
# If the file doesn't exist, use default values
pass
def init(start: Callable[[], None], destroy: Callable[[], None]) -> tkdnd.TkinterDnD.Tk:
global ROOT, PREVIEW
global ROOT, PREVIEW, donate_frame
ROOT = create_root(start, destroy)
PREVIEW = create_preview(ROOT)
@@ -211,7 +399,9 @@ def init(start: Callable[[], None], destroy: Callable[[], None]) -> tkdnd.Tkinte
def create_root(
start: Callable[[], None], destroy: Callable[[], None]
) -> tkdnd.TkinterDnD.Tk:
global source_label, target_label, status_label
global source_label, target_label, status_label, donate_frame, show_fps_switch
load_switch_states()
ctk.set_appearance_mode("dark")
ctk.set_default_color_theme("blue")
@@ -223,7 +413,8 @@ def create_root(
root.configure(bg="#1a1a1a")
root.protocol("WM_DELETE_WINDOW", lambda: destroy())
root.resizable(True, True)
root.attributes("-alpha", 1.0) # Set window opacity to fully opaque
root.attributes("-alpha", 1.0)
root.minsize(650, 870)
main_frame = ctk.CTkFrame(root, fg_color="#1a1a1a")
main_frame.pack(fill="both", expand=True, padx=20, pady=20)
@@ -286,83 +477,100 @@ def create_root(
options_frame = ctk.CTkFrame(main_frame, fg_color="#2a2d2e", corner_radius=15)
options_frame.grid(row=1, column=0, columnspan=3, padx=10, pady=10, sticky="nsew")
# Create a single column for options, centered
options_column = ctk.CTkFrame(options_frame, fg_color="#2a2d2e")
options_column.pack(expand=True)
# Create two columns for options
left_column = ctk.CTkFrame(options_frame, fg_color="#2a2d2e")
left_column.pack(side="left", padx=20, expand=True)
# Switches
right_column = ctk.CTkFrame(options_frame, fg_color="#2a2d2e")
right_column.pack(side="right", padx=20, expand=True)
# Left column - Video/Processing Options
keep_fps_value = ctk.BooleanVar(value=modules.globals.keep_fps)
keep_fps_checkbox = ctk.CTkSwitch(
options_column,
left_column,
text="Keep fps",
variable=keep_fps_value,
cursor="hand2",
command=lambda: setattr(
modules.globals, "keep_fps", not modules.globals.keep_fps
command=lambda: (
setattr(modules.globals, "keep_fps", keep_fps_value.get()),
save_switch_states(),
),
progress_color="#3a7ebf",
font=("Roboto", 14, "bold"),
)
keep_fps_checkbox.pack(pady=5, anchor="w")
# Move many faces switch to left column
many_faces_value = ctk.BooleanVar(value=modules.globals.many_faces)
many_faces_switch = ctk.CTkSwitch(
left_column,
text="Many faces",
variable=many_faces_value,
cursor="hand2",
command=lambda: (
setattr(modules.globals, "many_faces", many_faces_value.get()),
save_switch_states(),
),
progress_color="#3a7ebf",
font=("Roboto", 14, "bold"),
)
many_faces_switch.pack(pady=5, anchor="w")
keep_audio_value = ctk.BooleanVar(value=modules.globals.keep_audio)
keep_audio_switch = ctk.CTkSwitch(
left_column,
text="Keep audio",
variable=keep_audio_value,
cursor="hand2",
command=lambda: (
setattr(modules.globals, "keep_audio", keep_audio_value.get()),
save_switch_states(),
),
progress_color="#3a7ebf",
font=("Roboto", 14, "bold"),
)
keep_audio_switch.pack(pady=5, anchor="w")
keep_frames_value = ctk.BooleanVar(value=modules.globals.keep_frames)
keep_frames_switch = ctk.CTkSwitch(
options_column,
left_column,
text="Keep frames",
variable=keep_frames_value,
cursor="hand2",
command=lambda: setattr(
modules.globals, "keep_frames", keep_frames_value.get()
command=lambda: (
setattr(modules.globals, "keep_frames", keep_frames_value.get()),
save_switch_states(),
),
progress_color="#3a7ebf",
font=("Roboto", 14, "bold"),
)
keep_frames_switch.pack(pady=5, anchor="w")
# Face Processing Options
enhancer_value = ctk.BooleanVar(value=modules.globals.fp_ui["face_enhancer"])
enhancer_switch = ctk.CTkSwitch(
options_column,
left_column,
text="Face Enhancer",
variable=enhancer_value,
cursor="hand2",
command=lambda: update_tumbler("face_enhancer", enhancer_value.get()),
command=lambda: (
update_tumbler("face_enhancer", enhancer_value.get()),
save_switch_states(),
),
progress_color="#3a7ebf",
font=("Roboto", 14, "bold"),
)
enhancer_switch.pack(pady=5, anchor="w")
keep_audio_value = ctk.BooleanVar(value=modules.globals.keep_audio)
keep_audio_switch = ctk.CTkSwitch(
options_column,
text="Keep audio",
variable=keep_audio_value,
cursor="hand2",
command=lambda: setattr(modules.globals, "keep_audio", keep_audio_value.get()),
progress_color="#3a7ebf",
font=("Roboto", 14, "bold"),
)
keep_audio_switch.pack(pady=5, anchor="w")
many_faces_value = ctk.BooleanVar(value=modules.globals.many_faces)
many_faces_switch = ctk.CTkSwitch(
options_column,
text="Many faces",
variable=many_faces_value,
cursor="hand2",
command=lambda: setattr(modules.globals, "many_faces", many_faces_value.get()),
progress_color="#3a7ebf",
font=("Roboto", 14, "bold"),
)
many_faces_switch.pack(pady=5, anchor="w")
color_correction_value = ctk.BooleanVar(value=modules.globals.color_correction)
color_correction_switch = ctk.CTkSwitch(
options_column,
left_column,
text="Fix Blueish Cam",
variable=color_correction_value,
cursor="hand2",
command=lambda: setattr(
modules.globals, "color_correction", color_correction_value.get()
command=lambda: (
setattr(modules.globals, "color_correction", color_correction_value.get()),
save_switch_states(),
),
progress_color="#3a7ebf",
font=("Roboto", 14, "bold"),
@@ -371,16 +579,137 @@ def create_root(
map_faces = ctk.BooleanVar(value=modules.globals.map_faces)
map_faces_switch = ctk.CTkSwitch(
options_column,
left_column,
text="Map faces",
variable=map_faces,
cursor="hand2",
command=lambda: setattr(modules.globals, "map_faces", map_faces.get()),
command=lambda: (
setattr(modules.globals, "map_faces", map_faces.get()),
save_switch_states(),
),
progress_color="#3a7ebf",
font=("Roboto", 14, "bold"),
)
map_faces_switch.pack(pady=5, anchor="w")
# Right column - Face Detection & Masking Options
show_fps_value = ctk.BooleanVar(value=modules.globals.show_fps)
show_fps_switch = ctk.CTkSwitch(
right_column,
text="Show FPS",
variable=show_fps_value,
cursor="hand2",
command=lambda: (
setattr(modules.globals, "show_fps", show_fps_value.get()),
save_switch_states(),
),
progress_color="#3a7ebf",
font=("Roboto", 14, "bold"),
)
show_fps_switch.pack(pady=5, anchor="w")
# Mouth Mask Controls
mouth_mask_var = ctk.BooleanVar(value=modules.globals.mouth_mask)
mouth_mask_switch = ctk.CTkSwitch(
right_column,
text="Mouth Mask",
variable=mouth_mask_var,
cursor="hand2",
command=lambda: (
setattr(modules.globals, "mouth_mask", mouth_mask_var.get()),
save_switch_states(),
),
progress_color="#3a7ebf",
font=("Roboto", 14, "bold"),
)
mouth_mask_switch.pack(pady=5, anchor="w")
show_mouth_mask_box_var = ctk.BooleanVar(value=modules.globals.show_mouth_mask_box)
show_mouth_mask_box_switch = ctk.CTkSwitch(
right_column,
text="Show Mouth Box",
variable=show_mouth_mask_box_var,
cursor="hand2",
command=lambda: (
setattr(
modules.globals, "show_mouth_mask_box", show_mouth_mask_box_var.get()
),
save_switch_states(),
),
progress_color="#3a7ebf",
font=("Roboto", 14, "bold"),
)
show_mouth_mask_box_switch.pack(pady=5, anchor="w")
# Face Opacity Controls - Moved under Show Mouth Box
opacity_frame = ctk.CTkFrame(right_column, fg_color="#2a2d2e")
opacity_frame.pack(pady=5, anchor="w", fill="x")
opacity_switch = ctk.CTkSwitch(
opacity_frame,
text="Face Opacity",
variable=ctk.BooleanVar(value=modules.globals.opacity_switch),
cursor="hand2",
command=lambda: setattr(
modules.globals, "opacity_switch", not modules.globals.opacity_switch
),
progress_color="#3a7ebf",
font=("Roboto", 14, "bold"),
)
opacity_switch.pack(side="left", padx=(0, 10))
opacity_slider = ctk.CTkSlider(
opacity_frame,
from_=0,
to=100,
number_of_steps=100,
command=update_opacity,
fg_color=("gray75", "gray25"),
progress_color="#3a7ebf",
button_color="#3a7ebf",
button_hover_color="#2b5d8b",
)
opacity_slider.pack(side="left", fill="x", expand=True)
opacity_slider.set(modules.globals.face_opacity)
# Mask Size Controls
mask_down_size_label = ctk.CTkLabel(
right_column, text="Mask Size:", font=("Roboto", 12)
)
mask_down_size_label.pack(pady=(5, 0), anchor="w")
mask_down_size_slider = ctk.CTkSlider(
right_column,
from_=0.1,
to=1.0,
number_of_steps=9,
command=lambda value: [
setattr(modules.globals, "mask_down_size", value),
save_switch_states(),
],
)
mask_down_size_slider.set(modules.globals.mask_down_size)
mask_down_size_slider.pack(pady=(0, 5), fill="x")
# Mask Feather Controls
mask_feather_label = ctk.CTkLabel(
right_column, text="Mask Feather:", font=("Roboto", 12)
)
mask_feather_label.pack(pady=(5, 0), anchor="w")
mask_feather_slider = ctk.CTkSlider(
right_column,
from_=4,
to=16,
number_of_steps=12,
command=lambda value: [
setattr(modules.globals, "mask_feather_ratio", int(value)),
save_switch_states(),
],
)
mask_feather_slider.set(modules.globals.mask_feather_ratio)
mask_feather_slider.pack(pady=(0, 5), fill="x")
button_frame = ctk.CTkFrame(main_frame, fg_color="#1a1a1a")
button_frame.grid(row=2, column=0, columnspan=3, padx=10, pady=10, sticky="nsew")
@@ -388,28 +717,24 @@ def create_root(
button_frame,
text="Start",
cursor="hand2",
command=lambda: analyze_target(start, root),
command=lambda: [
(
donate_frame.destroy()
if "donate_frame" in globals() and donate_frame.winfo_exists()
else None
),
analyze_target(start, root),
],
fg_color="#4CAF50",
hover_color="#45a049",
)
start_button.pack(side="left", padx=10, expand=True)
preview_button = ModernButton(
button_frame,
text="Preview",
cursor="hand2",
command=lambda: toggle_preview(),
button_frame, text="Preview", cursor="hand2", command=lambda: toggle_preview()
)
preview_button.pack(side="left", padx=10, expand=True)
live_button = ModernButton(
button_frame,
text="Live",
cursor="hand2",
command=lambda: webcam_preview(root),
)
live_button.pack(side="left", padx=10, expand=True)
stop_button = ModernButton(
button_frame,
text="Destroy",
@@ -420,10 +745,55 @@ def create_root(
)
stop_button.pack(side="left", padx=10, expand=True)
# Camera Selection
camera_frame = ctk.CTkFrame(main_frame, fg_color="#2a2d2e", corner_radius=15)
camera_frame.grid(row=3, column=0, columnspan=3, padx=10, pady=(0, 10), sticky="ew")
camera_label = ctk.CTkLabel(
camera_frame, text="Select Camera:", font=("Roboto", 14)
)
camera_label.pack(side="left", padx=(20, 10), pady=10)
available_cameras = get_available_cameras()
available_camera_indices, available_camera_strings = available_cameras
camera_variable = ctk.StringVar(
value=(
available_camera_strings[0]
if available_camera_strings
else "No cameras found"
)
)
camera_optionmenu = ModernOptionMenu(
camera_frame,
values=available_camera_strings,
command=lambda value: print(f"Selected: {value}"), # Add your command here
)
camera_optionmenu.pack(side="left", padx=(10, 20), pady=10, fill="x", expand=True)
live_button = ModernButton(
camera_frame,
text="Live",
cursor="hand2",
command=lambda: [
(
donate_frame.destroy()
if "donate_frame" in globals() and donate_frame.winfo_exists()
else None
),
webcam_preview(
root,
available_camera_indices[
available_camera_strings.index(camera_optionmenu.get())
],
),
],
)
live_button.pack(side="left", padx=10, pady=10)
status_label = ModernLabel(
main_frame, text=None, justify="center", fg_color="#1a1a1a"
)
status_label.grid(row=3, column=0, columnspan=3, pady=10, sticky="ew")
status_label.grid(row=4, column=0, columnspan=3, pady=10, sticky="ew")
donate_frame = ctk.CTkFrame(main_frame, fg_color="#1a1a1a")
donate_frame.grid(row=4, column=0, columnspan=3, pady=5, sticky="ew")
@@ -437,7 +807,6 @@ def create_root(
text_color="#1870c4",
)
donate_label.pack(side="left", expand=True)
donate_label.bind(
"<Button>", lambda event: webbrowser.open("https://paypal.me/hacksider")
)
@@ -682,6 +1051,10 @@ def create_preview(parent: ctk.CTkToplevel) -> ctk.CTkToplevel:
)
preview_slider.pack(fill="x", padx=20, pady=10)
# Add keyboard bindings for left and right arrow keys
preview.bind("<Left>", lambda event: navigate_frames(-1))
preview.bind("<Right>", lambda event: navigate_frames(1))
return preview
@@ -720,7 +1093,7 @@ def select_source_path() -> None:
else:
modules.globals.source_path = None
source_label.configure(image=None)
source_label.configure(text="Drag & Drop Source Image Here")
source_label.configure(text="Drag & Drop\nSource Image Here")
def swap_faces_paths() -> None:
@@ -867,36 +1240,77 @@ def toggle_preview() -> None:
if PREVIEW.state() == "normal":
PREVIEW.withdraw()
elif modules.globals.source_path and modules.globals.target_path:
init_preview()
update_preview()
try:
init_preview()
update_preview()
except Exception as e:
print(f"Error initializing preview: {str(e)}")
# Optionally, show an error message to the user
update_status(f"Error initializing preview: {str(e)}")
def init_preview() -> None:
global preview_slider
if is_image(modules.globals.target_path):
preview_slider.pack_forget()
if is_video(modules.globals.target_path):
if hasattr(preview_slider, "pack_forget"):
preview_slider.pack_forget()
elif is_video(modules.globals.target_path):
video_frame_total = get_video_frame_total(modules.globals.target_path)
preview_slider.configure(to=video_frame_total)
preview_slider.pack(fill="x")
preview_slider.set(0)
# Check if preview_slider exists and is a valid widget
if hasattr(preview_slider, "winfo_exists") and preview_slider.winfo_exists():
try:
preview_slider.configure(to=video_frame_total)
preview_slider.pack(fill="x")
preview_slider.set(0)
except tk.TclError:
print("Error: Preview slider widget not available. Recreating it.")
create_preview_slider()
else:
print("Preview slider not found. Creating a new one.")
create_preview_slider()
def create_preview_slider():
global preview_slider, PREVIEW
# Ensure PREVIEW window exists
if not hasattr(PREVIEW, "winfo_exists") or not PREVIEW.winfo_exists():
print("Error: Preview window does not exist.")
return
# Create a new slider
preview_slider = ctk.CTkSlider(
PREVIEW,
from_=0,
to=get_video_frame_total(modules.globals.target_path),
command=lambda frame_value: update_preview(int(frame_value)),
fg_color=("gray75", "gray25"),
progress_color=("DodgerBlue", "DodgerBlue"),
button_color=("DodgerBlue", "DodgerBlue"),
button_hover_color=("RoyalBlue", "RoyalBlue"),
)
preview_slider.pack(fill="x", padx=20, pady=10)
def navigate_frames(direction: int) -> None:
current_frame = int(preview_slider.get())
new_frame = max(0, min(current_frame + direction, int(preview_slider.cget("to"))))
preview_slider.set(new_frame)
update_preview(new_frame)
def update_preview(frame_number: int = 0) -> None:
if modules.globals.source_path and modules.globals.target_path:
update_status("Processing...")
# Debug: Print the target path and frame number
print(
f"Target path: {modules.globals.target_path}, Frame number: {frame_number}"
)
temp_frame = None
if is_video(modules.globals.target_path):
temp_frame = get_video_frame(modules.globals.target_path, frame_number)
elif is_image(modules.globals.target_path):
temp_frame = cv2.imread(modules.globals.target_path)
# Debug: Check if temp_frame is None
if temp_frame is None:
print("Error: temp_frame is None")
update_status("Error: Could not read frame from video or image.")
@@ -908,19 +1322,10 @@ def update_preview(frame_number: int = 0) -> None:
for frame_processor in get_frame_processors_modules(
modules.globals.frame_processors
):
# Debug: Print the type of frame_processor
print(f"Processing frame with: {type(frame_processor).__name__}")
temp_frame = frame_processor.process_frame(
get_one_face(cv2.imread(modules.globals.source_path)), temp_frame
)
# Debug: Check if temp_frame is None after processing
if temp_frame is None:
print("Error: temp_frame is None after processing")
update_status("Error: Frame processing failed.")
return
image = Image.fromarray(cv2.cvtColor(temp_frame, cv2.COLOR_BGR2RGB))
image = ImageOps.contain(
image, (PREVIEW_MAX_WIDTH, PREVIEW_MAX_HEIGHT), Image.LANCZOS
@@ -931,72 +1336,140 @@ def update_preview(frame_number: int = 0) -> None:
PREVIEW.deiconify()
def webcam_preview(root: ctk.CTk):
def webcam_preview(root: ctk.CTk, camera_index: int):
if not modules.globals.map_faces:
if modules.globals.source_path is None:
# No image selected
return
create_webcam_preview()
create_webcam_preview(camera_index)
else:
modules.globals.souce_target_map = []
create_source_target_popup_for_webcam(root, modules.globals.souce_target_map)
def create_webcam_preview():
def get_available_cameras():
"""Returns a list of available camera names and indices."""
camera_indices = []
camera_names = []
for camera in enumerate_cameras():
cap = cv2.VideoCapture(camera.index)
if cap.isOpened():
camera_indices.append(camera.index)
camera_names.append(camera.name)
cap.release()
return (camera_indices, camera_names)
# Add this function to update the opacity value
def update_opacity(value):
modules.globals.face_opacity = int(value)
def create_webcam_preview(camera_index: int):
global preview_label, PREVIEW
camera = cv2.VideoCapture(
0
) # Use index for the webcam (adjust the index accordingly if necessary)
camera.set(
cv2.CAP_PROP_FRAME_WIDTH, PREVIEW_DEFAULT_WIDTH
) # Set the width of the resolution
camera.set(
cv2.CAP_PROP_FRAME_HEIGHT, PREVIEW_DEFAULT_HEIGHT
) # Set the height of the resolution
camera.set(cv2.CAP_PROP_FPS, 60) # Set the frame rate of the webcam
camera = cv2.VideoCapture(camera_index)
if not camera.isOpened():
update_status(f"Error: Could not open camera with index {camera_index}")
return
preview_label.configure(
width=PREVIEW_DEFAULT_WIDTH, height=PREVIEW_DEFAULT_HEIGHT
) # Reset the preview image before startup
camera.set(cv2.CAP_PROP_FRAME_WIDTH, PREVIEW_DEFAULT_WIDTH)
camera.set(cv2.CAP_PROP_FRAME_HEIGHT, PREVIEW_DEFAULT_HEIGHT)
camera.set(cv2.CAP_PROP_FPS, 60)
PREVIEW.deiconify() # Open preview window
PREVIEW.deiconify()
# Clear any existing widgets in the PREVIEW window
for widget in PREVIEW.winfo_children():
widget.destroy()
# Create a main frame to contain all widgets
main_frame = ctk.CTkFrame(PREVIEW)
main_frame.pack(fill="both", expand=True)
# Create a frame for the preview label
preview_frame = ctk.CTkFrame(main_frame)
preview_frame.pack(fill="both", expand=True, padx=10, pady=10)
preview_label = ctk.CTkLabel(preview_frame, text="")
preview_label.pack(fill="both", expand=True)
# Initialize frame processors
frame_processors = get_frame_processors_modules(modules.globals.frame_processors)
source_image = None # Initialize variable for the selected face image
# Variables for source image and FPS calculation
source_image = None
prev_time = time.time()
fps_update_interval = 0.5
frame_count = 0
fps = 0
while camera:
# Function to update frame size when the window is resized
def update_frame_size(event):
nonlocal temp_frame
if modules.globals.live_resizable:
temp_frame = fit_image_to_size(temp_frame, event.width, event.height)
preview_frame.bind("<Configure>", update_frame_size)
# Main loop for capturing and processing frames
while camera.isOpened() and PREVIEW.state() != "withdrawn":
ret, frame = camera.read()
if not ret:
break
temp_frame = frame.copy() # Create a copy of the frame
temp_frame = frame.copy()
# Apply mirroring if enabled
if modules.globals.live_mirror:
temp_frame = cv2.flip(temp_frame, 1) # horizontal flipping
temp_frame = cv2.flip(temp_frame, 1)
# Resize frame if enabled
if modules.globals.live_resizable:
temp_frame = fit_image_to_size(
temp_frame, PREVIEW.winfo_width(), PREVIEW.winfo_height()
)
# Process frame based on face mapping mode
if not modules.globals.map_faces:
# Select and save face image only once
if source_image is None and modules.globals.source_path:
# Update source image if path has changed
if modules.globals.source_path and (
source_image is None
or modules.globals.source_path != source_image["location"]
):
source_image = get_one_face(cv2.imread(modules.globals.source_path))
source_image["location"] = modules.globals.source_path
# Apply frame processors (e.g., face swapping, enhancement)
for frame_processor in frame_processors:
temp_frame = frame_processor.process_frame(source_image, temp_frame)
else:
modules.globals.target_path = None
for frame_processor in frame_processors:
temp_frame = frame_processor.process_frame_v2(temp_frame)
image = cv2.cvtColor(
temp_frame, cv2.COLOR_BGR2RGB
) # Convert the image to RGB format to display it with Tkinter
# Calculate and display FPS
current_time = time.time()
frame_count += 1
if current_time - prev_time >= fps_update_interval:
fps = frame_count / (current_time - prev_time)
frame_count = 0
prev_time = current_time
if modules.globals.show_fps:
cv2.putText(
temp_frame,
f"FPS: {fps:.1f}",
(10, 30),
cv2.FONT_HERSHEY_SIMPLEX,
1,
(0, 255, 0),
2,
)
# Convert frame to RGB and display in preview label
image = cv2.cvtColor(temp_frame, cv2.COLOR_BGR2RGB)
image = Image.fromarray(image)
image = ImageOps.contain(
image, (temp_frame.shape[1], temp_frame.shape[0]), Image.LANCZOS
@@ -1005,11 +1478,9 @@ def create_webcam_preview():
preview_label.configure(image=image)
ROOT.update()
if PREVIEW.state() == "withdrawn":
break
# Release camera and close preview window
camera.release()
PREVIEW.withdraw() # Close preview window when loop is finished
PREVIEW.withdraw()
def create_source_target_popup_for_webcam(root: ctk.CTk, map: list) -> None:
@@ -1024,7 +1495,12 @@ def create_source_target_popup_for_webcam(root: ctk.CTk, map: list) -> None:
if has_valid_map():
POPUP_LIVE.destroy()
simplify_maps()
create_webcam_preview()
# Get the selected camera index
selected_camera = camera_optionmenu.get()
camera_index = available_camera_indices[
available_camera_strings.index(selected_camera)
]
create_webcam_preview(camera_index)
else:
update_pop_live_status("At least 1 source with target is required!")
@@ -1033,7 +1509,10 @@ def create_source_target_popup_for_webcam(root: ctk.CTk, map: list) -> None:
refresh_data(map)
update_pop_live_status("Please provide mapping!")
popup_status_label_live = ctk.CTkLabel(POPUP_LIVE, text=None, justify="center")
# Rest of the popup content
popup_status_label_live = ctk.CTkLabel(
POPUP_LIVE, text=None, justify="center", font=("Roboto", 14)
)
popup_status_label_live.grid(row=1, column=0, pady=15)
add_button = ctk.CTkButton(
@@ -1056,6 +1535,29 @@ def create_source_target_popup_for_webcam(root: ctk.CTk, map: list) -> None:
)
close_button.place(relx=0.6, rely=0.92, relwidth=0.2, relheight=0.05)
# Create a better styled camera selection frame
camera_frame = ctk.CTkFrame(POPUP_LIVE, fg_color="#2a2d2e", corner_radius=15)
camera_frame.grid(row=2, column=0, pady=15, padx=20, sticky="ew")
POPUP_LIVE.grid_columnconfigure(0, weight=1)
camera_label = ctk.CTkLabel(
camera_frame,
text="Select Camera:",
font=("Roboto", 14, "bold"),
text_color="#DCE4EE",
)
camera_label.pack(side="left", padx=20, pady=10)
available_cameras = get_available_cameras()
available_camera_indices, available_camera_strings = available_cameras
camera_optionmenu = ModernOptionMenu(
camera_frame,
values=available_camera_strings,
command=lambda value: print(f"Selected: {value}"), # Add your command here
)
camera_optionmenu.pack(side="left", padx=(10, 20), pady=10, fill="x", expand=True)
refresh_data(map) # Initial data refresh
+1 -1
View File
@@ -2,11 +2,11 @@
numpy>=1.23.5,<2
opencv-python==4.8.1.78
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
torch==2.0.1+cu118; sys_platform != 'darwin'
torch==2.0.1; sys_platform == 'darwin'