Spaces:
Running
Running
update lib
Browse files- README.md +15 -18
- app.py +136 -136
- requirements.txt +8 -5
README.md
CHANGED
@@ -1,30 +1,27 @@
|
|
1 |
---
|
2 |
-
title:
|
3 |
-
emoji:
|
4 |
-
colorFrom:
|
5 |
-
colorTo:
|
6 |
sdk: gradio
|
7 |
sdk_version: 4.0.2
|
8 |
app_file: app.py
|
9 |
pinned: false
|
10 |
---
|
11 |
|
12 |
-
#
|
13 |
|
14 |
-
Ứng dụng này sử dụng
|
15 |
|
16 |
## Cách sử dụng
|
17 |
-
1. Tải lên ảnh chứa người
|
18 |
-
2.
|
19 |
-
3. Điều chỉnh các
|
20 |
-
4. Nhấn "Tạo video"
|
21 |
|
22 |
-
##
|
23 |
-
|
24 |
-
- **Mức độ chuyển động**: Điều chỉnh cường độ chuyển động (1-255)
|
25 |
-
- **FPS**: Số khung hình mỗi giây của video kết quả
|
26 |
|
27 |
-
##
|
28 |
-
-
|
29 |
-
-
|
30 |
-
- Thử nghiệm với các mô tả khác nhau để có hiệu quả tốt nhất
|
|
|
1 |
---
|
2 |
+
title: First Order Motion Model Animation
|
3 |
+
emoji: 🎬
|
4 |
+
colorFrom: blue
|
5 |
+
colorTo: indigo
|
6 |
sdk: gradio
|
7 |
sdk_version: 4.0.2
|
8 |
app_file: app.py
|
9 |
pinned: false
|
10 |
---
|
11 |
|
12 |
+
# First Order Motion Model
|
13 |
|
14 |
+
Ứng dụng này sử dụng First Order Motion Model để tạo video người chuyển động từ một ảnh tĩnh.
|
15 |
|
16 |
## Cách sử dụng
|
17 |
+
1. Tải lên ảnh nguồn chứa người/đối tượng bạn muốn làm chuyển động
|
18 |
+
2. Tải lên video tham chiếu có chuyển động bạn muốn áp dụng
|
19 |
+
3. Điều chỉnh các tùy chọn (tùy chọn)
|
20 |
+
4. Nhấn "Tạo video" và chờ kết quả
|
21 |
|
22 |
+
## Mô hình được sử dụng
|
23 |
+
First Order Motion Model (FOMM) là một mô hình deep learning cho phép tạo chuyển động cho một đối tượng trong ảnh tĩnh dựa trên chuyển động từ video tham chiếu.
|
|
|
|
|
24 |
|
25 |
+
## Paper & Code
|
26 |
+
- Paper: [First Order Motion Model for Image Animation](https://arxiv.org/abs/2003.00196)
|
27 |
+
- Code gốc: [AliaksandrSiarohin/first-order-model](https://github.com/AliaksandrSiarohin/first-order-model)
|
|
app.py
CHANGED
@@ -1,156 +1,151 @@
|
|
1 |
import gradio as gr
|
2 |
-
import
|
3 |
import numpy as np
|
|
|
4 |
import imageio
|
5 |
-
import
|
6 |
-
from
|
7 |
-
import
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
8 |
|
9 |
-
#
|
10 |
-
def
|
11 |
-
|
12 |
-
|
|
|
|
|
|
|
|
|
|
|
13 |
|
14 |
-
|
15 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
16 |
|
17 |
-
#
|
18 |
-
|
19 |
-
|
20 |
|
21 |
-
#
|
22 |
-
|
23 |
-
|
24 |
-
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
|
25 |
|
26 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
27 |
|
28 |
-
#
|
29 |
-
def
|
30 |
-
if
|
31 |
-
return None, "Vui lòng tải lên
|
32 |
|
33 |
try:
|
34 |
-
#
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
# Thay đổi kích thước hình ảnh
|
39 |
-
image = image.resize((512, 512))
|
40 |
-
image_array = np.array(image)
|
41 |
|
42 |
-
#
|
43 |
-
|
44 |
|
45 |
-
#
|
46 |
-
|
47 |
-
|
48 |
|
49 |
-
#
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
if movement_type == "Đi bộ":
|
58 |
-
# Mô phỏng đi bộ - di chuyển lên xuống và sang ngang
|
59 |
-
for i in range(num_frames):
|
60 |
-
y_offset = int(np.sin(i/8 * 2 * np.pi) * 10)
|
61 |
-
x_offset = i % 4 - 2 # Nhịp bước nhỏ
|
62 |
-
|
63 |
-
# Tạo frame mới với nền tĩnh
|
64 |
-
frame = background.copy()
|
65 |
-
|
66 |
-
# Thêm người với offset
|
67 |
-
M = np.float32([[1, 0, x_offset], [0, 1, y_offset]])
|
68 |
-
moved_person = cv2.warpAffine(person, M, (512, 512))
|
69 |
-
|
70 |
-
# Kết hợp nền và người
|
71 |
-
frame = frame + moved_person
|
72 |
-
frames.append(frame.astype(np.uint8))
|
73 |
-
|
74 |
-
elif movement_type == "Vẫy tay":
|
75 |
-
# Mô phỏng vẫy tay - xoay nhẹ phần trên
|
76 |
-
for i in range(num_frames):
|
77 |
-
angle = np.sin(i/6 * 2 * np.pi) * 5 # Xoay ±5 độ
|
78 |
-
|
79 |
-
# Tạo ma trận xoay
|
80 |
-
center = (256, 200) # Giả sử tâm xoay ở phần trên của người
|
81 |
-
M = cv2.getRotationMatrix2D(center, angle, 1.0)
|
82 |
-
|
83 |
-
# Xoay người
|
84 |
-
rotated_person = cv2.warpAffine(person, M, (512, 512))
|
85 |
-
|
86 |
-
# Kết hợp nền và người đã xoay
|
87 |
-
frame = background.copy() + rotated_person
|
88 |
-
frames.append(frame.astype(np.uint8))
|
89 |
-
|
90 |
-
elif movement_type == "Nhảy múa":
|
91 |
-
# Mô phỏng nhảy múa - kết hợp chuyển động
|
92 |
-
for i in range(num_frames):
|
93 |
-
y_offset = int(np.sin(i/6 * 2 * np.pi) * 15)
|
94 |
-
x_offset = int(np.sin(i/4 * 2 * np.pi) * 10)
|
95 |
-
angle = np.sin(i/8 * 2 * np.pi) * 3
|
96 |
-
|
97 |
-
# Xoay người
|
98 |
-
center = (256, 256)
|
99 |
-
M_rot = cv2.getRotationMatrix2D(center, angle, 1.0)
|
100 |
-
rotated_person = cv2.warpAffine(person, M_rot, (512, 512))
|
101 |
-
|
102 |
-
# Di chuyển người đã xoay
|
103 |
-
M_trans = np.float32([[1, 0, x_offset], [0, 1, y_offset]])
|
104 |
-
moved_person = cv2.warpAffine(rotated_person, M_trans, (512, 512))
|
105 |
-
|
106 |
-
# Kết hợp nền và người đã di chuyển
|
107 |
-
frame = background.copy() + moved_person
|
108 |
-
frames.append(frame.astype(np.uint8))
|
109 |
-
|
110 |
-
else: # Chuyển động nhẹ
|
111 |
-
for i in range(num_frames):
|
112 |
-
angle = np.sin(i/12 * 2 * np.pi) * 2
|
113 |
-
y_offset = int(np.sin(i/10 * 2 * np.pi) * 5)
|
114 |
-
|
115 |
-
# Xoay người
|
116 |
-
center = (256, 256)
|
117 |
-
M_rot = cv2.getRotationMatrix2D(center, angle, 1.0)
|
118 |
-
rotated_person = cv2.warpAffine(person, M_rot, (512, 512))
|
119 |
-
|
120 |
-
# Di chuyển người đã xoay
|
121 |
-
M_trans = np.float32([[1, 0, 0], [0, 1, y_offset]])
|
122 |
-
moved_person = cv2.warpAffine(rotated_person, M_trans, (512, 512))
|
123 |
-
|
124 |
-
# Kết hợp nền và người đã di chuyển
|
125 |
-
frame = background.copy() + moved_person
|
126 |
-
frames.append(frame.astype(np.uint8))
|
127 |
-
|
128 |
-
# Lưu video
|
129 |
-
output_path = "animated_person.mp4"
|
130 |
-
imageio.mimsave(output_path, frames, fps=8)
|
131 |
-
|
132 |
-
return output_path, "Video được tạo thành công!"
|
133 |
|
|
|
134 |
except Exception as e:
|
135 |
return None, f"Lỗi: {str(e)}"
|
136 |
|
137 |
# Tạo giao diện Gradio
|
138 |
-
with gr.Blocks(title="
|
139 |
-
gr.Markdown("#
|
140 |
-
gr.Markdown("Tạo video
|
141 |
|
142 |
with gr.Row():
|
143 |
with gr.Column():
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
-
value="Chuyển động
|
149 |
-
|
150 |
-
|
151 |
-
minimum=12, maximum=36, value=24, step=4,
|
152 |
-
label="Số khung hình"
|
153 |
-
)
|
154 |
submit_btn = gr.Button("Tạo video")
|
155 |
|
156 |
with gr.Column():
|
@@ -158,14 +153,19 @@ with gr.Blocks(title="Ứng dụng tạo chuyển động cho người trong ả
|
|
158 |
output_message = gr.Textbox(label="Thông báo")
|
159 |
|
160 |
submit_btn.click(
|
161 |
-
fn=
|
162 |
-
inputs=[
|
163 |
outputs=[output_video, output_message]
|
164 |
)
|
165 |
|
|
|
|
|
|
|
|
|
|
|
166 |
gr.Markdown("### Lưu ý")
|
167 |
-
gr.Markdown("-
|
168 |
-
gr.Markdown("-
|
169 |
-
gr.Markdown("-
|
170 |
|
171 |
demo.launch()
|
|
|
1 |
import gradio as gr
|
2 |
+
import os
|
3 |
import numpy as np
|
4 |
+
import torch
|
5 |
import imageio
|
6 |
+
import subprocess
|
7 |
+
from skimage.transform import resize
|
8 |
+
from skimage import img_as_ubyte
|
9 |
+
|
10 |
+
# Clone repo nếu chưa có
|
11 |
+
if not os.path.exists('first-order-model'):
|
12 |
+
subprocess.call(['git', 'clone', 'https://github.com/AliaksandrSiarohin/first-order-model.git'])
|
13 |
+
os.rename('first-order-model', 'first_order_model')
|
14 |
+
|
15 |
+
# Thêm đường dẫn vào PYTHONPATH
|
16 |
+
import sys
|
17 |
+
sys.path.append('first_order_model')
|
18 |
+
|
19 |
+
# Import các module cần thiết từ repo
|
20 |
+
from first_order_model.demo import load_checkpoints
|
21 |
+
from first_order_model.animate import normalize_kp
|
22 |
|
23 |
+
# Tải mô hình pre-trained
|
24 |
+
def download_model():
|
25 |
+
model_path = 'checkpoints/vox-cpk.pth.tar'
|
26 |
+
if not os.path.exists(model_path):
|
27 |
+
os.makedirs('checkpoints', exist_ok=True)
|
28 |
+
subprocess.call([
|
29 |
+
'wget', 'https://drive.google.com/uc?export=download&id=1PyQJmkdCsAkOYwUyaj_l-l0as-iLDgeH',
|
30 |
+
'-O', model_path
|
31 |
+
])
|
32 |
|
33 |
+
config_path = 'first_order_model/config/vox-256.yaml'
|
34 |
+
if not os.path.exists(config_path):
|
35 |
+
os.makedirs('first_order_model/config', exist_ok=True)
|
36 |
+
subprocess.call([
|
37 |
+
'wget', 'https://drive.google.com/uc?export=download&id=1pZUMNRjkBiuBEM68oj9nskuWgJR-5QMn',
|
38 |
+
'-O', config_path
|
39 |
+
])
|
40 |
+
|
41 |
+
return config_path, model_path
|
42 |
+
|
43 |
+
# Hàm tạo animation
|
44 |
+
def make_animation(source_image, driving_video, relative=True, adapt_movement_scale=True):
|
45 |
+
config_path, checkpoint_path = download_model()
|
46 |
+
|
47 |
+
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
48 |
+
|
49 |
+
# Tải mô hình và cấu hình
|
50 |
+
generator, kp_detector = load_checkpoints(config_path, checkpoint_path, device=device)
|
51 |
+
|
52 |
+
# Đọc source_image và driving_video
|
53 |
+
source = imageio.imread(source_image)
|
54 |
+
reader = imageio.get_reader(driving_video)
|
55 |
+
fps = reader.get_meta_data()['fps']
|
56 |
+
driving = []
|
57 |
+
try:
|
58 |
+
for im in reader:
|
59 |
+
driving.append(im)
|
60 |
+
except RuntimeError:
|
61 |
+
pass
|
62 |
+
reader.close()
|
63 |
|
64 |
+
# Tiền xử lý
|
65 |
+
source = resize(source, (256, 256))[..., :3]
|
66 |
+
driving = [resize(frame, (256, 256))[..., :3] for frame in driving]
|
67 |
|
68 |
+
# Chuyển đổi thành tensor
|
69 |
+
source = torch.tensor(source[np.newaxis].astype(np.float32)).permute(0, 3, 1, 2).to(device)
|
70 |
+
driving = torch.tensor(np.array(driving).astype(np.float32)).permute(0, 3, 1, 2).to(device)
|
|
|
71 |
|
72 |
+
# Trích xuất keypoints
|
73 |
+
kp_source = kp_detector(source)
|
74 |
+
kp_driving_initial = kp_detector(driving[0:1])
|
75 |
+
|
76 |
+
# Tạo animation
|
77 |
+
with torch.no_grad():
|
78 |
+
predictions = []
|
79 |
+
for frame_idx in range(driving.shape[0]):
|
80 |
+
driving_frame = driving[frame_idx:frame_idx+1]
|
81 |
+
kp_driving = kp_detector(driving_frame)
|
82 |
+
|
83 |
+
# Chuẩn hóa keypoints
|
84 |
+
if relative:
|
85 |
+
kp_norm = normalize_kp(
|
86 |
+
kp_source=kp_source,
|
87 |
+
kp_driving=kp_driving,
|
88 |
+
kp_driving_initial=kp_driving_initial,
|
89 |
+
use_relative_movement=relative,
|
90 |
+
use_relative_jacobian=relative,
|
91 |
+
adapt_movement_scale=adapt_movement_scale
|
92 |
+
)
|
93 |
+
else:
|
94 |
+
kp_norm = kp_driving
|
95 |
+
|
96 |
+
# Tạo frame
|
97 |
+
out = generator(source, kp_source=kp_source, kp_driving=kp_norm)
|
98 |
+
predictions.append(np.transpose(out['prediction'].data.cpu().numpy(), [0, 2, 3, 1])[0])
|
99 |
+
|
100 |
+
# Lưu video kết quả
|
101 |
+
output_path = 'result.mp4'
|
102 |
+
imageio.mimsave(output_path, [img_as_ubyte(frame) for frame in predictions], fps=fps)
|
103 |
+
|
104 |
+
return output_path
|
105 |
|
106 |
+
# Định nghĩa giao diện Gradio
|
107 |
+
def animate_fomm(source_image, driving_video, relative=True, adapt_scale=True):
|
108 |
+
if source_image is None or driving_video is None:
|
109 |
+
return None, "Vui lòng tải lên cả ảnh nguồn và video tham chiếu."
|
110 |
|
111 |
try:
|
112 |
+
# Lưu tạm ảnh và video tải lên
|
113 |
+
source_path = "source_image.jpg"
|
114 |
+
driving_path = "driving_video.mp4"
|
|
|
|
|
|
|
|
|
115 |
|
116 |
+
# Lưu ảnh nguồn
|
117 |
+
source_image.save(source_path)
|
118 |
|
119 |
+
# Lưu video tham chiếu
|
120 |
+
with open(driving_path, 'wb') as f:
|
121 |
+
f.write(driving_video)
|
122 |
|
123 |
+
# Tạo animation
|
124 |
+
result_path = make_animation(
|
125 |
+
source_path,
|
126 |
+
driving_path,
|
127 |
+
relative=relative,
|
128 |
+
adapt_movement_scale=adapt_scale
|
129 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
130 |
|
131 |
+
return result_path, "Video được tạo thành công!"
|
132 |
except Exception as e:
|
133 |
return None, f"Lỗi: {str(e)}"
|
134 |
|
135 |
# Tạo giao diện Gradio
|
136 |
+
with gr.Blocks(title="First Order Motion Model - Tạo video người chuyển động") as demo:
|
137 |
+
gr.Markdown("# First Order Motion Model")
|
138 |
+
gr.Markdown("Tạo video người chuyển động từ một ảnh tĩnh và video tham chiếu")
|
139 |
|
140 |
with gr.Row():
|
141 |
with gr.Column():
|
142 |
+
source_image = gr.Image(type="pil", label="Tải lên ảnh nguồn")
|
143 |
+
driving_video = gr.Video(label="Tải lên video tham chiếu")
|
144 |
+
|
145 |
+
with gr.Row():
|
146 |
+
relative = gr.Checkbox(value=True, label="Chuyển động tương đối")
|
147 |
+
adapt_scale = gr.Checkbox(value=True, label="Điều chỉnh tỷ lệ chuyển động")
|
148 |
+
|
|
|
|
|
|
|
149 |
submit_btn = gr.Button("Tạo video")
|
150 |
|
151 |
with gr.Column():
|
|
|
153 |
output_message = gr.Textbox(label="Thông báo")
|
154 |
|
155 |
submit_btn.click(
|
156 |
+
fn=animate_fomm,
|
157 |
+
inputs=[source_image, driving_video, relative, adapt_scale],
|
158 |
outputs=[output_video, output_message]
|
159 |
)
|
160 |
|
161 |
+
gr.Markdown("### Cách sử dụng")
|
162 |
+
gr.Markdown("1. Tải lên **ảnh nguồn** - ảnh chứa người/đối tượng bạn muốn làm chuyển động")
|
163 |
+
gr.Markdown("2. Tải lên **video tham chiếu** - video có chuyển động bạn muốn áp dụng")
|
164 |
+
gr.Markdown("3. Nhấn **Tạo video** và chờ kết quả")
|
165 |
+
|
166 |
gr.Markdown("### Lưu ý")
|
167 |
+
gr.Markdown("- Ảnh nguồn và video tham chiếu nên có đối tượng tương tự (người với người, mặt với mặt)")
|
168 |
+
gr.Markdown("- Đối tượng nên ở vị trí tương tự trong cả ảnh nguồn và khung đầu tiên của video tham chiếu")
|
169 |
+
gr.Markdown("- Quá trình tạo video có thể mất vài phút")
|
170 |
|
171 |
demo.launch()
|
requirements.txt
CHANGED
@@ -1,7 +1,10 @@
|
|
1 |
gradio==4.0.2
|
2 |
-
torch
|
|
|
3 |
numpy
|
4 |
-
|
5 |
-
imageio==
|
6 |
-
|
7 |
-
|
|
|
|
|
|
1 |
gradio==4.0.2
|
2 |
+
torch==1.13.1
|
3 |
+
torchvision==0.14.1
|
4 |
numpy
|
5 |
+
imageio==2.9.0
|
6 |
+
imageio-ffmpeg==0.4.5
|
7 |
+
scikit-image==0.19.3
|
8 |
+
matplotlib
|
9 |
+
PyYAML==5.3.1
|
10 |
+
face-alignment==1.3.5
|