Add Image Crop Location and Image Paste Crop nodes

Add general cropping and pasting nodes.
Add side profile face detection for Image Crop Face
This commit is contained in:
Jordan Thompson
2023-04-22 16:04:11 -07:00
parent cf03ade369
commit 8b196259a9
3 changed files with 29802 additions and 5 deletions
+4
View File
@@ -60,8 +60,12 @@
- Sometimes face crop is black, this is because the padding is too large and intersected with the image edge. Use a smaller padding size.
- face_recognition mode sometimes finds random things as faces. It also requires a [CUDA] GPU.
- Only detects one face. This is a design choice to make it's use easy.
- **Notes:**
- Detection runs in succession. If nothing is found with the selected detection cascades, it will try the next available cascades file.
- Image Crop Location: Crop a image to specified location in top, left, right, and bottom locations relating to the pixel dimensions of the image in X and Y coordinats.
- Image Paste Face Crop: Paste face crop back on a image at it's original location and size
- Features a better blending funciton than GFPGAN/CodeFormer so there shouldn't be visible seams, and coupled with Diffusion Result, looks better than GFPGAN/CodeFormer.
- Image Paste Crop: Paste a crop (such as from Image Crop Location) at it's original location and size utilizing the `crop_data` node input.
- Image Dragan Photography Filter: Apply a Andrzej Dragan photography style to a image
- Image Edge Detection Filter: Detect edges in a image
- Image Film Grain: Apply film grain to a image
+108 -5
View File
@@ -1402,6 +1402,7 @@ class WAS_Image_Crop_Face:
"haarcascade_frontalface_alt.xml",
"haarcascade_frontalface_alt2.xml",
"haarcascade_frontalface_alt_tree.xml",
"haarcascade_profileface.xml",
"haarcascade_upperbody.xml"
],),
"use_face_recognition_gpu": (["false","true"],),
@@ -1441,12 +1442,13 @@ class WAS_Image_Crop_Face:
else:
face_location = None
cascades = [os.path.join(os.path.join(WAS_SUITE_ROOT, 'res'), 'haarcascade_frontalface_default.xml'),
cascades = [ os.path.join(os.path.join(WAS_SUITE_ROOT, 'res'), 'lbpcascade_animeface.xml'),
os.path.join(os.path.join(WAS_SUITE_ROOT, 'res'), 'haarcascade_frontalface_default.xml'),
os.path.join(os.path.join(WAS_SUITE_ROOT, 'res'), 'haarcascade_frontalface_alt.xml'),
os.path.join(os.path.join(WAS_SUITE_ROOT, 'res'), 'haarcascade_frontalface_alt2.xml'),
os.path.join(os.path.join(WAS_SUITE_ROOT, 'res'), 'haarcascade_frontalface_alt_tree.xml'),
os.path.join(os.path.join(WAS_SUITE_ROOT, 'res'), 'haarcascade_upperbody.xml'),
os.path.join(os.path.join(WAS_SUITE_ROOT, 'res'), 'lbpcascade_animeface.xml')]
os.path.join(os.path.join(WAS_SUITE_ROOT, 'res'), 'haarcascade_profileface.xml'),
os.path.join(os.path.join(WAS_SUITE_ROOT, 'res'), 'haarcascade_upperbody.xml') ]
if cascade_name:
for cascade in cascades:
@@ -1463,7 +1465,7 @@ class WAS_Image_Crop_Face:
if not os.path.exists(cascade):
print(f"\033[34mWAS NS\033[0m Error: Unable to find cascade XML file at `{cascade}`.",
"Did you pull the latest files from https://github.com/WASasquatch/was-node-suite-comfyui repo?")
return (pil2tensor(Image.new("RGB", (512,512), (0,0,0))), ((0,0),(0,0,0,0)))
return (pil2tensor(Image.new("RGB", (512,512), (0,0,0))), False)
face_cascade = cv2.CascadeClassifier(cascade)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5)
@@ -1472,7 +1474,7 @@ class WAS_Image_Crop_Face:
break
if len(faces) == 0:
print("\033[34mWAS NS\033[0m Warning: No faces found in the image!")
return (pil2tensor(Image.new("RGB", (512,512), (0,0,0))), ((0,0),(0,0,0,0)))
return (pil2tensor(Image.new("RGB", (512,512), (0,0,0))), False)
else:
print("\033[34mWAS NS\033[0m: Face found with: face_recognition model")
faces = face_location
@@ -1569,6 +1571,10 @@ class WAS_Image_Paste_Face_Crop:
CATEGORY = "WAS Suite/Image/Process"
def image_paste_face(self, image, crop_image, crop_data=None, crop_blending=0.25, crop_sharpening=0):
if crop_data == False:
print("\033[34mWAS NS\033[0m Error: No valid crop data found!")
return (pil2tensor(Image.new("RGB", (512,512), (0,0,0))), pil2tensor(Image.new("RGB", (512,512), (0,0,0))))
result_image, result_mask = self.paste_face(tensor2pil(image), tensor2pil(crop_image), crop_data[0], crop_data[1], crop_blending, crop_sharpening)
return(result_image, result_mask)
@@ -1605,6 +1611,101 @@ class WAS_Image_Paste_Face_Crop:
return (pil2tensor(image.convert('RGB')), pil2tensor(mask.convert('RGB')))
# IMAGE CROP LOCATION
class WAS_Image_Crop_Location:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"image": ("IMAGE",),
"top": ("INT", {"default":0, "max": 10000000, "min":0, "step":1}),
"left": ("INT", {"default":0, "max": 10000000, "min":0, "step":1}),
"right": ("INT", {"default":0, "max": 10000000, "min":0, "step":1}),
"bottom": ("INT", {"default":0, "max": 10000000, "min":0, "step":1}),
}
}
RETURN_TYPES = ("IMAGE", "CROP_DATA")
FUNCTION = "image_crop_location"
CATEGORY = "WAS Suite/Image/Process"
def image_crop_location(self, image, top=0, left=0, right=100, bottom=100):
image = tensor2pil(image)
crop = image.crop((left, top, right, bottom))
crop_data = (crop.copy().size, (top, left, bottom, right))
crop = crop.resize((((crop.size[0] // 8) * 8 + 8), ((crop.size[1] // 8) * 8 + 8)))
return (pil2tensor(crop), crop_data)
# IMAGE PASTE CROP
class WAS_Image_Paste_Crop:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"image": ("IMAGE",),
"crop_image": ("IMAGE",),
"crop_data": ("CROP_DATA",),
"crop_blending": ("FLOAT", {"default": 0.25, "min": 0.0, "max": 1.0, "step": 0.01}),
"crop_sharpening": ("INT", {"default": 0, "min": 0, "max": 3, "step": 1}),
}
}
RETURN_TYPES = ("IMAGE", "IMAGE")
FUNCTION = "image_paste_crop"
CATEGORY = "WAS Suite/Image/Process"
def image_paste_crop(self, image, crop_image, crop_data=None, crop_blending=0.25, crop_sharpening=0):
if crop_data == False:
print("\033[34mWAS NS\033[0m Error: No valid crop data found!")
return (pil2tensor(Image.new("RGB", (512,512), (0,0,0))), pil2tensor(Image.new("RGB", (512,512), (0,0,0))))
result_image, result_mask = self.paste_image(tensor2pil(image), crop_data, tensor2pil(crop_image), crop_blending, crop_sharpening)
return (result_image, result_mask)
def paste_image(self, image, crop_data, crop_img, blend_amount=0.25, sharpen_amount=1):
crop_size, crop_coords = crop_data
crop_img = crop_img.convert("RGB").resize(crop_size)
if sharpen_amount > 0:
for _ in range(sharpen_amount):
crop_img = crop_img.filter(ImageFilter.SHARPEN)
if blend_amount > 1.0:
blend_amount = 1.0
elif blend_amount < 0.0:
blend_amount = 0.0
blend_ratio = (max(crop_img.size[0], crop_img.size[1]) / 2) * float(blend_amount)
blend = image.convert("RGBA")
mask = Image.new("L", image.size, 0)
offset_x = int(crop_size[0] * (blend_amount + blend_amount / 2.5))
offset_y = int(crop_size[1] * (blend_amount + blend_amount / 2.5))
mask_block_size = (crop_size[0]-offset_x, crop_size[1]-offset_y)
mask_block = Image.new("L", mask_block_size, 255)
Image.Image.paste(mask, mask_block, (int(crop_coords[1]+offset_x/2), int(crop_coords[0]+offset_y/2)))
Image.Image.paste(blend, crop_img, (crop_coords[1], crop_coords[0]))
mask = mask.filter(ImageFilter.BoxBlur(radius=blend_ratio/2))
mask = mask.filter(ImageFilter.GaussianBlur(radius=blend_ratio/2))
blend.putalpha(mask)
image = Image.alpha_composite(image.convert("RGBA"), blend)
return (pil2tensor(image.convert('RGB')), pil2tensor(mask.convert('RGB')))
# COMBINE NODE
class WAS_Image_Blending_Mode:
@@ -6407,7 +6508,9 @@ NODE_CLASS_MAPPINGS = {
"Image Chromatic Aberration": WAS_Image_Chromatic_Aberration,
"Image Color Palette": WAS_Image_Color_Palette,
"Image Crop Face": WAS_Image_Crop_Face,
"Image Crop Location": WAS_Image_Crop_Location,
"Image Paste Face": WAS_Image_Paste_Face_Crop,
"Image Paste Crop": WAS_Image_Paste_Crop,
"Image Dragan Photography Filter": WAS_Dragon_Filter,
"Image Edge Detection Filter": WAS_Image_Edge,
"Image Film Grain": WAS_Film_Grain,
File diff suppressed because it is too large Load Diff