1.2.0 Retargeting

This commit is contained in:
shadowcz007
2024-07-07 23:55:53 +08:00
parent c2429e4fbd
commit 00797ae5c4
6 changed files with 510 additions and 399 deletions
+1 -1
View File
@@ -51,7 +51,7 @@ face_index:指定要处理的人脸索引,默认值为 -1,表示处理所
debug:开启或关闭调试模式。设置为 true 时,会输出调试图像以便查看人脸检测和裁剪区域;设置为 false 时,不输出调试图像。
##### Retargeting 可开关eye lip是否驱动
### models
+5 -3
View File
@@ -1,16 +1,18 @@
from .nodes.live_portrait import LivePortraitNode,FaceCropInfo
from .nodes.live_portrait import LivePortraitNode,FaceCropInfo,Retargeting
NODE_CLASS_MAPPINGS = {
"LivePortraitNode": LivePortraitNode,
"FaceCropInfo":FaceCropInfo
"FaceCropInfo":FaceCropInfo,
"Retargeting":Retargeting
}
# dict = { "key":value }
NODE_DISPLAY_NAME_MAPPINGS = {
"LivePortraitNode":"Live Portrait",
"FaceCropInfo":"Face Crop Info"
"FaceCropInfo":"Face Crop Info",
"Retargeting":"Retargeting"
}
# web ui的节点功能
+429 -381
View File
@@ -1,208 +1,7 @@
{
"last_node_id": 65,
"last_link_id": 117,
"last_node_id": 68,
"last_link_id": 125,
"nodes": [
{
"id": 50,
"type": "VideoCombine_Adv",
"pos": [
2190.4750166215863,
768.5878407330013
],
"size": [
474.4087219238281,
686.4087219238281
],
"flags": {},
"order": 21,
"mode": 0,
"inputs": [
{
"name": "image_batch",
"type": "IMAGE",
"link": 91
},
{
"name": "frame_rate",
"type": "INT",
"link": 96,
"widget": {
"name": "frame_rate"
}
}
],
"outputs": [
{
"name": "scenes_video",
"type": "SCENE_VIDEO",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "VideoCombine_Adv"
},
"widgets_values": [
18,
0,
"Comfyui",
"video/h264-mp4",
false,
false,
false,
"/view?filename=Comfyui_00002_.mp4&subfolder=&type=temp&format=video%2Fh264-mp4"
]
},
{
"id": 51,
"type": "ScenesNode_",
"pos": [
1750.4750166215856,
758.5878407330013
],
"size": {
"0": 315,
"1": 78
},
"flags": {},
"order": 20,
"mode": 0,
"inputs": [
{
"name": "scenes_video",
"type": "SCENE_VIDEO",
"link": 92
}
],
"outputs": [
{
"name": "frames",
"type": "IMAGE",
"links": [
91
],
"shape": 3
},
{
"name": "count",
"type": "INT",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "ScenesNode_"
},
"widgets_values": [
0
]
},
{
"id": 52,
"type": "LivePortraitNode",
"pos": [
1749.4750166215856,
911.5878407330013
],
"size": {
"0": 330,
"1": 98
},
"flags": {},
"order": 19,
"mode": 0,
"inputs": [
{
"name": "source_image",
"type": "IMAGE",
"link": 95
},
{
"name": "driving_video",
"type": "SCENE_VIDEO",
"link": 105
},
{
"name": "crop_info",
"type": "CROP_INFO",
"link": 109
}
],
"outputs": [
{
"name": "video",
"type": "SCENE_VIDEO",
"links": [
92
],
"shape": 3
},
{
"name": "video_concat",
"type": "SCENE_VIDEO",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "LivePortraitNode"
},
"widgets_values": [
false
]
},
{
"id": 44,
"type": "SwitchByIndex",
"pos": [
1362,
810
],
"size": {
"0": 315,
"1": 102
},
"flags": {},
"order": 9,
"mode": 0,
"inputs": [
{
"name": "A",
"type": "*",
"link": 75
},
{
"name": "B",
"type": "*",
"link": null
}
],
"outputs": [
{
"name": "list",
"type": "*",
"links": [
77
],
"shape": 6,
"slot_index": 0
},
{
"name": "count",
"type": "INT",
"links": null,
"shape": 3
}
],
"title": "第一个人脸",
"properties": {
"Node name for S&R": "SwitchByIndex"
},
"widgets_values": [
0,
"on"
]
},
{
"id": 9,
"type": "LoadImagesToBatch",
@@ -248,10 +47,10 @@
691,
1089
],
"size": [
567.827510530492,
337.2503309362837
],
"size": {
"0": 567.8275146484375,
"1": 337.2503356933594
},
"flags": {},
"order": 12,
"mode": 0,
@@ -321,57 +120,6 @@
"video"
]
},
{
"id": 55,
"type": "SwitchByIndex",
"pos": [
1282,
83
],
"size": {
"0": 315,
"1": 102
},
"flags": {},
"order": 13,
"mode": 0,
"inputs": [
{
"name": "A",
"type": "*",
"link": 98
},
{
"name": "B",
"type": "*",
"link": 99
}
],
"outputs": [
{
"name": "list",
"type": "*",
"links": [
104
],
"shape": 6,
"slot_index": 0
},
{
"name": "count",
"type": "INT",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "SwitchByIndex"
},
"widgets_values": [
-1,
"on"
]
},
{
"id": 59,
"type": "SwitchByIndex",
@@ -579,110 +327,6 @@
"on"
]
},
{
"id": 46,
"type": "SwitchByIndex",
"pos": [
1374,
1171
],
"size": {
"0": 315,
"1": 102
},
"flags": {},
"order": 15,
"mode": 0,
"inputs": [
{
"name": "A",
"type": "*",
"link": 77
},
{
"name": "B",
"type": "*",
"link": 83
}
],
"outputs": [
{
"name": "list",
"type": "*",
"links": [
107
],
"shape": 6,
"slot_index": 0
},
{
"name": "count",
"type": "INT",
"links": null,
"shape": 3
}
],
"title": "合并crop_info",
"properties": {
"Node name for S&R": "SwitchByIndex"
},
"widgets_values": [
-1,
"on"
]
},
{
"id": 61,
"type": "SwitchByIndex",
"pos": [
1757,
1142
],
"size": {
"0": 315,
"1": 102
},
"flags": {},
"order": 18,
"mode": 0,
"inputs": [
{
"name": "A",
"type": "*",
"link": 107
},
{
"name": "B",
"type": "*",
"link": 108
}
],
"outputs": [
{
"name": "list",
"type": "*",
"links": [
109
],
"shape": 6,
"slot_index": 0
},
{
"name": "count",
"type": "INT",
"links": null,
"shape": 3
}
],
"title": "合并crop_info",
"properties": {
"Node name for S&R": "SwitchByIndex"
},
"widgets_values": [
-1,
"on"
]
},
{
"id": 58,
"type": "SwitchByIndex",
@@ -1033,12 +677,317 @@
2
]
},
{
"id": 50,
"type": "VideoCombine_Adv",
"pos": [
2547,
772
],
"size": [
474.4087219238281,
686.4087219238281
],
"flags": {},
"order": 22,
"mode": 0,
"inputs": [
{
"name": "image_batch",
"type": "IMAGE",
"link": 91
},
{
"name": "frame_rate",
"type": "INT",
"link": 96,
"widget": {
"name": "frame_rate"
}
}
],
"outputs": [
{
"name": "scenes_video",
"type": "SCENE_VIDEO",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "VideoCombine_Adv"
},
"widgets_values": [
18,
0,
"Comfyui",
"video/h264-mp4",
false,
false,
false,
"/view?filename=Comfyui_00002_.mp4&subfolder=&type=temp&format=video%2Fh264-mp4"
]
},
{
"id": 51,
"type": "ScenesNode_",
"pos": [
2179,
770
],
"size": {
"0": 315,
"1": 78
},
"flags": {},
"order": 21,
"mode": 0,
"inputs": [
{
"name": "scenes_video",
"type": "SCENE_VIDEO",
"link": 92
}
],
"outputs": [
{
"name": "frames",
"type": "IMAGE",
"links": [
91
],
"shape": 3
},
{
"name": "count",
"type": "INT",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "ScenesNode_"
},
"widgets_values": [
0
]
},
{
"id": 52,
"type": "LivePortraitNode",
"pos": [
2193,
916
],
"size": {
"0": 330,
"1": 98
},
"flags": {},
"order": 20,
"mode": 0,
"inputs": [
{
"name": "source_image",
"type": "IMAGE",
"link": 95
},
{
"name": "driving_video",
"type": "SCENE_VIDEO",
"link": 105
},
{
"name": "crop_info",
"type": "CROP_INFO",
"link": 109
}
],
"outputs": [
{
"name": "video",
"type": "SCENE_VIDEO",
"links": [
92
],
"shape": 3
},
{
"name": "video_concat",
"type": "SCENE_VIDEO",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "LivePortraitNode"
},
"widgets_values": [
false
]
},
{
"id": 46,
"type": "SwitchByIndex",
"pos": [
1821,
1084
],
"size": {
"0": 315,
"1": 102
},
"flags": {},
"order": 18,
"mode": 0,
"inputs": [
{
"name": "A",
"type": "*",
"link": 124
},
{
"name": "B",
"type": "*",
"link": 83
}
],
"outputs": [
{
"name": "list",
"type": "*",
"links": [
107
],
"shape": 6,
"slot_index": 0
},
{
"name": "count",
"type": "INT",
"links": null,
"shape": 3
}
],
"title": "合并crop_info",
"properties": {
"Node name for S&R": "SwitchByIndex"
},
"widgets_values": [
-1,
"on"
]
},
{
"id": 44,
"type": "SwitchByIndex",
"pos": [
1362,
810
],
"size": {
"0": 315,
"1": 102
},
"flags": {},
"order": 9,
"mode": 0,
"inputs": [
{
"name": "A",
"type": "*",
"link": 75
},
{
"name": "B",
"type": "*",
"link": null
}
],
"outputs": [
{
"name": "list",
"type": "*",
"links": [
123
],
"shape": 6,
"slot_index": 0
},
{
"name": "count",
"type": "INT",
"links": null,
"shape": 3
}
],
"title": "第一个人脸",
"properties": {
"Node name for S&R": "SwitchByIndex"
},
"widgets_values": [
0,
"on"
]
},
{
"id": 61,
"type": "SwitchByIndex",
"pos": [
2212,
1109
],
"size": {
"0": 315,
"1": 102
},
"flags": {},
"order": 19,
"mode": 0,
"inputs": [
{
"name": "A",
"type": "*",
"link": 107
},
{
"name": "B",
"type": "*",
"link": 108
}
],
"outputs": [
{
"name": "list",
"type": "*",
"links": [
109
],
"shape": 6,
"slot_index": 0
},
{
"name": "count",
"type": "INT",
"links": null,
"shape": 3
}
],
"title": "合并crop_info",
"properties": {
"Node name for S&R": "SwitchByIndex"
},
"widgets_values": [
-1,
"on"
]
},
{
"id": 34,
"type": "SwitchByIndex",
"pos": [
822,
38
455,
9
],
"size": {
"0": 315,
@@ -1064,7 +1013,7 @@
"name": "list",
"type": "*",
"links": [
98
122
],
"shape": 6,
"slot_index": 0
@@ -1084,6 +1033,97 @@
0,
"on"
]
},
{
"id": 55,
"type": "SwitchByIndex",
"pos": [
1282,
83
],
"size": {
"0": 315,
"1": 102
},
"flags": {},
"order": 13,
"mode": 0,
"inputs": [
{
"name": "A",
"type": "*",
"link": 122
},
{
"name": "B",
"type": "*",
"link": 99
}
],
"outputs": [
{
"name": "list",
"type": "*",
"links": [
104
],
"shape": 6,
"slot_index": 0
},
{
"name": "count",
"type": "INT",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "SwitchByIndex"
},
"widgets_values": [
-1,
"on"
]
},
{
"id": 66,
"type": "Retargeting",
"pos": [
1759,
820
],
"size": {
"0": 315,
"1": 82
},
"flags": {},
"order": 15,
"mode": 0,
"inputs": [
{
"name": "crop_info",
"type": "CROP_INFO",
"link": 123
}
],
"outputs": [
{
"name": "crop_info",
"type": "CROP_INFO",
"links": [
124
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "Retargeting"
},
"widgets_values": [
false,
true
]
}
],
"links": [
@@ -1119,14 +1159,6 @@
0,
"*"
],
[
77,
44,
0,
46,
0,
"*"
],
[
83,
45,
@@ -1175,14 +1207,6 @@
0,
"*"
],
[
98,
34,
0,
55,
0,
"*"
],
[
99,
54,
@@ -1294,6 +1318,30 @@
62,
0,
"IMAGE"
],
[
122,
34,
0,
55,
0,
"*"
],
[
123,
44,
0,
66,
0,
"CROP_INFO"
],
[
124,
66,
0,
46,
0,
"*"
]
],
"groups": [
@@ -249,6 +249,10 @@ class LivePortraitPipeline(object):
img_rgb = args.source_image
# 增加人脸好的
crop_info_list = args.crop_info
# eye lip
__eye__s=[c[0]['__eye__'] for c in crop_info_list]
__lip__s=[c[0]['__lip__'] for c in crop_info_list]
# 对齐多个驱动视频的长度
align_mode=args.align_mode
@@ -266,8 +270,15 @@ class LivePortraitPipeline(object):
n_frames_s=[]
I_d_lst_s=[]
for driving_info in driving_infos:
pbar = comfy.utils.ProgressBar(len(driving_infos))
for z in range(len(driving_infos)):
driving_info=driving_infos[z]
crop_info=crop_info_list[z]
# print('###',z,len(driving_infos),len(crop_info_list))
# print('#crop_info_list[z]',crop_info_list[z])
__eye__=__eye__s[z]
__lip__=__lip__s[z]
if is_video(driving_info):
log(f"Load from video file (mp4 mov avi etc...): {driving_info}")
@@ -280,10 +291,14 @@ class LivePortraitPipeline(object):
I_d_lst_s.append(I_d_lst)
n_frames_s.append(n_frames)
if inference_cfg.flag_eye_retargeting or inference_cfg.flag_lip_retargeting:
source_lmk = crop_info['lmk_crop']
if __eye__ or __lip__:
driving_lmk_lst = self.cropper.get_retargeting_lmk_info(driving_rgb_lst)
driving_lmk_lst_s.append(driving_lmk_lst)
# input_eye_ratio_lst, input_lip_ratio_lst = self.live_portrait_wrapper.calc_retargeting_ratio(source_lmk, driving_lmk_lst)
input_eye_ratio_lst, input_lip_ratio_lst = self.live_portrait_wrapper.calc_retargeting_ratio(
source_lmk,
driving_lmk_lst
)
# elif is_template(args.driving_info):
# log(f"Load from video templates {args.driving_info}")
# with open(args.driving_info, 'rb') as f:
@@ -294,6 +309,7 @@ class LivePortraitPipeline(object):
# raise Exception("Unsupported driving types!")
#########################################
# print('#driving_lmk_lst',self.driving_lmk_lst)
pbar.update(1)
# 对齐
max_n_frames = max(n_frames_s)
@@ -311,6 +327,10 @@ class LivePortraitPipeline(object):
crop_info=crop_info_list[index]
# 地一张脸
__eye__=__eye__s[index]
__lip__=__lip__s[index]
print('#crop_info',crop_info.keys())
source_lmk = crop_info['lmk_crop']
img_crop, img_crop_256x256 = crop_info['img_crop'], crop_info['img_crop_256x256']
if inference_cfg.flag_do_crop:
@@ -386,13 +406,13 @@ class LivePortraitPipeline(object):
x_d_i_new = scale_new * (x_c_s @ R_new + delta_new) + t_new
# Algorithm 1:
if not inference_cfg.flag_stitching and not inference_cfg.flag_eye_retargeting and not inference_cfg.flag_lip_retargeting:
if not inference_cfg.flag_stitching and not __eye__ and not __lip__:
# without stitching or retargeting
if inference_cfg.flag_lip_zero:
x_d_i_new += lip_delta_before_animation.reshape(-1, x_s.shape[1], 3)
else:
pass
elif inference_cfg.flag_stitching and not inference_cfg.flag_eye_retargeting and not inference_cfg.flag_lip_retargeting:
elif inference_cfg.flag_stitching and not __eye__ and not __lip__:
# with stitching and without retargeting
if inference_cfg.flag_lip_zero:
x_d_i_new = self.live_portrait_wrapper.stitching(x_s, x_d_i_new) + lip_delta_before_animation.reshape(-1, x_s.shape[1], 3)
@@ -400,12 +420,12 @@ class LivePortraitPipeline(object):
x_d_i_new = self.live_portrait_wrapper.stitching(x_s, x_d_i_new)
else:
eyes_delta, lip_delta = None, None
if inference_cfg.flag_eye_retargeting:
if __eye__:
c_d_eyes_i = input_eye_ratio_lst[i]
combined_eye_ratio_tensor = self.live_portrait_wrapper.calc_combined_eye_ratio(c_d_eyes_i, source_lmk)
# ∆_eyes,i = R_eyes(x_s; c_s,eyes, c_d,eyes,i)
eyes_delta = self.live_portrait_wrapper.retarget_eye(x_s, combined_eye_ratio_tensor)
if inference_cfg.flag_lip_retargeting:
if __lip__:
c_d_lip_i = input_lip_ratio_lst[i]
combined_lip_ratio_tensor = self.live_portrait_wrapper.calc_combined_lip_ratio(c_d_lip_i, source_lmk)
# ∆_lip,i = R_lip(x_s; c_s,lip, c_d,lip,i)
+46 -5
View File
@@ -50,7 +50,7 @@ class ArgumentConfig:
align_mode=True,
flag_lip_zero=True,
flag_eye_retargeting=False,
flag_lip_retargeting=False,
flag_lip_retargeting=False,
flag_stitching=True,
flag_relative=True,
flag_pasteback=True,
@@ -73,7 +73,7 @@ class ArgumentConfig:
self.device_id = device_id
self.flag_lip_zero = flag_lip_zero
self.flag_eye_retargeting = flag_eye_retargeting
self.flag_lip_retargeting = flag_lip_retargeting
self.flag_lip_retargeting = flag_lip_retargeting
self.flag_stitching = flag_stitching
self.flag_relative = flag_relative
self.flag_pasteback = flag_pasteback
@@ -194,7 +194,7 @@ class FaceCropInfo:
OUTPUT_NODE = True
CATEGORY = "♾️Mixlab/Video"
CATEGORY = "♾️Mixlab/Video/LivePortrait"
INPUT_IS_LIST = False
OUTPUT_IS_LIST = (True,False,) #list 列表 [1,2,3]
@@ -225,7 +225,48 @@ class FaceCropInfo:
#只输出一张 [face]
crop_info=[crop_info[face_index]]
return (crop_info,debug_image,)
result=[]
for c in crop_info:
c['__eye__']=True
c['__lip__']=True
result.append(c)
return (result,debug_image,)
# 人脸检测并裁切
class Retargeting:
@classmethod
def INPUT_TYPES(s):
return {"required": {
"crop_info": ("CROP_INFO",),
},
"optional":{
"lip":("BOOLEAN", {"default": True},),
"eye":("BOOLEAN", {"default": True},),
}
}
RETURN_TYPES = ("CROP_INFO",)
RETURN_NAMES = ("crop_info",)
FUNCTION = "run"
OUTPUT_NODE = True
CATEGORY = "♾️Mixlab/Video/LivePortrait"
INPUT_IS_LIST = False
OUTPUT_IS_LIST = (False,) #list 列表 [1,2,3]
def run(self,crop_info,lip=True,eye=True):
crop_info['__eye__']=eye
crop_info['__lip__']=lip
return (crop_info,)
# 驱动模板制作
@@ -282,7 +323,7 @@ class LivePortraitNode:
FUNCTION = "run"
CATEGORY = "♾️Mixlab/Video"
CATEGORY = "♾️Mixlab/Video/LivePortrait"
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (False,False,) #list 列表 [1,2,3]
+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "comfyui-liveportrait"
description = "The ComfyUI version of [a/LivePortrait](https://github.com/KwaiVGI/LivePortrait)."
version = "1.1.0"
version = "1.2.0"
license = "LICENSE"
dependencies = ["numpy>=1.26.4", "opencv-python-headless", "imageio>=2.34.2", "lmdb>=1.4.1", "timm>=1.0.7", "rich>=13.7.1", "ffmpeg>=1.4", "onnxruntime-gpu>=1.18.0", "onnx>=1.16.1", "scikit-image>=0.24.0", "albumentations>=1.4.10", "matplotlib>=3.9.0", "imageio-ffmpeg>=0.5.1"]