From feb48d12c6d260216bb1f9d86d46f8c7d11a61f9 Mon Sep 17 00:00:00 2001 From: KBRASK Date: Sun, 17 Aug 2025 11:54:01 +0800 Subject: [PATCH] update workflow --- example/face_swap.json | 244 ++++++++++++++++++++++++----------------- videoinput.py | 13 +-- 2 files changed, 151 insertions(+), 106 deletions(-) diff --git a/example/face_swap.json b/example/face_swap.json index cbc53b7..cafb617 100644 --- a/example/face_swap.json +++ b/example/face_swap.json @@ -1,8 +1,8 @@ { "id": "206247b6-9fec-4ed2-8927-e4f388c674d4", "revision": 0, - "last_node_id": 142, - "last_link_id": 251, + "last_node_id": 145, + "last_link_id": 254, "nodes": [ { "id": 50, @@ -16,7 +16,7 @@ 200 ], "flags": {}, - "order": 17, + "order": 18, "mode": 2, "inputs": [ { @@ -56,7 +56,7 @@ 200 ], "flags": {}, - "order": 16, + "order": 17, "mode": 2, "inputs": [ { @@ -202,7 +202,7 @@ 46 ], "flags": {}, - "order": 26, + "order": 27, "mode": 0, "inputs": [ { @@ -289,7 +289,7 @@ 198 ], "flags": {}, - "order": 32, + "order": 33, "mode": 0, "inputs": [ { @@ -406,7 +406,7 @@ 46 ], "flags": {}, - "order": 29, + "order": 30, "mode": 0, "inputs": [ { @@ -502,7 +502,7 @@ 274 ], "flags": {}, - "order": 19, + "order": 20, "mode": 0, "inputs": [ { @@ -588,7 +588,7 @@ "flags": { "collapsed": true }, - "order": 21, + "order": 22, "mode": 2, "inputs": [ { @@ -629,7 +629,7 @@ 246 ], "flags": {}, - "order": 22, + "order": 23, "mode": 0, "inputs": [ { @@ -727,7 +727,7 @@ 57.857322692871094 ], "flags": {}, - "order": 30, + "order": 31, "mode": 0, "inputs": [ { @@ -769,7 +769,7 @@ 873.39453125 ], "flags": {}, - "order": 31, + "order": 32, "mode": 0, "inputs": [ { @@ -871,6 +871,15 @@ "shape": 7, "type": "FREEINITARGS", "link": null + }, + { + "name": "denoise_strength", + "shape": 7, + "type": "FLOAT", + "widget": { + "name": "denoise_strength" + }, + "link": 252 } ], "outputs": [ @@ -963,7 +972,7 @@ 355.67169189453125 ], "flags": {}, - "order": 25, + "order": 26, "mode": 0, "inputs": [ { @@ -1050,7 +1059,7 @@ 198 ], "flags": {}, - "order": 18, + "order": 19, "mode": 0, "inputs": [ { @@ -1095,58 +1104,6 @@ false ] }, - { - "id": 138, - "type": "VideoInputPreprocessor", - "pos": [ - -274.0709533691406, - 247.68026733398438 - ], - "size": [ - 306.6597595214844, - 194 - ], - "flags": {}, - "order": 24, - "mode": 0, - "inputs": [ - { - "name": "face_processor", - "type": "FACE_PROCESSOR", - "link": 242 - }, - { - "name": "images", - "type": "IMAGE", - "link": 244 - }, - { - "name": "face_rgba", - "type": "IMAGE", - "link": 243 - } - ], - "outputs": [ - { - "name": "processed_images", - "type": "IMAGE", - "links": [ - 245 - ] - } - ], - "properties": { - "aux_id": "WeChatCV/Stand-In_Preprocessor_ComfyUI", - "ver": "db0d3d966dada4c5c242f9da5287988b533120e4", - "Node name for S&R": "VideoInputPreprocessor" - }, - "widgets_values": [ - 0.5, - 1.5, - 10, - true - ] - }, { "id": 134, "type": "WanVideoEncode", @@ -1159,7 +1116,7 @@ 242 ], "flags": {}, - "order": 28, + "order": 29, "mode": 0, "inputs": [ { @@ -1215,7 +1172,7 @@ 150 ], "flags": {}, - "order": 33, + "order": 34, "mode": 0, "inputs": [ { @@ -1261,7 +1218,7 @@ 334 ], "flags": {}, - "order": 34, + "order": 35, "mode": 0, "inputs": [ { @@ -1338,7 +1295,7 @@ 242 ], "flags": {}, - "order": 23, + "order": 24, "mode": 0, "inputs": [ { @@ -1443,7 +1400,7 @@ 126 ], "flags": {}, - "order": 27, + "order": 28, "mode": 0, "inputs": [ { @@ -1630,29 +1587,6 @@ } } }, - { - "id": 126, - "type": "MarkdownNote", - "pos": [ - -286.70123291015625, - -406.58197021484375 - ], - "size": [ - 362.6117858886719, - 143.0945281982422 - ], - "flags": {}, - "order": 15, - "mode": 0, - "inputs": [], - "outputs": [], - "properties": {}, - "widgets_values": [ - "LoRA link:\n\n[https://huggingface.co/BowenXue/Stand-In](https://huggingface.co/BowenXue/Stand-In)\n\nThis LoRA is not the traditional kind and it HAS TO connect to the model loader directly." - ], - "color": "#432", - "bgcolor": "#653" - }, { "id": 142, "type": "VHS_VideoInfo", @@ -1665,7 +1599,7 @@ 206 ], "flags": {}, - "order": 20, + "order": 21, "mode": 0, "inputs": [ { @@ -1736,6 +1670,112 @@ "Node name for S&R": "VHS_VideoInfo" }, "widgets_values": {} + }, + { + "id": 126, + "type": "MarkdownNote", + "pos": [ + -286.70123291015625, + -406.58197021484375 + ], + "size": [ + 362.6117858886719, + 143.0945281982422 + ], + "flags": {}, + "order": 15, + "mode": 0, + "inputs": [], + "outputs": [], + "properties": {}, + "widgets_values": [ + "LoRA link:\n\n[https://huggingface.co/BowenXue/Stand-In](https://huggingface.co/BowenXue/Stand-In)\n\nThis LoRA is not the traditional kind and it HAS TO connect to the model loader directly." + ], + "color": "#432", + "bgcolor": "#653" + }, + { + "id": 143, + "type": "MarkdownNote", + "pos": [ + -270.9808044433594, + 479.54229736328125 + ], + "size": [ + 300, + 240 + ], + "flags": {}, + "order": 16, + "mode": 0, + "inputs": [], + "outputs": [], + "properties": {}, + "widgets_values": [ + "## **With a higher denoise strength, the face blends better with the background, but the background changes slightly more. With a lower denoise strength, the background changes less. You can adjust it according to your needs.**\n" + ], + "color": "#432", + "bgcolor": "#653" + }, + { + "id": 138, + "type": "VideoInputPreprocessor", + "pos": [ + -274.0709533691406, + 247.68026733398438 + ], + "size": [ + 306.6597595214844, + 194 + ], + "flags": {}, + "order": 25, + "mode": 0, + "inputs": [ + { + "name": "face_processor", + "type": "FACE_PROCESSOR", + "link": 242 + }, + { + "name": "images", + "type": "IMAGE", + "link": 244 + }, + { + "name": "face_rgba", + "type": "IMAGE", + "link": 243 + } + ], + "outputs": [ + { + "name": "processed_images", + "type": "IMAGE", + "links": [ + 245 + ] + }, + { + "name": "denoise_strength", + "type": "FLOAT", + "links": [ + 252 + ] + } + ], + "properties": { + "aux_id": "WeChatCV/Stand-In_Preprocessor_ComfyUI", + "ver": "db0d3d966dada4c5c242f9da5287988b533120e4", + "Node name for S&R": "VideoInputPreprocessor" + }, + "widgets_values": [ + 0.5, + 0.5, + 1.5, + 10, + true + ] } ], "links": [ @@ -2034,6 +2074,14 @@ 103, 3, "INT" + ], + [ + 252, + 138, + 1, + 27, + 17, + "FLOAT" ] ], "groups": [ @@ -2054,10 +2102,10 @@ "config": {}, "extra": { "ds": { - "scale": 0.6115909044841601, + "scale": 0.8140274938684186, "offset": [ - 1552.3325791014386, - 871.5007309079728 + 1354.2272102810791, + 250.24273344699088 ] }, "frontendVersion": "1.24.4", diff --git a/videoinput.py b/videoinput.py index 2b511b1..32d1d56 100644 --- a/videoinput.py +++ b/videoinput.py @@ -28,6 +28,7 @@ class VideoInputPreprocessor: "face_processor": ("FACE_PROCESSOR",), "images": ("IMAGE",), "face_rgba": ("IMAGE",), + "denoise_strength": ("FLOAT", {"default": 0.5, "min": 0.1, "max": 1.0, "step": 0.01}), "confidence_threshold": ("FLOAT", {"default": 0.5, "min": 0.1, "max": 1.0, "step": 0.05}), "face_crop_scale": ("FLOAT", {"default": 1.5, "min": 1.0, "max": 3.0, "step": 0.1}), "dilation_kernel_size": ("INT", {"default": 10, "min": 0, "max": 50, "step": 1}), @@ -35,12 +36,12 @@ class VideoInputPreprocessor: } } - RETURN_TYPES = ("IMAGE",) - RETURN_NAMES = ("processed_images",) + RETURN_TYPES = ("IMAGE", "FLOAT") + RETURN_NAMES = ("processed_images", "denoise_strength") FUNCTION = "generate_mask_and_paste" CATEGORY = "Stand-In" - def generate_mask_and_paste(self, face_processor, images: torch.Tensor, face_rgba: torch.Tensor, confidence_threshold: float, face_crop_scale: float, dilation_kernel_size: int, with_neck: bool): + def generate_mask_and_paste(self, face_processor, images: torch.Tensor, face_rgba: torch.Tensor, denoise_strength: float, confidence_threshold: float, face_crop_scale: float, dilation_kernel_size: int, with_neck: bool): detection_model, parsing_model, device = face_processor total_frames, h, w = images.shape[0], images.shape[1], images.shape[2] @@ -59,7 +60,6 @@ class VideoInputPreprocessor: frame_tensor = images[i] frame_bgr = tensor_to_cv2_img(frame_tensor) - # --- 1. Generate Face Mask (same logic as before) --- results = detection_model(frame_bgr, verbose=False) confident_boxes = results[0].boxes.xyxy[results[0].boxes.conf > confidence_threshold] @@ -101,11 +101,8 @@ class VideoInputPreprocessor: mask_resized_to_crop = cv2.resize(final_mask_512, (face_crop_bgr.shape[1], face_crop_bgr.shape[0]), interpolation=cv2.INTER_NEAREST) full_mask_np[crop_y1:crop_y2, crop_x1:crop_x2] = mask_resized_to_crop - # --- 2. Paste Image onto Frame --- - # Use a copy to avoid modifying the original data target_frame_pil = tensor_to_pil(frame_tensor).copy() - # Find the bounding box of the largest contour in the mask if confident_boxes.shape[0] > 0 and 'face_crop_bgr' in locals() and face_crop_bgr.size > 0: x = crop_x1 @@ -125,7 +122,7 @@ class VideoInputPreprocessor: output_image_batch = torch.stack(processed_frames_tensors) - return (output_image_batch,) + return (output_image_batch,denoise_strength) NODE_CLASS_MAPPINGS = { "VideoInputPreprocessor": VideoInputPreprocessor,