Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
BlackThompson committed Nov 8, 2023
0 parents commit fbe84c6
Show file tree
Hide file tree
Showing 20 changed files with 369 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
video_split/
video_split_swap/
test.ipynb
test.py
8 changes: 8 additions & 0 deletions .idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions .idea/deployment.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions .idea/face_fusion_damo.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 27 additions & 0 deletions .idea/inspectionProfiles/Project_Default.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/inspectionProfiles/profiles_settings.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions .idea/modules.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

59 changes: 59 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# 🎭 Painted Skin 🎭

> Welcome to the 🎭 Painted Skin 🎭! Get ready to have some fun with faces. 😄
![License](https://img.shields.io/badge/license-Apache 2.0-blue)

## 🐾 Preview

![image-20231108160501352](https://black-thompson.oss-cn-beijing.aliyuncs.com/img/image-20231108160501352.png)



## 🪄 How to use

1. **Clone the Repository:**

```bash
git clone https://github.com/BlackThompson/Painted-Skin.git
```

2. **Create a New Environment and Install Dependencies:**

- Note: Python version should be >=3.8
- Note: The versions of `torch`, `torchvision`, and `torchaudio` should align with your CUDA version.

```bash
conda create --name painted_skin python=3.8
conda activate painted_skin
pip install -r requirements.txt
```

3. **Run `gradio_UI.py`:**
```bash
gradio gradio_UI.py
```

4. **Click the Local URL**

## 🦾 How it Works

This tool allows you to perform a face swap. Simply follow these steps:

1. **Upload Source:**
- Choose between `src_picture` and `src_video` for the face you want to swap.
- Note: You can only upload one source at a time, and the tool processes one task at a time.
2. **Upload Target:**
- Upload `target_picture`, and our model will swap the face onto the source image or video.
3. **Submit and Wait:**
- Click the submit button and patiently wait for the process to complete.
- Keep in mind that face swapping in videos might take some time, so be patient! 😅

## ❗ Important Note
- Ensure that `target_picture` is uploaded for the face swap to work effectively.

Enjoy swapping faces and have a good laugh! 😆

## 💌 Acknowledgements

This repository borrows heavily from [facefusion-damo](https://www.modelscope.cn/models/damo/cv_unet-image-face-fusion_damo/summary) and [face-change](https://github.com/Quietbe/mv_face_change/blob/main/video_cut_cv_h.py). Thanks to the authors for sharing their code and models.
Binary file added __pycache__/face_swap_utils.cpython-38.pyc
Binary file not shown.
Binary file added __pycache__/gradio_UI.cpython-38.pyc
Binary file not shown.
Binary file added example/CXK.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added example/James.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added example/Obama.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added example/Trump.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
161 changes: 161 additions & 0 deletions face_swap_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
# _*_ coding : utf-8 _*_
# @Time : 2023/11/6 17:04
# @Author : Black
# @File : face_swap_utils
# @Project : face_fusion_damo

import cv2
import os
from moviepy.video.io.ffmpeg_tools import ffmpeg_extract_audio
import moviepy.editor as mp
import torch
from modelscope.outputs import OutputKeys
from modelscope.pipelines import pipeline
from modelscope.utils.constant import Tasks
from moviepy.editor import VideoFileClip, AudioFileClip, ImageSequenceClip
import natsort


# 将视频video_path分割成图片和音频文件,保存到save_path文件夹中
def video2mp3_img(video_path, save_path):
def _video2img(video_path, save_path):
cap = cv2.VideoCapture(video_path)
frame_rate = int(cap.get(cv2.CAP_PROP_FPS)) # 获取视频帧率
i = 0
while True:
ret, frame = cap.read()
if ret:
cv2.imwrite(save_path + '/' + str(i) + '.jpg', frame)
i += 1
else:
break
cap.release()
print(f"Video frame rate: {frame_rate}")
return frame_rate

def _video2mp3(video_path, save_path):
# 提取音频并保存为临时文件(默认是WAV格式)
audio_temp_file = os.path.join(save_path, 'audio.wav')
ffmpeg_extract_audio(video_path, audio_temp_file)

# 将音频转换为MP3格式
audio_clip = mp.AudioFileClip(audio_temp_file)
audio_temp_file = os.path.join(save_path, 'audio.mp3')
audio_clip.write_audiofile(audio_temp_file)
# 删除临时音频文件
audio_clip.close()

if not os.path.exists(save_path):
os.makedirs(save_path)

# 视频分割
frame_rate = _video2img(video_path, save_path)
print("split picture finished!")
# 视频提取音频
_video2mp3(video_path, save_path)
print("extract audio finished!")

return frame_rate


# 将图片文件夹中的图片进行人脸替换,保存到save_dir文件夹中
def replace_all_img(template_dir, user_path, save_dir):
# image_face_fusion = pipeline(Tasks.image_face_fusion, model='damo/cv_unet-image-face-fusion_damo')
for root, dirs, files in os.walk(template_dir):
for file in files:
if file.endswith(".jpg") or file.endswith(".png"):
template_path = os.path.join(root, file)
replace_single_img(template_path=template_path, user_path=user_path,
save_dir=save_dir)


# 将单张图片进行人脸替换,保存到save_dir文件夹中
def replace_single_img(template_path, user_path, save_dir):
image_face_fusion = pipeline(Tasks.image_face_fusion, model='damo/cv_unet-image-face-fusion_damo')
filename = os.path.basename(template_path)
print(f"{filename} start face swapping!")
# filename = os.path.splitext(os.path.basename(template_path))[0]

# 替换面部依赖: template为原图,即视频拆分图; user为用户要替换脸的图
result = image_face_fusion(dict(template=template_path, user=user_path))
filepath = os.path.join(save_dir, filename)
cv2.imwrite(filepath, result[OutputKeys.OUTPUT_IMG])

# 释放CUDA内存
# 只是删除了该内部函数作用域中的变量引用,而不会影响外部函数的变量
del image_face_fusion
torch.cuda.empty_cache()
print(f"{filename} finished!")
return filepath


# 将图片文件夹中的图片合成视频,保存到output_dir文件夹中
def img2mp4(save_name, output_dir, img_folder, mp3_folder, fps=25):
images = []
for root, dirs, files in os.walk(img_folder):
for file in files:
if file.endswith(".jpg") or file.endswith(".png"):
file_path = os.path.join(root, file)
images.append(file_path)
# 将图片按照文件名进行自然排序
images = natsort.natsorted(images)

clips = [ImageSequenceClip(images, fps=fps)]
video_clip = clips[0]
audio_path = os.path.join(mp3_folder, 'audio.wav')
audio_clip = AudioFileClip(audio_path)

# 如果音频和视频时长不匹配,可以选择截取或重复音频以使其匹配视频长度
if audio_clip.duration > video_clip.duration:
audio_clip = audio_clip.subclip(0, video_clip.duration)
else:
video_clip = video_clip.subclip(0, audio_clip.duration)

video_clip = video_clip.set_audio(audio_clip)

save_name = save_name + '.mp4'
video_clip.write_videofile(os.path.join(output_dir, save_name), codec="libx264")
return os.path.join(output_dir, save_name)


# 整个视频的替换人脸
def replace_video(video_path, user_path):
# .py文件所在路径
BASE = os.path.dirname(__file__)
template_dir = os.path.join(BASE, 'video_split')
save_dir = os.path.join(BASE, 'video_split_swap')
output_dir = os.path.join(BASE, 'output')

if not os.path.exists(template_dir):
os.makedirs(template_dir)
if not os.path.exists(save_dir):
os.makedirs(save_dir)
if not os.path.exists(output_dir):
os.makedirs(output_dir)

# 将视频分割成图片和音频
fps = video2mp3_img(video_path=video_path, save_path=template_dir)
replace_all_img(template_dir=template_dir, user_path=user_path, save_dir=save_dir)
save_path = img2mp4(save_name='swapped_video', output_dir=output_dir, img_folder=save_dir, mp3_folder=template_dir,
fps=fps)
return save_path


if __name__ == '__main__':
# .py文件所在路径
BASE = os.path.dirname(__file__)
# .ipynb文件所在路径
# BASE = os.getcwd()
# 设置工作路径为当前脚本所在的路径
os.chdir(BASE)

user_path = r'./input/Trump.jpg'
template_dir = r'./middle'
save_dir = r'./middle_after_swap'
output_dir = r'./output'
video_path = r'./input/zhan.mp4'

# 将视频分割成图片和音频
fps = video2mp3_img(video_path=video_path, save_path=template_dir)
replace_all_img(template_dir=template_dir, user_path=user_path, save_dir=save_dir)
img2mp4(save_name='Zhan_Trump', output_dir=output_dir, img_folder=save_dir, mp3_folder=template_dir, fps=fps)
64 changes: 64 additions & 0 deletions gradio_UI.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# _*_ coding : utf-8 _*_
# @Time : 2023/11/7 21:42
# @Author : Black
# @File : gradio_UI
# @Project : face_fusion_damo

import gradio as gr
import os
from face_swap_utils import *


def face_swap(src_picture=None, src_video=None,
target_picture=os.path.join(os.path.dirname(__file__), 'example/Trump.jpg')):
save_dir = os.path.join(os.path.dirname(__file__), 'output')
# 检查是否存在临时文件夹,如果不存在则创建
if not os.path.exists(save_dir):
os.makedirs(save_dir)

if src_picture is not None:
print(src_picture)
result = replace_single_img(src_picture, target_picture, save_dir)
return result, None
elif src_video is not None:
print(src_video)
result = replace_video(src_video, target_picture)
return None, result


demo = gr.Interface(fn=face_swap,
inputs=[gr.Image(type='filepath'), gr.Video(), gr.Image(type='filepath')],
outputs=[gr.Image(type='filepath', label='swapped_image'),
gr.Video(label='swapped_video')],
title="🎭 Painted Skin 🎭",
description="# Face Swap Tool\n"
"Welcome to the 🎭 Painted Skin 🎭! Get ready to have some fun with faces. 😄\n"
"## How it Works\n"
"This tool allows you to perform a face swap. Simply follow these steps:\n"
"1. **Upload Source:**\n"
" - Choose between `src_picture` and `src_video` for the face you want to swap.\n"
" - Note: You can only upload one source at a time, and the tool processes one task at a time.\n"
"2. **Upload Target:**\n"
" - Upload `target_picture`, and our model will swap the face onto the source image or video.\n"
"3. **Submit and Wait:**\n"
" - Click the submit button and patiently wait for the process to complete.\n"
" - Keep in mind that face swapping in videos might take some time, so be patient! 😅\n"
"## Important Note\n"
"- Ensure that `target_picture` is uploaded for the face swap to work effectively.\n"
"Enjoy swapping faces and have a good laugh! 😆\n",

# css="body {background-color: red;}",
# article="Attention is all you need!",
examples=[[os.path.join(os.path.dirname(__file__), 'example/Trump.jpg'),
None,
os.path.join(os.path.dirname(__file__), 'example/Obama.jpg')],
[os.path.join(os.path.dirname(__file__), 'example/CXK.jpg'),
None,
os.path.join(os.path.dirname(__file__), 'example/James.jpg')]
],
# allow_flagging="never",
# cache_examples=True
)

if __name__ == "__main__":
demo.launch(share=True)
Binary file added output/Zhan_Trump.mp4
Binary file not shown.
Binary file added output/image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
gradio==4.1.2
modelscope==1.9.4
moviepy==1.0.3
natsort==8.4.0
opencv_python==4.8.1.78
torch==1.13.1+cu116

0 comments on commit fbe84c6

Please sign in to comment.